fix(runtime): accept camelcase miniapp bridge methods
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { CachedVerifiedSurfaceDocumentResolver, parseReadyMessage, productionSurfaceDocument, surfaceDocument } from "@/runtime/surfaces/isolated-surface-host";
|
||||
import { CachedVerifiedSurfaceDocumentResolver, parseBridgeMessage, parseReadyMessage, productionSurfaceDocument, surfaceDocument } from "@/runtime/surfaces/isolated-surface-host";
|
||||
import { VerifiedSurfaceBundleCache, type SurfaceBundleDigest, type SurfaceManifestSignatureVerifier } from "@/runtime/surfaces/surface-bundle-cache";
|
||||
|
||||
describe("isolated Surface bridge contract", () => {
|
||||
@@ -19,6 +19,10 @@ describe("isolated Surface bridge contract", () => {
|
||||
expect(document).not.toContain("https://");
|
||||
});
|
||||
|
||||
it("accepts the SDK camelCase reportProgress bridge method", () => {
|
||||
expect(parseBridgeMessage({ v: 1, type: "lineup.miniapp.v1.request", instance_id: "task:001", request_id: "req_1", method: "tools.reportProgress", payload: { call_id: "call-1", progress: { percent: 10 } } })).toMatchObject({ method: "tools.reportProgress" });
|
||||
});
|
||||
|
||||
it("wraps only an exact verified production bundle in the host default-deny CSP", async () => {
|
||||
// Digest mismatch behavior belongs to the cache suite. This test isolates
|
||||
// the resolver's exact-version lookup and Host-owned CSP wrapping.
|
||||
|
||||
@@ -89,6 +89,12 @@ export class IsolatedSurfaceHost {
|
||||
}
|
||||
|
||||
private readonly receiveMessage = (event: MessageEvent<unknown>): void => {
|
||||
if (event.data && typeof event.data === "object") {
|
||||
const candidate = event.data as { type?: unknown; instance_id?: unknown; method?: unknown };
|
||||
if (candidate.type === BRIDGE_REQUEST_TYPE || candidate.type === READY_TYPE) {
|
||||
console.info("[LineUp Surface] bridge.raw", { type: String(candidate.type), instance_id: String(candidate.instance_id ?? ""), method: typeof candidate.method === "string" ? candidate.method : "" });
|
||||
}
|
||||
}
|
||||
const message = parseBridgeMessage(event.data);
|
||||
if (!message) return;
|
||||
const hosted = this.surfaces.get(message.instance_id);
|
||||
@@ -104,9 +110,17 @@ export class IsolatedSurfaceHost {
|
||||
}
|
||||
if (message.type === BRIDGE_REQUEST_TYPE) {
|
||||
if (!hosted.ready || !this.callbacks.request) return;
|
||||
console.info("[LineUp Surface] bridge.request", { instance_id: message.instance_id, method: message.method });
|
||||
void this.callbacks.request(hosted.instance.instance_id, message.method, message.payload)
|
||||
.then(result => this.sendResponse(hosted, message.request_id, result))
|
||||
.catch(error => this.sendError(hosted, message.request_id, error instanceof Error ? error.message : "bridge_request_failed"));
|
||||
.then(result => {
|
||||
console.info("[LineUp Surface] bridge.response", { instance_id: message.instance_id, method: message.method, disposition: typeof result?.disposition === "string" ? result.disposition : "data" });
|
||||
this.sendResponse(hosted, message.request_id, result);
|
||||
})
|
||||
.catch(error => {
|
||||
const reason = error instanceof Error ? error.message : "bridge_request_failed";
|
||||
console.info("[LineUp Surface] bridge.error", { instance_id: message.instance_id, method: message.method, reason });
|
||||
this.sendError(hosted, message.request_id, reason);
|
||||
});
|
||||
return;
|
||||
}
|
||||
if (!hosted.ready || hosted.cancelSent) return;
|
||||
@@ -157,13 +171,13 @@ export function parseReadyMessage(value: unknown): SurfaceReadyMessage | undefin
|
||||
return parsed?.type === READY_TYPE ? parsed : undefined;
|
||||
}
|
||||
|
||||
function parseBridgeMessage(value: unknown): SurfaceReadyMessage | SurfaceEventMessage | BridgeRequestMessage | undefined {
|
||||
export function parseBridgeMessage(value: unknown): SurfaceReadyMessage | SurfaceEventMessage | BridgeRequestMessage | undefined {
|
||||
if (!isPlainObject(value) || value.v !== BRIDGE_VERSION || (value.type !== READY_TYPE && value.type !== "lineup.surface.v1.event" && value.type !== BRIDGE_REQUEST_TYPE) || typeof value.instance_id !== "string"
|
||||
|| !/^[A-Za-z0-9._:-]{1,128}$/.test(value.instance_id)) return undefined;
|
||||
if (value.type === READY_TYPE && Object.keys(value).every(key => key === "v" || key === "type" || key === "instance_id")) return { v: 1, type: READY_TYPE, instance_id: value.instance_id };
|
||||
if (value.type === "lineup.surface.v1.event" && value.event === "cancel" && Object.keys(value).every(key => key === "v" || key === "type" || key === "instance_id" || key === "event")) return { v: 1, type: "lineup.surface.v1.event", instance_id: value.instance_id, event: "cancel" };
|
||||
if (value.type === BRIDGE_REQUEST_TYPE && typeof value.request_id === "string" && /^[A-Za-z0-9._:-]{1,128}$/.test(value.request_id)
|
||||
&& typeof value.method === "string" && /^[a-z][a-z0-9._-]{0,63}$/.test(value.method) && isPlainObject(value.payload)
|
||||
&& typeof value.method === "string" && /^[a-z][A-Za-z0-9._-]{0,63}$/.test(value.method) && isPlainObject(value.payload)
|
||||
&& Object.keys(value).every(key => key === "v" || key === "type" || key === "instance_id" || key === "request_id" || key === "method" || key === "payload")) {
|
||||
return { v: 1, type: BRIDGE_REQUEST_TYPE, instance_id: value.instance_id, request_id: value.request_id, method: value.method, payload: cloneObject(value.payload) };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user