├── .gitignore ├── assets └── ext-icon.png ├── popup.js ├── popup.css ├── README.md ├── popup.html ├── manifest.json ├── contentScript.js └── background.js /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | .DS_Store 3 | -------------------------------------------------------------------------------- /assets/ext-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rexfm/SpeedyMeet/HEAD/assets/ext-icon.png -------------------------------------------------------------------------------- /popup.js: -------------------------------------------------------------------------------- 1 | window.addEventListener('click',function(e){ 2 | if(e.target.href!==undefined){ 3 | chrome.tabs.create({url:e.target.href}) 4 | } 5 | }) 6 | -------------------------------------------------------------------------------- /popup.css: -------------------------------------------------------------------------------- 1 | .container { 2 | width: 280px; 3 | color: #314d3e; 4 | } 5 | 6 | .title { 7 | font-size: 14px; 8 | font-weight: bold; 9 | padding: 8px; 10 | } 11 | 12 | .description { 13 | margin: 5px 5px; 14 | padding: 3px; 15 | } 16 | 17 | 18 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpeedyMeet 2 | Chrome Extension to automatically open Google Meet links in the Google Meet PWA 3 | 4 | ## Installation 5 | Download this repo, and load as an Unpacked Extension in chrome://extensions 6 | 7 | ## Usage 8 | Once the Extension is installed, open the Google Meet PWA and then open a Google Meet link. You should see the Meet link open in a Tab and then get moved over to the PWA automatically. 9 | 10 | ## Notes 11 | Currently it won't do anything if the PWA is not already open. 12 | -------------------------------------------------------------------------------- /popup.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 |
8 |
9 |
SpeedyMeet Usage
10 | 11 |
Open the Google Meet PWA and then open a Google Meet link. The Meet 12 | session should be moved to the PWA. In case of errors report 13 | them as issues here. 14 |
15 |
16 |
17 | -------------------------------------------------------------------------------- /manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Speedy Meet", 3 | "version": "0.1.0", 4 | "description": "Open Google Meet links in the PWA by default", 5 | "permissions": [ 6 | "scripting", 7 | "storage", 8 | "tabs" 9 | ], 10 | "host_permissions": [ 11 | "https://meet.google.com/*" 12 | ], 13 | "background": { 14 | "service_worker": "background.js" 15 | }, 16 | "content_scripts": [ 17 | { 18 | "matches": [ 19 | "https://meet.google.com/*" 20 | ], 21 | "js": [ 22 | "contentScript.js" 23 | ] 24 | } 25 | ], 26 | "web_accessible_resources": [ 27 | { 28 | "resources": [ 29 | "assets/ext-icon.png" 30 | ], 31 | "matches": [ 32 | "https://meet.google.com/*" 33 | ] 34 | } 35 | ], 36 | "action": { 37 | "default_icon": { 38 | "16": "assets/ext-icon.png", 39 | "24": "assets/ext-icon.png", 40 | "32": "assets/ext-icon.png" 41 | }, 42 | "default_title": "Speedy Meet", 43 | "default_popup": "popup.html" 44 | }, 45 | "icons": { 46 | "16": "assets/ext-icon.png", 47 | "32": "assets/ext-icon.png", 48 | "48": "assets/ext-icon.png", 49 | "128": "assets/ext-icon.png" 50 | }, 51 | "manifest_version": 3 52 | } -------------------------------------------------------------------------------- /contentScript.js: -------------------------------------------------------------------------------- 1 | /* 2 | * contentScript.js is injected onto any meet.google.com page. This has different logic depending on if 3 | * it is running in the PWA or a normal tab. The PWA portion will redirect it to the correct meeting 4 | * (if not currently on a meeting). The normal tab will replace the content on the original page 5 | * informing the user they were redirected to the PWA. 6 | */ 7 | 8 | (() => { 9 | if (isPwa()) { 10 | chrome.storage.onChanged.addListener(function (changes) { 11 | if ( 12 | changes['queryParams'] && 13 | changes['queryParams'].newValue !== '__gmInitialState' 14 | ) { 15 | const icons = document.getElementsByClassName('google-material-icons'); 16 | let onCall = false; 17 | for (const i in icons) { 18 | if (icons[i].outerText === 'call_end') { 19 | onCall = true; 20 | } 21 | } 22 | 23 | if (onCall) { 24 | return; 25 | } 26 | 27 | const qp = changes['queryParams'].newValue; 28 | const newQueryParams = qp.includes('?') 29 | ? qp.includes('authuser=') 30 | ? qp 31 | : qp + '&authuser=0' 32 | : qp + '?authuser=0'; 33 | 34 | const currentHref = window.location.href; 35 | const newHref = 'https://meet.google.com/' + newQueryParams; 36 | if (currentHref !== newHref) { 37 | window.location.href = 'https://meet.google.com/' + newQueryParams; 38 | } 39 | 40 | // close original tab 41 | chrome.storage.local.set({ 42 | googleMeetOpenedUrl: new Date().toISOString(), 43 | }); 44 | } 45 | }); 46 | } else { 47 | // Normal tab, add listener to replace UI with 48 | chrome.storage.onChanged.addListener(function (changes) { 49 | if (changes['originatingTabId'] && changes['originatingTabId'].newValue) { 50 | // could improve this. it only properly replaces if you navigated to a meet.google.com/some-slug 51 | // it does not know how to replace the landing page. we could make it look a lot nicer 52 | document.body.childNodes[1].style.display = 'none'; 53 | const textnode = document.createTextNode('Opening in Google Meet app'); 54 | document.body.appendChild(textnode); 55 | } 56 | }); 57 | } 58 | })(); 59 | 60 | function isPwa() { 61 | return ['fullscreen', 'standalone', 'minimal-ui'].some( 62 | (displayMode) => 63 | window.matchMedia('(display-mode: ' + displayMode + ')').matches 64 | ); 65 | } 66 | -------------------------------------------------------------------------------- /background.js: -------------------------------------------------------------------------------- 1 | /* 2 | * background.js runs in the background on Chrome. It has access to manage the windows/tabs. 3 | * This will start the process to redirect the open tab into the PWA. 4 | */ 5 | 6 | let googleMeetWindowId; 7 | 8 | // clear referring state on page load 9 | chrome.tabs.onCreated.addListener(() => { 10 | chrome.storage.local.set({ 11 | originatingTabId: '', 12 | queryParams: '__gmInitialState', 13 | source: '', 14 | }); 15 | }); 16 | 17 | chrome.tabs.onUpdated.addListener((tabId, tabChangeInfo, tab) => { 18 | if (tab.url && tab.url.includes('meet.google.com/new')) { 19 | // Special handling if it's a "/new" URL 20 | // This allows users to send follow-up slack from the PWA 21 | chrome.windows.getAll( 22 | { populate: true, windowTypes: ['app'] }, 23 | function (windows) { 24 | windows.forEach((window) => { 25 | if ( 26 | window.tabs.length === 1 && 27 | window.tabs[0].url.startsWith('https://meet.google.com/') 28 | ) { 29 | googleMeetWindowId = window.id; 30 | } 31 | }); 32 | 33 | if (!googleMeetWindowId) { 34 | // skipping redirect since PWA isn't open 35 | // we could inject a button onto the page to inform the user of this 36 | return; 37 | } 38 | 39 | // only attempt a redirect when not the PWA 40 | if (tab.windowId !== googleMeetWindowId) { 41 | chrome.scripting.executeScript( 42 | { 43 | target: { tabId: tab.id }, 44 | injectImmediately: true, 45 | func: () => { 46 | window.stop(); 47 | }, 48 | }, 49 | function () { 50 | const queryParameters = tab.url.split('/')[3]; 51 | chrome.storage.local.set({ 52 | originatingTabId: tabId, 53 | queryParams: queryParameters, 54 | source: 'NEW_MEETING', 55 | }); 56 | } 57 | ); 58 | } 59 | } 60 | ); 61 | } else if ( 62 | tabChangeInfo.status === 'complete' && 63 | tab.url && 64 | tab.url.includes('meet.google.com') 65 | ) { 66 | // find Google Meet PWA window id 67 | chrome.windows.getAll( 68 | { populate: true, windowTypes: ['app'] }, 69 | function (windows) { 70 | windows.forEach((window) => { 71 | if ( 72 | window.tabs.length === 1 && 73 | window.tabs[0].url.startsWith('https://meet.google.com/') 74 | ) { 75 | googleMeetWindowId = window.id; 76 | } 77 | }); 78 | 79 | if (!googleMeetWindowId) { 80 | // skipping redirect since PWA isn't open 81 | // we could inject a button onto the page to inform the user of this 82 | return; 83 | } 84 | 85 | // only attempt a redirect when not the PWA 86 | if (tab.windowId !== googleMeetWindowId) { 87 | const parameters = tab.url.split('/')[3]; 88 | if (!parameters.startsWith('new') && !parameters.startsWith('_meet')) { 89 | // if empty, set the landing page 90 | chrome.storage.local.set({ 91 | originatingTabId: tabId, 92 | queryParams: parameters, 93 | }); 94 | } 95 | } 96 | } 97 | ); 98 | } 99 | }); 100 | 101 | chrome.storage.onChanged.addListener(function (changes) { 102 | if (changes['googleMeetOpenedUrl']) { 103 | // bring Google Meet PWA into focus 104 | chrome.windows.update(googleMeetWindowId, { focused: true }, function () { 105 | // close the tab that originally started the process if it wasn't the landing page 106 | chrome.storage.local.get( 107 | ['originatingTabId', 'queryParams', 'source'], 108 | function ({ originatingTabId, queryParams, source }) { 109 | let timeout = 3000; 110 | if (source === 'NEW_MEETING') { 111 | timeout = 0; 112 | } 113 | setTimeout(function () { 114 | if (queryParams !== '') { 115 | chrome.tabs.remove(originatingTabId); 116 | } 117 | }, timeout); 118 | } 119 | ); 120 | }); 121 | } 122 | }); 123 | --------------------------------------------------------------------------------