diff --git a/src/api/system.js b/src/api/system.js index 957699b..04b0ed4 100644 --- a/src/api/system.js +++ b/src/api/system.js @@ -123,7 +123,7 @@ const initDatabase = (namespace, fallbacks = {}) => { namespace, fallbacks, operation: type, - ...args, + args, }); return { get: (key) => operation("get", { key }), diff --git a/src/worker.js b/src/worker.js index 5ce41bd..18a1bbc 100644 --- a/src/worker.js +++ b/src/worker.js @@ -8,93 +8,19 @@ const IS_ELECTRON = typeof module !== "undefined"; -if (IS_ELECTRON) { - const { app, ipcMain } = require("electron"), - reloadApp = () => { - const args = process.argv.slice(1).filter((arg) => arg !== "--startup"); - app.relaunch({ args }); - app.exit(); - }; - - ipcMain.on("notion-enhancer", (_event, message) => { - if (message === "open-menu") { - // todo - } else if (message === "reload-app") { - reloadApp(); - } - }); - ipcMain.handle("notion-enhancer", (_event, message) => { - if (message === "get-user-data-folder") { - return app.getPath("userData"); - } - }); -} else { - const notionUrl = "https://www.notion.so/", - isNotionTab = (tab) => tab?.url?.startsWith(notionUrl); - - const tabQueue = new Set(), - openEnhancerMenu = async (tab) => { - if (!isNotionTab(tab)) { - const openTabs = await chrome.tabs.query({ - windowId: chrome.windows.WINDOW_ID_CURRENT, - }); - tab = openTabs.find(isNotionTab); - tab ??= await chrome.tabs.create({ url: notionUrl }); - } - chrome.tabs.highlight({ tabs: [tab.index] }); - if (tab.status === "complete") { - chrome.tabs.sendMessage(tab.id, { - channel: "notion-enhancer", - message: "open-menu", - }); - } else tabQueue.add(tab.id); - }, - reloadNotionTabs = async () => { - const openTabs = await chrome.tabs.query({ - windowId: chrome.windows.WINDOW_ID_CURRENT, - }), - notionTabs = openTabs.filter(isNotionTab); - notionTabs.forEach((tab) => chrome.tabs.reload(tab.id)); - }; - - // listen for invoke: https://developer.chrome.com/docs/extensions/mv3/messaging/ - - chrome.action.onClicked.addListener(openEnhancerMenu); - chrome.runtime.onMessage.addListener((msg, sender) => { - if (msg?.channel !== "notion-enhancer") return; - if (sender.tab && msg.message === "load-complete") { - if (tabQueue.has(sender.tab.id)) { - chrome.tabs.sendMessage(sender.tab.id, { - channel: "notion-enhancer", - message: "open-menu", - }); - tabQueue.delete(sender.tab.id); - } - } else if (msg.message === "reload-app") { - reloadNotionTabs(); - } - }); -} - let __db, __statements, __transactions; -const initDatabase = (namespace, fallbacks = {}) => { - if (Array.isArray(namespace)) namespace = namespace.join("__"); - namespace = namespace ? namespace + "__" : ""; - const namespaceify = (key) => - key.startsWith(namespace) ? key : namespace + key; +const initDatabase = async () => { + if (!IS_ELECTRON) return chrome.storage.local; - // schema: - // - ("agreedToTerms") -> string: semver - // - ("lastTelemetryPing") -> string: iso - // - ("telemetryEnabled") -> boolean - // - ("profileIds") -> $profileId[] - // - ("activeProfile") -> $profileId - // - $profileId: ("profileName") -> string - // - $profileId__enabledMods: ($modId) -> boolean - // - $profileId__$modId: ($optionKey) -> value - - __db ??= (async () => { - if (!IS_ELECTRON) return; + // schema: + // - ("agreedToTerms") -> string: semver + // - ("lastTelemetryPing") -> string: iso + // - ("telemetryEnabled") -> boolean + // - ("profileIds") -> $profileId[] + // - ("activeProfile") -> $profileId + // - $profileId: ("profileName") -> string + // - $profileId__enabledMods: ($modId) -> boolean + // - $profileId__$modId: ($optionKey) -> value const table = "kvstore", { app } = require("electron"), @@ -127,58 +53,156 @@ const initDatabase = (namespace, fallbacks = {}) => { }), }; return db; - })(); + }, + executeOperation = async (namespace, fallbacks, operation, args) => { + namespace ??= ""; + if (Array.isArray(namespace)) namespace = namespace.join("__"); + if (namespace?.length) namespace += "__"; + const namespaceify = (key) => + key.startsWith(namespace) ? key : namespace + key; - return { - async get(key) { - await __db; - let value; - const fallback = fallbacks[key]; - key = namespaceify(key); - if (IS_ELECTRON) { - try { - value = JSON.parse(__statements.select.get(key)?.value); - } catch {} - } else value = (await chrome.storage.local.get([key]))[key]; - return value ?? fallback; - }, - async set(key, value) { - await __db; - key = namespaceify(key); - return IS_ELECTRON - ? // returns true instead of transaction completion data type - (__transactions.set({ [key]: JSON.stringify(value) }), true) - : chrome.storage.local.set({ [key]: value }); - }, - async remove(keys) { - await __db; - keys = Array.isArray(keys) ? keys : [keys]; - keys = keys.map(namespaceify); - return IS_ELECTRON - ? (__transactions.remove(keys), true) - : chrome.storage.local.remove(keys); - }, - async export() { - await __db; - // returns key/value pairs within scope w/out namespace - // prefix e.g. to streamline importing from one profile and - // then into another (where a diff. namespace is used) - let entries = IS_ELECTRON - ? __statements.dump.all().map(({ key, value }) => [key, value]) - : Object.entries(await chrome.storage.local.get()); - entries = entries - .filter(([key]) => key.startsWith(namespace)) - .map(([key, value]) => [key.slice(namespace.length), value]); - return Object.fromEntries(entries); - }, - async import(obj) { - await __db; - let entries = Object.entries(obj); - entries = entries.map(([key, value]) => [namespace + key, value]); - entries = Object.fromEntries(entries); - return IS_ELECTRON - ? (__transactions.set(entries), true) - : chrome.storage.local.set(entries); - }, + await (__db ??= initDatabase()); + switch (operation) { + case "get": { + const key = namespaceify(args.key); + let value; + if (IS_ELECTRON) { + try { + value = JSON.parse(__statements.select.get(key)?.value); + } catch {} + } else value = (await chrome.storage.local.get([key]))[key]; + return value ?? fallbacks[key]; + } + case "set": { + const key = namespaceify(args.key), + value = args.value; + return IS_ELECTRON + ? // returns true instead of transaction completion data type + (__transactions.set({ [key]: JSON.stringify(value) }), true) + : chrome.storage.local.set({ [key]: value }); + } + case "remove": { + let { keys } = args; + if (!Array.isArray(args.keys)) keys = [keys]; + keys = keys.map(namespaceify); + return IS_ELECTRON + ? (__transactions.remove(keys), true) + : chrome.storage.local.remove(keys); + } + + case "export": { + // returns key/value pairs within scope w/out namespace + // prefix e.g. to streamline importing from one profile and + // then into another (where a diff. namespace is used) + let entries = IS_ELECTRON + ? __statements.dump.all().map(({ key, value }) => [key, value]) + : Object.entries(await chrome.storage.local.get()); + entries = entries + .filter(([key]) => key.startsWith(namespace)) + .map(([key, value]) => [key.slice(namespace.length), value]); + return Object.fromEntries(entries); + } + case "import": { + let entries = Object.entries(args.obj); + entries = entries.map(([key, value]) => [namespace + key, value]); + entries = Object.fromEntries(entries); + return IS_ELECTRON + ? (__transactions.set(entries), true) + : chrome.storage.local.set(entries); + } + } }; -}; + +if (IS_ELECTRON) { + const { app, ipcMain } = require("electron"), + reloadApp = () => { + const args = process.argv.slice(1).filter((arg) => arg !== "--startup"); + app.relaunch({ args }); + app.exit(); + }; + + ipcMain.handle("notion-enhancer:db", (_event, message) => { + return executeOperation( + message.namespace, + message.fallbacks, + message.operation, + message.args + ); + }); + + ipcMain.on("notion-enhancer", (_event, message) => { + if (message === "open-menu") { + // todo + } else if (message === "reload-app") { + reloadApp(); + } + }); +} else { + const notionUrl = "https://www.notion.so/", + isNotionTab = (tab) => tab?.url?.startsWith(notionUrl); + + const tabQueue = new Set(), + openEnhancerMenu = async (tab) => { + if (!isNotionTab(tab)) { + const openTabs = await chrome.tabs.query({ + windowId: chrome.windows.WINDOW_ID_CURRENT, + }); + tab = openTabs.find(isNotionTab); + tab ??= await chrome.tabs.create({ url: notionUrl }); + } + chrome.tabs.highlight({ tabs: [tab.index] }); + if (tab.status === "complete") { + chrome.tabs.sendMessage(tab.id, { + channel: "notion-enhancer", + message: "open-menu", + }); + } else tabQueue.add(tab.id); + }, + reloadNotionTabs = async () => { + const openTabs = await chrome.tabs.query({ + windowId: chrome.windows.WINDOW_ID_CURRENT, + }), + notionTabs = openTabs.filter(isNotionTab); + notionTabs.forEach((tab) => chrome.tabs.reload(tab.id)); + }; + + // listen for invoke: https://developer.chrome.com/docs/extensions/mv3/messaging/ + ipcMain.handle("notion-enhancer:db", (_event, message) => { + return executeOperation( + message.namespace, + message.fallbacks, + message.operation, + message.args + ); + }); + + chrome.action.onClicked.addListener(openEnhancerMenu); + chrome.runtime.onConnect.addListener((port) => { + port.onMessage.addListener((msg, sender) => { + if (msg?.channel === "notion-enhancer:db") { + const { invocation } = msg, + res = executeOperation( + msg.namespace, + msg.fallbacks, + msg.operation, + msg.args + ); + if (invocation) port.postMessage({ invocation, message: res }); + } + + if (msg?.channel === "notion-enhancer") { + if (sender.tab && msg.message === "load-complete") { + if (tabQueue.has(sender.tab.id)) { + chrome.tabs.sendMessage(sender.tab.id, { + channel: "notion-enhancer", + message: "open-menu", + }); + tabQueue.delete(sender.tab.id); + } + } else if (msg.message === "reload-app") { + reloadNotionTabs(); + } + } + }); + }); +}