mirror of
https://github.com/notion-enhancer/notion-enhancer.git
synced 2025-04-10 15:39:01 +00:00
162 lines
5.6 KiB
JavaScript
162 lines
5.6 KiB
JavaScript
/**
|
|
* 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",
|
|
IS_RENDERER = IS_ELECTRON && process.type === "renderer";
|
|
|
|
// 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),
|
|
// require a file from the root of notion's app/ folder,
|
|
// only available in an electron main process
|
|
notionRequire = (target) =>
|
|
IS_ELECTRON && !IS_RENDERER ? require(`../../../${target}`) : undefined;
|
|
|
|
let __port;
|
|
const connectToPort = () => {
|
|
if (__port) return;
|
|
__port = chrome.runtime.connect();
|
|
__port.onDisconnect.addListener(() => (__port = null));
|
|
},
|
|
onMessage = (channel, listener) => {
|
|
// from worker to client
|
|
if (IS_RENDERER) {
|
|
const { ipcRenderer } = require("electron");
|
|
ipcRenderer.on(channel, listener);
|
|
} else if (!IS_ELECTRON) {
|
|
const onMessage = (msg) => {
|
|
if (msg?.channel !== channel || msg?.invocation) return;
|
|
listener(msg.message);
|
|
};
|
|
connectToPort();
|
|
__port.onMessage.addListener(onMessage);
|
|
chrome.runtime.onMessage.addListener(onMessage);
|
|
}
|
|
},
|
|
sendMessage = (channel, message) => {
|
|
// to worker from client
|
|
if (IS_RENDERER) {
|
|
const { ipcRenderer } = require("electron");
|
|
ipcRenderer.send(channel, message);
|
|
} else if (!IS_ELECTRON) {
|
|
connectToPort();
|
|
__port.postMessage({ channel, message });
|
|
}
|
|
},
|
|
invokeInWorker = (channel, message) => {
|
|
// sends a payload to the worker/main
|
|
// process and waits for a response
|
|
if (IS_RENDERER) {
|
|
const { ipcRenderer } = require("electron");
|
|
return ipcRenderer.invoke(channel, message);
|
|
} else if (!IS_ELECTRON) {
|
|
// polyfills the electron.ipcRenderer.invoke method in
|
|
// the browser: uses a long-lived ipc connection to
|
|
// pass messages and handle responses asynchronously
|
|
let fulfilled;
|
|
connectToPort();
|
|
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}`));
|
|
}
|
|
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());
|
|
};
|
|
|
|
const initDatabase = (namespace, fallbacks = {}) => {
|
|
// all db operations are performed via ipc:
|
|
// with nodeintegration disabled, sqlite cannot
|
|
// be require()-d from the renderer process
|
|
const query = (query, args = {}) =>
|
|
IS_ELECTRON && !IS_RENDERER
|
|
? globalThis.__enhancerApi.queryDatabase(namespace, query, args)
|
|
: invokeInWorker("notion-enhancer", {
|
|
action: "query-database",
|
|
data: { namespace, query, args },
|
|
});
|
|
return {
|
|
get: (key) => query("get", { key, fallbacks }),
|
|
set: (key, value) => query("set", { key, value }),
|
|
remove: (keys) => query("remove", { keys }),
|
|
export: () => query("export"),
|
|
import: (obj) => query("import", { obj }),
|
|
};
|
|
},
|
|
reloadApp = () => {
|
|
if (IS_ELECTRON && !IS_RENDERER) {
|
|
const { app } = require("electron"),
|
|
args = process.argv.slice(1).filter((arg) => arg !== "--startup");
|
|
app.relaunch({ args });
|
|
app.exit();
|
|
} else sendMessage("notion-enhancer", "reload-app");
|
|
};
|
|
|
|
Object.assign((globalThis.__enhancerApi ??= {}), {
|
|
platform,
|
|
version,
|
|
enhancerUrl,
|
|
notionRequire,
|
|
onMessage,
|
|
sendMessage,
|
|
invokeInWorker,
|
|
readFile,
|
|
readJson,
|
|
initDatabase,
|
|
reloadApp,
|
|
});
|