import { assert, describe, it } from "@effect/vitest"; import * as Cause from "effect/Cause"; import * as Effect from "effect/Effect"; import { beforeEach, vi } from "vite-plus/test"; const { handleMock, netFetchMock, unhandleMock } = vi.hoisted(() => ({ handleMock: vi.fn(), netFetchMock: vi.fn(), unhandleMock: vi.fn(), })); vi.mock("electron", () => ({ net: { fetch: netFetchMock }, protocol: { handle: handleMock, unhandle: unhandleMock }, })); import * as ElectronProtocol from "./ElectronProtocol.ts"; describe("ElectronProtocol", () => { beforeEach(() => { handleMock.mockReset(); netFetchMock.mockReset(); unhandleMock.mockReset(); }); it.effect("proxies the stable renderer origin to the current app server", () => Effect.gen(function* () { let handler: ((request: Request) => Promise) | undefined; handleMock.mockImplementation((_scheme, nextHandler) => { handler = nextHandler; }); netFetchMock.mockResolvedValue(new Response("ok")); yield* Effect.scoped( Effect.gen(function* () { const protocol = yield* ElectronProtocol.ElectronProtocol; yield* protocol.registerDesktopProtocol({ scheme: "t3code-dev", targetOrigin: new URL("http://127.0.0.1:3773/"), backendOrigin: new URL("http://127.0.0.1:3774/"), clerkFrontendApiHostname: "clerk.t3.codes", }); assert.isDefined(handler); const response = yield* Effect.promise(() => handler!( new Request("t3code-dev://app/api/health?verbose=1", { headers: { accept: "application/json", origin: "t3code-dev://app", referer: "t3code-dev://app/", "sec-fetch-site": "same-origin", }, }), ), ); assert.equal(yield* Effect.promise(() => response.text()), "ok"); assert.include( response.headers.get("content-security-policy") ?? "", "script-src 'self' 'unsafe-inline' 'wasm-unsafe-eval' https://clerk.t3.codes https://challenges.cloudflare.com", ); assert.include( response.headers.get("content-security-policy") ?? "", "connect-src 'self' http: https: ws: wss:", ); assert.include( response.headers.get("content-security-policy") ?? "", "img-src 'self' t3code-dev: blob: data: http: https:", ); assert.include( response.headers.get("content-security-policy") ?? "", "font-src 'self' t3code-dev: data:", ); }), ); assert.deepEqual( handleMock.mock.calls.map((call) => call[0]), ["t3code-dev"], ); assert.equal(netFetchMock.mock.calls[0]?.[0], "http://127.0.0.1:3773/api/health?verbose=1"); const forwardedHeaders = new Headers(netFetchMock.mock.calls[0]?.[1]?.headers); assert.equal(forwardedHeaders.get("accept"), "application/json"); assert.isNull(forwardedHeaders.get("origin")); assert.isNull(forwardedHeaders.get("referer")); assert.isNull(forwardedHeaders.get("sec-fetch-site")); assert.deepEqual(unhandleMock.mock.calls, [["t3code-dev"]]); }).pipe(Effect.provide(ElectronProtocol.layer)), ); it.effect("rejects custom protocol requests for another host", () => Effect.gen(function* () { let handler: ((request: Request) => Promise) | undefined; handleMock.mockImplementation((_scheme, nextHandler) => { handler = nextHandler; }); const response = yield* Effect.scoped( Effect.gen(function* () { const protocol = yield* ElectronProtocol.ElectronProtocol; yield* protocol.registerDesktopProtocol({ scheme: "t3code", targetOrigin: new URL("http://127.0.0.1:3773/"), backendOrigin: new URL("http://127.0.0.1:3773/"), clerkFrontendApiHostname: undefined, }); return yield* Effect.promise(() => handler!(new Request("t3code://other/"))); }), ); assert.equal(response.status, 404); assert.equal(netFetchMock.mock.calls.length, 0); }).pipe(Effect.provide(ElectronProtocol.layer)), ); it.effect("retries transient renderer target failures", () => Effect.gen(function* () { let handler: ((request: Request) => Promise) | undefined; handleMock.mockImplementation((_scheme, nextHandler) => { handler = nextHandler; }); netFetchMock .mockRejectedValueOnce(new Error("connect ECONNREFUSED 127.0.0.1:5733")) .mockResolvedValueOnce(new Response("ready")); const response = yield* Effect.scoped( Effect.gen(function* () { const protocol = yield* ElectronProtocol.ElectronProtocol; yield* protocol.registerDesktopProtocol({ scheme: "t3code-dev", targetOrigin: new URL("http://127.0.0.1:5733/"), backendOrigin: new URL("http://127.0.0.1:3773/"), clerkFrontendApiHostname: undefined, }); return yield* Effect.promise(() => handler!(new Request("t3code-dev://app/"))); }), ); assert.equal(yield* Effect.promise(() => response.text()), "ready"); assert.equal(netFetchMock.mock.calls.length, 2); }).pipe(Effect.provide(ElectronProtocol.layer)), ); it.effect("preserves protocol registration failures", () => Effect.gen(function* () { const cause = new Error("protocol registration failed"); handleMock.mockImplementationOnce(() => { throw cause; }); const protocol = yield* ElectronProtocol.ElectronProtocol; const error = yield* Effect.scoped( protocol.registerDesktopProtocol({ scheme: "t3code-dev", targetOrigin: new URL("http://127.0.0.1:3773/"), backendOrigin: new URL("http://127.0.0.1:3774/"), clerkFrontendApiHostname: undefined, }), ).pipe(Effect.flip); assert.instanceOf(error, ElectronProtocol.ElectronProtocolRegistrationError); assert.equal(error.scheme, "t3code-dev"); assert.strictEqual(error.cause, cause); assert.equal(error.message, 'Failed to register Electron protocol scheme "t3code-dev".'); }).pipe(Effect.provide(ElectronProtocol.layer)), ); it.effect("preserves protocol unregistration failures", () => Effect.gen(function* () { const cause = new Error("protocol unregistration failed"); unhandleMock.mockImplementationOnce(() => { throw cause; }); const protocol = yield* ElectronProtocol.ElectronProtocol; const exit = yield* Effect.exit( Effect.scoped( protocol.registerDesktopProtocol({ scheme: "t3code", targetOrigin: new URL("http://127.0.0.1:3773/"), backendOrigin: new URL("http://127.0.0.1:3773/"), clerkFrontendApiHostname: undefined, }), ), ); assert.equal(exit._tag, "Failure"); if (exit._tag === "Failure") { const error = Cause.squash(exit.cause); assert.instanceOf(error, ElectronProtocol.ElectronProtocolUnregistrationError); assert.equal(error.scheme, "t3code"); assert.strictEqual(error.cause, cause); assert.equal(error.message, 'Failed to unregister Electron protocol scheme "t3code".'); } }).pipe(Effect.provide(ElectronProtocol.layer)), ); it("keeps executable sources host-restricted while allowing runtime network resources", () => { const policy = ElectronProtocol.makeDesktopContentSecurityPolicy({ scheme: "t3code", targetOrigin: new URL("http://127.0.0.1:3773/"), backendOrigin: new URL("http://127.0.0.1:3773/"), clerkFrontendApiHostname: "clerk.t3.codes", }); const directives = Object.fromEntries( policy.split("; ").map((directive) => { const [name, ...sources] = directive.split(" "); return [name, sources]; }), ); assert.deepEqual(directives["script-src"], [ "'self'", "'unsafe-inline'", "'wasm-unsafe-eval'", "https://clerk.t3.codes", "https://challenges.cloudflare.com", ]); assert.deepEqual(directives["connect-src"], ["'self'", "http:", "https:", "ws:", "wss:"]); assert.deepEqual(directives["img-src"], [ "'self'", "t3code:", "blob:", "data:", "http:", "https:", ]); assert.deepEqual(directives["media-src"], ["'self'", "t3code:", "blob:", "http:", "https:"]); assert.deepEqual(directives["font-src"], ["'self'", "t3code:", "data:"]); }); });