Skip to content

Commit f54c9bf

Browse files
authored
Re-inject site scripts when background process wakes up from idle (#40)
* Re-inject site scripts when background process wakes up from idle * Make the onDOMContentLoaded even listener a global listener so it persists * Inject GK buttons into current tabs on startup and on install * Clear injectionDomains cache instead of reloading extension when permissions are granted
1 parent da53b84 commit f54c9bf

File tree

3 files changed

+62
-35
lines changed

3 files changed

+62
-35
lines changed

src/background.ts

Lines changed: 60 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,10 @@
1-
import type { WebNavigation } from 'webextension-polyfill';
2-
import { runtime, scripting, tabs, webNavigation } from 'webextension-polyfill';
1+
import { runtime, scripting, storage, tabs, webNavigation } from 'webextension-polyfill';
32
import { fetchUser } from './gkApi';
43
import { injectionScope as inject_azureDevops } from './hosts/azureDevops';
54
import { injectionScope as inject_bitbucket } from './hosts/bitbucket';
65
import { injectionScope as inject_github } from './hosts/github';
76
import { injectionScope as inject_gitlab } from './hosts/gitlab';
8-
import { refreshPermissions } from './permissions-helper';
7+
import { domainToMatchPattern, refreshPermissions } from './permissions-helper';
98
import { getEnterpriseConnections, GKDotDevUrl, PermissionsGrantedMessage, PopupInitMessage } from './shared';
109
import type { CacheContext } from './types';
1110

@@ -23,6 +22,19 @@ const DefaultInjectionDomains: InjectionDomains = {
2322
azureDevops: ['dev.azure.com'],
2423
};
2524

25+
webNavigation.onDOMContentLoaded.addListener(async details => {
26+
const injectionDomains = await getInjectionDomains();
27+
28+
const injectionFn = getInjectionFn(details.url, injectionDomains);
29+
if (injectionFn) {
30+
void scripting.executeScript({
31+
target: { tabId: details.tabId },
32+
func: injectionFn,
33+
args: [details.url, GKDotDevUrl],
34+
});
35+
}
36+
});
37+
2638
webNavigation.onHistoryStateUpdated.addListener(details => {
2739
// used to detect when the user navigates to a different page in the same tab
2840
const url = new URL(details.url);
@@ -39,14 +51,55 @@ runtime.onMessage.addListener(async msg => {
3951
const context: CacheContext = {};
4052
return refreshPermissions(context);
4153
} else if (msg === PermissionsGrantedMessage) {
42-
// Reload extension to update injection listener
43-
runtime.reload();
54+
await storage.session.remove('injectionDomains');
4455
return undefined;
4556
}
4657
console.error('Recevied unknown runtime message', msg);
4758
return undefined;
4859
});
4960

61+
runtime.onInstalled.addListener(injectIntoCurrentTabs);
62+
runtime.onStartup.addListener(injectIntoCurrentTabs);
63+
64+
async function injectIntoCurrentTabs() {
65+
const injectionDomains = await getInjectionDomains();
66+
const allDomains = Object.values<string[]>(injectionDomains as any).flat();
67+
68+
const currentTabs = await tabs.query({
69+
url: allDomains.map(domainToMatchPattern),
70+
status: 'complete',
71+
discarded: false,
72+
});
73+
currentTabs.forEach(tab => {
74+
if (tab.id && tab.url) {
75+
const injectionFn = getInjectionFn(tab.url, injectionDomains);
76+
if (injectionFn) {
77+
void scripting.executeScript({
78+
target: { tabId: tab.id },
79+
func: injectionFn,
80+
args: [tab.url, GKDotDevUrl],
81+
});
82+
}
83+
}
84+
});
85+
}
86+
87+
async function getInjectionDomains() {
88+
let { injectionDomains } = (await storage.session.get('injectionDomains')) as {
89+
injectionDomains?: InjectionDomains;
90+
};
91+
if (!injectionDomains) {
92+
const context: CacheContext = {};
93+
// This removes unneded permissions
94+
await refreshPermissions(context);
95+
96+
injectionDomains = await computeInjectionDomains(context);
97+
await storage.session.set({ injectionDomains: injectionDomains });
98+
}
99+
100+
return injectionDomains;
101+
}
102+
50103
async function computeInjectionDomains(context: CacheContext) {
51104
const injectionDomains = structuredClone(DefaultInjectionDomains);
52105
const enterpriseConnections = await getEnterpriseConnections(context);
@@ -63,33 +116,14 @@ async function computeInjectionDomains(context: CacheContext) {
63116
return injectionDomains;
64117
}
65118

66-
async function addInjectionListener(context: CacheContext) {
67-
const injectionDomains = await computeInjectionDomains(context);
68-
const allDomains = Object.values<string[]>(injectionDomains as any).flat();
69-
70-
// note: This is a closure over injectionDomains
71-
const injectScript = (details: WebNavigation.OnDOMContentLoadedDetailsType) => {
72-
void scripting.executeScript({
73-
target: { tabId: details.tabId },
74-
// injectImmediately: true,
75-
func: getInjectionFn(details.url, injectionDomains),
76-
args: [details.url, GKDotDevUrl],
77-
});
78-
};
79-
80-
webNavigation.onDOMContentLoaded.addListener(injectScript, {
81-
url: allDomains.map(domain => ({ hostContains: domain })),
82-
});
83-
}
84-
85119
function urlHostHasDomain(url: URL, domains: string[]): boolean {
86120
return domains.some(domain => url.hostname.endsWith(domain));
87121
}
88122

89123
function getInjectionFn(
90124
rawUrl: string,
91125
injectionDomains: InjectionDomains,
92-
): (url: string, gkDotDevUrl: string) => void {
126+
): ((url: string, gkDotDevUrl: string) => void) | null {
93127
const url = new URL(rawUrl);
94128
if (urlHostHasDomain(url, injectionDomains.github)) {
95129
return inject_github;
@@ -107,20 +141,12 @@ function getInjectionFn(
107141
return inject_azureDevops;
108142
}
109143

110-
console.error('Unsupported host');
111-
throw new Error('Unsupported host');
144+
return null;
112145
}
113146

114147
async function main() {
115148
// The fetchUser function also updates the extension icon if the user is logged in
116149
await fetchUser();
117-
118-
const context: CacheContext = {};
119-
// This removes unneded permissions
120-
await refreshPermissions(context);
121-
// NOTE: This may request hosts that we may not have permissions for, which will log errors for the extension
122-
// This does not cause any issues, and eliminating the errors requires more logic
123-
await addInjectionListener(context);
124150
}
125151

126152
void main();

src/permissions-helper.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import { permissions } from 'webextension-polyfill';
33
import { arrayDifference, CloudProviders, getEnterpriseConnections } from './shared';
44
import type { CacheContext } from './types';
55

6-
function domainToMatchPattern(domain: string): string {
6+
export function domainToMatchPattern(domain: string): string {
77
return `*://*.${domain}/*`;
88
}
99

src/popup/components/RequestPermissionsBanner.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export const RequestPermissionsBanner = ({ permissionsRequest }: { permissionsRe
3333
const granted = await permissions.request(permissionsRequest.request);
3434
if (granted) {
3535
await sendPermissionsGranted();
36+
window.close();
3637
}
3738
}}
3839
>

0 commit comments

Comments
 (0)