@appstrata/protocol 0.1.0

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 (56) hide show
  1. package/README.md +106 -0
  2. package/dist/client/index.d.ts +6 -0
  3. package/dist/client/index.d.ts.map +1 -0
  4. package/dist/client/index.js +5 -0
  5. package/dist/client/proxy.d.ts +72 -0
  6. package/dist/client/proxy.d.ts.map +1 -0
  7. package/dist/client/proxy.js +446 -0
  8. package/dist/client/rpc.d.ts +62 -0
  9. package/dist/client/rpc.d.ts.map +1 -0
  10. package/dist/client/rpc.js +138 -0
  11. package/dist/host/index.d.ts +5 -0
  12. package/dist/host/index.d.ts.map +1 -0
  13. package/dist/host/index.js +4 -0
  14. package/dist/host/message-bridge.d.ts +146 -0
  15. package/dist/host/message-bridge.d.ts.map +1 -0
  16. package/dist/host/message-bridge.js +360 -0
  17. package/dist/index.d.ts +14 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +17 -0
  20. package/dist/messages.d.ts +197 -0
  21. package/dist/messages.d.ts.map +1 -0
  22. package/dist/messages.js +141 -0
  23. package/dist/transport/http/client-polling.d.ts +32 -0
  24. package/dist/transport/http/client-polling.d.ts.map +1 -0
  25. package/dist/transport/http/client-polling.js +181 -0
  26. package/dist/transport/http/client-sse.d.ts +30 -0
  27. package/dist/transport/http/client-sse.d.ts.map +1 -0
  28. package/dist/transport/http/client-sse.js +155 -0
  29. package/dist/transport/http/host-manager.d.ts +63 -0
  30. package/dist/transport/http/host-manager.d.ts.map +1 -0
  31. package/dist/transport/http/host-manager.js +74 -0
  32. package/dist/transport/http/host-polling.d.ts +65 -0
  33. package/dist/transport/http/host-polling.d.ts.map +1 -0
  34. package/dist/transport/http/host-polling.js +143 -0
  35. package/dist/transport/http/host-sse.d.ts +56 -0
  36. package/dist/transport/http/host-sse.d.ts.map +1 -0
  37. package/dist/transport/http/host-sse.js +149 -0
  38. package/dist/transport/http/index.d.ts +13 -0
  39. package/dist/transport/http/index.d.ts.map +1 -0
  40. package/dist/transport/http/index.js +12 -0
  41. package/dist/transport/http/types.d.ts +73 -0
  42. package/dist/transport/http/types.d.ts.map +1 -0
  43. package/dist/transport/http/types.js +8 -0
  44. package/dist/transport/index.d.ts +33 -0
  45. package/dist/transport/index.d.ts.map +1 -0
  46. package/dist/transport/index.js +37 -0
  47. package/dist/transport/postMessage.d.ts +70 -0
  48. package/dist/transport/postMessage.d.ts.map +1 -0
  49. package/dist/transport/postMessage.js +94 -0
  50. package/dist/transport/relay.d.ts +118 -0
  51. package/dist/transport/relay.d.ts.map +1 -0
  52. package/dist/transport/relay.js +216 -0
  53. package/dist/transport/types.d.ts +30 -0
  54. package/dist/transport/types.d.ts.map +1 -0
  55. package/dist/transport/types.js +6 -0
  56. package/package.json +60 -0
@@ -0,0 +1,62 @@
1
+ /**
2
+ * RPC (Request/Response) layer for capability operations.
3
+ *
4
+ * Handles sending requests and matching responses.
5
+ */
6
+ import type { Transport } from "../transport/index.js";
7
+ /**
8
+ * RPC client for sending requests to the host.
9
+ *
10
+ * Manages request/response correlation and timeouts.
11
+ */
12
+ export declare class RpcClient {
13
+ private transport;
14
+ private pending;
15
+ private defaultTimeout;
16
+ private readyPromise;
17
+ private unsubscribe?;
18
+ /**
19
+ * Create an RPC client.
20
+ *
21
+ * @param transport - Transport for sending/receiving messages
22
+ * @param readyPromise - Promise that resolves when the handshake is complete.
23
+ * Per spec §2.2, REQUESTs must not be sent before READY/READY_ACK.
24
+ * @param options - RPC options
25
+ */
26
+ constructor(transport: Transport, readyPromise: Promise<void>, options?: {
27
+ defaultTimeout?: number;
28
+ });
29
+ /**
30
+ * Send a notification (fire-and-forget request, no response expected).
31
+ *
32
+ * The REQUEST is sent without a `requestId`, signalling to the Host
33
+ * that no RESPONSE should be sent (spec §4.4).
34
+ *
35
+ * @param op - Operation name
36
+ * @param payload - Operation payload
37
+ */
38
+ notify(op: string, payload?: unknown): Promise<void>;
39
+ /**
40
+ * Send a request and wait for response.
41
+ *
42
+ * @param op - Operation name
43
+ * @param payload - Operation payload
44
+ * @param timeout - Request timeout in milliseconds (default: 30000)
45
+ * @returns Promise that resolves with the result
46
+ *
47
+ * @example
48
+ * ```ts
49
+ * const result = await rpc.call("storage.get", { key: "myKey" });
50
+ * ```
51
+ */
52
+ call(op: string, payload?: unknown, timeout?: number): Promise<unknown>;
53
+ /**
54
+ * Handle response message.
55
+ */
56
+ private handleResponse;
57
+ /**
58
+ * Close the RPC client and cancel all pending requests.
59
+ */
60
+ close(): void;
61
+ }
62
+ //# sourceMappingURL=rpc.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"rpc.d.ts","sourceRoot":"","sources":["../../src/client/rpc.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAavD;;;;GAIG;AACH,qBAAa,SAAS;IACpB,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,cAAc,CAAS;IAC/B,OAAO,CAAC,YAAY,CAAgB;IACpC,OAAO,CAAC,WAAW,CAAC,CAAa;IAEjC;;;;;;;OAOG;gBAED,SAAS,EAAE,SAAS,EACpB,YAAY,EAAE,OAAO,CAAC,IAAI,CAAC,EAC3B,OAAO,GAAE;QAAE,cAAc,CAAC,EAAE,MAAM,CAAA;KAAO;IAc3C;;;;;;;;OAQG;IACG,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,GAAG,OAAO,CAAC,IAAI,CAAC;IAe1D;;;;;;;;;;;;OAYG;IACG,IAAI,CAAC,EAAE,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,OAAO,EAAE,OAAO,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAgC7E;;OAEG;IACH,OAAO,CAAC,cAAc;IA2BtB;;OAEG;IACH,KAAK,IAAI,IAAI;CAiBd"}
@@ -0,0 +1,138 @@
1
+ /**
2
+ * RPC (Request/Response) layer for capability operations.
3
+ *
4
+ * Handles sending requests and matching responses.
5
+ */
6
+ import { generateRequestId, isResponseMessage } from "../messages.js";
7
+ /**
8
+ * RPC client for sending requests to the host.
9
+ *
10
+ * Manages request/response correlation and timeouts.
11
+ */
12
+ export class RpcClient {
13
+ /**
14
+ * Create an RPC client.
15
+ *
16
+ * @param transport - Transport for sending/receiving messages
17
+ * @param readyPromise - Promise that resolves when the handshake is complete.
18
+ * Per spec §2.2, REQUESTs must not be sent before READY/READY_ACK.
19
+ * @param options - RPC options
20
+ */
21
+ constructor(transport, readyPromise, options = {}) {
22
+ this.pending = new Map();
23
+ this.transport = transport;
24
+ this.readyPromise = readyPromise;
25
+ this.defaultTimeout = options.defaultTimeout ?? 30000; // 30 seconds default
26
+ // Listen for responses
27
+ this.unsubscribe = transport.onMessage((msg) => {
28
+ if (isResponseMessage(msg)) {
29
+ this.handleResponse(msg);
30
+ }
31
+ });
32
+ }
33
+ /**
34
+ * Send a notification (fire-and-forget request, no response expected).
35
+ *
36
+ * The REQUEST is sent without a `requestId`, signalling to the Host
37
+ * that no RESPONSE should be sent (spec §4.4).
38
+ *
39
+ * @param op - Operation name
40
+ * @param payload - Operation payload
41
+ */
42
+ async notify(op, payload) {
43
+ await this.readyPromise;
44
+ const request = {
45
+ protocol: "appstrata-protocol",
46
+ version: "1.0",
47
+ type: "REQUEST",
48
+ timestamp: Date.now(),
49
+ op,
50
+ payload
51
+ };
52
+ this.transport.send(request);
53
+ }
54
+ /**
55
+ * Send a request and wait for response.
56
+ *
57
+ * @param op - Operation name
58
+ * @param payload - Operation payload
59
+ * @param timeout - Request timeout in milliseconds (default: 30000)
60
+ * @returns Promise that resolves with the result
61
+ *
62
+ * @example
63
+ * ```ts
64
+ * const result = await rpc.call("storage.get", { key: "myKey" });
65
+ * ```
66
+ */
67
+ async call(op, payload, timeout) {
68
+ // Spec §2.2: Apps MUST NOT send REQUEST messages until handshake is complete
69
+ await this.readyPromise;
70
+ const requestId = generateRequestId();
71
+ const timeoutMs = timeout ?? this.defaultTimeout;
72
+ return new Promise((resolve, reject) => {
73
+ // Set up timeout
74
+ const timeoutId = setTimeout(() => {
75
+ this.pending.delete(requestId);
76
+ reject(new Error(`RPC timeout after ${timeoutMs}ms for operation: ${op}`));
77
+ }, timeoutMs);
78
+ // Store pending request
79
+ this.pending.set(requestId, { resolve, reject, timeoutId });
80
+ // Send request
81
+ const request = {
82
+ protocol: "appstrata-protocol",
83
+ version: "1.0",
84
+ type: "REQUEST",
85
+ timestamp: Date.now(),
86
+ requestId,
87
+ op,
88
+ payload
89
+ };
90
+ this.transport.send(request);
91
+ });
92
+ }
93
+ /**
94
+ * Handle response message.
95
+ */
96
+ handleResponse(msg) {
97
+ const pending = this.pending.get(msg.requestId);
98
+ if (!pending) {
99
+ // No pending request for this response (maybe already timed out)
100
+ console.warn("[AppStrata] Received response for unknown request:", msg.requestId);
101
+ return;
102
+ }
103
+ // Clear timeout
104
+ if (pending.timeoutId) {
105
+ clearTimeout(pending.timeoutId);
106
+ }
107
+ // Remove from pending
108
+ this.pending.delete(msg.requestId);
109
+ // Resolve or reject
110
+ if (msg.ok) {
111
+ pending.resolve(msg.result);
112
+ }
113
+ else {
114
+ const error = new Error(msg.error?.message || "RPC call failed");
115
+ error.code = msg.error?.code;
116
+ error.details = msg.error?.details;
117
+ pending.reject(error);
118
+ }
119
+ }
120
+ /**
121
+ * Close the RPC client and cancel all pending requests.
122
+ */
123
+ close() {
124
+ // Unsubscribe from transport
125
+ if (this.unsubscribe) {
126
+ this.unsubscribe();
127
+ this.unsubscribe = undefined;
128
+ }
129
+ // Cancel all pending requests
130
+ for (const [, pending] of this.pending) {
131
+ if (pending.timeoutId) {
132
+ clearTimeout(pending.timeoutId);
133
+ }
134
+ pending.reject(new Error("RPC client closed"));
135
+ }
136
+ this.pending.clear();
137
+ }
138
+ }
@@ -0,0 +1,5 @@
1
+ /**
2
+ * Host-side protocol implementation.
3
+ */
4
+ export * from "./message-bridge.js";
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/host/index.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,cAAc,qBAAqB,CAAC"}
@@ -0,0 +1,4 @@
1
+ /**
2
+ * Host-side protocol implementation.
3
+ */
4
+ export * from "./message-bridge.js";
@@ -0,0 +1,146 @@
1
+ /**
2
+ * MessageBridge - Host-side message protocol bridge.
3
+ *
4
+ * Bridges communication between the player and app via the message protocol.
5
+ * Handles the HELLO/READY/READY_ACK handshake and subsequent RPC/event communication.
6
+ *
7
+ * Key responsibilities:
8
+ * - Listen for HELLO messages from apps
9
+ * - Send READY messages with AppContext
10
+ * - Retry READY until READY_ACK received (if HELLO not received)
11
+ * - Handle REQUEST messages and send RESPONSE
12
+ * - Fire lifecycle EVENT messages
13
+ */
14
+ import { type AppContext, type SignagePlayer } from "@appstrata/core";
15
+ import type { Transport } from "../transport/index.js";
16
+ export interface MessageBridgeConfig {
17
+ /**
18
+ * Transport for sending/receiving messages.
19
+ */
20
+ transport: Transport;
21
+ /**
22
+ * SignagePlayer instance to delegate RPC calls to.
23
+ * RPC requests will be routed to the player's API methods.
24
+ */
25
+ player: SignagePlayer;
26
+ /**
27
+ * Must return the current AppContext.
28
+ * Called when READY needs to be sent.
29
+ */
30
+ getContext: () => Promise<AppContext>;
31
+ /**
32
+ * READY retry interval in milliseconds.
33
+ * @default 1000 (1 second)
34
+ */
35
+ retryInterval?: number;
36
+ /**
37
+ * Maximum number of READY retries.
38
+ * @default 10
39
+ */
40
+ maxRetries?: number;
41
+ }
42
+ /**
43
+ * MessageBridge - Host-side protocol bridge.
44
+ *
45
+ * **Single-session:** one `MessageBridge` instance covers one App page load.
46
+ * Once the handshake completes (`receivedReadyAck = true`), the bridge enters
47
+ * the ACTIVE state and does not reset. When the App reloads, the old bridge
48
+ * must be destroyed and a new one created.
49
+ *
50
+ * Bridges the player to apps via the message protocol.
51
+ *
52
+ * Usage:
53
+ * ```typescript
54
+ * const bridge = new MessageBridge({
55
+ * transport: createPostMessageTransport({ targetWindow: iframe.contentWindow!, targetOrigin: "*" }),
56
+ * player: myPlayer,
57
+ * getContext: async () => buildAppContext(),
58
+ * });
59
+ *
60
+ * // Start the handshake (sends READY)
61
+ * bridge.start();
62
+ *
63
+ * // Send lifecycle events
64
+ * bridge.fireShow();
65
+ * bridge.fireStart();
66
+ * ```
67
+ */
68
+ export declare class MessageBridge {
69
+ private config;
70
+ private transport;
71
+ private receivedHello;
72
+ private receivedReadyAck;
73
+ private helloRequestId?;
74
+ private retryTimer?;
75
+ private retryCount;
76
+ private started;
77
+ private unsubscribeFromTransport?;
78
+ private pendingEvents;
79
+ constructor(config: MessageBridgeConfig);
80
+ /**
81
+ * Start the bridge (send initial READY message).
82
+ */
83
+ start(): void;
84
+ /**
85
+ * Handle HELLO message from app.
86
+ */
87
+ private handleHello;
88
+ /**
89
+ * Handle READY_ACK message from app.
90
+ */
91
+ private handleReadyAck;
92
+ /**
93
+ * Flush pending events that were queued before READY_ACK.
94
+ */
95
+ private flushPendingEvents;
96
+ /**
97
+ * Handle REQUEST message from app.
98
+ */
99
+ private handleRequest;
100
+ /**
101
+ * Handle request by routing to player API.
102
+ */
103
+ private handlePlayerRequest;
104
+ /**
105
+ * Helper to check capability availability.
106
+ */
107
+ private requireCapability;
108
+ /**
109
+ * Send latest AppContext as READY message to app.
110
+ *
111
+ * Awaits the AppContext to be available.
112
+ * If proactive is true, and HELLO arrives by the time the context is available,
113
+ * no READY is sent.
114
+ *
115
+ * @param reason - The phase at which the READY message is being sent. Only used for logging.
116
+ */
117
+ private sendReady;
118
+ /**
119
+ * Schedule next retry for READY messages.
120
+ */
121
+ private scheduleNextRetry;
122
+ /**
123
+ * Cancel the next retry for READY messages.
124
+ */
125
+ private cancelNextRetry;
126
+ /**
127
+ * Send an EVENT message through the transport.
128
+ * If the handshake is not yet complete (READY_ACK not received), the event
129
+ * is queued and will be flushed when READY_ACK arrives.
130
+ */
131
+ private sendEventMessage;
132
+ fireShow(): void;
133
+ fireStart(): void;
134
+ fireHide(): void;
135
+ fireStop(): void;
136
+ fireContextChange(context: AppContext): void;
137
+ /**
138
+ * Cleanup.
139
+ *
140
+ * Unsubscribes from the transport but does NOT close it.
141
+ * The transport lifecycle is owned by whoever created it
142
+ * (e.g., the session manager), not by the bridge.
143
+ */
144
+ destroy(): void;
145
+ }
146
+ //# sourceMappingURL=message-bridge.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-bridge.d.ts","sourceRoot":"","sources":["../../src/host/message-bridge.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAmC,KAAK,UAAU,EAAE,KAAK,aAAa,EAA0B,MAAM,iBAAiB,CAAC;AAG/H,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAkBvD,MAAM,WAAW,mBAAmB;IAElC;;OAEG;IACH,SAAS,EAAE,SAAS,CAAC;IAErB;;;OAGG;IACH,MAAM,EAAE,aAAa,CAAC;IAEtB;;;OAGG;IACH,UAAU,EAAE,MAAM,OAAO,CAAC,UAAU,CAAC,CAAC;IAEtC;;;OAGG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IAEvB;;;OAGG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;CAErB;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,qBAAa,aAAa;IACxB,OAAO,CAAC,MAAM,CAA4E;IAC1F,OAAO,CAAC,SAAS,CAAY;IAC7B,OAAO,CAAC,aAAa,CAAS;IAC9B,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,cAAc,CAAC,CAAS;IAChC,OAAO,CAAC,UAAU,CAAC,CAAgC;IACnD,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAS;IACxB,OAAO,CAAC,wBAAwB,CAAC,CAAa;IAC9C,OAAO,CAAC,aAAa,CAAsB;gBAE/B,MAAM,EAAE,mBAAmB;IAqBvC;;OAEG;IACH,KAAK,IAAI,IAAI;IAcb;;OAEG;IACH,OAAO,CAAC,WAAW;IAoBnB;;OAEG;IACH,OAAO,CAAC,cAAc;IAWtB;;OAEG;IACH,OAAO,CAAC,kBAAkB;IAQ1B;;OAEG;YACW,aAAa;IAsD3B;;OAEG;YACW,mBAAmB;IAoEjC;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAMzB;;;;;;;;OAQG;YACW,SAAS;IAyBvB;;OAEG;IACH,OAAO,CAAC,iBAAiB;IAqBzB;;OAEG;IACH,OAAO,CAAC,eAAe;IAOvB;;;;OAIG;IACH,OAAO,CAAC,gBAAgB;IAoBxB,QAAQ,IAAI,IAAI;IAIhB,SAAS,IAAI,IAAI;IAIjB,QAAQ,IAAI,IAAI;IAIhB,QAAQ,IAAI,IAAI;IAIhB,iBAAiB,CAAC,OAAO,EAAE,UAAU,GAAG,IAAI;IAK5C;;;;;;OAMG;IACH,OAAO,IAAI,IAAI;CAUhB"}