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"; vi.mock("electron", async (importOriginal) => ({ ...(await importOriginal<typeof import("electron")>()), session: { fromPartition: vi.fn(() => ({ getUserAgent: vi.fn(() => "Mozilla/5.0 Electron/41.5.0 t3code/1.2.3"), setPermissionRequestHandler: vi.fn(), setUserAgent: vi.fn(), })), },})); 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";import * as PreviewManager from "../preview/Manager.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<string, (...args: readonly unknown[]) => 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(), setAutoHideCursor: 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, setAutoHideCursor: window.setAutoHideCursor, webContentsListeners, };} const desktopAssetsLayer = Layer.succeed(DesktopAssets.DesktopAssets, { iconPaths: Effect.succeed({ ico: Option.none<string>(), icns: Option.none<string>(), png: Option.none<string>(), }), resolveResourcePath: () => Effect.succeed(Option.none<string>()),} 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<number>; readonly mainWindow: Ref.Ref<Option.Option<Electron.BrowserWindow>>; readonly createdWindowOptions?: Electron.BrowserWindowConstructorOptions[]; readonly openedExternalUrls?: unknown[];}) { const electronWindowLayer = Layer.succeed(ElectronWindow.ElectronWindow, { create: (options) => Effect.sync(() => { input.createdWindowOptions?.push(options); }).pipe( Effect.andThen(Ref.update(input.createCount, (count) => count + 1)), 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, Layer.mock(PreviewManager.PreviewManager)({ getBrowserSession: () => Effect.succeed({} as Electron.Session), setMainWindow: () => Effect.void, isBrowserPartition: (partition) => partition.startsWith("persist:t3code-preview-"), getBrowserPartition: () => Effect.succeed("persist:t3code-preview-test"), }), ), ), );} 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/connections", }), ); 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.Option<Electron.BrowserWindow>>(Option.none()); const createdWindowOptions: Electron.BrowserWindowConstructorOptions[] = []; const layer = makeTestLayer({ window: fakeWindow.window, createCount, mainWindow, createdWindowOptions, }); 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.isTrue(createdWindowOptions[0]?.disableAutoHideCursor); assert.deepEqual(fakeWindow.setAutoHideCursor.mock.calls, [[false]]); 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.Option<Electron.BrowserWindow>>(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)); }), );});
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";
vi.mock("electron", async (importOriginal) => ({
...(await importOriginal<typeof import("electron")>()),
session: {
fromPartition: vi.fn(() => ({
getUserAgent: vi.fn(() => "Mozilla/5.0 Electron/41.5.0 t3code/1.2.3"),
setPermissionRequestHandler: vi.fn(),
setUserAgent: vi.fn(),
})),
},
}));
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";
import * as PreviewManager from "../preview/Manager.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<string, (...args: readonly unknown[]) => 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(),
setAutoHideCursor: 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,
setAutoHideCursor: window.setAutoHideCursor,
webContentsListeners,
};
}
const desktopAssetsLayer = Layer.succeed(DesktopAssets.DesktopAssets, {
iconPaths: Effect.succeed({
ico: Option.none<string>(),
icns: Option.none<string>(),
png: Option.none<string>(),
}),
resolveResourcePath: () => Effect.succeed(Option.none<string>()),
} 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<number>;
readonly mainWindow: Ref.Ref<Option.Option<Electron.BrowserWindow>>;
readonly createdWindowOptions?: Electron.BrowserWindowConstructorOptions[];
readonly openedExternalUrls?: unknown[];
}) {
const electronWindowLayer = Layer.succeed(ElectronWindow.ElectronWindow, {
create: (options) =>
Effect.sync(() => {
input.createdWindowOptions?.push(options);
}).pipe(
Effect.andThen(Ref.update(input.createCount, (count) => count + 1)),
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,
Layer.mock(PreviewManager.PreviewManager)({
getBrowserSession: () => Effect.succeed({} as Electron.Session),
setMainWindow: () => Effect.void,
isBrowserPartition: (partition) => partition.startsWith("persist:t3code-preview-"),
getBrowserPartition: () => Effect.succeed("persist:t3code-preview-test"),
}),
),
),
);
}
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/connections",
}),
);
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.Option<Electron.BrowserWindow>>(Option.none());
const createdWindowOptions: Electron.BrowserWindowConstructorOptions[] = [];
const layer = makeTestLayer({
window: fakeWindow.window,
createCount,
mainWindow,
createdWindowOptions,
});
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.isTrue(createdWindowOptions[0]?.disableAutoHideCursor);
assert.deepEqual(fakeWindow.setAutoHideCursor.mock.calls, [[false]]);
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.Option<Electron.BrowserWindow>>(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));
}),
);
});