Skip to content
Merged
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
55 changes: 55 additions & 0 deletions apps/desktop/src/electron/ElectronProtocol.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { assert, describe, it } from "@effect/vitest";
import * as Cause from "effect/Cause";
import * as Effect from "effect/Effect";
import { beforeEach, vi } from "vite-plus/test";

Expand Down Expand Up @@ -98,6 +99,60 @@ describe("ElectronProtocol", () => {
}).pipe(Effect.provide(ElectronProtocol.layer)),
);

it.effect("preserves protocol registration failures", () =>
Effect.gen(function* () {
const cause = new Error("protocol registration failed");
handleMock.mockImplementationOnce(() => {
throw cause;
});

const protocol = yield* ElectronProtocol.ElectronProtocol;
const error = yield* Effect.scoped(
protocol.registerDesktopProtocol({
scheme: "t3code-dev",
targetOrigin: new URL("http://127.0.0.1:3773/"),
backendOrigin: new URL("http://127.0.0.1:3774/"),
clerkFrontendApiHostname: undefined,
}),
).pipe(Effect.flip);

assert.instanceOf(error, ElectronProtocol.ElectronProtocolRegistrationError);
assert.equal(error.scheme, "t3code-dev");
assert.strictEqual(error.cause, cause);
assert.equal(error.message, 'Failed to register Electron protocol scheme "t3code-dev".');
}).pipe(Effect.provide(ElectronProtocol.layer)),
);

it.effect("preserves protocol unregistration failures", () =>
Effect.gen(function* () {
const cause = new Error("protocol unregistration failed");
unhandleMock.mockImplementationOnce(() => {
throw cause;
});

const protocol = yield* ElectronProtocol.ElectronProtocol;
const exit = yield* Effect.exit(
Effect.scoped(
protocol.registerDesktopProtocol({
scheme: "t3code",
targetOrigin: new URL("http://127.0.0.1:3773/"),
backendOrigin: new URL("http://127.0.0.1:3773/"),
clerkFrontendApiHostname: undefined,
}),
),
);

assert.equal(exit._tag, "Failure");
if (exit._tag === "Failure") {
const error = Cause.squash(exit.cause);
assert.instanceOf(error, ElectronProtocol.ElectronProtocolUnregistrationError);
assert.equal(error.scheme, "t3code");
assert.strictEqual(error.cause, cause);
assert.equal(error.message, 'Failed to unregister Electron protocol scheme "t3code".');
}
}).pipe(Effect.provide(ElectronProtocol.layer)),
);

it("keeps executable sources host-restricted while allowing runtime network resources", () => {
const policy = ElectronProtocol.makeDesktopContentSecurityPolicy({
scheme: "t3code",
Expand Down
25 changes: 21 additions & 4 deletions apps/desktop/src/electron/ElectronProtocol.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,19 @@ export class ElectronProtocolRegistrationError extends Schema.TaggedErrorClass<E
},
) {
override get message(): string {
return `Failed to register ${this.scheme}: protocol.`;
return `Failed to register Electron protocol scheme "${this.scheme}".`;
}
}

export class ElectronProtocolUnregistrationError extends Schema.TaggedErrorClass<ElectronProtocolUnregistrationError>()(
"ElectronProtocolUnregistrationError",
{
scheme: Schema.String,
cause: Schema.Defect(),
},
) {
override get message(): string {
return `Failed to unregister Electron protocol scheme "${this.scheme}".`;
}
}

Expand Down Expand Up @@ -133,9 +145,14 @@ export const make = Effect.gen(function* () {
catch: (cause) => new ElectronProtocolRegistrationError({ scheme: input.scheme, cause }),
}).pipe(Effect.andThen(Ref.set(registered, true))),
() =>
Effect.sync(() => {
Electron.protocol.unhandle(input.scheme);
}).pipe(Effect.andThen(Ref.set(registered, false))),
Effect.try({
try: () => Electron.protocol.unhandle(input.scheme),
catch: (cause) =>
new ElectronProtocolUnregistrationError({
scheme: input.scheme,
cause,
}),
}).pipe(Effect.andThen(Ref.set(registered, false)), Effect.orDie),
);
},
);
Expand Down
Loading