summaryrefslogtreecommitdiff
path: root/source/background.ts
blob: 4dcf17bc3b0eb336b7385c83c87c650df7a7e622 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// eslint-disable-next-line import/no-extraneous-dependencies
import {type Runtime} from 'webextension-polyfill';
import 'webext-dynamic-content-scripts';
import {globalCache} from 'webext-storage-cache'; // Also needed to regularly clear the cache
import {isSafari} from 'webext-detect-page';
import {objectKeys} from 'ts-extras';
import addDomainPermissionToggle from 'webext-domain-permission-toggle';

import optionsStorage from './options-storage.js';
import isDevelopmentVersion from './helpers/is-development-version.js';
import getStorageBytesInUse from './helpers/used-storage.js';
import {doesBrowserActionOpenOptions} from './helpers/feature-utils.js';
import {styleHotfixes} from './helpers/hotfix.js';

const {version} = browser.runtime.getManifest();

// GHE support
addDomainPermissionToggle();

const messageHandlers = {
	async openUrls(urls: string[], {tab}: Runtime.MessageSender) {
		for (const [i, url] of urls.entries()) {
			void browser.tabs.create({
				url,
				index: tab!.index + i + 1,
				active: false,
			});
		}
	},
	async closeTab(_: any, {tab}: Runtime.MessageSender) {
		void browser.tabs.remove(tab!.id!);
	},
	async fetch(url: string) {
		const response = await fetch(url);
		return response.text();
	},
	async fetchJSON(url: string) {
		const response = await fetch(url);
		return response.json();
	},
	async openOptionsPage() {
		return browser.runtime.openOptionsPage();
	},
	async getStyleHotfixes() {
		return styleHotfixes.get(version);
	},
	// They must return a promise to mark the message as handled
} satisfies Record<string, (...arguments_: any[]) => Promise<any>>;

browser.runtime.onMessage.addListener((message: typeof messageHandlers, sender): Promise<unknown> | void => {
	for (const id of objectKeys(message)) {
		if (id in messageHandlers) {
			return messageHandlers[id](message[id], sender);
		}
	}
});

browser.browserAction.onClicked.addListener(async tab => {
	if (doesBrowserActionOpenOptions) {
		void browser.runtime.openOptionsPage();
		return;
	}

	const {actionUrl} = await optionsStorage.getAll();
	void browser.tabs.create({
		openerTabId: tab.id,
		url: actionUrl || 'https://github.com',
	});
});

async function hasUsedStorage(): Promise<boolean> {
	return (
		await getStorageBytesInUse('sync') > 0
		|| Number(await getStorageBytesInUse('local')) > 0
	);
}

async function isFirstInstall(suggestedReason: string): Promise<boolean> {
	return (
		// Always exclude local installs from the welcome screen
		!isDevelopmentVersion()

		// Only if the reason is explicitly "install"
		&& suggestedReason === 'install'

		// Safari reports "install" even on updates #5412
		&& !(isSafari() && await hasUsedStorage())
	);
}

browser.runtime.onInstalled.addListener(async ({reason}) => {
	// Only notify on install
	if (await isFirstInstall(reason)) {
		await browser.tabs.create({
			url: 'https://github.com/refined-github/refined-github/issues/3543',
		});
	}

	if (isDevelopmentVersion()) {
		await globalCache.clear();
	}
});