mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-02 19:59:03 +00:00
wip: migrate db ops to use ipc (nodeintegration is disabled in latest app)
This commit is contained in:
parent
8e809d4233
commit
bf07257ae8
2
LICENSE
2
LICENSE
@ -1,6 +1,6 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
Copyright (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
|
2
bin.mjs
2
bin.mjs
@ -2,7 +2,7 @@
|
||||
|
||||
/**
|
||||
* notion-enhancer
|
||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
|
@ -35,8 +35,8 @@
|
||||
"notion-enhancer"
|
||||
],
|
||||
"dependencies": {
|
||||
"@electron/asar": "^3.2.2",
|
||||
"@electron/asar": "^3.2.4",
|
||||
"arg": "^5.0.2",
|
||||
"chalk-template": "^0.4.0"
|
||||
"chalk-template": "^1.0.0"
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* notion-enhancer
|
||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* notion-enhancer
|
||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
@ -40,7 +40,7 @@ const patches = {
|
||||
schemePrefix.length,
|
||||
-(search.length + hash.length) || undefined
|
||||
)}\`;
|
||||
callback({
|
||||
return callback({
|
||||
data: require("fs").createReadStream(require("path").resolve(\`\${__dirname}/\${filePath}\`)),
|
||||
headers: { "content-type": require("notion-enhancer/vendor/content-types.min.js").get(fileExt) },
|
||||
});
|
||||
@ -52,8 +52,8 @@ const patches = {
|
||||
|
||||
"main/systemMenu": async (scriptContent) => {
|
||||
// exposes template for modification
|
||||
const searchValue = "electron_1.Menu.setApplicationMenu(menu);",
|
||||
replaceValue = `${searchValue} return template;`;
|
||||
const searchValue = "}\nexports.setupSystemMenu = setupSystemMenu;",
|
||||
replaceValue = ` return template;\n${searchValue}`;
|
||||
if (scriptContent.includes(replaceValue)) return scriptContent;
|
||||
return scriptContent.replace(searchValue, replaceValue);
|
||||
},
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* notion-enhancer
|
||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
|
@ -1,88 +0,0 @@
|
||||
/**
|
||||
* notion-enhancer
|
||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const platform = navigator.userAgent.includes("Firefox")
|
||||
? "firefox"
|
||||
: "chromium",
|
||||
version = chrome.runtime.getManifest().version,
|
||||
enhancerUrl = (target) => chrome.runtime.getURL(target);
|
||||
|
||||
const readFile = async (file) => {
|
||||
file = file.startsWith("http") ? file : enhancerUrl(file);
|
||||
const res = await fetch(file);
|
||||
return await res.text();
|
||||
},
|
||||
readJson = async (file) => {
|
||||
file = file.startsWith("http") ? file : enhancerUrl(file);
|
||||
const res = await fetch(file);
|
||||
return await res.json();
|
||||
},
|
||||
reloadApp = () => {
|
||||
chrome.runtime.sendMessage({
|
||||
channel: "notion-enhancer",
|
||||
message: "reload-app",
|
||||
});
|
||||
};
|
||||
|
||||
const sendMessage = (channel, message) => {
|
||||
chrome.runtime.sendMessage({ channel, message });
|
||||
},
|
||||
onMessage = (channel, listener) => {
|
||||
chrome.runtime.onMessage.addListener((msg) => {
|
||||
if (msg?.channel === channel) listener(msg.message);
|
||||
});
|
||||
};
|
||||
|
||||
const initDatabase = (namespace, fallbacks = {}) => {
|
||||
if (Array.isArray(namespace)) namespace = namespace.join("__");
|
||||
namespace = namespace ? namespace + "__" : "";
|
||||
const namespaceify = (key) =>
|
||||
key.startsWith(namespace) ? key : namespace + key;
|
||||
return {
|
||||
get: async (key) => {
|
||||
const fallback = fallbacks[key];
|
||||
key = namespaceify(key);
|
||||
return (await chrome.storage.local.get([key]))[key] ?? fallback;
|
||||
},
|
||||
set: (key, value) => {
|
||||
key = namespaceify(key);
|
||||
return chrome.storage.local.set({ [key]: value });
|
||||
},
|
||||
remove: (keys) => {
|
||||
keys = Array.isArray(keys) ? keys : [keys];
|
||||
keys = keys.map(namespaceify);
|
||||
return chrome.storage.local.remove(keys);
|
||||
},
|
||||
export: async () => {
|
||||
const obj = await chrome.storage.local.get();
|
||||
if (!namespace) return obj;
|
||||
const entries = Object.entries(obj)
|
||||
.filter(([key]) => key.startsWith(namespace))
|
||||
.map(([key, value]) => [key.slice(namespace.length), value]);
|
||||
return Object.fromEntries(entries);
|
||||
},
|
||||
import: (obj) => {
|
||||
const entries = Object.entries(obj) //
|
||||
.map(([key, value]) => [namespace + key, value]);
|
||||
return chrome.storage.local.set(Object.fromEntries(entries));
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
globalThis.__enhancerApi ??= {};
|
||||
Object.assign(globalThis.__enhancerApi, {
|
||||
platform,
|
||||
version,
|
||||
enhancerUrl,
|
||||
readFile,
|
||||
readJson,
|
||||
reloadApp,
|
||||
sendMessage,
|
||||
onMessage,
|
||||
initDatabase,
|
||||
});
|
@ -1,160 +0,0 @@
|
||||
/**
|
||||
* notion-enhancer
|
||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const fs = require("fs"),
|
||||
path = require("path"),
|
||||
notionRequire = (target) => require(`../../../${target}`);
|
||||
|
||||
const platform = process.platform,
|
||||
version = require("notion-enhancer/package.json").version,
|
||||
enhancerUrl = (target) =>
|
||||
`notion://www.notion.so/__notion-enhancer/${target.replace(/^\//, "")}`;
|
||||
|
||||
const readFile = (file) => {
|
||||
// prettier-ignore
|
||||
file = file.replace(/^https:\/\/www\.notion\.so\//, "notion://www.notion.so/");
|
||||
const useFetch = file.startsWith("http") || file.startsWith("notion://");
|
||||
if (useFetch) return fetch(file).then((res) => res.text());
|
||||
return fs.readFileSync(path.resolve(`${__dirname}/../${file}`), "utf-8");
|
||||
},
|
||||
readJson = (file) => {
|
||||
// prettier-ignore
|
||||
file = file.replace(/^https:\/\/www\.notion\.so\//, "notion://www.notion.so/");
|
||||
const useFetch = file.startsWith("http") || file.startsWith("notion://");
|
||||
if (useFetch) return fetch(file).then((res) => res.json());
|
||||
return require(path.resolve(`${__dirname}/../${file}`));
|
||||
},
|
||||
reloadApp = () => {
|
||||
const { app, ipcRenderer } = require("electron");
|
||||
if (app) {
|
||||
const args = process.argv.slice(1).filter((arg) => arg !== "--startup");
|
||||
app.relaunch({ args });
|
||||
app.exit();
|
||||
} else ipcRenderer.send("notion-enhancer", "reload-app");
|
||||
};
|
||||
|
||||
const sendMessage = (channel, message) => {
|
||||
const { ipcRenderer } = require("electron");
|
||||
ipcRenderer.send(channel, message);
|
||||
},
|
||||
onMessage = (channel, listener) => {
|
||||
const { ipcRenderer } = require("electron");
|
||||
ipcRenderer.on(channel, listener);
|
||||
};
|
||||
|
||||
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;
|
||||
|
||||
__db ??= (async () => {
|
||||
const { app, ipcRenderer } = require("electron"),
|
||||
isRenderer = process?.type === "renderer",
|
||||
userData = isRenderer
|
||||
? await ipcRenderer.invoke("notion-enhancer", "get-user-data-folder")
|
||||
: app.getPath("userData");
|
||||
|
||||
const table = "settings",
|
||||
sqlite = require("better-sqlite3"),
|
||||
db = sqlite(path.resolve(`${userData}/notion-enhancer.db`)),
|
||||
init = db.prepare(`CREATE TABLE IF NOT EXISTS ${table} (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
)`);
|
||||
init.run();
|
||||
|
||||
// schema:
|
||||
// - ("agreedToTerms") -> string: semver
|
||||
// - ("lastTelemetryPing") -> string: iso
|
||||
// - ("telemetryEnabled") -> boolean
|
||||
// - ("profileIds") -> $profileId[]
|
||||
// - ("activeProfile") -> $profileId
|
||||
// - $profileId: ("profileName") -> string
|
||||
// - $profileId__enabledMods: ($modId) -> boolean
|
||||
// - $profileId__$modId: ($optionKey) -> value
|
||||
|
||||
__statements = {
|
||||
insert: db.prepare(`INSERT INTO ${table} (key, value) VALUES (?, ?)`),
|
||||
update: db.prepare(`UPDATE ${table} SET value = ? WHERE key = ?`),
|
||||
select: db.prepare(`SELECT * FROM ${table} WHERE key = ? LIMIT 1`),
|
||||
delete: db.prepare(`DELETE FROM ${table} WHERE key = ?`),
|
||||
dump: db.prepare(`SELECT * FROM ${table}`),
|
||||
};
|
||||
__transactions = {
|
||||
remove: db.transaction((arr) => {
|
||||
arr.forEach((key) => __statements.delete.run(key));
|
||||
}),
|
||||
set: db.transaction((obj) => {
|
||||
for (const key in obj) {
|
||||
if (__statements.select.get(key) === undefined) {
|
||||
__statements.insert.run(key, obj[key]);
|
||||
} else __statements.update.run(obj[key], key);
|
||||
}
|
||||
}),
|
||||
};
|
||||
return db;
|
||||
})();
|
||||
|
||||
return {
|
||||
get: async (key) => {
|
||||
await __db;
|
||||
const fallback = fallbacks[key];
|
||||
key = namespaceify(key);
|
||||
try {
|
||||
const value = JSON.parse(__statements.select.get(key)?.value);
|
||||
return value ?? fallback;
|
||||
} catch {}
|
||||
return fallback;
|
||||
},
|
||||
set: async (key, value) => {
|
||||
await __db;
|
||||
key = namespaceify(key);
|
||||
value = JSON.stringify(value);
|
||||
__transactions.set({ [key]: value });
|
||||
return true;
|
||||
},
|
||||
remove: async (keys) => {
|
||||
await __db;
|
||||
keys = Array.isArray(keys) ? keys : [keys];
|
||||
keys = keys.map(namespaceify);
|
||||
__transactions.remove(keys);
|
||||
return true;
|
||||
},
|
||||
export: async () => {
|
||||
await __db;
|
||||
const entries = __statements.dump
|
||||
.all()
|
||||
.filter(({ key }) => key.startsWith(namespace))
|
||||
.map(({ key, value }) => [key.slice(namespace.length), value]);
|
||||
return Object.fromEntries(entries);
|
||||
},
|
||||
import: async (obj) => {
|
||||
await __db;
|
||||
const entries = Object.entries(obj) //
|
||||
.map(([key, value]) => [key.slice(namespace.length), value]);
|
||||
__transactions.set(Object.fromEntries(entries));
|
||||
return true;
|
||||
},
|
||||
};
|
||||
};
|
||||
|
||||
globalThis.__enhancerApi ??= {};
|
||||
Object.assign(globalThis.__enhancerApi, {
|
||||
notionRequire,
|
||||
platform,
|
||||
version,
|
||||
enhancerUrl,
|
||||
readFile,
|
||||
readJson,
|
||||
reloadApp,
|
||||
sendMessage,
|
||||
onMessage,
|
||||
initDatabase,
|
||||
});
|
@ -49,7 +49,7 @@ attachObserver();
|
||||
let keyListeners = [];
|
||||
// accelerators approximately match electron accelerators.
|
||||
// logic used when recording hotkeys in menu matches logic used
|
||||
// when triggering hotkeys ∴ detection should be reliable.
|
||||
// when triggering hotkeys => detection should be reliable.
|
||||
// default hotkeys using "alt" may trigger an altcode or
|
||||
// accented character on some keyboard layouts (not recommended).
|
||||
const modifierAliases = [
|
||||
|
@ -1,6 +1,6 @@
|
||||
/**
|
||||
* notion-enhancer
|
||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
|
150
src/api/system.js
Normal file
150
src/api/system.js
Normal file
@ -0,0 +1,150 @@
|
||||
/**
|
||||
* notion-enhancer
|
||||
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const IS_ELECTRON = typeof module !== "undefined";
|
||||
|
||||
// expected values: 'linux', 'win32', 'darwin' (== macos), 'firefox'
|
||||
// and 'chromium' (inc. chromium-based browsers like edge and brave)
|
||||
// other possible values: 'aix', 'freebsd', 'openbsd', 'sunos'
|
||||
const platform = IS_ELECTRON
|
||||
? process.platform
|
||||
: navigator.userAgent.includes("Firefox")
|
||||
? "firefox"
|
||||
: "chromium",
|
||||
// currently installed version of the notion-enhancer
|
||||
version = IS_ELECTRON
|
||||
? require("notion-enhancer/package.json").version
|
||||
: chrome.runtime.getManifest().version,
|
||||
// forms a url to a notion-enhancer asset or source file
|
||||
// that can be accessed reliably over http
|
||||
enhancerUrl = (target) =>
|
||||
IS_ELECTRON
|
||||
? `notion://www.notion.so/__notion-enhancer/${target.replace(/^\//, "")}`
|
||||
: chrome.runtime.getURL(target),
|
||||
// should only be used from an electron main process, does nothing elsewhere
|
||||
notionRequire = (target) => IS_ELECTRON && require(`../../../${target}`);
|
||||
|
||||
let __port;
|
||||
const onMessage = (channel, listener) => {
|
||||
// from worker to client
|
||||
if (IS_ELECTRON) {
|
||||
const { ipcRenderer } = require("electron");
|
||||
ipcRenderer.on(channel, listener);
|
||||
} else {
|
||||
__port ??= chrome.runtime.connect();
|
||||
__port.onMessage.addListener((msg) => {
|
||||
if (msg?.channel !== channel || msg?.invocation) return;
|
||||
listener(msg.message);
|
||||
});
|
||||
}
|
||||
},
|
||||
sendMessage = (channel, message) => {
|
||||
// to worker from client
|
||||
if (IS_ELECTRON) {
|
||||
const { ipcRenderer } = require("electron");
|
||||
ipcRenderer.send(channel, message);
|
||||
} else {
|
||||
__port ??= chrome.runtime.connect();
|
||||
__port.postMessage({ channel, message });
|
||||
}
|
||||
},
|
||||
invokeInWorker = (channel, message) => {
|
||||
if (IS_ELECTRON) {
|
||||
const { ipcRenderer } = require("electron");
|
||||
return ipcRenderer.invoke(channel, message);
|
||||
} else {
|
||||
// polyfills the electron.ipcRenderer.invoke method in
|
||||
// the browser: uses a long-lived ipc connection to
|
||||
// pass messages and handle responses asynchronously
|
||||
let fulfilled;
|
||||
__port ??= chrome.runtime.connect();
|
||||
const id = crypto.randomUUID();
|
||||
return new Promise((res, rej) => {
|
||||
__port.onMessage.addListener((msg) => {
|
||||
if (msg?.invocation !== id || fulfilled) return;
|
||||
fulfilled = true;
|
||||
res(msg.message);
|
||||
});
|
||||
__port.postMessage({ channel, message, invocation: id });
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const readFile = (file) => {
|
||||
if (IS_ELECTRON) {
|
||||
// read directly from filesys if possible,
|
||||
// treating notion-enhancer/src as fs root
|
||||
if (!file.startsWith("http")) {
|
||||
const fsp = require("fs/promises"),
|
||||
{ resolve } = require("path");
|
||||
return fsp.readFile(resolve(`${__dirname}/../${file}`), "utf-8");
|
||||
}
|
||||
// prefer using versions of files cached by the app
|
||||
// or routed through the notion-enhancer's url interception
|
||||
const notionProtocol = "notion://www.notion.so/";
|
||||
file = file.replace(/^https:\/\/www\.notion\.so\//, notionProtocol);
|
||||
} else file = file.startsWith("http") ? file : enhancerUrl(file);
|
||||
return fetch(file).then((res) => res.text());
|
||||
},
|
||||
readJson = (file) => {
|
||||
// as above, uses require instead of readFile
|
||||
// and res.json() instead of res.text() to return
|
||||
// json content of file in object form
|
||||
if (IS_ELECTRON) {
|
||||
if (!file.startsWith("http")) {
|
||||
const { resolve } = require("path");
|
||||
return require(resolve(`${__dirname}/../${file}`), "utf-8");
|
||||
}
|
||||
const notionProtocol = "notion://www.notion.so/";
|
||||
file = file.replace(/^https:\/\/www\.notion\.so\//, notionProtocol);
|
||||
} else file = file.startsWith("http") ? file : enhancerUrl(file);
|
||||
return fetch(file).then((res) => res.json());
|
||||
},
|
||||
reloadApp = () => {
|
||||
if (IS_ELECTRON && require("electron").app) {
|
||||
const { app } = require("electron"),
|
||||
args = process.argv.slice(1).filter((arg) => arg !== "--startup");
|
||||
app.relaunch({ args });
|
||||
app.exit();
|
||||
} else sendMessage("notion-enhancer", "reload-app");
|
||||
};
|
||||
|
||||
const initDatabase = (namespace, fallbacks = {}) => {
|
||||
// all db operations are performed via ipc:
|
||||
// with nodeintegration disabled, sqlite cannot
|
||||
// be require()-d from the renderer process
|
||||
const operation = (type, args = {}) =>
|
||||
invokeInWorker("notion-enhancer:db", {
|
||||
namespace,
|
||||
fallbacks,
|
||||
operation: type,
|
||||
...args,
|
||||
});
|
||||
return {
|
||||
get: (key) => operation("get", { key }),
|
||||
set: (key, value) => operation("set", { key, value }),
|
||||
remove: (keys) => operation("remove", { keys }),
|
||||
export: () => operation("export"),
|
||||
import: (obj) => operation("import", { obj }),
|
||||
};
|
||||
};
|
||||
|
||||
globalThis.__enhancerApi ??= {};
|
||||
Object.assign(globalThis.__enhancerApi, {
|
||||
platform,
|
||||
version,
|
||||
enhancerUrl,
|
||||
notionRequire,
|
||||
onMessage,
|
||||
sendMessage,
|
||||
invokeInWorker,
|
||||
readFile,
|
||||
readJson,
|
||||
reloadApp,
|
||||
initDatabase,
|
||||
});
|
@ -43,9 +43,9 @@ function List({ id, mods, description }) {
|
||||
const _get = () => isEnabled(mod.id),
|
||||
_set = async (enabled) => {
|
||||
await setEnabled(mod.id, enabled);
|
||||
// only one theme of each mode may be
|
||||
// enabled at a time ∴ disable others
|
||||
// on theme of same mode enabled
|
||||
// only one theme may be enabled per
|
||||
// mode at a time => auto-disable other
|
||||
// enabled themes of matching mode
|
||||
if (enabled && id === "themes") {
|
||||
const isDark = mod.tags.includes("dark"),
|
||||
isLight = mod.tags.includes("light");
|
||||
|
@ -197,9 +197,9 @@ useState(["rerender"], async () => {
|
||||
if (!theme || !icon) return;
|
||||
// chrome extensions run in an isolated execution context
|
||||
// but extension:// pages can access chrome apis
|
||||
// ∴ notion-enhancer api is imported directly
|
||||
// => notion-enhancer api is imported directly
|
||||
if (typeof globalThis.__enhancerApi === "undefined") {
|
||||
await import("../../api/browser.js");
|
||||
await import("../../api/system.js");
|
||||
// in electron this isn't necessary, as a) scripts are
|
||||
// not running in an isolated execution context and b)
|
||||
// the notion:// protocol csp bypass allows scripts to
|
||||
|
@ -14,7 +14,7 @@ const isElectron = () => {
|
||||
};
|
||||
|
||||
if (isElectron()) {
|
||||
require("./api/electron.cjs");
|
||||
require("./api/system.js");
|
||||
require("./api/mods.js");
|
||||
const { enhancerUrl } = globalThis.__enhancerApi,
|
||||
{ getMods, isEnabled, modDatabase } = globalThis.__enhancerApi;
|
||||
@ -48,7 +48,7 @@ if (isElectron()) {
|
||||
} else {
|
||||
// clientStyles
|
||||
// clientScripts
|
||||
import(chrome.runtime.getURL("/api/browser.js")).then(() => {
|
||||
import(chrome.runtime.getURL("/api/system.js")).then(() => {
|
||||
import(chrome.runtime.getURL("/load.mjs"));
|
||||
});
|
||||
}
|
||||
|
122
src/worker.js
122
src/worker.js
@ -1,19 +1,14 @@
|
||||
/*
|
||||
* notion-enhancer
|
||||
* (c) 2022 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (c) 2023 dragonwocky <thedragonring.bod@gmail.com> (https://dragonwocky.me/)
|
||||
* (https://notion-enhancer.github.io/) under the MIT license
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const isElectron = () => {
|
||||
try {
|
||||
return typeof module !== "undefined";
|
||||
} catch {}
|
||||
return false;
|
||||
};
|
||||
const IS_ELECTRON = typeof module !== "undefined";
|
||||
|
||||
if (isElectron()) {
|
||||
if (IS_ELECTRON) {
|
||||
const { app, ipcMain } = require("electron"),
|
||||
reloadApp = () => {
|
||||
const args = process.argv.slice(1).filter((arg) => arg !== "--startup");
|
||||
@ -23,7 +18,7 @@ if (isElectron()) {
|
||||
|
||||
ipcMain.on("notion-enhancer", (_event, message) => {
|
||||
if (message === "open-menu") {
|
||||
//
|
||||
// todo
|
||||
} else if (message === "reload-app") {
|
||||
reloadApp();
|
||||
}
|
||||
@ -62,6 +57,8 @@ if (isElectron()) {
|
||||
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;
|
||||
@ -78,3 +75,110 @@ if (isElectron()) {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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;
|
||||
|
||||
// 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;
|
||||
|
||||
const table = "kvstore",
|
||||
{ app } = require("electron"),
|
||||
{ resolve } = require("path"),
|
||||
sqlite = require("better-sqlite3"),
|
||||
db = sqlite(resolve(`${app.getPath("userData")}/notion-enhancer.db`)),
|
||||
init = db.prepare(`CREATE TABLE IF NOT EXISTS ${table} (
|
||||
key TEXT PRIMARY KEY,
|
||||
value TEXT
|
||||
)`);
|
||||
init.run();
|
||||
|
||||
__statements = {
|
||||
insert: db.prepare(`INSERT INTO ${table} (key, value) VALUES (?, ?)`),
|
||||
update: db.prepare(`UPDATE ${table} SET value = ? WHERE key = ?`),
|
||||
select: db.prepare(`SELECT * FROM ${table} WHERE key = ? LIMIT 1`),
|
||||
delete: db.prepare(`DELETE FROM ${table} WHERE key = ?`),
|
||||
dump: db.prepare(`SELECT * FROM ${table}`),
|
||||
};
|
||||
__transactions = {
|
||||
remove: db.transaction((arr) => {
|
||||
arr.forEach((key) => __statements.delete.run(key));
|
||||
}),
|
||||
set: db.transaction((obj) => {
|
||||
for (const key in obj) {
|
||||
if (__statements.select.get(key) === undefined) {
|
||||
__statements.insert.run(key, obj[key]);
|
||||
} else __statements.update.run(obj[key], key);
|
||||
}
|
||||
}),
|
||||
};
|
||||
return db;
|
||||
})();
|
||||
|
||||
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);
|
||||
},
|
||||
};
|
||||
};
|
||||
|
102
yarn.lock
102
yarn.lock
@ -5,54 +5,17 @@ __metadata:
|
||||
version: 6
|
||||
cacheKey: 8
|
||||
|
||||
"@electron/asar@npm:^3.2.2":
|
||||
version: 3.2.2
|
||||
resolution: "@electron/asar@npm:3.2.2"
|
||||
"@electron/asar@npm:^3.2.4":
|
||||
version: 3.2.4
|
||||
resolution: "@electron/asar@npm:3.2.4"
|
||||
dependencies:
|
||||
"@types/glob": ^7.1.1
|
||||
chromium-pickle-js: ^0.2.0
|
||||
commander: ^5.0.0
|
||||
glob: ^7.1.6
|
||||
minimatch: ^3.0.4
|
||||
dependenciesMeta:
|
||||
"@types/glob":
|
||||
optional: true
|
||||
bin:
|
||||
asar: bin/asar.js
|
||||
checksum: 38a3b4a47180f2033a599421175f03706941ba05a32591a639127f6374e0007c6a7c8bde550129de394f4072a0bf39c24aea202540fe1faba6d74b4181c007a8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/glob@npm:^7.1.1":
|
||||
version: 7.2.0
|
||||
resolution: "@types/glob@npm:7.2.0"
|
||||
dependencies:
|
||||
"@types/minimatch": "*"
|
||||
"@types/node": "*"
|
||||
checksum: 6ae717fedfdfdad25f3d5a568323926c64f52ef35897bcac8aca8e19bc50c0bd84630bbd063e5d52078b2137d8e7d3c26eabebd1a2f03ff350fff8a91e79fc19
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/minimatch@npm:*":
|
||||
version: 5.1.2
|
||||
resolution: "@types/minimatch@npm:5.1.2"
|
||||
checksum: 0391a282860c7cb6fe262c12b99564732401bdaa5e395bee9ca323c312c1a0f45efbf34dce974682036e857db59a5c9b1da522f3d6055aeead7097264c8705a8
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@types/node@npm:*":
|
||||
version: 18.11.11
|
||||
resolution: "@types/node@npm:18.11.11"
|
||||
checksum: c4b1176a8f1714a3ee3fc2a5e1d568b0cd50209000282db5c68154b3c975952928dbb834ef3a0ce55bd7b345ae29f2cbf4a34635a070294d135a24254231386a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ansi-styles@npm:^4.1.0":
|
||||
version: 4.3.0
|
||||
resolution: "ansi-styles@npm:4.3.0"
|
||||
dependencies:
|
||||
color-convert: ^2.0.1
|
||||
checksum: 513b44c3b2105dd14cc42a19271e80f386466c4be574bccf60b627432f9198571ebf4ab1e4c3ba17347658f4ee1711c163d574248c0c1cdc2d5917a0ad582ec4
|
||||
checksum: 06e3e8fe7c894f7e7727410af5a9957ec77088f775b22441acf4ef718a9e6642a4dc1672f77ee1ce325fc367c8d59ac1e02f7db07869c8ced8a00132a3b54643
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -80,22 +43,19 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk-template@npm:^0.4.0":
|
||||
version: 0.4.0
|
||||
resolution: "chalk-template@npm:0.4.0"
|
||||
"chalk-template@npm:^1.0.0":
|
||||
version: 1.0.0
|
||||
resolution: "chalk-template@npm:1.0.0"
|
||||
dependencies:
|
||||
chalk: ^4.1.2
|
||||
checksum: 6c706802a79a7963cbce18f022b046fe86e438a67843151868852f80ea7346e975a6a9749991601e7e5d3b6a6c4852a04c53dc966a9a3d04031bd0e0ed53c819
|
||||
chalk: ^5.2.0
|
||||
checksum: 2cd8ae86d7e2ccc546a8fa93871931f7e5731b812e867be1bb77487f83ad6bd657dd25483a99dd0e4d615163551fde4457e48995597b69d58b8f1f633bc21952
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"chalk@npm:^4.1.2":
|
||||
version: 4.1.2
|
||||
resolution: "chalk@npm:4.1.2"
|
||||
dependencies:
|
||||
ansi-styles: ^4.1.0
|
||||
supports-color: ^7.1.0
|
||||
checksum: fe75c9d5c76a7a98d45495b91b2172fa3b7a09e0cc9370e5c8feb1c567b85c4288e2b3fded7cfdd7359ac28d6b3844feb8b82b8686842e93d23c827c417e83fc
|
||||
"chalk@npm:^5.2.0":
|
||||
version: 5.2.0
|
||||
resolution: "chalk@npm:5.2.0"
|
||||
checksum: 03d8060277de6cf2fd567dc25fcf770593eb5bb85f460ce443e49255a30ff1242edd0c90a06a03803b0466ff0687a939b41db1757bec987113e83de89a003caa
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
@ -106,22 +66,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color-convert@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "color-convert@npm:2.0.1"
|
||||
dependencies:
|
||||
color-name: ~1.1.4
|
||||
checksum: 79e6bdb9fd479a205c71d89574fccfb22bd9053bd98c6c4d870d65c132e5e904e6034978e55b43d69fcaa7433af2016ee203ce76eeba9cfa554b373e7f7db336
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"color-name@npm:~1.1.4":
|
||||
version: 1.1.4
|
||||
resolution: "color-name@npm:1.1.4"
|
||||
checksum: b0445859521eb4021cd0fb0cc1a75cecf67fceecae89b63f62b201cca8d345baf8b952c966862a9d9a2632987d4f6581f0ec8d957dfacece86f0a7919316f610
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"commander@npm:^5.0.0":
|
||||
version: 5.1.0
|
||||
resolution: "commander@npm:5.1.0"
|
||||
@ -157,13 +101,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"has-flag@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "has-flag@npm:4.0.0"
|
||||
checksum: 261a1357037ead75e338156b1f9452c016a37dcd3283a972a30d9e4a87441ba372c8b81f818cd0fbcd9c0354b4ae7e18b9e1afa1971164aef6d18c2b6095a8ad
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"inflight@npm:^1.0.4":
|
||||
version: 1.0.6
|
||||
resolution: "inflight@npm:1.0.6"
|
||||
@ -194,9 +131,9 @@ __metadata:
|
||||
version: 0.0.0-use.local
|
||||
resolution: "notion-enhancer@workspace:."
|
||||
dependencies:
|
||||
"@electron/asar": ^3.2.2
|
||||
"@electron/asar": ^3.2.4
|
||||
arg: ^5.0.2
|
||||
chalk-template: ^0.4.0
|
||||
chalk-template: ^1.0.0
|
||||
bin:
|
||||
notion-enhancer: bin.mjs
|
||||
languageName: unknown
|
||||
@ -218,15 +155,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"supports-color@npm:^7.1.0":
|
||||
version: 7.2.0
|
||||
resolution: "supports-color@npm:7.2.0"
|
||||
dependencies:
|
||||
has-flag: ^4.0.0
|
||||
checksum: 3dda818de06ebbe5b9653e07842d9479f3555ebc77e9a0280caf5a14fb877ffee9ed57007c3b78f5a6324b8dbeec648d9e97a24e2ed9fdb81ddc69ea07100f4a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"wrappy@npm:1":
|
||||
version: 1.0.2
|
||||
resolution: "wrappy@npm:1.0.2"
|
||||
|
Loading…
Reference in New Issue
Block a user