@adobe/data-rpc 0.10.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (87) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +159 -0
  3. package/dist/arg-async.test.d.ts +1 -0
  4. package/dist/arg-async.test.js +147 -0
  5. package/dist/arg-async.test.js.map +1 -0
  6. package/dist/arg-marshal.d.ts +34 -0
  7. package/dist/arg-marshal.js +90 -0
  8. package/dist/arg-marshal.js.map +1 -0
  9. package/dist/arg-observe.test.d.ts +1 -0
  10. package/dist/arg-observe.test.js +114 -0
  11. package/dist/arg-observe.test.js.map +1 -0
  12. package/dist/consume/caller-context.d.ts +47 -0
  13. package/dist/consume/caller-context.js +3 -0
  14. package/dist/consume/caller-context.js.map +1 -0
  15. package/dist/consume/make-generator.d.ts +16 -0
  16. package/dist/consume/make-generator.js +75 -0
  17. package/dist/consume/make-generator.js.map +1 -0
  18. package/dist/consume/make-observe.d.ts +16 -0
  19. package/dist/consume/make-observe.js +29 -0
  20. package/dist/consume/make-observe.js.map +1 -0
  21. package/dist/consume/make-promise.d.ts +12 -0
  22. package/dist/consume/make-promise.js +32 -0
  23. package/dist/consume/make-promise.js.map +1 -0
  24. package/dist/consume/make-void.d.ts +12 -0
  25. package/dist/consume/make-void.js +17 -0
  26. package/dist/consume/make-void.js.map +1 -0
  27. package/dist/consume/reconstruct-error.d.ts +7 -0
  28. package/dist/consume/reconstruct-error.js +14 -0
  29. package/dist/consume/reconstruct-error.js.map +1 -0
  30. package/dist/consume/synthesize-service.d.ts +10 -0
  31. package/dist/consume/synthesize-service.js +77 -0
  32. package/dist/consume/synthesize-service.js.map +1 -0
  33. package/dist/create-endpoint.d.ts +8 -0
  34. package/dist/create-endpoint.js +462 -0
  35. package/dist/create-endpoint.js.map +1 -0
  36. package/dist/endpoint.d.ts +64 -0
  37. package/dist/endpoint.js +3 -0
  38. package/dist/endpoint.js.map +1 -0
  39. package/dist/host/host-context.d.ts +28 -0
  40. package/dist/host/host-context.js +3 -0
  41. package/dist/host/host-context.js.map +1 -0
  42. package/dist/host/marshal-error.d.ts +8 -0
  43. package/dist/host/marshal-error.js +15 -0
  44. package/dist/host/marshal-error.js.map +1 -0
  45. package/dist/host/register-service.d.ts +10 -0
  46. package/dist/host/register-service.js +169 -0
  47. package/dist/host/register-service.js.map +1 -0
  48. package/dist/id.d.ts +7 -0
  49. package/dist/id.js +12 -0
  50. package/dist/id.js.map +1 -0
  51. package/dist/index.d.ts +7 -0
  52. package/dist/index.js +7 -0
  53. package/dist/index.js.map +1 -0
  54. package/dist/nested.test.d.ts +1 -0
  55. package/dist/nested.test.js +128 -0
  56. package/dist/nested.test.js.map +1 -0
  57. package/dist/protocol.d.ts +157 -0
  58. package/dist/protocol.js +18 -0
  59. package/dist/protocol.js.map +1 -0
  60. package/dist/review-fixes.test.d.ts +1 -0
  61. package/dist/review-fixes.test.js +200 -0
  62. package/dist/review-fixes.test.js.map +1 -0
  63. package/dist/rpc.test.d.ts +1 -0
  64. package/dist/rpc.test.js +250 -0
  65. package/dist/rpc.test.js.map +1 -0
  66. package/dist/transport.d.ts +32 -0
  67. package/dist/transport.js +3 -0
  68. package/dist/transport.js.map +1 -0
  69. package/dist/transports/loopback-transport.d.ts +13 -0
  70. package/dist/transports/loopback-transport.js +102 -0
  71. package/dist/transports/loopback-transport.js.map +1 -0
  72. package/dist/transports/loopback-transport.test.d.ts +1 -0
  73. package/dist/transports/loopback-transport.test.js +42 -0
  74. package/dist/transports/loopback-transport.test.js.map +1 -0
  75. package/dist/transports/message-port-transport.d.ts +14 -0
  76. package/dist/transports/message-port-transport.js +63 -0
  77. package/dist/transports/message-port-transport.js.map +1 -0
  78. package/dist/transports/message-port-transport.test.d.ts +1 -0
  79. package/dist/transports/message-port-transport.test.js +49 -0
  80. package/dist/transports/message-port-transport.test.js.map +1 -0
  81. package/dist/transports/window-transport.d.ts +20 -0
  82. package/dist/transports/window-transport.js +69 -0
  83. package/dist/transports/window-transport.js.map +1 -0
  84. package/dist/transports/window-transport.test.d.ts +1 -0
  85. package/dist/transports/window-transport.test.js +47 -0
  86. package/dist/transports/window-transport.test.js.map +1 -0
  87. package/package.json +38 -0
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,42 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { describe, it, expect, vi } from "vitest";
3
+ import { createRpcLoopbackTransport } from "./loopback-transport.js";
4
+ const msg = (protocol) => ({ kind: "hello", protocol });
5
+ describe("createRpcLoopbackTransport", () => {
6
+ it("delivers asynchronously (never synchronously with send)", async () => {
7
+ const { a, b } = createRpcLoopbackTransport();
8
+ const received = [];
9
+ b.onMessage((m) => received.push(m));
10
+ a.send(msg(1));
11
+ expect(received).toEqual([]); // not yet — delivery is scheduled
12
+ await new Promise((r) => setTimeout(r, 0));
13
+ expect(received).toEqual([msg(1)]);
14
+ });
15
+ it("buffers messages sent before a listener registers, in order", async () => {
16
+ const { a, b } = createRpcLoopbackTransport();
17
+ a.send(msg(1));
18
+ a.send(msg(2));
19
+ const received = [];
20
+ b.onMessage((m) => received.push(m));
21
+ await new Promise((r) => setTimeout(r, 0));
22
+ expect(received).toEqual([msg(1), msg(2)]);
23
+ });
24
+ it("fires every onClose listener once when either side closes", async () => {
25
+ const { a, b } = createRpcLoopbackTransport();
26
+ const onCloseA = vi.fn();
27
+ const onCloseB = vi.fn();
28
+ a.onClose(onCloseA);
29
+ b.onClose(onCloseB);
30
+ b.close();
31
+ expect(onCloseA).toHaveBeenCalledOnce();
32
+ expect(onCloseB).toHaveBeenCalledOnce();
33
+ a.close(); // idempotent — no second fire
34
+ expect(onCloseA).toHaveBeenCalledOnce();
35
+ });
36
+ it("throws on a non-cloneable payload, like a real port", () => {
37
+ const { a } = createRpcLoopbackTransport();
38
+ // A function is not structured-cloneable; sending one must throw here too.
39
+ expect(() => a.send({ kind: "next", id: 1, value: (() => 0) })).toThrow();
40
+ });
41
+ });
42
+ //# sourceMappingURL=loopback-transport.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loopback-transport.test.js","sourceRoot":"","sources":["../../src/transports/loopback-transport.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,14 @@
1
+ import type { RpcTransport } from "../transport.js";
2
+ /**
3
+ * The primary iframe/worker transport: an {@link RpcTransport} over a
4
+ * `MessagePort`. Assigning `onmessage` implicitly starts the port; we also call
5
+ * `start()` explicitly so the transport is robust if the caller later switches
6
+ * to `addEventListener`. Messages arriving before a listener registers are
7
+ * buffered and flushed on the first `onMessage`, mirroring the loopback and
8
+ * matching the fact that the port begins delivering as soon as it is wired.
9
+ *
10
+ * A `MessagePort` has no native "remote closed" event, so `onClose` fires only
11
+ * on a local `close()`; a peer going away is surfaced instead via the
12
+ * `unavailable` announcement (or a per-call timeout).
13
+ */
14
+ export declare function createMessagePortTransport(port: MessagePort): RpcTransport;
@@ -0,0 +1,63 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ /**
3
+ * The primary iframe/worker transport: an {@link RpcTransport} over a
4
+ * `MessagePort`. Assigning `onmessage` implicitly starts the port; we also call
5
+ * `start()` explicitly so the transport is robust if the caller later switches
6
+ * to `addEventListener`. Messages arriving before a listener registers are
7
+ * buffered and flushed on the first `onMessage`, mirroring the loopback and
8
+ * matching the fact that the port begins delivering as soon as it is wired.
9
+ *
10
+ * A `MessagePort` has no native "remote closed" event, so `onClose` fires only
11
+ * on a local `close()`; a peer going away is surfaced instead via the
12
+ * `unavailable` announcement (or a per-call timeout).
13
+ */
14
+ export function createMessagePortTransport(port) {
15
+ const listeners = new Set();
16
+ const closeListeners = new Set();
17
+ const pending = [];
18
+ let closed = false;
19
+ port.onmessage = (event) => {
20
+ // Invariant: only RpcMessages are posted on a data-rpc port.
21
+ const msg = event.data;
22
+ if (listeners.size === 0) {
23
+ pending.push(msg);
24
+ return;
25
+ }
26
+ for (const l of listeners)
27
+ l(msg);
28
+ };
29
+ port.start();
30
+ return {
31
+ send(msg, transfer) {
32
+ if (closed)
33
+ return;
34
+ port.postMessage(msg, transfer ? [...transfer] : []);
35
+ },
36
+ onMessage(listener) {
37
+ listeners.add(listener);
38
+ for (const msg of pending.splice(0))
39
+ listener(msg);
40
+ return () => listeners.delete(listener);
41
+ },
42
+ onClose(listener) {
43
+ if (closed) {
44
+ listener();
45
+ return () => undefined;
46
+ }
47
+ closeListeners.add(listener);
48
+ return () => closeListeners.delete(listener);
49
+ },
50
+ close() {
51
+ if (closed)
52
+ return;
53
+ closed = true;
54
+ port.onmessage = null;
55
+ port.close();
56
+ for (const l of closeListeners)
57
+ l();
58
+ closeListeners.clear();
59
+ listeners.clear();
60
+ },
61
+ };
62
+ }
63
+ //# sourceMappingURL=message-port-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-port-transport.js","sourceRoot":"","sources":["../../src/transports/message-port-transport.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAKvD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,0BAA0B,CAAC,IAAiB;IACxD,MAAM,SAAS,GAAG,IAAI,GAAG,EAA6B,CAAC;IACvD,MAAM,cAAc,GAAG,IAAI,GAAG,EAAc,CAAC;IAC7C,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,IAAI,CAAC,SAAS,GAAG,CAAC,KAAmB,EAAE,EAAE;QACrC,6DAA6D;QAC7D,MAAM,GAAG,GAAG,KAAK,CAAC,IAAkB,CAAC;QACrC,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO;QACX,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,SAAS;YAAE,CAAC,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC,CAAC;IACF,IAAI,CAAC,KAAK,EAAE,CAAC;IAEb,OAAO;QACH,IAAI,CAAC,GAAG,EAAE,QAAQ;YACd,IAAI,MAAM;gBAAE,OAAO;YACnB,IAAI,CAAC,WAAW,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACzD,CAAC;QACD,SAAS,CAAC,QAAQ;YACd,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACxB,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;gBAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;YACnD,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,CAAC,QAAQ;YACZ,IAAI,MAAM,EAAE,CAAC;gBACT,QAAQ,EAAE,CAAC;gBACX,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;YAC3B,CAAC;YACD,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7B,OAAO,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACjD,CAAC;QACD,KAAK;YACD,IAAI,MAAM;gBAAE,OAAO;YACnB,MAAM,GAAG,IAAI,CAAC;YACd,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;YACtB,IAAI,CAAC,KAAK,EAAE,CAAC;YACb,KAAK,MAAM,CAAC,IAAI,cAAc;gBAAE,CAAC,EAAE,CAAC;YACpC,cAAc,CAAC,KAAK,EAAE,CAAC;YACvB,SAAS,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;KACJ,CAAC;AACN,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,49 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ import { describe, it, expect } from "vitest";
3
+ import { Observe } from "@adobe/data/observe";
4
+ import { createRpcEndpoint } from "../create-endpoint.js";
5
+ import { createMessagePortTransport } from "./message-port-transport.js";
6
+ const calcSchema = {
7
+ type: "object",
8
+ properties: {
9
+ value: { type: "observe", value: { type: "number" } },
10
+ add: { type: "function", signature: { parameters: [{ type: "number" }, { type: "number" }], returns: { type: "promise", value: { type: "number" } } } },
11
+ },
12
+ required: ["value", "add"],
13
+ additionalProperties: false,
14
+ };
15
+ function makeCalc() {
16
+ return {
17
+ serviceName: "calc",
18
+ schema: Observe.fromConstant(calcSchema),
19
+ value: Observe.fromConstant(1),
20
+ add: async (a, b) => a + b,
21
+ };
22
+ }
23
+ describe("createMessagePortTransport", () => {
24
+ it("carries a full RPC round-trip over a MessageChannel", async () => {
25
+ const { port1, port2 } = new MessageChannel();
26
+ const ea = createRpcEndpoint(createMessagePortTransport(port1));
27
+ const eb = createRpcEndpoint(createMessagePortTransport(port2));
28
+ ea.expose("calc", makeCalc(), calcSchema);
29
+ const remote = eb.consume("calc", calcSchema);
30
+ expect(await remote.add(2, 40)).toBe(42);
31
+ ea.close();
32
+ eb.close();
33
+ });
34
+ it("buffers messages that arrive before a listener registers, then flushes them", async () => {
35
+ const { port1, port2 } = new MessageChannel();
36
+ // Send on port1 before wrapping port2 with a transport/listener.
37
+ port1.postMessage({ kind: "hello", protocol: 1 });
38
+ const transport = createMessagePortTransport(port2);
39
+ // Give the port a tick to receive-and-buffer the pre-listener message.
40
+ await new Promise((r) => setTimeout(r, 0));
41
+ const received = [];
42
+ transport.onMessage((m) => received.push(m));
43
+ await new Promise((r) => setTimeout(r, 0));
44
+ expect(received).toEqual([{ kind: "hello", protocol: 1 }]);
45
+ transport.close();
46
+ port1.close();
47
+ });
48
+ });
49
+ //# sourceMappingURL=message-port-transport.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-port-transport.test.js","sourceRoot":"","sources":["../../src/transports/message-port-transport.test.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import type { RpcTransport } from "../transport.js";
2
+ /** Options for {@link createWindowTransport}. */
3
+ export interface WindowTransportOptions {
4
+ /** Origins accepted on inbound `message` events; all others are dropped (v1 trust boundary). */
5
+ readonly allowedOrigins: readonly string[];
6
+ /**
7
+ * `targetOrigin` for outbound `postMessage`. Defaults to the sole
8
+ * `allowedOrigins` entry when there is exactly one, otherwise `"*"`.
9
+ * Specify explicitly whenever posting sensitive payloads.
10
+ */
11
+ readonly targetOrigin?: string;
12
+ }
13
+ /**
14
+ * An {@link RpcTransport} over `window.postMessage`, primarily for the initial
15
+ * `MessagePort` handoff between a page and its iframe (after which both sides
16
+ * switch to {@link createMessagePortTransport}). Inbound events are dropped
17
+ * unless their `origin` is allowlisted and the payload looks like an
18
+ * {@link RpcMessage}. Listens on the current global `window`; sends to `target`.
19
+ */
20
+ export declare function createWindowTransport(target: Window, options: WindowTransportOptions): RpcTransport;
@@ -0,0 +1,69 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ /**
3
+ * An {@link RpcTransport} over `window.postMessage`, primarily for the initial
4
+ * `MessagePort` handoff between a page and its iframe (after which both sides
5
+ * switch to {@link createMessagePortTransport}). Inbound events are dropped
6
+ * unless their `origin` is allowlisted and the payload looks like an
7
+ * {@link RpcMessage}. Listens on the current global `window`; sends to `target`.
8
+ */
9
+ export function createWindowTransport(target, options) {
10
+ const listeners = new Set();
11
+ const closeListeners = new Set();
12
+ const pending = [];
13
+ let closed = false;
14
+ // Never default outbound to "*" — that would post (possibly sensitive) replies
15
+ // to whatever origin currently occupies the target frame. Require an explicit
16
+ // targetOrigin unless there is exactly one allowed origin to infer it from.
17
+ const targetOrigin = options.targetOrigin ?? (options.allowedOrigins.length === 1 ? options.allowedOrigins[0] : undefined);
18
+ if (targetOrigin === undefined) {
19
+ throw new Error("createWindowTransport: specify `targetOrigin` when `allowedOrigins` is not exactly one origin");
20
+ }
21
+ const handler = (event) => {
22
+ if (!options.allowedOrigins.includes(event.origin))
23
+ return; // untrusted origin
24
+ const data = event.data;
25
+ if (typeof data !== "object" || data === null || typeof data.kind !== "string") {
26
+ return; // not one of ours
27
+ }
28
+ const msg = data;
29
+ if (listeners.size === 0) {
30
+ pending.push(msg);
31
+ return;
32
+ }
33
+ for (const l of listeners)
34
+ l(msg);
35
+ };
36
+ window.addEventListener("message", handler);
37
+ return {
38
+ send(msg, transfer) {
39
+ if (closed)
40
+ return;
41
+ target.postMessage(msg, targetOrigin, transfer ? [...transfer] : []);
42
+ },
43
+ onMessage(listener) {
44
+ listeners.add(listener);
45
+ for (const msg of pending.splice(0))
46
+ listener(msg);
47
+ return () => listeners.delete(listener);
48
+ },
49
+ onClose(listener) {
50
+ if (closed) {
51
+ listener();
52
+ return () => undefined;
53
+ }
54
+ closeListeners.add(listener);
55
+ return () => closeListeners.delete(listener);
56
+ },
57
+ close() {
58
+ if (closed)
59
+ return;
60
+ closed = true;
61
+ window.removeEventListener("message", handler);
62
+ for (const l of closeListeners)
63
+ l();
64
+ closeListeners.clear();
65
+ listeners.clear();
66
+ },
67
+ };
68
+ }
69
+ //# sourceMappingURL=window-transport.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"window-transport.js","sourceRoot":"","sources":["../../src/transports/window-transport.ts"],"names":[],"mappings":"AAAA,uDAAuD;AAiBvD;;;;;;GAMG;AACH,MAAM,UAAU,qBAAqB,CAAC,MAAc,EAAE,OAA+B;IACjF,MAAM,SAAS,GAAG,IAAI,GAAG,EAA6B,CAAC;IACvD,MAAM,cAAc,GAAG,IAAI,GAAG,EAAc,CAAC;IAC7C,MAAM,OAAO,GAAiB,EAAE,CAAC;IACjC,IAAI,MAAM,GAAG,KAAK,CAAC;IAEnB,+EAA+E;IAC/E,8EAA8E;IAC9E,4EAA4E;IAC5E,MAAM,YAAY,GAAG,OAAO,CAAC,YAAY,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;IAC3H,IAAI,YAAY,KAAK,SAAS,EAAE,CAAC;QAC7B,MAAM,IAAI,KAAK,CAAC,+FAA+F,CAAC,CAAC;IACrH,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,KAAmB,EAAE,EAAE;QACpC,IAAI,CAAC,OAAO,CAAC,cAAc,CAAC,QAAQ,CAAC,KAAK,CAAC,MAAM,CAAC;YAAE,OAAO,CAAC,mBAAmB;QAC/E,MAAM,IAAI,GAAY,KAAK,CAAC,IAAI,CAAC;QACjC,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,IAAI,OAAQ,IAA2B,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrG,OAAO,CAAC,kBAAkB;QAC9B,CAAC;QACD,MAAM,GAAG,GAAG,IAAkB,CAAC;QAC/B,IAAI,SAAS,CAAC,IAAI,KAAK,CAAC,EAAE,CAAC;YACvB,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;YAClB,OAAO;QACX,CAAC;QACD,KAAK,MAAM,CAAC,IAAI,SAAS;YAAE,CAAC,CAAC,GAAG,CAAC,CAAC;IACtC,CAAC,CAAC;IACF,MAAM,CAAC,gBAAgB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;IAE5C,OAAO;QACH,IAAI,CAAC,GAAG,EAAE,QAAQ;YACd,IAAI,MAAM;gBAAE,OAAO;YACnB,MAAM,CAAC,WAAW,CAAC,GAAG,EAAE,YAAY,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QACzE,CAAC;QACD,SAAS,CAAC,QAAQ;YACd,SAAS,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YACxB,KAAK,MAAM,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC;gBAAE,QAAQ,CAAC,GAAG,CAAC,CAAC;YACnD,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QAC5C,CAAC;QACD,OAAO,CAAC,QAAQ;YACZ,IAAI,MAAM,EAAE,CAAC;gBACT,QAAQ,EAAE,CAAC;gBACX,OAAO,GAAG,EAAE,CAAC,SAAS,CAAC;YAC3B,CAAC;YACD,cAAc,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;YAC7B,OAAO,GAAG,EAAE,CAAC,cAAc,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACjD,CAAC;QACD,KAAK;YACD,IAAI,MAAM;gBAAE,OAAO;YACnB,MAAM,GAAG,IAAI,CAAC;YACd,MAAM,CAAC,mBAAmB,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YAC/C,KAAK,MAAM,CAAC,IAAI,cAAc;gBAAE,CAAC,EAAE,CAAC;YACpC,cAAc,CAAC,KAAK,EAAE,CAAC;YACvB,SAAS,CAAC,KAAK,EAAE,CAAC;QACtB,CAAC;KACJ,CAAC;AACN,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,47 @@
1
+ // © 2026 Adobe. MIT License. See /LICENSE for details.
2
+ // @vitest-environment jsdom
3
+ import { describe, it, expect } from "vitest";
4
+ import { createWindowTransport } from "./window-transport.js";
5
+ const hello = { kind: "hello", protocol: 1 };
6
+ describe("createWindowTransport", () => {
7
+ it("delivers messages from an allowed origin", async () => {
8
+ const transport = createWindowTransport(window, { allowedOrigins: ["https://trusted.example"] });
9
+ const received = [];
10
+ transport.onMessage((m) => received.push(m));
11
+ window.dispatchEvent(new MessageEvent("message", { data: hello, origin: "https://trusted.example" }));
12
+ expect(received).toEqual([hello]);
13
+ transport.close();
14
+ });
15
+ it("drops messages from a disallowed origin", async () => {
16
+ const transport = createWindowTransport(window, { allowedOrigins: ["https://trusted.example"] });
17
+ const received = [];
18
+ transport.onMessage((m) => received.push(m));
19
+ window.dispatchEvent(new MessageEvent("message", { data: hello, origin: "https://evil.example" }));
20
+ expect(received).toEqual([]);
21
+ transport.close();
22
+ });
23
+ it("ignores non-RpcMessage payloads even from an allowed origin", async () => {
24
+ const transport = createWindowTransport(window, { allowedOrigins: ["https://trusted.example"] });
25
+ const received = [];
26
+ transport.onMessage((m) => received.push(m));
27
+ window.dispatchEvent(new MessageEvent("message", { data: "just a string", origin: "https://trusted.example" }));
28
+ window.dispatchEvent(new MessageEvent("message", { data: { foo: 1 }, origin: "https://trusted.example" }));
29
+ expect(received).toEqual([]);
30
+ transport.close();
31
+ });
32
+ it("requires an explicit targetOrigin when allowedOrigins is ambiguous (N1)", () => {
33
+ expect(() => createWindowTransport(window, { allowedOrigins: ["https://a.example", "https://b.example"] })).toThrow();
34
+ // A single allowed origin (or an explicit targetOrigin) is fine.
35
+ expect(() => createWindowTransport(window, { allowedOrigins: ["https://a.example"] })).not.toThrow();
36
+ expect(() => createWindowTransport(window, { allowedOrigins: ["https://a.example", "https://b.example"], targetOrigin: "https://a.example" })).not.toThrow();
37
+ });
38
+ it("stops delivering after close", async () => {
39
+ const transport = createWindowTransport(window, { allowedOrigins: ["https://trusted.example"] });
40
+ const received = [];
41
+ transport.onMessage((m) => received.push(m));
42
+ transport.close();
43
+ window.dispatchEvent(new MessageEvent("message", { data: hello, origin: "https://trusted.example" }));
44
+ expect(received).toEqual([]);
45
+ });
46
+ });
47
+ //# sourceMappingURL=window-transport.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"window-transport.test.js","sourceRoot":"","sources":["../../src/transports/window-transport.test.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@adobe/data-rpc",
3
+ "version": "0.10.11",
4
+ "description": "Schema-driven, bidirectional projection of @adobe/data async data services across a boundary (iframe / MessagePort / Worker). Only Data crosses the wire.",
5
+ "type": "module",
6
+ "sideEffects": false,
7
+ "private": false,
8
+ "publishConfig": {
9
+ "registry": "https://registry.npmjs.org/",
10
+ "access": "public"
11
+ },
12
+ "files": [
13
+ "dist",
14
+ "README.md",
15
+ "LICENSE"
16
+ ],
17
+ "dependencies": {
18
+ "@adobe/data": "0.10.11"
19
+ },
20
+ "devDependencies": {
21
+ "jsdom": "^24.1.0",
22
+ "typescript": "^5.8.3",
23
+ "vitest": "^1.6.0"
24
+ },
25
+ "exports": {
26
+ ".": {
27
+ "import": "./dist/index.js",
28
+ "types": "./dist/index.d.ts"
29
+ }
30
+ },
31
+ "scripts": {
32
+ "build": "cp ../../LICENSE . 2>/dev/null || true; tsc -b",
33
+ "clean": "rm -rf dist node_modules",
34
+ "dev": "tsc -b -w --preserveWatchOutput",
35
+ "test": "vitest --run",
36
+ "typecheck": "tsc -b"
37
+ }
38
+ }