Files

44 lines
2.9 KiB
TypeScript

import { tmpdir } from "node:os";
import { join } from "node:path";
import { pathToFileURL } from "node:url";
import { describe, expect, test, vi } from "vitest";
const registered = new Map<string, (event: unknown, raw: unknown) => unknown>();
vi.mock("electron", () => ({
ipcMain: { handle: (channel: string, listener: (event: unknown, raw: unknown) => unknown) => registered.set(channel, listener) },
}));
const { createIpcGate } = await import("../../src/main/app/ipc-gate");
const { IPC_CHANNELS } = await import("../../src/shared/ipc");
const mainWindow = { webContents: { id: 1 } } as unknown as Electron.BrowserWindow;
const appPage = pathToFileURL(join(tmpdir(), "app", "index.html")).href;
const fromApp = { sender: { id: 1 }, senderFrame: { parent: null, url: `${appPage}#/work` } };
describe("the IPC gate", () => {
test("only the app's own page reaches a service, and only with input that matches the call's contract", () => {
const handle = createIpcGate(mainWindow, appPage);
const devHandle = createIpcGate(mainWindow, "http://localhost:5173/");
const run = vi.fn((input: unknown) => ({ ok: true, data: input }));
handle("stopRun", run);
const call = (event: unknown, raw: unknown) => registered.get(IPC_CHANNELS.stopRun)!(event, raw);
expect(call({ ...fromApp, sender: { id: 2 } }, { runId: "run_1" })).toMatchObject({ ok: false, error: { code: "forbidden_sender" } });
expect(call({ ...fromApp, senderFrame: { parent: {}, url: "file:///C:/app/index.html" } }, { runId: "run_1" })).toMatchObject({ ok: false, error: { code: "forbidden_sender" } });
expect(call({ ...fromApp, senderFrame: { parent: null, url: "https://evil.example/" } }, { runId: "run_1" })).toMatchObject({ ok: false, error: { code: "forbidden_sender" } });
// Another local page is not the app's own, even though it is a file too.
const untrusted = pathToFileURL(join(tmpdir(), "Downloads", "untrusted.html")).href;
expect(call({ ...fromApp, senderFrame: { parent: null, url: untrusted } }, { runId: "run_1" })).toMatchObject({ ok: false, error: { code: "forbidden_sender" } });
expect(call(fromApp, { runId: 7 })).toMatchObject({ ok: false, error: { code: "invalid_input" } });
expect(call(fromApp, { runId: "run_1", extra: true })).toMatchObject({ ok: false, error: { code: "invalid_input" } });
expect(run).not.toHaveBeenCalled();
expect(call(fromApp, { runId: "run_1" })).toEqual({ ok: true, data: { runId: "run_1" } });
expect(run).toHaveBeenCalledTimes(1);
// In development the dev server's origin is the app's page.
devHandle("stopRun", run);
expect(call({ ...fromApp, senderFrame: { parent: null, url: "http://localhost:5173/#/work" } }, { runId: "run_2" })).toEqual({ ok: true, data: { runId: "run_2" } });
expect(call({ ...fromApp, senderFrame: { parent: null, url: appPage } }, { runId: "run_3" })).toMatchObject({ ok: false, error: { code: "forbidden_sender" } });
});
});