Skip to content

Commit 9d5ca2c

Browse files
[codex] Structure Electron protocol teardown failures (#3310)
Co-authored-by: codex <codex@users.noreply.github.com>
1 parent ac77fe4 commit 9d5ca2c

2 files changed

Lines changed: 76 additions & 4 deletions

File tree

apps/desktop/src/electron/ElectronProtocol.test.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { assert, describe, it } from "@effect/vitest";
2+
import * as Cause from "effect/Cause";
23
import * as Effect from "effect/Effect";
34
import { beforeEach, vi } from "vite-plus/test";
45

@@ -98,6 +99,60 @@ describe("ElectronProtocol", () => {
9899
}).pipe(Effect.provide(ElectronProtocol.layer)),
99100
);
100101

102+
it.effect("preserves protocol registration failures", () =>
103+
Effect.gen(function* () {
104+
const cause = new Error("protocol registration failed");
105+
handleMock.mockImplementationOnce(() => {
106+
throw cause;
107+
});
108+
109+
const protocol = yield* ElectronProtocol.ElectronProtocol;
110+
const error = yield* Effect.scoped(
111+
protocol.registerDesktopProtocol({
112+
scheme: "t3code-dev",
113+
targetOrigin: new URL("http://127.0.0.1:3773/"),
114+
backendOrigin: new URL("http://127.0.0.1:3774/"),
115+
clerkFrontendApiHostname: undefined,
116+
}),
117+
).pipe(Effect.flip);
118+
119+
assert.instanceOf(error, ElectronProtocol.ElectronProtocolRegistrationError);
120+
assert.equal(error.scheme, "t3code-dev");
121+
assert.strictEqual(error.cause, cause);
122+
assert.equal(error.message, 'Failed to register Electron protocol scheme "t3code-dev".');
123+
}).pipe(Effect.provide(ElectronProtocol.layer)),
124+
);
125+
126+
it.effect("preserves protocol unregistration failures", () =>
127+
Effect.gen(function* () {
128+
const cause = new Error("protocol unregistration failed");
129+
unhandleMock.mockImplementationOnce(() => {
130+
throw cause;
131+
});
132+
133+
const protocol = yield* ElectronProtocol.ElectronProtocol;
134+
const exit = yield* Effect.exit(
135+
Effect.scoped(
136+
protocol.registerDesktopProtocol({
137+
scheme: "t3code",
138+
targetOrigin: new URL("http://127.0.0.1:3773/"),
139+
backendOrigin: new URL("http://127.0.0.1:3773/"),
140+
clerkFrontendApiHostname: undefined,
141+
}),
142+
),
143+
);
144+
145+
assert.equal(exit._tag, "Failure");
146+
if (exit._tag === "Failure") {
147+
const error = Cause.squash(exit.cause);
148+
assert.instanceOf(error, ElectronProtocol.ElectronProtocolUnregistrationError);
149+
assert.equal(error.scheme, "t3code");
150+
assert.strictEqual(error.cause, cause);
151+
assert.equal(error.message, 'Failed to unregister Electron protocol scheme "t3code".');
152+
}
153+
}).pipe(Effect.provide(ElectronProtocol.layer)),
154+
);
155+
101156
it("keeps executable sources host-restricted while allowing runtime network resources", () => {
102157
const policy = ElectronProtocol.makeDesktopContentSecurityPolicy({
103158
scheme: "t3code",

apps/desktop/src/electron/ElectronProtocol.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,19 @@ export class ElectronProtocolRegistrationError extends Schema.TaggedErrorClass<E
3131
},
3232
) {
3333
override get message(): string {
34-
return `Failed to register ${this.scheme}: protocol.`;
34+
return `Failed to register Electron protocol scheme "${this.scheme}".`;
35+
}
36+
}
37+
38+
export class ElectronProtocolUnregistrationError extends Schema.TaggedErrorClass<ElectronProtocolUnregistrationError>()(
39+
"ElectronProtocolUnregistrationError",
40+
{
41+
scheme: Schema.String,
42+
cause: Schema.Defect(),
43+
},
44+
) {
45+
override get message(): string {
46+
return `Failed to unregister Electron protocol scheme "${this.scheme}".`;
3547
}
3648
}
3749

@@ -133,9 +145,14 @@ export const make = Effect.gen(function* () {
133145
catch: (cause) => new ElectronProtocolRegistrationError({ scheme: input.scheme, cause }),
134146
}).pipe(Effect.andThen(Ref.set(registered, true))),
135147
() =>
136-
Effect.sync(() => {
137-
Electron.protocol.unhandle(input.scheme);
138-
}).pipe(Effect.andThen(Ref.set(registered, false))),
148+
Effect.try({
149+
try: () => Electron.protocol.unhandle(input.scheme),
150+
catch: (cause) =>
151+
new ElectronProtocolUnregistrationError({
152+
scheme: input.scheme,
153+
cause,
154+
}),
155+
}).pipe(Effect.andThen(Ref.set(registered, false)), Effect.orDie),
139156
);
140157
},
141158
);

0 commit comments

Comments
 (0)