Skip to content

Commit ea60f40

Browse files
committed
fix(desktop): resolve Windows userData synchronously before the Clerk bridge (QM-189)
On win32 resolveUserDataPath (from pingdotgg#12480) inspects the profile dirs through the async Node FileSystem, which yields to the event loop; Electron emits ready first and createClerkBridge's protocol.registerSchemesAsPrivileged throws, so every Windows launch exits with DesktopClerkBridgeInitializationError. Provide a node:fs-backed synchronous FileSystem for that one call. Windows-only deploy branch; macOS returns before any I/O.
1 parent f98eaf5 commit ea60f40

1 file changed

Lines changed: 57 additions & 1 deletion

File tree

‎apps/desktop/src/app/DesktopClerk.ts‎

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,14 @@
1+
// @effect-diagnostics nodeBuiltinImport:off - userData must resolve synchronously before Electron is ready (the Clerk bridge registers privileged schemes).
2+
import * as NodeFs from "node:fs";
3+
14
import { createClerkBridge } from "@clerk/electron";
25
import { storage } from "@clerk/electron/storage";
36
import * as Context from "effect/Context";
47
import * as Effect from "effect/Effect";
8+
import * as FileSystem from "effect/FileSystem";
59
import * as Layer from "effect/Layer";
610
import * as Option from "effect/Option";
11+
import * as PlatformError from "effect/PlatformError";
712
import * as Schema from "effect/Schema";
813
import * as Scope from "effect/Scope";
914

@@ -83,13 +88,64 @@ function createDesktopClerkBridge(stateDir: string, isDevelopment: boolean) {
8388
});
8489
}
8590

91+
const syncSystemErrorTag = (cause: unknown): PlatformError.SystemErrorTag => {
92+
switch ((cause as NodeJS.ErrnoException | undefined)?.code) {
93+
case "EEXIST":
94+
return "AlreadyExists";
95+
case "ENOENT":
96+
return "NotFound";
97+
case "EACCES":
98+
case "EPERM":
99+
return "PermissionDenied";
100+
case "EBUSY":
101+
return "Busy";
102+
default:
103+
return "Unknown";
104+
}
105+
};
106+
107+
const syncFsCall = <A>(method: string, path: string, run: () => A) =>
108+
Effect.try({
109+
try: run,
110+
catch: (cause) =>
111+
PlatformError.systemError({
112+
_tag: syncSystemErrorTag(cause),
113+
module: "FileSystem",
114+
method,
115+
pathOrDescriptor: path,
116+
cause,
117+
}),
118+
});
119+
120+
/**
121+
* createClerkBridge calls protocol.registerSchemesAsPrivileged, which throws once Electron
122+
* is ready. The Windows userData resolution touches the disk; through the async Node
123+
* FileSystem it yields to the event loop and Electron emits ready first, so every Windows
124+
* launch exits with DesktopClerkBridgeInitializationError. Resolve through synchronous fs.
125+
*/
126+
const preReadySyncFileSystem = FileSystem.makeNoop({
127+
exists: (path) => syncFsCall("exists", path, () => NodeFs.existsSync(path)),
128+
readFileString: (path) =>
129+
syncFsCall("readFileString", path, () => NodeFs.readFileSync(path, "utf8")),
130+
makeDirectory: (path, options) =>
131+
syncFsCall("makeDirectory", path, () => {
132+
NodeFs.mkdirSync(path, { recursive: options?.recursive ?? false });
133+
}),
134+
writeFileString: (path, data, options) =>
135+
syncFsCall("writeFileString", path, () =>
136+
NodeFs.writeFileSync(path, data, { flag: options?.flag ?? "w" }),
137+
),
138+
});
139+
86140
/** @public Service construction is part of the canonical Effect module API. */
87141
export const make = Effect.gen(function* () {
88142
const environment = yield* DesktopEnvironment.DesktopEnvironment;
89143
const electronApp = yield* ElectronApp.ElectronApp;
90144

91145
// The SDK bridge acquires Electron's profile-scoped single-instance lock.
92-
const userDataPath = yield* DesktopUserData.resolveUserDataPath(environment);
146+
const userDataPath = yield* DesktopUserData.resolveUserDataPath(environment).pipe(
147+
Effect.provideService(FileSystem.FileSystem, preReadySyncFileSystem),
148+
);
93149
yield* electronApp.setPath("userData", userDataPath);
94150

95151
const bridge = yield* Effect.acquireRelease(

0 commit comments

Comments
 (0)