├── .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 |
--------------------------------------------------------------------------------