import { readFileSync } from "node:fs"; import path from "node:path"; import vm from "node:vm"; import type { RakazoDesktop, RakazoSetup } from "@rakazo/contracts"; import { describe, expect, it, vi } from "vitest"; function runPreload(file: string, ipc: { invoke?: unknown; on?: unknown; off?: unknown } = {}) { const invoke = (ipc.invoke as ReturnType) ?? vi.fn(async (channel: string) => ({ channel })); const on = (ipc.on as ReturnType) ?? vi.fn(); const off = (ipc.off as ReturnType) ?? vi.fn(); const exposeInMainWorld = vi.fn(); const source = readFileSync(path.join(import.meta.dirname, file), "utf8"); vm.runInNewContext(source, { process: { platform: "linux" }, require(moduleName: string) { if (moduleName !== "electron") throw new Error(`Unexpected preload import: ${moduleName}`); return { contextBridge: { exposeInMainWorld }, ipcRenderer: { invoke, on, off } }; }, }); return { invoke, on, off, exposeInMainWorld }; } describe("desktop preload bridge", () => { it("exposes the scoped desktop bridges", async () => { const { invoke, exposeInMainWorld } = runPreload("preload.cjs"); expect(exposeInMainWorld).toHaveBeenCalledTimes(1); const [globalName, bridge] = exposeInMainWorld.mock.calls[0] as [string, RakazoDesktop]; expect(globalName).toBe("rakazoDesktop"); expect(bridge.platform).toBe("linux"); expect(Object.keys(bridge).sort()).toEqual([ "files", "localSettings", "oauth", "platform", "update", "window", ]); expect(Object.keys(bridge.window).sort()).toEqual([ "close", "minimize", "state", "toggleMaximize", ]); expect(Object.keys(bridge.update).sort()).toEqual(["check", "download", "install", "state"]); await bridge.files?.pickDirectory(); await bridge.oauth.open?.("https://provider.example.com/authorize"); await bridge.oauth.cancel?.("https://provider.example.com/authorize"); await bridge.window.close(); await bridge.window.minimize(); await bridge.window.toggleMaximize(); await bridge.window.state(); await bridge.update.state(); await bridge.update.check(); await bridge.update.download(); await bridge.update.install(); expect(invoke.mock.calls.map(([channel]) => channel)).toEqual([ "desktop.files.pickDirectory", "desktop.oauth.open", "desktop.oauth.cancel", "desktop.window.close", "desktop.window.minimize", "desktop.window.toggleMaximize", "desktop.window.state", "desktop.update.state", "desktop.update.check", "desktop.update.download", "desktop.update.install", ]); }); it("keeps setup off the app bridge so a connected server cannot re-point the app", () => { const { exposeInMainWorld } = runPreload("preload.cjs"); const [, bridge] = exposeInMainWorld.mock.calls[0] as [string, Record]; expect(Object.keys(bridge).sort()).toEqual([ "files", "localSettings", "oauth", "platform", "update", "window", ]); }); it("forwards captured codes without leaking the IPC event to the renderer", () => { const listeners: Array<(event: unknown, callback: unknown) => void> = []; const on = vi.fn((_channel: string, handler: (event: unknown, callback: unknown) => void) => { listeners.push(handler); }); const off = vi.fn(); const { exposeInMainWorld } = runPreload("preload.cjs", { on, off }); const [, bridge] = exposeInMainWorld.mock.calls[0] as [string, RakazoDesktop]; const received: unknown[] = []; const unsubscribe = bridge.oauth.onCallback((callback) => received.push(callback)); expect(on).toHaveBeenCalledWith("desktop.oauth.callback", expect.any(Function)); listeners[0]?.({ sender: "ipc-event" }, { code: "ac_123", state: "verifier_456" }); expect(received).toEqual([{ code: "ac_123", state: "verifier_456" }]); unsubscribe(); expect(off).toHaveBeenCalledWith("desktop.oauth.callback", expect.any(Function)); }); }); describe("setup preload bridge", () => { it("exposes only the first-run setup operations", async () => { const { invoke, on, exposeInMainWorld } = runPreload("setup-preload.cjs"); expect(exposeInMainWorld).toHaveBeenCalledTimes(1); const [globalName, bridge] = exposeInMainWorld.mock.calls[0] as [string, RakazoSetup]; expect(globalName).toBe("rakazoSetup"); expect(bridge.platform).toBe("linux"); expect(Object.keys(bridge).sort()).toEqual([ "openLink", "platform", "quit", "save", "stack", "state", "test", ]); expect(Object.keys(bridge.stack).sort()).toEqual(["onChange", "start", "state"]); await bridge.state(); await bridge.test("http://127.0.0.1:5173"); await bridge.save({ mode: "new", serverUrl: "http://127.0.0.1:5173" }); await bridge.quit(); await bridge.openLink("orbstack"); await bridge.stack.state(); await bridge.stack.start(); expect(invoke.mock.calls.map(([channel]) => channel)).toEqual([ "desktop.setup.state", "desktop.setup.test", "desktop.setup.save", "desktop.setup.quit", "desktop.setup.openLink", "desktop.setup.stack.state", "desktop.setup.stack.start", ]); expect(invoke).toHaveBeenCalledWith("desktop.setup.openLink", "orbstack"); const listener = vi.fn(); bridge.stack.onChange(listener); const [channel, handler] = on.mock.calls.at(-1) as [string, (...args: unknown[]) => void]; expect(channel).toBe("desktop.setup.stack.changed"); handler({}, { phase: "pulling" }); expect(listener).toHaveBeenCalledWith({ phase: "pulling" }); }); });