import * as NodeServices from "@effect/platform-node/NodeServices"; import { assert, describe, it } from "@effect/vitest"; import * as Effect from "effect/Effect"; import * as Layer from "effect/Layer"; import * as Option from "effect/Option"; import * as Ref from "effect/Ref"; import type * as Electron from "electron"; import { vi } from "vite-plus/test"; import * as DesktopAssets from "../app/DesktopAssets.ts"; import * as DesktopConfig from "../app/DesktopConfig.ts"; import * as DesktopEnvironment from "../app/DesktopEnvironment.ts"; import * as DesktopState from "../app/DesktopState.ts"; import * as ElectronMenu from "../electron/ElectronMenu.ts"; import * as ElectronShell from "../electron/ElectronShell.ts"; import * as ElectronTheme from "../electron/ElectronTheme.ts"; import * as ElectronWindow from "../electron/ElectronWindow.ts"; import * as DesktopServerExposure from "../backend/DesktopServerExposure.ts"; import * as DesktopWindow from "./DesktopWindow.ts"; const environmentInput = { dirname: "/repo/apps/desktop/dist-electron", homeDirectory: "/Users/alice", platform: "darwin", processArch: "arm64", appVersion: "1.2.3", appPath: "/repo", isPackaged: false, resourcesPath: "/repo/resources", runningUnderArm64Translation: false, } satisfies DesktopEnvironment.MakeDesktopEnvironmentInput; function makeFakeBrowserWindow() { const webContentsListeners = new Map void>(); const webContents = { copyImageAt: vi.fn(), isLoadingMainFrame: vi.fn(() => false), on: vi.fn((eventName: string, listener: (...args: readonly unknown[]) => void) => { webContentsListeners.set(eventName, listener); }), once: vi.fn(), openDevTools: vi.fn(), replaceMisspelling: vi.fn(), send: vi.fn(), setWindowOpenHandler: vi.fn(), }; const window = { focus: vi.fn(), isDestroyed: vi.fn(() => false), isMinimized: vi.fn(() => false), isVisible: vi.fn(() => true), loadURL: vi.fn(() => Promise.resolve()), on: vi.fn(), once: vi.fn(), restore: vi.fn(), setBackgroundColor: vi.fn(), setTitle: vi.fn(), setTitleBarOverlay: vi.fn(), show: vi.fn(), webContents, }; return { window: window as unknown as Electron.BrowserWindow, loadURL: window.loadURL, openDevTools: webContents.openDevTools, webContentsListeners, }; } const desktopAssetsLayer = Layer.succeed(DesktopAssets.DesktopAssets, { iconPaths: Effect.succeed({ ico: Option.none(), icns: Option.none(), png: Option.none(), }), resolveResourcePath: () => Effect.succeed(Option.none()), } satisfies DesktopAssets.DesktopAssetsShape); const desktopServerExposureLayer = Layer.succeed(DesktopServerExposure.DesktopServerExposure, { getState: Effect.die("unexpected getState"), backendConfig: Effect.succeed({ port: 3773, bindHost: "127.0.0.1", httpBaseUrl: new URL("http://127.0.0.1:3773"), tailscaleServeEnabled: false, tailscaleServePort: 443, }), configureFromSettings: () => Effect.die("unexpected configureFromSettings"), setMode: () => Effect.die("unexpected setMode"), setTailscaleServeEnabled: () => Effect.die("unexpected setTailscaleServeEnabled"), getAdvertisedEndpoints: Effect.die("unexpected getAdvertisedEndpoints"), } satisfies DesktopServerExposure.DesktopServerExposureShape); const electronMenuLayer = Layer.succeed(ElectronMenu.ElectronMenu, { setApplicationMenu: () => Effect.void, popupTemplate: () => Effect.void, showContextMenu: () => Effect.succeed(Option.none()), } satisfies ElectronMenu.ElectronMenuShape); const electronThemeLayer = Layer.succeed(ElectronTheme.ElectronTheme, { shouldUseDarkColors: Effect.succeed(false), setSource: () => Effect.void, onUpdated: () => Effect.void, } satisfies ElectronTheme.ElectronThemeShape); const desktopEnvironmentLayer = DesktopEnvironment.layer(environmentInput).pipe( Layer.provide( Layer.mergeAll( NodeServices.layer, DesktopConfig.layerTest({ T3CODE_PORT: "3773", VITE_DEV_SERVER_URL: "http://127.0.0.1:5733", }), ), ), ); function makeTestLayer(input: { readonly window: Electron.BrowserWindow; readonly createCount: Ref.Ref; readonly mainWindow: Ref.Ref>; readonly openedExternalUrls?: unknown[]; }) { const electronWindowLayer = Layer.succeed(ElectronWindow.ElectronWindow, { create: () => Ref.update(input.createCount, (count) => count + 1).pipe(Effect.as(input.window)), main: Ref.get(input.mainWindow), currentMainOrFirst: Ref.get(input.mainWindow), focusedMainOrFirst: Ref.get(input.mainWindow), setMain: (window) => Ref.set(input.mainWindow, Option.some(window)), clearMain: () => Ref.set(input.mainWindow, Option.none()), reveal: () => Effect.void, sendAll: () => Effect.void, destroyAll: Effect.void, syncAllAppearance: (sync) => sync(input.window), } satisfies ElectronWindow.ElectronWindowShape); return DesktopWindow.layer.pipe( Layer.provide( Layer.mergeAll( desktopAssetsLayer, desktopEnvironmentLayer, desktopServerExposureLayer, DesktopState.layer, electronMenuLayer, Layer.succeed(ElectronShell.ElectronShell, { openExternal: (url) => Effect.sync(() => { input.openedExternalUrls?.push(url); return true; }), copyText: () => Effect.void, } satisfies ElectronShell.ElectronShellShape), electronThemeLayer, electronWindowLayer, ), ), ); } describe("DesktopWindow", () => { it("recognizes only same-origin renderer navigations", () => { assert.isTrue( DesktopWindow.isSameOriginRendererNavigation({ applicationUrl: "http://127.0.0.1:3773/", navigationUrl: "http://127.0.0.1:3773/settings/cloud", }), ); assert.isFalse( DesktopWindow.isSameOriginRendererNavigation({ applicationUrl: "http://127.0.0.1:3773/", navigationUrl: "https://accounts.microsoft.com/oauth", }), ); assert.isFalse( DesktopWindow.isSameOriginRendererNavigation({ applicationUrl: "http://127.0.0.1:3773/", navigationUrl: "not a url", }), ); }); it.effect("does not open a development window until the backend is ready", () => Effect.gen(function* () { const fakeWindow = makeFakeBrowserWindow(); const createCount = yield* Ref.make(0); const mainWindow = yield* Ref.make>(Option.none()); const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow, }); yield* Effect.gen(function* () { const desktopWindow = yield* DesktopWindow.DesktopWindow; yield* desktopWindow.activate; assert.equal(yield* Ref.get(createCount), 0); yield* desktopWindow.handleBackendReady; assert.equal(yield* Ref.get(createCount), 1); assert.deepEqual(fakeWindow.loadURL.mock.calls[0], ["http://127.0.0.1:5733/"]); assert.equal(fakeWindow.openDevTools.mock.calls.length, 1); }).pipe(Effect.provide(layer)); }), ); it.effect("opens safe off-origin renderer navigations in the system browser", () => Effect.gen(function* () { const fakeWindow = makeFakeBrowserWindow(); const createCount = yield* Ref.make(0); const mainWindow = yield* Ref.make>(Option.none()); const openedExternalUrls: unknown[] = []; const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow, openedExternalUrls, }); yield* Effect.gen(function* () { const desktopWindow = yield* DesktopWindow.DesktopWindow; yield* desktopWindow.handleBackendReady; const willNavigate = fakeWindow.webContentsListeners.get("will-navigate"); if (!willNavigate) { return yield* Effect.die("will-navigate listener was not registered"); } let prevented = false; willNavigate( { preventDefault: () => { prevented = true; }, }, "https://accounts.microsoft.com/oauth", ); yield* Effect.promise(() => Promise.resolve()); assert.isTrue(prevented); assert.deepEqual(openedExternalUrls, ["https://accounts.microsoft.com/oauth"]); }).pipe(Effect.provide(layer)); }), ); });