102 lines
5.1 KiB
JavaScript
102 lines
5.1 KiB
JavaScript
// version: v5
|
|
// Electron preload — context bridge for privileged native APIs.
|
|
//
|
|
// Anything renderer-side that needs to talk to the OS (file save dialog,
|
|
// open new window, etc.) goes through here.
|
|
//
|
|
// v5 — F-24 (Security wave 2): exposes secureSavePassword /
|
|
// secureLoadPassword / secureClearPassword that route to main's
|
|
// safeStorage via IPC. Replaces consumer-side plaintext password
|
|
// in localStorage (audit F-24 vulnerability). LoginScreen.jsx v50
|
|
// consumes these; one-time migration in v50 reads any legacy
|
|
// localStorage.mk_saved_pass, encrypts it, then deletes the
|
|
// plaintext. See main.js v9 for IPC handler details + storage path.
|
|
// v4 — exposes downloadAndInstall(url, onProgress) for the in-app updater.
|
|
// The main process streams the installer to %TEMP%, fires
|
|
// "mk:installer-progress" events for the UI, then spawns the .exe
|
|
// detached and quits the terminal. Renderer-side callers wire the
|
|
// onProgress callback to a progress bar and await the return value.
|
|
|
|
const { contextBridge, ipcRenderer } = require("electron");
|
|
|
|
contextBridge.exposeInMainWorld("mkElectron", {
|
|
isElectron: true,
|
|
|
|
// saveScreenshot(suggestedName, bytes)
|
|
// suggestedName: string filename ("US500_M15_2026-04-27.png")
|
|
// bytes: Uint8Array | ArrayBuffer of raw PNG bytes
|
|
// returns: Promise<{ saved: boolean, path?: string }>
|
|
//
|
|
// Bypasses Chromium's download manager — instead the main process pops
|
|
// a native dialog.showSaveDialog and fs.writeFile-s the buffer directly.
|
|
// Much faster than `link.click()` on a data URL.
|
|
saveScreenshot: async (suggestedName, bytes) => {
|
|
// Bytes can be Uint8Array (good) or ArrayBuffer (we wrap). IPC handles
|
|
// typed arrays as Buffer on the main side.
|
|
const buf = bytes instanceof ArrayBuffer ? new Uint8Array(bytes) : bytes;
|
|
return ipcRenderer.invoke("mk:save-screenshot", { suggestedName, bytes: buf });
|
|
},
|
|
|
|
// openInNewWindow(pageId)
|
|
// pageId: one of "quant" | "fundamental" | "flow" | "options" | "stress" | "learn"
|
|
// returns: Promise<{ ok: boolean, error?: string }>
|
|
//
|
|
// Asks the main process to spawn a child BrowserWindow that loads the
|
|
// same React app with `?detached=<pageId>` so the renderer boots into
|
|
// DetachedShell mode. Each child window has parent: mainWindow so it
|
|
// closes automatically when the user closes the main window.
|
|
openInNewWindow: (pageId) => ipcRenderer.invoke("mk:open-in-new-window", { pageId }),
|
|
|
|
// fetchTrumpNews({ force? })
|
|
// returns Promise<{ ok, items?: [{title, link, pubDate, source}], fetchedAt?, cached?, error? }>
|
|
//
|
|
// Main process pulls the Google News RSS feed (search=Trump), parses items,
|
|
// caches in-memory for 5 min. Pass { force: true } to bypass cache.
|
|
fetchTrumpNews: (opts) => ipcRenderer.invoke("mk:fetch-trump-news", opts || {}),
|
|
|
|
// openExternal(url)
|
|
// Opens the given URL in the user's default browser via shell.openExternal.
|
|
// Only http(s) URLs are allowed.
|
|
openExternal: (url) => ipcRenderer.invoke("mk:open-external", url),
|
|
|
|
// v5: secureSavePassword(email, password)
|
|
// F-24 — Replaces localStorage.mk_saved_pass with OS-keychain
|
|
// encrypted storage via main process safeStorage. Email kept plain
|
|
// (not sensitive); password encrypted at rest. Returns
|
|
// Promise<{ ok, error? }>. error="encryption_unavailable" on
|
|
// platforms where safeStorage can't access keychain (rare Linux).
|
|
secureSavePassword: (email, password) =>
|
|
ipcRenderer.invoke("mk:secure-save-password", { email, password }),
|
|
|
|
// v5: secureLoadPassword()
|
|
// Returns Promise<{ ok, email?, password?, error? }>. When no creds
|
|
// saved, returns { ok: true, email: "", password: "" }. Decryption
|
|
// failures (encrypted-but-keychain-rotated) return { ok: false,
|
|
// error: ... }. Callers should treat any error path as "no saved
|
|
// password" and prompt the user.
|
|
secureLoadPassword: () => ipcRenderer.invoke("mk:secure-load-password"),
|
|
|
|
// v5: secureClearPassword()
|
|
// Deletes the encrypted creds file. Returns Promise<{ ok, error? }>.
|
|
// Idempotent — succeeds even if no file exists.
|
|
secureClearPassword: () => ipcRenderer.invoke("mk:secure-clear-password"),
|
|
|
|
// v4: downloadAndInstall(url, onProgress?)
|
|
// Streams installer to %TEMP%, spawns it detached, then quits the
|
|
// running terminal. onProgress({ received, total, pct, done? }) is
|
|
// called periodically while downloading.
|
|
// Returns Promise<{ ok, file?, error? }> — terminal will quit ~800ms
|
|
// after { ok: true } so the renderer typically only sees the resolve
|
|
// moment briefly before the window closes.
|
|
downloadAndInstall: (url, onProgress) => {
|
|
const handler = (_e, data) => {
|
|
try { if (typeof onProgress === "function") onProgress(data || {}); } catch {}
|
|
};
|
|
ipcRenderer.on("mk:installer-progress", handler);
|
|
const cleanup = () => { try { ipcRenderer.removeListener("mk:installer-progress", handler); } catch {} };
|
|
return ipcRenderer.invoke("mk:download-and-install", url)
|
|
.then((res) => { cleanup(); return res; })
|
|
.catch((e) => { cleanup(); throw e; });
|
|
},
|
|
});
|