mirror of
https://github.com/morluto/rea.git
synced 2026-11-07 05:56:19 -10:00
fix(electron): retain wrapped protocol handler registrations
This commit is contained in:
1 parent
8c857ce56c
commit
c28d47bf13
4 files changed
+180
-4
No files matched your search
@@ -289,8 +289,18 @@ describe("Electron protocol registrations without a scheme", () => {
|
||||
|
||||
describe("Electron protocol calls that register nothing", () => {
|
||||
it.each([
|
||||
["a handler without a callback", 'protocol.handle("app");'],
|
||||
["a handler with a null callback", 'protocol.handle("app", null);'],
|
||||
[
|
||||
"a legacy handler without a callback",
|
||||
'protocol.registerStringProtocol("app");',
|
||||
],
|
||||
[
|
||||
"a legacy handler with a null callback",
|
||||
'protocol.registerStringProtocol("app", null);',
|
||||
],
|
||||
[
|
||||
"a legacy handler with a void callback",
|
||||
'protocol.registerStringProtocol("app", void 0);',
|
||||
],
|
||||
[
|
||||
"an interceptor without a callback",
|
||||
'protocol.interceptHttpProtocol("https");',
|
||||
@@ -331,3 +341,31 @@ describe("Electron protocol calls that register nothing", () => {
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("Electron wrapped protocol.handle registrations", () => {
|
||||
it.each([
|
||||
["protocol", "default-protocol"],
|
||||
["session.defaultSession.protocol", "default-session"],
|
||||
])(
|
||||
"retains %s registrations before handler invocation",
|
||||
(receiver, session) => {
|
||||
const protocols = protocolsOf(`
|
||||
const { protocol, session } = require("electron");
|
||||
${receiver}.handle("omitted");
|
||||
${receiver}.handle("null", null);
|
||||
${receiver}.handle("undefined", void 0);
|
||||
`);
|
||||
expect(protocols).toMatchObject([
|
||||
{ api: "handle", role: "handler", session, scheme: literal("omitted") },
|
||||
{ api: "handle", role: "handler", session, scheme: literal("null") },
|
||||
{
|
||||
api: "handle",
|
||||
role: "handler",
|
||||
session,
|
||||
scheme: literal("undefined"),
|
||||
},
|
||||
]);
|
||||
expect(protocols).toHaveLength(3);
|
||||
},
|
||||
);
|
||||
});
|
||||
@@ -87,7 +87,12 @@ export const inspectElectronProtocolNode = (
|
||||
: INTERCEPTORS.has(target.api)
|
||||
? "interceptor"
|
||||
: undefined;
|
||||
if (role !== undefined && !handlerKnownMissing(node))
|
||||
// handle installs its own native callback and invokes the supplied handler
|
||||
// only when a request arrives; a missing handler does not prevent registration.
|
||||
if (
|
||||
role !== undefined &&
|
||||
(target.api === "handle" || !handlerKnownMissing(node))
|
||||
)
|
||||
addProtocol(context, {
|
||||
node,
|
||||
api: target.api,
|
||||
@@ -97,7 +102,7 @@ export const inspectElectronProtocolNode = (
|
||||
});
|
||||
};
|
||||
|
||||
/** Electron requires the callback, so a call without one installs nothing. */
|
||||
/** Legacy native registration APIs require a callback before installing it. */
|
||||
const handlerKnownMissing = (
|
||||
node: t.CallExpression | t.OptionalCallExpression,
|
||||
): boolean => {
|
||||
|
||||
@@ -0,0 +1,87 @@
|
||||
import { writeFile } from "node:fs/promises";
|
||||
import { join } from "node:path";
|
||||
|
||||
import { expect } from "vitest";
|
||||
import { z } from "zod";
|
||||
|
||||
import { javascriptApplicationAnalysisResultSchema } from "../../../src/domain/javascript/javascriptApplicationAnalysis.js";
|
||||
import { connectLocalToolsMcp } from "../../fixtures/localToolsMcp.js";
|
||||
import { createTestTempDirectory } from "../../fixtures/temporaryDirectory.js";
|
||||
import { cliTest } from "../../support/cli/cliFixture.js";
|
||||
|
||||
cliTest.for(["CLI", "stdio MCP"] as const)(
|
||||
"retains wrapped protocol.handle source registrations through %s",
|
||||
async (surface, { cli }) => {
|
||||
const root = await createTestTempDirectory("rea-electron-handle-presence-");
|
||||
await writeFile(
|
||||
join(root, "package.json"),
|
||||
JSON.stringify({ main: "main.cjs" }),
|
||||
);
|
||||
await writeFile(
|
||||
join(root, "main.cjs"),
|
||||
`
|
||||
const { protocol, session } = require("electron");
|
||||
protocol.handle("module-omitted");
|
||||
protocol.handle("module-null", null);
|
||||
protocol.handle("module-undefined", void 0);
|
||||
session.defaultSession.protocol.handle("session-omitted");
|
||||
session.defaultSession.protocol.handle("session-null", null);
|
||||
session.defaultSession.protocol.handle("session-undefined", void 0);
|
||||
protocol.registerStringProtocol("legacy-omitted");
|
||||
protocol.registerStringProtocol("legacy-null", null);
|
||||
protocol.registerStringProtocol("legacy-undefined", void 0);
|
||||
throw new Error("analysis fixture must remain inert");
|
||||
`,
|
||||
);
|
||||
let document: unknown;
|
||||
if (surface === "CLI") {
|
||||
const response = await cli.run({
|
||||
arguments: ["analyze-javascript-application", root, "--json"],
|
||||
});
|
||||
expect(response.exitCode, response.stderr).toBe(0);
|
||||
document = response.json;
|
||||
} else {
|
||||
const { call } = await connectLocalToolsMcp();
|
||||
const response = await call("analyze_javascript_application", {
|
||||
input_path: root,
|
||||
});
|
||||
expect(response.isError, JSON.stringify(response)).not.toBe(true);
|
||||
document = response.structuredContent;
|
||||
}
|
||||
const result = z
|
||||
.object({ normalized_result: javascriptApplicationAnalysisResultSchema })
|
||||
.parse(document).normalized_result;
|
||||
expect(result.summary.protocol_registrations).toBe(6);
|
||||
const schemes = result.graph.nodes.filter(
|
||||
({ kind }) => kind === "url-scheme",
|
||||
);
|
||||
expect(
|
||||
schemes.map((node) => node.observations[0]?.label).toSorted(),
|
||||
).toEqual([
|
||||
"module-null",
|
||||
"module-omitted",
|
||||
"module-undefined",
|
||||
"session-null",
|
||||
"session-omitted",
|
||||
"session-undefined",
|
||||
]);
|
||||
for (const scheme of schemes) {
|
||||
const observation = scheme.observations[0];
|
||||
expect(observation?.properties).toMatchObject({
|
||||
api: "handle",
|
||||
role: "handler",
|
||||
resolution: "literal",
|
||||
});
|
||||
expect(observation?.evidence).toMatchObject({
|
||||
authority: "ast-static-analysis",
|
||||
state: "observed",
|
||||
limitations: expect.arrayContaining([
|
||||
expect.stringContaining("successful registration"),
|
||||
]),
|
||||
});
|
||||
}
|
||||
expect(
|
||||
result.graph.edges.filter((edge) => edge.relation === "handles"),
|
||||
).toHaveLength(6);
|
||||
},
|
||||
);
|
||||
@@ -37,6 +37,52 @@ const protocolFixture = async (): Promise<string> => {
|
||||
return root;
|
||||
};
|
||||
|
||||
describe("Electron wrapped protocol.handle graph", () => {
|
||||
it("retains wrapped handle registrations while rejecting missing legacy callbacks", async () => {
|
||||
const root = await createTestTempDirectory(
|
||||
"rea-electron-wrapped-protocol-",
|
||||
);
|
||||
await writeFile(
|
||||
join(root, "main.cjs"),
|
||||
`
|
||||
const { protocol, session } = require("electron");
|
||||
protocol.handle("module-omitted");
|
||||
protocol.handle("module-null", null);
|
||||
protocol.handle("module-undefined", void 0);
|
||||
session.defaultSession.protocol.handle("session-omitted");
|
||||
session.defaultSession.protocol.handle("session-null", null);
|
||||
session.defaultSession.protocol.handle("session-undefined", void 0);
|
||||
protocol.registerStringProtocol("legacy-omitted");
|
||||
protocol.registerStringProtocol("legacy-null", null);
|
||||
protocol.registerStringProtocol("legacy-undefined", void 0);
|
||||
`,
|
||||
);
|
||||
const result = await reconstructJavaScriptArtifact({ input_path: root });
|
||||
const graph = parseJavaScriptApplicationGraph(result.graph);
|
||||
const schemes = graph.nodes.filter(({ kind }) => kind === "url-scheme");
|
||||
expect(
|
||||
schemes.map((node) => node.observations[0]?.label).toSorted(),
|
||||
).toEqual([
|
||||
"module-null",
|
||||
"module-omitted",
|
||||
"module-undefined",
|
||||
"session-null",
|
||||
"session-omitted",
|
||||
"session-undefined",
|
||||
]);
|
||||
expect(
|
||||
graph.edges.filter((edge) => edge.relation === "handles"),
|
||||
).toHaveLength(6);
|
||||
for (const scheme of schemes) {
|
||||
expect(scheme.observations[0]?.properties).toMatchObject({
|
||||
api: "handle",
|
||||
role: "handler",
|
||||
resolution: "literal",
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe("Electron URL scheme boundaries", () => {
|
||||
it("joins case-insensitive scheme identities while preserving declared spellings", async () => {
|
||||
const root = await createTestTempDirectory("rea-electron-protocol-case-");
|
||||
|
||||
Reference in new issue
Block a user