Files
koodo-reader/preload.js
T

180 lines
8.3 KiB
JavaScript

const { contextBridge, ipcRenderer } = require("electron");
const INVOKE_CHANNELS = new Set([
"cancel-download-app", "discord-rpc-update", "discord-rpc-clear", "update-win-app",
"open-book", "generate-tts", "get-tts-voices", "cloud-upload", "cloud-download", "cloud-progress",
"picker-download", "picker-progress", "cloud-reset", "cloud-stats", "cloud-delete",
"cloud-list", "picker-list", "cloud-exist", "cloud-close", "clear-tts", "select-path",
"select-file", "encrypt-data", "decrypt-data", "check-cloud-url", "get-proxy-config",
"set-proxy-config", "test-proxy-connection", "get-mac",
"get-device-name", "get-biometric-capability", "prompt-biometric-auth",
"reset-reader-position", "reset-main-position", "select-zip-file", "select-book",
"custom-database-command", "database-command", "close-database", "set-always-on-top",
"set-auto-maximize", "toggle-auto-launch", "toggle-minimize-to-tray", "open-explorer-folder",
"get-debug-logs", "hide-reader", "open-console", "reload-reader", "reload-main", "new-chat",
"clear-all-data", "new-tab", "reload-tab", "adjust-tab-size", "exit-tab",
"enter-tab-fullscreen", "exit-tab-fullscreen", "enter-fullscreen", "exit-fullscreen",
"open-url", "switch-moyu", "set-native-theme-source", "system-ocr", "file-command",
"open-external", "dict-lookup", "partial-md5", "get-cover-url",
"crypto-file-md5", "backup-path",
"ai-request", "ai-chat-stream"
]);
const SEND_CHANNELS = new Set(["reader-close-ready", "tab-close-ready"]);
const SEND_SYNC_CHANNELS = new Set([
"storage-location", "url-window-status", "get-dirname", "system-color",
"get-file-data", "check-file-data", "user-data", "file-command-sync", "clipboard-read-text-sync"
]);
const EVENT_CHANNELS = new Set([
"oauth-callback", "before-reader-close", "before-tab-close", "reading-finished",
"chat-message", "import-url-from-link", "open-book-from-link", "open-note-from-link",
"picker-finished", "download-app-progress", "backup-progress",
"ai-chat-chunk", "ai-chat-done", "ai-chat-error"
]);
const assertChannel = (set, channel) => {
if (!set.has(channel)) throw new Error(`IPC channel is not allowed: ${channel}`);
};
const invoke = (channel, ...args) => {
assertChannel(INVOKE_CHANNELS, channel);
return ipcRenderer.invoke(channel, ...args);
};
const send = (channel, ...args) => {
assertChannel(SEND_CHANNELS, channel);
return ipcRenderer.send(channel, ...args);
};
const sendSync = (channel, ...args) => {
assertChannel(SEND_SYNC_CHANNELS, channel);
return ipcRenderer.sendSync(channel, ...args);
};
const eventHandlers = new Map();
const listen = (channel, listener, once = false) => {
assertChannel(EVENT_CHANNELS, channel);
const handler = (_event, payload) => {
if (once) removeListener(channel, listener, handler);
listener(payload);
};
const handlers = eventHandlers.get(channel) || new Map();
const listeners = handlers.get(listener) || [];
listeners.push(handler);
handlers.set(listener, listeners);
eventHandlers.set(channel, handlers);
ipcRenderer[once ? "once" : "on"](channel, handler);
return () => removeListener(channel, listener, handler);
};
const removeListener = (channel, listener, handler) => {
assertChannel(EVENT_CHANNELS, channel);
const handlers = eventHandlers.get(channel);
const listeners = handlers && handlers.get(listener);
const target = handler || (listeners && listeners[listeners.length - 1]);
if (target) {
ipcRenderer.removeListener(channel, target);
if (listeners) {
const index = listeners.indexOf(target);
if (index >= 0) listeners.splice(index, 1);
if (listeners.length === 0) handlers.delete(listener);
}
}
};
const fileArgs = (args) => (args && typeof args === "object" ? args : {});
const fileSync = (operation, args) => {
const result = sendSync("file-command-sync", { operation, ...fileArgs(args) });
if (!result || result.ok !== true) {
const error = new Error(result?.error?.message || "File command failed");
if (result?.error?.code) error.code = result.error.code;
throw error;
}
return result.value;
};
const fileInvoke = (operation, args) => invoke("file-command", { operation, ...fileArgs(args) });
const bytes = (value) => {
if (value instanceof ArrayBuffer) return new Uint8Array(value);
if (ArrayBuffer.isView(value)) return new Uint8Array(value.buffer, value.byteOffset, value.byteLength);
return value;
};
const callbackResult = (promise, callback) => {
promise.then((value) => callback(null, bytes(value))).catch((error) => callback(error));
};
const file = {
existsSync: (filePath) => fileSync("exists", { path: filePath }),
mkdirSync: (filePath, options) => fileSync("mkdir", { path: filePath, options }),
writeFileSync: (filePath, data, options) => fileSync("write", { path: filePath, data: bytes(data), options }),
appendFileSync: (filePath, data, options) => fileSync("append", { path: filePath, data: bytes(data), options }),
readFileSync: (filePath, options) => bytes(fileSync("read", { path: filePath, options })),
readdirSync: (filePath, options) => fileSync("readdir", { path: filePath, options }),
statSync: (filePath) => fileSync("stat", { path: filePath }),
unlinkSync: (filePath) => fileSync("unlink", { path: filePath }),
copyFileSync: (source, destination) => fileSync("copyFile", { source, destination }),
renameSync: (source, destination) => fileSync("rename", { source, destination }),
rmSync: (filePath, options) => fileSync("rm", { path: filePath, options }),
emptyDirSync: (filePath) => fileSync("emptyDir", { path: filePath }),
copy: (source, destination) => fileInvoke("copy", { source, destination }),
rm: (filePath, options) => fileInvoke("rm", { path: filePath, options }),
readFile: (filePath, options, callback) => {
const cb = typeof options === "function" ? options : callback;
const readOptions = typeof options === "function" ? undefined : options;
const promise = fileInvoke("read", { path: filePath, options: readOptions });
if (cb) callbackResult(promise, cb);
return cb ? undefined : promise.then(bytes);
},
writeFile: (filePath, data, options, callback) => {
const cb = typeof options === "function" ? options : callback;
const writeOptions = typeof options === "function" ? undefined : options;
const promise = fileInvoke("write", { path: filePath, data: bytes(data), options: writeOptions });
if (cb) promise.then(() => cb(null)).catch((error) => cb(error));
return cb ? undefined : promise;
},
promises: {
readFile: (filePath, options) => fileInvoke("read", { path: filePath, options }).then(bytes),
readdir: (filePath, options) => fileInvoke("readdir", { path: filePath, options }),
stat: (filePath) => fileInvoke("stat", { path: filePath }),
mkdir: (filePath, options) => fileInvoke("mkdir", { path: filePath, options }),
},
};
const nodeSync = (operation, args = {}) => {
const result = ipcRenderer.sendSync("node-command-sync", { operation, ...args });
if (!result || result.ok !== true) {
const error = new Error(result?.error?.message || "Node command failed");
if (result?.error?.code) error.code = result.error.code;
throw error;
}
return result.value;
};
const pathApi = {
join: (...parts) => nodeSync("path-join", { values: parts }),
dirname: (value) => nodeSync("path-dirname", { value }),
basename: (value, suffix) => nodeSync("path-basename", { value, suffix }),
extname: (value) => nodeSync("path-extname", { value }),
resolve: (...parts) => nodeSync("path-resolve", { values: parts }),
posix: { join: (...parts) => nodeSync("path-posix-join", { values: parts }) },
};
const cryptoApi = {
md5: (data) => nodeSync("crypto-md5", { data: bytes(data) }),
partialMd5: (filePath) => invoke("partial-md5", filePath),
fileMd5: (filePath) => invoke("crypto-file-md5", filePath),
};
contextBridge.exposeInMainWorld("electronAPI", {
invoke,
send,
sendSync,
on: (channel, listener) => listen(channel, listener),
once: (channel, listener) => listen(channel, listener, true),
removeListener,
fs: file,
path: pathApi,
os: {
platform: () => process.platform,
homedir: () => nodeSync("os-homedir"),
},
runtime: { platform: process.platform, windowsStore: process.windowsStore === true },
crypto: cryptoApi,
shell: { openExternal: (url) => invoke("open-external", url) },
clipboard: {
readText: () => sendSync("clipboard-read-text-sync"),
},
});