Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 8 additions & 6 deletions apps/desktop/src/app/DesktopApp.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,12 +170,14 @@ const bootstrap = Effect.gen(function* () {
const rendererTarget = environment.isDevelopment
? Option.getOrThrow(environment.devServerUrl)
: backendConfig.httpBaseUrl;
yield* electronProtocol.registerDesktopProtocol({
scheme: ElectronProtocol.getDesktopScheme(environment.isDevelopment),
targetOrigin: rendererTarget,
backendOrigin: backendConfig.httpBaseUrl,
clerkFrontendApiHostname: DesktopClerk.desktopClerkFrontendApiHostname,
});
if (!environment.isDevelopment || DesktopClerk.isDesktopClerkBridgeEnabled()) {
yield* electronProtocol.registerDesktopProtocol({
scheme: ElectronProtocol.getDesktopScheme(environment.isDevelopment),
targetOrigin: rendererTarget,
backendOrigin: backendConfig.httpBaseUrl,
clerkFrontendApiHostname: DesktopClerk.getDesktopClerkFrontendApiHostname(),
});
}
yield* logBootstrapInfo("bootstrap resolved backend endpoint", {
baseUrl: backendConfig.httpBaseUrl.href,
});
Expand Down
23 changes: 22 additions & 1 deletion apps/desktop/src/app/DesktopClerk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const makeDesktopClerkLayer = (isDevelopment = true) => {
isDevelopment,
} as unknown as DesktopEnvironment.DesktopEnvironment["Service"]);

return DesktopClerk.layer.pipe(
return DesktopClerk.makeDesktopClerkLayer(true).pipe(
Layer.provide(Layer.succeed(DesktopEnvironment.DesktopEnvironment, environment)),
);
};
Expand Down Expand Up @@ -76,6 +76,27 @@ describe("DesktopClerk", () => {
});
});

it.effect("skips the SDK bridge when the build has no Clerk publishable key", () => {
storageMock.mockReturnValue(storageAdapter);
createClerkBridgeMock.mockReturnValue({ cleanup: vi.fn() });

return Effect.gen(function* () {
yield* Effect.scoped(
Layer.build(DesktopClerk.makeDesktopClerkLayer(false)).pipe(
Effect.provide(
Layer.succeed(DesktopEnvironment.DesktopEnvironment, {
stateDir: "/tmp/t3-state",
isDevelopment: true,
} as unknown as DesktopEnvironment.DesktopEnvironment["Service"]),
),
),
);

assert.deepEqual(storageMock.mock.calls, []);
assert.deepEqual(createClerkBridgeMock.mock.calls, []);
});
});

it.effect("preserves bridge initialization failures", () => {
const cause = new Error("bridge initialization failed");
storageMock.mockReturnValue(storageAdapter);
Expand Down
127 changes: 61 additions & 66 deletions apps/desktop/src/app/DesktopClerk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,21 @@ import * as Option from "effect/Option";
import * as Schema from "effect/Schema";
import * as Scope from "effect/Scope";

import { clerkFrontendApiHostnameFromPublishableKey } from "@t3tools/shared/relayAuth";
import * as ElectronApp from "../electron/ElectronApp.ts";
import * as ElectronProtocol from "../electron/ElectronProtocol.ts";
import * as ElectronWindow from "../electron/ElectronWindow.ts";
import {
getDesktopClerkFrontendApiHostname,
isDesktopClerkBridgeEnabled,
resolveDesktopClerkFrontendApiHostname,
} from "./DesktopClerkConfig.ts";
import * as DesktopEnvironment from "./DesktopEnvironment.ts";

declare const __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: string | undefined;
export {
getDesktopClerkFrontendApiHostname,
isDesktopClerkBridgeEnabled,
resolveDesktopClerkFrontendApiHostname,
} from "./DesktopClerkConfig.ts";

export class DesktopClerkBridgeInitializationError extends Schema.TaggedErrorClass<DesktopClerkBridgeInitializationError>()(
"DesktopClerkBridgeInitializationError",
Expand Down Expand Up @@ -52,25 +60,6 @@ export class DesktopClerk extends Context.Service<
}
>()("@t3tools/desktop/app/DesktopClerk") {}

export function resolveDesktopClerkFrontendApiHostname(
publishableKey: string | undefined,
): string | undefined {
const normalizedKey = publishableKey?.trim();
if (!normalizedKey) return undefined;

try {
return clerkFrontendApiHostnameFromPublishableKey(normalizedKey);
} catch {
return undefined;
}
}

export const desktopClerkFrontendApiHostname = resolveDesktopClerkFrontendApiHostname(
typeof __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__ === "undefined"
? undefined
: __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__,
);

export function createDesktopClerkBridge(stateDir: string, isDevelopment: boolean) {
return createClerkBridge({
storage: storage({ path: stateDir }),
Expand All @@ -82,54 +71,60 @@ export function createDesktopClerkBridge(stateDir: string, isDevelopment: boolea
});
}

export const make = Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
yield* Effect.acquireRelease(
Effect.try({
try: () => createDesktopClerkBridge(environment.stateDir, environment.isDevelopment),
catch: (cause) =>
new DesktopClerkBridgeInitializationError({
stateDir: environment.stateDir,
isDevelopment: environment.isDevelopment,
cause,
export function makeDesktopClerkLayer(enabled = isDesktopClerkBridgeEnabled()) {
const make = Effect.gen(function* () {
const environment = yield* DesktopEnvironment.DesktopEnvironment;
if (enabled) {
yield* Effect.acquireRelease(
Effect.try({
try: () => createDesktopClerkBridge(environment.stateDir, environment.isDevelopment),
catch: (cause) =>
new DesktopClerkBridgeInitializationError({
stateDir: environment.stateDir,
isDevelopment: environment.isDevelopment,
cause,
}),
}),
}),
(bridge) =>
Effect.try({
try: () => bridge.cleanup(),
catch: (cause) =>
new DesktopClerkBridgeCleanupError({
stateDir: environment.stateDir,
isDevelopment: environment.isDevelopment,
cause,
}),
}).pipe(Effect.orDie),
);
(bridge) =>
Effect.try({
try: () => bridge.cleanup(),
catch: (cause) =>
new DesktopClerkBridgeCleanupError({
stateDir: environment.stateDir,
isDevelopment: environment.isDevelopment,
cause,
}),
}).pipe(Effect.orDie),
);
}

return DesktopClerk.of({
configure: Effect.gen(function* () {
const electronApp = yield* ElectronApp.ElectronApp;
const electronWindow = yield* ElectronWindow.ElectronWindow;
const context = yield* Effect.context<ElectronWindow.ElectronWindow>();
const runPromise = Effect.runPromiseWith(context);
return DesktopClerk.of({
configure: Effect.gen(function* () {
const electronApp = yield* ElectronApp.ElectronApp;
const electronWindow = yield* ElectronWindow.ElectronWindow;
const context = yield* Effect.context<ElectronWindow.ElectronWindow>();
const runPromise = Effect.runPromiseWith(context);

if (!(yield* electronApp.requestSingleInstanceLock)) {
yield* electronApp.quit;
return yield* Effect.interrupt;
}
if (!(yield* electronApp.requestSingleInstanceLock)) {
yield* electronApp.quit;
return yield* Effect.interrupt;
}

yield* electronApp.on("second-instance", () => {
void runPromise(
Effect.gen(function* () {
const mainWindow = yield* electronWindow.currentMainOrFirst;
if (Option.isSome(mainWindow)) {
yield* electronWindow.reveal(mainWindow.value);
}
}),
);
});
}).pipe(Effect.withSpan("desktop.clerk.configure")),
yield* electronApp.on("second-instance", () => {
void runPromise(
Effect.gen(function* () {
const mainWindow = yield* electronWindow.currentMainOrFirst;
if (Option.isSome(mainWindow)) {
yield* electronWindow.reveal(mainWindow.value);
}
}),
);
});
}).pipe(Effect.withSpan("desktop.clerk.configure")),
});
});
});

export const layer = Layer.effect(DesktopClerk, make);
return Layer.effect(DesktopClerk, make);
}

export const layer = makeDesktopClerkLayer();
32 changes: 32 additions & 0 deletions apps/desktop/src/app/DesktopClerkConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
declare const __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__: string | undefined;

export function resolveDesktopClerkFrontendApiHostname(
publishableKey: string | undefined,
): string | undefined {
const normalizedKey = publishableKey?.trim();
if (!normalizedKey) return undefined;

try {
const encodedFrontendApi = normalizedKey.split("_").slice(2).join("_");
const frontendApi = globalThis.atob(encodedFrontendApi).replace(/\$$/u, "");
if (frontendApi.length === 0 || frontendApi.includes("/")) return undefined;

return new URL(`https://${frontendApi}`).hostname;
} catch {
return undefined;
}
}

const desktopClerkFrontendApiHostname = resolveDesktopClerkFrontendApiHostname(
typeof __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__ === "undefined"
? undefined
: __T3CODE_BUILD_CLERK_PUBLISHABLE_KEY__,
);

export function getDesktopClerkFrontendApiHostname(): string | undefined {
return desktopClerkFrontendApiHostname;
}

export function isDesktopClerkBridgeEnabled(): boolean {
return Boolean(desktopClerkFrontendApiHostname);
}
8 changes: 6 additions & 2 deletions apps/desktop/src/preload.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,16 @@ import type {
DesktopPreviewRecordingFrame,
DesktopPreviewTabState,
} from "@t3tools/contracts";
import { exposeClerkBridge } from "@clerk/electron/preload";
import { contextBridge, ipcRenderer } from "electron";

import { isDesktopClerkBridgeEnabled } from "./app/DesktopClerkConfig.ts";
import * as IpcChannels from "./ipc/channels.ts";

exposeClerkBridge({ passkeys: true });
if (isDesktopClerkBridgeEnabled()) {
const { exposeClerkBridge } =
require("@clerk/electron/preload") as typeof import("@clerk/electron/preload");
exposeClerkBridge({ passkeys: true });
Comment thread
huxcrux marked this conversation as resolved.
}

function unwrapEnsureSshEnvironmentResult(result: unknown) {
if (
Expand Down
2 changes: 1 addition & 1 deletion apps/desktop/src/window/DesktopWindow.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ describe("DesktopWindow", () => {
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], ["t3code-dev://app/"]);
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));
}),
Expand Down
6 changes: 5 additions & 1 deletion apps/desktop/src/window/DesktopWindow.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import * as Ref from "effect/Ref";
import type * as Electron from "electron";

import * as DesktopAssets from "../app/DesktopAssets.ts";
import * as DesktopClerk from "../app/DesktopClerk.ts";
import * as DesktopEnvironment from "../app/DesktopEnvironment.ts";
import { makeComponentLogger } from "../app/DesktopObservability.ts";
import * as DesktopState from "../app/DesktopState.ts";
Expand Down Expand Up @@ -159,7 +160,10 @@ export const make = Effect.gen(function* () {
DesktopWindowError
> {
yield* previewManager.getBrowserSession();
const applicationUrl = getDesktopUrl(environment.isDevelopment);
const applicationUrl =
environment.isDevelopment && !DesktopClerk.isDesktopClerkBridgeEnabled()
? Option.getOrThrow(environment.devServerUrl).href
: getDesktopUrl(environment.isDevelopment);
const iconPaths = yield* assets.iconPaths;
const iconOption = getIconOption(iconPaths, environment.platform);
const shouldUseDarkColors = yield* electronTheme.shouldUseDarkColors;
Expand Down
22 changes: 0 additions & 22 deletions apps/web/src/AppRoot.test.tsx

This file was deleted.

19 changes: 0 additions & 19 deletions apps/web/src/AppRoot.tsx

This file was deleted.

Loading
Loading