mvp working

This commit is contained in:
nick comer
2023-11-19 17:11:19 -05:00
committed by nick comer
parent d5c4e18a17
commit 92e99149c1
66 changed files with 2694 additions and 1735 deletions

132
src/background/main.ts Normal file
View File

@@ -0,0 +1,132 @@
declare const browser: typeof chrome;
// tabSwitches is a stack of all tab activations. every time a tab becomes
// active it is prepended (unshift) to the front of the array. this means that
// there will be duplicate IDs in the array.
//
// this is handled in 2 ways:
// 1. when the popup opens and asks for a list of tabs, we will only insert the tab
// in the result when it is _first_ seen and ignored thereafter. however, over
// time this will lead to a lot of wasted work since most of the iteration will
// be just skipping elements in this array.
//
// 2. the array is periodically "compacted" by simply running it through a uniq()
// operation, therefore reducing most wasted work most of the time.
let tabSwitches: number[] = [];
function uniq<T = any>(array: T[]): T[] {
if (array.length <= 1) {
return array;
}
const seen = new Set<T>();
const result = [];
for (let ele of array) {
if (seen.has(ele)) {
continue;
}
seen.add(ele);
result.push(ele);
}
return result;
}
setInterval(() => {
// periodically compact tabSwitches
tabSwitches = uniq(tabSwitches);
}, 1000);
try {
browser.runtime.onConnect.addListener((port) => {
port.onMessage.addListener((message) => {
switch (message.rpc) {
case "listTabs":
console.time(`rpc:listTabs:${message.id}`);
browser.tabs
.query({})
.then((tabs) => {
// filter out file:///... things, safari does not really have
// safari://... things like chrome
tabs = tabs.filter((t) => t.url || t.title);
const hit = new Map<number, boolean>(
tabs.filter((t) => !!t.id).map((t) => [t.id!, false])
);
const tabsById = new Map<number, chrome.tabs.Tab>(
tabs.filter((t) => !!t.id).map((t) => [t.id!, t])
);
const resultTabs = [];
for (let tabId of tabSwitches.slice(1)) {
if (hit.get(tabId)) {
continue;
}
hit.set(tabId, true);
const tab = tabsById.get(tabId);
if (!tab) {
continue;
}
resultTabs.push(tab);
}
for (let [tabId, didHit] of hit.entries()) {
if (didHit) {
continue;
}
const tab = tabsById.get(tabId);
if (!tab) {
continue;
}
resultTabs.push(tab);
}
port.postMessage({
result: resultTabs,
id: message.id,
});
})
.finally(() => {
console.timeEnd(`rpc:listTabs:${message.id}`);
});
return;
default:
port.postMessage({
error: `unknown rpc method: ${message.rpc}`,
id: message.id,
});
}
});
});
type Command = () => Promise<void> | void;
browser.tabs.onActivated.addListener((activeInfo) => {
if (activeInfo.tabId) {
tabSwitches.unshift(activeInfo.tabId);
}
});
const commands = new Map<string, Command>([
[
"openTabSwitcher",
async () => {
console.log("open that tab switcher!");
await (browser as any).browserAction.openPopup();
// await browser.action.openPopup();
},
],
]);
browser.commands.onCommand.addListener(async (command) => {
console.log(`received keyboard shortcut: ${command}`);
const fn = commands.get(command);
if (!fn) {
throw new Error(`unmapped command: ${command}`);
}
try {
await Promise.resolve(fn());
} catch (e) {
console.error(`command function failed: ${e}`);
}
});
} catch (e) {
// wrapping everything in a try/catch because F-ING SAFARI refuses to
// help you understand why background scripts fail. (https://developer.apple.com/forums/thread/705321)
console.error(`background startup failure: ${e}`);
}