fix(browser): handle port disconnection b/w client and worker

This commit is contained in:
dragonwocky 2023-08-03 22:34:18 +10:00
parent 025bbca44c
commit 9a1b35afd9
Signed by: dragonwocky
GPG Key ID: 7998D08F7D7BD7A8
3 changed files with 34 additions and 21 deletions

View File

@ -116,11 +116,12 @@ const initMenu = async (db) => {
}; };
export default async (api, db) => { export default async (api, db) => {
const { sendMessage } = globalThis.__enhancerApi;
await Promise.all([ await Promise.all([
overrideThemes(db), overrideThemes(db),
insertCustomStyles(db), insertCustomStyles(db),
initMenu(db), initMenu(db),
sendTelemetryPing(), sendTelemetryPing(),
]); ]);
api.sendMessage("notion-enhancer", "load-complete"); sendMessage("notion-enhancer", "load-complete");
}; };

View File

@ -33,17 +33,24 @@ const platform = IS_ELECTRON
IS_ELECTRON && !IS_RENDERER ? require(`../../../${target}`) : undefined; IS_ELECTRON && !IS_RENDERER ? require(`../../../${target}`) : undefined;
let __port; let __port;
const onMessage = (channel, listener) => { const connectToPort = () => {
if (__port) return;
__port = chrome.runtime.connect();
__port.onDisconnect.addListener(() => (__port = null));
},
onMessage = (channel, listener) => {
// from worker to client // from worker to client
if (IS_RENDERER) { if (IS_RENDERER) {
const { ipcRenderer } = require("electron"); const { ipcRenderer } = require("electron");
ipcRenderer.on(channel, listener); ipcRenderer.on(channel, listener);
} else if (!IS_ELECTRON) { } else if (!IS_ELECTRON) {
__port ??= chrome.runtime.connect(); const onMessage = (msg) => {
__port.onMessage.addListener((msg) => {
if (msg?.channel !== channel || msg?.invocation) return; if (msg?.channel !== channel || msg?.invocation) return;
listener(msg.message); listener(msg.message);
}); };
connectToPort();
__port.onMessage.addListener(onMessage);
chrome.runtime.onMessage.addListener(onMessage);
} }
}, },
sendMessage = (channel, message) => { sendMessage = (channel, message) => {
@ -52,7 +59,7 @@ const onMessage = (channel, listener) => {
const { ipcRenderer } = require("electron"); const { ipcRenderer } = require("electron");
ipcRenderer.send(channel, message); ipcRenderer.send(channel, message);
} else if (!IS_ELECTRON) { } else if (!IS_ELECTRON) {
__port ??= chrome.runtime.connect(); connectToPort();
__port.postMessage({ channel, message }); __port.postMessage({ channel, message });
} }
}, },
@ -67,7 +74,7 @@ const onMessage = (channel, listener) => {
// the browser: uses a long-lived ipc connection to // the browser: uses a long-lived ipc connection to
// pass messages and handle responses asynchronously // pass messages and handle responses asynchronously
let fulfilled; let fulfilled;
__port ??= chrome.runtime.connect(); connectToPort();
const id = crypto.randomUUID(); const id = crypto.randomUUID();
return new Promise((res, rej) => { return new Promise((res, rej) => {
__port.onMessage.addListener((msg) => { __port.onMessage.addListener((msg) => {
@ -118,7 +125,7 @@ const initDatabase = (namespace, fallbacks = {}) => {
const query = (query, args = {}) => const query = (query, args = {}) =>
IS_ELECTRON && !IS_RENDERER IS_ELECTRON && !IS_RENDERER
? globalThis.__enhancerApi.queryDatabase(namespace, query, args) ? globalThis.__enhancerApi.queryDatabase(namespace, query, args)
: invokeInWorker("notion-enhancer:db", { : invokeInWorker("notion-enhancer", {
action: "query-database", action: "query-database",
data: { namespace, query, args }, data: { namespace, query, args },
}); });

View File

@ -116,7 +116,7 @@ const initDatabase = async () => {
if (IS_ELECTRON) { if (IS_ELECTRON) {
const { ipcMain } = require("electron"), const { ipcMain } = require("electron"),
{ reloadApp } = globalThis.__enhancerApi; { reloadApp } = globalThis.__enhancerApi;
ipcMain.handle("notion-enhancer:db", ({}, message) => { ipcMain.handle("notion-enhancer", ({}, message) => {
if (message?.action !== "query-database") return; if (message?.action !== "query-database") return;
const { namespace, query, args } = message.data; const { namespace, query, args } = message.data;
return queryDatabase(namespace, query, args); return queryDatabase(namespace, query, args);
@ -128,19 +128,19 @@ if (IS_ELECTRON) {
const notionUrl = "https://www.notion.so/", const notionUrl = "https://www.notion.so/",
isNotionTab = (tab) => tab?.url?.startsWith(notionUrl); isNotionTab = (tab) => tab?.url?.startsWith(notionUrl);
const tabQueue = new Set(), const connectedTabs = new Set(),
openMenuInTabs = new Set(),
openMenu = { channel: "notion-enhancer", message: "open-menu" }, openMenu = { channel: "notion-enhancer", message: "open-menu" },
openEnhancerMenu = async (tab) => { openEnhancerMenu = async (tab) => {
if (!isNotionTab(tab)) { if (!isNotionTab(tab)) {
const windowId = chrome.windows.WINDOW_ID_CURRENT, const windowId = chrome.windows.WINDOW_ID_CURRENT;
windowTabs = await chrome.tabs.query({ windowId }); tab = (await chrome.tabs.query({ windowId })).find(isNotionTab);
tab = windowTabs.find(isNotionTab);
tab ??= await chrome.tabs.create({ url: notionUrl }); tab ??= await chrome.tabs.create({ url: notionUrl });
} }
chrome.tabs.highlight({ tabs: [tab.index] }); chrome.tabs.highlight({ tabs: [tab.index] });
if (tab.status === "complete") { if (connectedTabs.has(tab.id)) {
chrome.tabs.sendMessage(tab.id, openMenu); chrome.tabs.sendMessage(tab.id, openMenu);
} else tabQueue.add(tab.id); } else openMenuInTabs.add(tab.id);
}, },
reloadNotionTabs = async () => { reloadNotionTabs = async () => {
const windowId = chrome.windows.WINDOW_ID_CURRENT; const windowId = chrome.windows.WINDOW_ID_CURRENT;
@ -149,24 +149,29 @@ if (IS_ELECTRON) {
.forEach((tab) => chrome.tabs.reload(tab.id)); .forEach((tab) => chrome.tabs.reload(tab.id));
}; };
// listen for invoke: https://developer.chrome.com/docs/extensions/mv3/messaging/
chrome.action.onClicked.addListener(openEnhancerMenu); chrome.action.onClicked.addListener(openEnhancerMenu);
// long-lived connection for rapid two-way messaging
// b/w client and worker, primarily used for db wrapper:
// https://developer.chrome.com/docs/extensions/mv3/messaging/
chrome.runtime.onConnect.addListener((port) => { chrome.runtime.onConnect.addListener((port) => {
port.onMessage.addListener((msg, sender) => { const tabId = port.sender.tab.id;
connectedTabs.add(tabId);
port.onMessage.addListener(async (msg) => {
if (msg?.channel !== "notion-enhancer") return; if (msg?.channel !== "notion-enhancer") return;
const { message, invocation } = msg; const { message, invocation } = msg;
if (message.action === "query-database") { if (message.action === "query-database") {
const { namespace, query, args } = message.data, const { namespace, query, args } = message.data,
res = queryDatabase(namespace, query, args); res = await queryDatabase(namespace, query, args);
if (invocation) port.postMessage({ invocation, message: res }); if (invocation) port.postMessage({ invocation, message: res });
} }
if (message === "load-complete") { if (message === "load-complete") {
if (!tabQueue.has(sender?.tab?.id)) return; if (!openMenuInTabs.has(tabId)) return;
chrome.tabs.sendMessage(sender.tab.id, openMenu); openMenuInTabs.delete(tabId);
tabQueue.delete(sender.tab.id); port.postMessage(openMenu);
} }
if (message === "reload-app") reloadNotionTabs(); if (message === "reload-app") reloadNotionTabs();
}); });
port.onDisconnect.addListener(() => connectedTabs.delete(tabId));
}); });
} }