@awarevue/agent-sdk-browser 2.0.91

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.
package/README.md ADDED
@@ -0,0 +1,203 @@
1
+ # @awarevue/agent-sdk-browser
2
+
3
+ Browser transport adapters for the [Aware Agent Protocol](../agent-sdk-typescript/README.md). Enables agents to run inside `<iframe>` elements and communicate with the host page over the [postMessage API](https://developer.mozilla.org/en-US/docs/Web/API/Window/postMessage) — a drop-in replacement for the WebSocket transport with 100% protocol parity.
4
+
5
+ ## Overview
6
+
7
+ ```
8
+ Host page iframe (agent)
9
+ ───────────────────────────────────────── ─────────────────────────────
10
+ PostMessageHostDuplexTransport(iframeEl) ←──→ PostMessageIframeDuplexTransport()
11
+ │ │
12
+ InMemoryHub.addPeer(id, transport) WsJsonEncoder
13
+ │ │
14
+ AgentServer LoggingDuplexTransport
15
+
16
+ AgentProtocol
17
+
18
+ AgentApp
19
+ ```
20
+
21
+ Both transports implement `DuplexTransport<string, string>` — the exact same interface as `WsDuplexTransport` / `WsServerDuplexTransport` from the core SDK. **Nothing in the protocol stack above the transport layer changes.** Switching between WebSocket and postMessage is a single constructor swap.
22
+
23
+ ## Installation
24
+
25
+ ```bash
26
+ npm install @awarevue/agent-sdk-browser
27
+ # peer dependencies
28
+ npm install @awarevue/agent-sdk @awarevue/api-types rxjs
29
+ ```
30
+
31
+ ## Example 1 — Agent inside an iframe
32
+
33
+ `createBrowserAgentApp` mirrors `createAgentApp` from the core SDK but defaults the transport to `PostMessageIframeDuplexTransport`.
34
+
35
+ ```ts
36
+ // agent/index.ts — bundled and loaded inside an <iframe>
37
+ import { createBrowserAgentApp } from '@awarevue/agent-sdk-browser';
38
+ import { Agent, RunContext } from '@awarevue/agent-sdk';
39
+
40
+ const myAgent: Agent = {
41
+ async start(provider: string, config: unknown, ctx: RunContext) {
42
+ ctx.pushState('door-1', { connected: true, locked: true });
43
+ },
44
+
45
+ async stop(provider: string) {},
46
+
47
+ async runCommand(provider, device, command) {
48
+ if (command.command === 'door.unlock') {
49
+ return { success: true };
50
+ }
51
+ return { success: false, error: 'Unknown command' };
52
+ },
53
+
54
+ async query() {
55
+ return [];
56
+ },
57
+ };
58
+
59
+ const app = createBrowserAgentApp(myAgent, {
60
+ agentId: 'my-agent',
61
+ providers: {
62
+ 'my-provider': {
63
+ title: 'My Provider',
64
+ configSchema: {},
65
+ configDefault: {},
66
+ },
67
+ },
68
+ });
69
+
70
+ app.start();
71
+ ```
72
+
73
+ ## Example 2 — Host page managing multiple iframe agents
74
+
75
+ Use `PostMessageHostDuplexTransport` (one per iframe), wire each into an `InMemoryHub`, then attach an `AgentServer` — the same server-side setup used with WebSocket agents.
76
+
77
+ ```ts
78
+ // host/index.ts — runs in the parent page
79
+ import {
80
+ AgentServer,
81
+ InMemoryHub,
82
+ WsJsonEncoder,
83
+ } from '@awarevue/agent-sdk';
84
+ import { PostMessageHostDuplexTransport } from '@awarevue/agent-sdk-browser';
85
+ import { FromAgent, FromServer, Message } from '@awarevue/api-types';
86
+
87
+ type AgentMsg = Message<FromAgent>;
88
+ type ServerMsg = Message<FromServer>;
89
+
90
+ const hub = new InMemoryHub<AgentMsg, ServerMsg, string>();
91
+ const server = new AgentServer(hub);
92
+ server.init();
93
+
94
+ // Called once per iframe, after the iframe's src has loaded
95
+ function registerIframeAgent(iframeEl: HTMLIFrameElement, peerId: string) {
96
+ const rawTransport = new PostMessageHostDuplexTransport(iframeEl);
97
+
98
+ // Wrap in the same JSON encoding layer used on the WebSocket path
99
+ const transport = new WsJsonEncoder(rawTransport) as unknown as import('@awarevue/agent-sdk').DuplexTransport<AgentMsg, ServerMsg>;
100
+
101
+ hub.addPeer(peerId, transport as any);
102
+ }
103
+
104
+ // Wire up iframes declared in your HTML
105
+ const iframe1 = document.getElementById('agent-iframe-1') as HTMLIFrameElement;
106
+ iframe1.addEventListener('load', () => registerIframeAgent(iframe1, 'agent-1'));
107
+ iframe1.src = '/agents/my-agent/index.html';
108
+ ```
109
+
110
+ > **Note:** `WsJsonEncoder` is applied on the host side too, because `PostMessageHostDuplexTransport` is a `DuplexTransport<string, string>` (raw strings). This matches exactly how `WsServerDuplexTransport` is used in server-side code.
111
+
112
+ ## Example 3 — Testing an iframe agent over WebSocket (transport swap)
113
+
114
+ Because `createBrowserAgentApp` accepts a `transport` override, you can replace the postMessage stack with the standard WebSocket stack for out-of-browser integration testing — zero changes to the agent code:
115
+
116
+ ```ts
117
+ import { createBrowserAgentApp } from '@awarevue/agent-sdk-browser';
118
+ import {
119
+ WsDuplexTransport,
120
+ WsJsonEncoder,
121
+ LoggingDuplexTransport,
122
+ } from '@awarevue/agent-sdk';
123
+
124
+ // The exact same agent code runs against a real Aware server over WebSocket
125
+ const app = createBrowserAgentApp(myAgent, {
126
+ agentId: 'my-agent',
127
+ providers: { 'my-provider': { title: 'My Provider', configSchema: {}, configDefault: {} } },
128
+ transport: new LoggingDuplexTransport(
129
+ new WsJsonEncoder(
130
+ new WsDuplexTransport({
131
+ url: 'wss://hub.example.com/agent',
132
+ headers: { Authorization: 'APIKey <key>' },
133
+ }),
134
+ ) as any,
135
+ ),
136
+ });
137
+
138
+ app.start();
139
+ ```
140
+
141
+ ## API reference
142
+
143
+ ### `PostMessageIframeDuplexTransport`
144
+
145
+ Agent-side transport. Listens for messages on `window` and sends to `window.parent`.
146
+
147
+ ```ts
148
+ new PostMessageIframeDuplexTransport(opts?: {
149
+ targetOrigin?: string; // default '*'
150
+ })
151
+ ```
152
+
153
+ | Member | Type | Description |
154
+ |--------|------|-------------|
155
+ | `connected$` | `Observable<boolean>` | Emits `true` immediately, then `false` on `close()` |
156
+ | `messages$` | `Observable<string>` | Inbound string messages from the host |
157
+ | `send(msg)` | `void` | Sends a string to `window.parent` |
158
+ | `close()` | `void` | Tears down all subscriptions; idempotent |
159
+
160
+ ### `PostMessageHostDuplexTransport`
161
+
162
+ Host-side transport. Wraps a single `HTMLIFrameElement` and filters `window` message events by `event.source`.
163
+
164
+ ```ts
165
+ new PostMessageHostDuplexTransport(
166
+ iframe: HTMLIFrameElement,
167
+ targetOrigin?: string, // default '*'
168
+ )
169
+ ```
170
+
171
+ | Member | Type | Description |
172
+ |--------|------|-------------|
173
+ | `connected$` | `Observable<boolean>` | Emits `true` immediately, then `false` on `close()` |
174
+ | `messages$` | `Observable<string>` | Inbound string messages from the specific iframe |
175
+ | `send(msg)` | `void` | Sends a string to `iframe.contentWindow` |
176
+ | `close()` | `void` | Tears down all subscriptions; idempotent |
177
+
178
+ ### `createBrowserAgentApp`
179
+
180
+ Convenience factory. Mirrors `createAgentApp` from the core SDK.
181
+
182
+ ```ts
183
+ createBrowserAgentApp(agent: Agent, options: BrowserAgentAppOptions): AgentApp
184
+ ```
185
+
186
+ `BrowserAgentAppOptions` extends `AgentOptions` (minus `transport`) with:
187
+
188
+ | Option | Type | Default | Description |
189
+ |--------|------|---------|-------------|
190
+ | `targetOrigin` | `string` | `'*'` | Passed to the default `PostMessageIframeDuplexTransport` |
191
+ | `transport` | `DuplexTransport<...>` | — | Override the full transport stack (e.g. for WebSocket testing) |
192
+
193
+ ## Origin security
194
+
195
+ Both transports default `targetOrigin` to `'*'` for ease of development. In production, set it to the exact origin of the other party to prevent cross-origin message leakage:
196
+
197
+ ```ts
198
+ // Agent side (inside iframe)
199
+ new PostMessageIframeDuplexTransport({ targetOrigin: 'https://app.example.com' })
200
+
201
+ // Host side
202
+ new PostMessageHostDuplexTransport(iframeEl, 'https://agents.example.com')
203
+ ```
@@ -0,0 +1,29 @@
1
+ import { FromAgent, FromServer, Message } from "@awarevue/api-types";
2
+ import { Agent, AgentApp, AgentOptions, DuplexTransport } from "@awarevue/agent-sdk";
3
+ export type BrowserAgentAppOptions = Omit<AgentOptions, "transport"> & {
4
+ /**
5
+ * Target origin passed to the default `PostMessageIframeDuplexTransport`.
6
+ * Ignored when a custom `transport` is provided.
7
+ * Defaults to `'*'`.
8
+ */
9
+ targetOrigin?: string;
10
+ /**
11
+ * Provide a fully-wired custom transport to replace the default
12
+ * postMessage stack. Use this to swap in a WebSocket transport for
13
+ * out-of-browser testing without changing any other code.
14
+ */
15
+ transport?: DuplexTransport<Message<FromServer>, Message<FromAgent>>;
16
+ };
17
+ /**
18
+ * Creates an AgentApp wired to the iframe postMessage transport by default.
19
+ *
20
+ * Transport stack (bottom → top):
21
+ * PostMessageIframeDuplexTransport (raw string channel)
22
+ * → WsJsonEncoder ({ event, data } ↔ Message<T>)
23
+ * → LoggingDuplexTransport (console logging)
24
+ * → AgentApp / AgentProtocol
25
+ *
26
+ * To test outside the browser, pass any DuplexTransport<Message<FromServer>,
27
+ * Message<FromAgent>> as the `transport` option — no other changes needed.
28
+ */
29
+ export declare function createBrowserAgentApp(agent: Agent, options: BrowserAgentAppOptions): AgentApp;
@@ -0,0 +1,35 @@
1
+ "use strict";
2
+ // browser-agent-app.ts
3
+ // ----------------------------------------------------------------
4
+ // Convenience factory that mirrors createAgentApp() from the core SDK
5
+ // but defaults to the postMessage transport stack instead of WebSocket.
6
+ //
7
+ // Consumers who want explicit control can pass a custom `transport`
8
+ // (e.g. the full WsJsonEncoder(...) stack) and skip this factory
9
+ // entirely — AgentApp / createAgentApp accept any DuplexTransport.
10
+ // ----------------------------------------------------------------
11
+ Object.defineProperty(exports, "__esModule", { value: true });
12
+ exports.createBrowserAgentApp = createBrowserAgentApp;
13
+ const agent_sdk_1 = require("@awarevue/agent-sdk");
14
+ const post_message_iframe_1 = require("./post-message-iframe");
15
+ /* ---------------------------------------------------------------- */
16
+ /* Factory */
17
+ /* ---------------------------------------------------------------- */
18
+ /**
19
+ * Creates an AgentApp wired to the iframe postMessage transport by default.
20
+ *
21
+ * Transport stack (bottom → top):
22
+ * PostMessageIframeDuplexTransport (raw string channel)
23
+ * → WsJsonEncoder ({ event, data } ↔ Message<T>)
24
+ * → LoggingDuplexTransport (console logging)
25
+ * → AgentApp / AgentProtocol
26
+ *
27
+ * To test outside the browser, pass any DuplexTransport<Message<FromServer>,
28
+ * Message<FromAgent>> as the `transport` option — no other changes needed.
29
+ */
30
+ function createBrowserAgentApp(agent, options) {
31
+ const { targetOrigin, transport, ...rest } = options;
32
+ const finalTransport = transport ??
33
+ new agent_sdk_1.LoggingDuplexTransport(new agent_sdk_1.WsJsonEncoder(new post_message_iframe_1.PostMessageIframeDuplexTransport({ targetOrigin })));
34
+ return new agent_sdk_1.AgentApp(agent, { ...rest, transport: finalTransport });
35
+ }
@@ -0,0 +1,4 @@
1
+ export * from "./post-message-iframe";
2
+ export * from "./post-message-host";
3
+ export * from "./browser-agent-app";
4
+ export * from "./post-message-hub";
package/dist/index.js ADDED
@@ -0,0 +1,20 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
14
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
15
+ };
16
+ Object.defineProperty(exports, "__esModule", { value: true });
17
+ __exportStar(require("./post-message-iframe"), exports);
18
+ __exportStar(require("./post-message-host"), exports);
19
+ __exportStar(require("./browser-agent-app"), exports);
20
+ __exportStar(require("./post-message-hub"), exports);
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@awarevue/agent-sdk-browser",
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "git+https://github.com/Linc-Security-Systems/aware-essentials.git"
6
+ },
7
+ "version": "2.0.91",
8
+ "description": "Browser transport adapters for the Aware agent protocol — iframe/postMessage drop-in replacement for the WebSocket transport.",
9
+ "author": "Yaser Awajan",
10
+ "license": "MIT",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "main": "dist/index.js",
15
+ "types": "dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "import": "./dist/index.js",
19
+ "require": "./dist/index.js",
20
+ "types": "./dist/index.d.ts"
21
+ }
22
+ },
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.json && cp package.json dist/",
26
+ "prepublishOnly": "yarn build",
27
+ "test": "vitest run",
28
+ "lint": "eslint \"src/**/*.{ts,tsx}\"",
29
+ "lint:fix": "yarn lint --fix"
30
+ },
31
+ "peerDependencies": {
32
+ "@awarevue/agent-sdk": "2.0.91",
33
+ "@awarevue/api-types": "2.0.91",
34
+ "rxjs": "^7.8.2"
35
+ },
36
+ "devDependencies": {
37
+ "@awarevue/agent-sdk": "2.0.91",
38
+ "@awarevue/api-types": "2.0.91",
39
+ "@typescript-eslint/eslint-plugin": "^8.31.1",
40
+ "@typescript-eslint/parser": "^8.31.1",
41
+ "eslint": "^9.25.1",
42
+ "eslint-config-prettier": "^10.1.2",
43
+ "eslint-plugin-import": "^2.31.0",
44
+ "jsdom": "^26.1.0",
45
+ "rxjs": "7.8.2",
46
+ "typescript": "^5.8.3",
47
+ "vitest": "^4.1.4"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "registry": "https://registry.npmjs.org/"
52
+ }
53
+ }
@@ -0,0 +1,20 @@
1
+ import { Observable } from "rxjs";
2
+ import { DuplexTransport } from "@awarevue/agent-sdk";
3
+ export declare class PostMessageHostDuplexTransport implements DuplexTransport<string, string> {
4
+ private readonly iframe;
5
+ readonly connected$: Observable<boolean>;
6
+ readonly messages$: Observable<string>;
7
+ private readonly _connected$;
8
+ private readonly destroy$;
9
+ private readonly targetOrigin;
10
+ private closed;
11
+ /**
12
+ * @param iframe The iframe element whose agent this transport represents.
13
+ * @param targetOrigin Target origin for outbound `postMessage` calls and for
14
+ * filtering inbound messages by `event.origin`.
15
+ * Defaults to `'*'`.
16
+ */
17
+ constructor(iframe: HTMLIFrameElement, targetOrigin?: string);
18
+ send(msg: string): void;
19
+ close(): void;
20
+ }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ // transports/post-message-host.ts
3
+ // ----------------------------------------------------------------
4
+ // DuplexTransport backed by the window.postMessage API — host side.
5
+ //
6
+ // Designed to run in the parent page. One instance wraps a single
7
+ // HTMLIFrameElement: inbound messages are accepted only when
8
+ // event.source matches that iframe's contentWindow, and outbound
9
+ // messages are sent directly to iframe.contentWindow.postMessage.
10
+ //
11
+ // Implements DuplexTransport<string, string> — the exact same
12
+ // interface contract as WsServerDuplexTransport — so it plugs into
13
+ // InMemoryHub + AgentServer without any changes to the server stack.
14
+ // ----------------------------------------------------------------
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.PostMessageHostDuplexTransport = void 0;
17
+ const rxjs_1 = require("rxjs");
18
+ const rxjs_2 = require("rxjs");
19
+ /* ---------------------------------------------------------------- */
20
+ /* Implementation */
21
+ /* ---------------------------------------------------------------- */
22
+ class PostMessageHostDuplexTransport {
23
+ /**
24
+ * @param iframe The iframe element whose agent this transport represents.
25
+ * @param targetOrigin Target origin for outbound `postMessage` calls and for
26
+ * filtering inbound messages by `event.origin`.
27
+ * Defaults to `'*'`.
28
+ */
29
+ constructor(iframe, targetOrigin = "*") {
30
+ this.iframe = iframe;
31
+ this._connected$ = new rxjs_1.BehaviorSubject(true);
32
+ this.destroy$ = new rxjs_1.Subject();
33
+ this.closed = false;
34
+ this.targetOrigin = targetOrigin;
35
+ this.connected$ = this._connected$.asObservable();
36
+ this.messages$ = (0, rxjs_1.fromEvent)(window, "message").pipe((0, rxjs_2.takeUntil)(this.destroy$), (0, rxjs_2.filter)((e) => e.source === this.iframe.contentWindow), (0, rxjs_2.filter)((e) => this.targetOrigin === "*" ? true : e.origin === this.targetOrigin), (0, rxjs_2.filter)((e) => typeof e.data === "string"), (0, rxjs_2.map)((e) => e.data));
37
+ }
38
+ send(msg) {
39
+ this.iframe.contentWindow.postMessage(msg, this.targetOrigin);
40
+ }
41
+ close() {
42
+ if (this.closed)
43
+ return;
44
+ this.closed = true;
45
+ this.destroy$.next();
46
+ this.destroy$.complete();
47
+ this._connected$.next(false);
48
+ this._connected$.complete();
49
+ }
50
+ }
51
+ exports.PostMessageHostDuplexTransport = PostMessageHostDuplexTransport;
@@ -0,0 +1,23 @@
1
+ import { Observable } from "rxjs";
2
+ import { DuplexTransport, HubTransport, PeerEvent } from "@awarevue/agent-sdk";
3
+ export declare class PostMessageHub implements HubTransport<string, string, string> {
4
+ private readonly targetOrigin;
5
+ private readonly _peerEvents$;
6
+ readonly peerEvents$: Observable<PeerEvent<string>>;
7
+ private readonly peers;
8
+ /** Maps source window reference → agentId */
9
+ private readonly sourceToAgent;
10
+ private readonly destroy$;
11
+ private readonly sub;
12
+ private closed;
13
+ /**
14
+ * @param targetOrigin Target origin for outbound `postMessage` calls.
15
+ * Defaults to `'*'`. Set to a specific origin in
16
+ * production to prevent cross-origin message leakage.
17
+ */
18
+ constructor(targetOrigin?: string);
19
+ private onMessage;
20
+ connection(peer: string): DuplexTransport<string, string> | null;
21
+ closePeer(peer: string): void;
22
+ close(): void;
23
+ }
@@ -0,0 +1,152 @@
1
+ "use strict";
2
+ // post-message-hub.ts
3
+ // ----------------------------------------------------------------
4
+ // HubTransport backed by window.postMessage — host side.
5
+ //
6
+ // Unlike PostMessageHostDuplexTransport (which is a 1:1 DuplexTransport
7
+ // that requires an iframe element upfront), this class is a
8
+ // server-style hub: it discovers peers automatically from incoming
9
+ // window 'message' events, just like a WebSocket server accepts
10
+ // incoming connections.
11
+ //
12
+ // How peer discovery works:
13
+ // 1. The first JSON message from an unknown source window is parsed.
14
+ // 2. The `data.from` field (the stable agent ID chosen by the
15
+ // developer, e.g. "demo_agent") is used as the TPeer identifier.
16
+ // 3. A PostMessagePeerTransport is created for that source window.
17
+ // 4. A 'join' PeerEvent is emitted so AgentServer can register it.
18
+ // 5. Subsequent messages from the same source are routed to the
19
+ // existing peer transport without another round of parsing.
20
+ //
21
+ // Implements HubTransport<string, string, string> so it slots directly
22
+ // into WsJsonHubAdapter → AgentServer.
23
+ // ----------------------------------------------------------------
24
+ Object.defineProperty(exports, "__esModule", { value: true });
25
+ exports.PostMessageHub = void 0;
26
+ const rxjs_1 = require("rxjs");
27
+ const rxjs_2 = require("rxjs");
28
+ /* ---------------------------------------------------------------- */
29
+ /* Internal: per-peer transport */
30
+ /* ---------------------------------------------------------------- */
31
+ class PostMessagePeerTransport {
32
+ constructor(source, targetOrigin, onClose) {
33
+ this.source = source;
34
+ this.targetOrigin = targetOrigin;
35
+ this.onClose = onClose;
36
+ this._connected$ = new rxjs_1.BehaviorSubject(true);
37
+ this._messages$ = new rxjs_1.Subject();
38
+ this.closed = false;
39
+ this.connected$ = this._connected$.asObservable();
40
+ this.messages$ = this._messages$.asObservable();
41
+ }
42
+ /** Called by PostMessageHub to route an inbound string to this peer. */
43
+ receive(msg) {
44
+ if (!this.closed) {
45
+ this._messages$.next(msg);
46
+ }
47
+ }
48
+ send(msg) {
49
+ if (!this.closed) {
50
+ this.source.postMessage(msg, this.targetOrigin);
51
+ }
52
+ }
53
+ close() {
54
+ if (this.closed)
55
+ return;
56
+ this.closed = true;
57
+ this._connected$.next(false);
58
+ this._connected$.complete();
59
+ this._messages$.complete();
60
+ this.onClose();
61
+ }
62
+ }
63
+ /* ---------------------------------------------------------------- */
64
+ /* Public: PostMessageHub */
65
+ /* ---------------------------------------------------------------- */
66
+ class PostMessageHub {
67
+ /**
68
+ * @param targetOrigin Target origin for outbound `postMessage` calls.
69
+ * Defaults to `'*'`. Set to a specific origin in
70
+ * production to prevent cross-origin message leakage.
71
+ */
72
+ constructor(targetOrigin = "*") {
73
+ this.targetOrigin = targetOrigin;
74
+ this._peerEvents$ = new rxjs_1.Subject();
75
+ this.peerEvents$ = this._peerEvents$.asObservable();
76
+ this.peers = new Map();
77
+ /** Maps source window reference → agentId */
78
+ this.sourceToAgent = new Map();
79
+ this.destroy$ = new rxjs_1.Subject();
80
+ this.closed = false;
81
+ this.sub = (0, rxjs_1.fromEvent)(window, "message")
82
+ .pipe((0, rxjs_2.takeUntil)(this.destroy$))
83
+ .subscribe((e) => this.onMessage(e));
84
+ }
85
+ onMessage(e) {
86
+ if (!e.source || typeof e.data !== "string")
87
+ return;
88
+ const existing = this.sourceToAgent.get(e.source);
89
+ if (existing !== undefined) {
90
+ // Route to existing peer
91
+ this.peers.get(existing)?.receive(e.data);
92
+ return;
93
+ }
94
+ // New source: parse the WsJsonEncoder envelope to extract agentId
95
+ let agentId;
96
+ try {
97
+ const envelope = JSON.parse(e.data);
98
+ const from = envelope?.data?.from;
99
+ if (typeof from === "string" && from.length > 0) {
100
+ agentId = from;
101
+ }
102
+ }
103
+ catch {
104
+ return; // not valid JSON — ignore
105
+ }
106
+ if (!agentId)
107
+ return;
108
+ // Guard against duplicate agentId from a different source
109
+ if (this.peers.has(agentId)) {
110
+ // Route anyway — same agent re-connecting on a different source
111
+ this.peers.get(agentId)?.receive(e.data);
112
+ return;
113
+ }
114
+ const transport = new PostMessagePeerTransport(e.source, this.targetOrigin, () => this.closePeer(agentId));
115
+ this.peers.set(agentId, transport);
116
+ this.sourceToAgent.set(e.source, agentId);
117
+ this._peerEvents$.next({ type: "join", peer: agentId });
118
+ // Replay the first message into the peer stream
119
+ transport.receive(e.data);
120
+ }
121
+ connection(peer) {
122
+ return this.peers.get(peer) ?? null;
123
+ }
124
+ closePeer(peer) {
125
+ const transport = this.peers.get(peer);
126
+ if (!transport)
127
+ return;
128
+ // Remove maps first to prevent re-entry from transport.close()
129
+ for (const [src, id] of this.sourceToAgent) {
130
+ if (id === peer) {
131
+ this.sourceToAgent.delete(src);
132
+ break;
133
+ }
134
+ }
135
+ this.peers.delete(peer);
136
+ transport.close(); // no-op if already closed
137
+ this._peerEvents$.next({ type: "leave", peer });
138
+ }
139
+ close() {
140
+ if (this.closed)
141
+ return;
142
+ this.closed = true;
143
+ this.destroy$.next();
144
+ this.destroy$.complete();
145
+ this.sub.unsubscribe();
146
+ for (const peer of [...this.peers.keys()]) {
147
+ this.closePeer(peer);
148
+ }
149
+ this._peerEvents$.complete();
150
+ }
151
+ }
152
+ exports.PostMessageHub = PostMessageHub;
@@ -0,0 +1,24 @@
1
+ import { Observable } from "rxjs";
2
+ import { DuplexTransport } from "@awarevue/agent-sdk";
3
+ export interface PostMessageIframeDuplexTransportOptions {
4
+ /**
5
+ * The target origin used for outbound `postMessage` calls and for
6
+ * filtering inbound messages by `event.origin`.
7
+ *
8
+ * Defaults to `'*'` (no origin filtering, send to any origin).
9
+ * Set to a specific origin (e.g. `'https://app.example.com'`) for
10
+ * production deployments to prevent cross-origin message leakage.
11
+ */
12
+ targetOrigin?: string;
13
+ }
14
+ export declare class PostMessageIframeDuplexTransport implements DuplexTransport<string, string> {
15
+ readonly connected$: Observable<boolean>;
16
+ readonly messages$: Observable<string>;
17
+ private readonly _connected$;
18
+ private readonly destroy$;
19
+ private readonly targetOrigin;
20
+ private closed;
21
+ constructor(opts?: PostMessageIframeDuplexTransportOptions);
22
+ send(msg: string): void;
23
+ close(): void;
24
+ }
@@ -0,0 +1,44 @@
1
+ "use strict";
2
+ // transports/post-message-iframe.ts
3
+ // ----------------------------------------------------------------
4
+ // DuplexTransport backed by the window.postMessage API — agent side.
5
+ //
6
+ // Designed to run inside an iframe. Messages are received from the
7
+ // parent via the window 'message' event and sent to the parent via
8
+ // window.parent.postMessage.
9
+ //
10
+ // Implements DuplexTransport<string, string> — the exact same
11
+ // interface contract as WsDuplexTransport — so the full transport
12
+ // stack (WsJsonEncoder → LoggingDuplexTransport → AgentProtocol) can
13
+ // be composed without modification.
14
+ // ----------------------------------------------------------------
15
+ Object.defineProperty(exports, "__esModule", { value: true });
16
+ exports.PostMessageIframeDuplexTransport = void 0;
17
+ const rxjs_1 = require("rxjs");
18
+ const rxjs_2 = require("rxjs");
19
+ /* ---------------------------------------------------------------- */
20
+ /* Implementation */
21
+ /* ---------------------------------------------------------------- */
22
+ class PostMessageIframeDuplexTransport {
23
+ constructor(opts = {}) {
24
+ this._connected$ = new rxjs_1.BehaviorSubject(true);
25
+ this.destroy$ = new rxjs_1.Subject();
26
+ this.closed = false;
27
+ this.targetOrigin = opts.targetOrigin ?? "*";
28
+ this.connected$ = this._connected$.asObservable();
29
+ this.messages$ = (0, rxjs_1.fromEvent)(window, "message").pipe((0, rxjs_2.takeUntil)(this.destroy$), (0, rxjs_2.filter)((e) => this.targetOrigin === "*" ? true : e.origin === this.targetOrigin), (0, rxjs_2.filter)((e) => typeof e.data === "string"), (0, rxjs_2.map)((e) => e.data));
30
+ }
31
+ send(msg) {
32
+ window.parent.postMessage(msg, this.targetOrigin);
33
+ }
34
+ close() {
35
+ if (this.closed)
36
+ return;
37
+ this.closed = true;
38
+ this.destroy$.next();
39
+ this.destroy$.complete();
40
+ this._connected$.next(false);
41
+ this._connected$.complete();
42
+ }
43
+ }
44
+ exports.PostMessageIframeDuplexTransport = PostMessageIframeDuplexTransport;
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@awarevue/agent-sdk-browser",
3
+ "repository": {
4
+ "type": "git",
5
+ "url": "git+https://github.com/Linc-Security-Systems/aware-essentials.git"
6
+ },
7
+ "version": "2.0.91",
8
+ "description": "Browser transport adapters for the Aware agent protocol — iframe/postMessage drop-in replacement for the WebSocket transport.",
9
+ "author": "Yaser Awajan",
10
+ "license": "MIT",
11
+ "files": [
12
+ "dist"
13
+ ],
14
+ "main": "dist/index.js",
15
+ "types": "dist/index.d.ts",
16
+ "exports": {
17
+ ".": {
18
+ "import": "./dist/index.js",
19
+ "require": "./dist/index.js",
20
+ "types": "./dist/index.d.ts"
21
+ }
22
+ },
23
+ "sideEffects": false,
24
+ "scripts": {
25
+ "build": "tsc -p tsconfig.json && cp package.json dist/",
26
+ "prepublishOnly": "yarn build",
27
+ "test": "vitest run",
28
+ "lint": "eslint \"src/**/*.{ts,tsx}\"",
29
+ "lint:fix": "yarn lint --fix"
30
+ },
31
+ "peerDependencies": {
32
+ "@awarevue/agent-sdk": "2.0.91",
33
+ "@awarevue/api-types": "2.0.91",
34
+ "rxjs": "^7.8.2"
35
+ },
36
+ "devDependencies": {
37
+ "@awarevue/agent-sdk": "2.0.91",
38
+ "@awarevue/api-types": "2.0.91",
39
+ "@typescript-eslint/eslint-plugin": "^8.31.1",
40
+ "@typescript-eslint/parser": "^8.31.1",
41
+ "eslint": "^9.25.1",
42
+ "eslint-config-prettier": "^10.1.2",
43
+ "eslint-plugin-import": "^2.31.0",
44
+ "jsdom": "^26.1.0",
45
+ "rxjs": "7.8.2",
46
+ "typescript": "^5.8.3",
47
+ "vitest": "^4.1.4"
48
+ },
49
+ "publishConfig": {
50
+ "access": "public",
51
+ "registry": "https://registry.npmjs.org/"
52
+ }
53
+ }