@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 +203 -0
- package/dist/browser-agent-app.d.ts +29 -0
- package/dist/browser-agent-app.js +35 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.js +20 -0
- package/dist/package.json +53 -0
- package/dist/post-message-host.d.ts +20 -0
- package/dist/post-message-host.js +51 -0
- package/dist/post-message-hub.d.ts +23 -0
- package/dist/post-message-hub.js +152 -0
- package/dist/post-message-iframe.d.ts +24 -0
- package/dist/post-message-iframe.js +44 -0
- package/package.json +53 -0
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
|
+
}
|
package/dist/index.d.ts
ADDED
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
|
+
}
|