@awarevue/agent-sdk 2.0.53 → 2.0.55

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.
@@ -1,23 +1,20 @@
1
- import { Transport } from './transport';
2
- import { PushEventRq, PushStateUpdateRq, ProviderSpecs, AccessControlCapabilityReport } from '@awarevue/api-types';
1
+ import { FromAgent, PushEventRq, PushStateUpdateRq, ProviderSpecs, AccessControlCapabilityReport, FromServer, Message } from '@awarevue/api-types';
3
2
  import { Agent } from './agent';
3
+ import { DuplexTransport } from './transport_types';
4
4
  export type DeviceActivity = Omit<PushStateUpdateRq, 'provider'> | Omit<PushEventRq, 'provider'>;
5
5
  export type AgentOptions = {
6
6
  providers: Record<string, ProviderSpecs>;
7
7
  accessControlProviders?: Record<string, AccessControlCapabilityReport>;
8
8
  agentId: string;
9
9
  replyTimeout?: number;
10
- transport: Transport;
10
+ transport: DuplexTransport<Message<FromServer>, Message<FromAgent>>;
11
11
  };
12
12
  export declare class AgentApp {
13
13
  private readonly agent;
14
14
  private readonly options;
15
- private static id;
16
- private static nextId;
15
+ private readonly protocol;
17
16
  private sub;
18
- private getReply$;
19
17
  private handleResponse$;
20
- private addEnvelope;
21
18
  private createAccessChangeContext;
22
19
  private runProvider$;
23
20
  private process$;
package/dist/agent-app.js CHANGED
@@ -5,29 +5,12 @@ const rxjs_1 = require("rxjs");
5
5
  const default_validator_1 = require("./default-validator");
6
6
  const utils_1 = require("./utils");
7
7
  const agent_error_1 = require("./agent-error");
8
- const constants_1 = require("./constants");
8
+ const agent_protocol_1 = require("./agent-protocol");
9
9
  class AgentApp {
10
10
  constructor(agent, options) {
11
11
  this.agent = agent;
12
12
  this.options = options;
13
13
  this.sub = null;
14
- // getReply$ is a generic function that sends a message to server and waits for a reply.
15
- this.getReply$ = (responseKind, payload) => {
16
- const reply$ = (id) => this.options.transport.messages$.pipe((0, rxjs_1.mergeMap)((message) => {
17
- if (message.kind === 'error-rs' && message.requestId === id) {
18
- const error = message.error;
19
- return (0, rxjs_1.throwError)(() => new Error(`Server failed to process message ${payload.kind}: ${error}`));
20
- }
21
- return (0, rxjs_1.of)(message);
22
- }), (0, rxjs_1.filter)((message) => message.kind === responseKind &&
23
- 'requestId' in message &&
24
- message.requestId === id), (0, rxjs_1.take)(1), (0, rxjs_1.timeout)(this.options.replyTimeout || 10000));
25
- return (0, rxjs_1.of)(this.addEnvelope({ ...payload, id: AgentApp.nextId() })).pipe(
26
- // send the message to the agent
27
- (0, rxjs_1.tap)((p) => this.options.transport.send(p)),
28
- // wait for the agent to reply
29
- (0, rxjs_1.mergeMap)(({ id }) => reply$(id)));
30
- };
31
14
  this.handleResponse$ = (requestId) => (obs) => obs.pipe((0, rxjs_1.tap)({
32
15
  error: (error) => console.error('Error processing request', requestId, (0, utils_1.stringifyError)(error)),
33
16
  }), (0, rxjs_1.catchError)((error) => (0, rxjs_1.of)({
@@ -35,14 +18,7 @@ class AgentApp {
35
18
  requestId,
36
19
  error: (0, utils_1.stringifyError)(error),
37
20
  code: error instanceof agent_error_1.AgentError ? error.code : undefined,
38
- })), (0, rxjs_1.tap)((rs) => this.options.transport.send(this.addEnvelope(rs))));
39
- this.addEnvelope = (payload) => ({
40
- ...payload,
41
- id: AgentApp.nextId(),
42
- from: this.options.agentId,
43
- version: constants_1.AGENT_PROTOCOL_VERSION,
44
- on: Date.now(),
45
- });
21
+ })), (0, rxjs_1.tap)((rs) => this.protocol.send(rs)));
46
22
  this.createAccessChangeContext = (context, refMap, objectCache) => {
47
23
  return {
48
24
  ...context,
@@ -73,14 +49,14 @@ class AgentApp {
73
49
  (0, rxjs_1.mergeMap)((message) => {
74
50
  // Send start-rs on the first emission (null) to signal we're ready
75
51
  if (message === null) {
76
- this.options.transport.send(this.addEnvelope({
52
+ this.protocol.send({
77
53
  kind: 'start-rs',
78
54
  requestId: context.startRequestId,
79
- }));
80
- return (0, rxjs_1.of)(null).pipe((0, rxjs_1.delay)(1000), (0, rxjs_1.mergeMap)(() => this.agent.run$(context).pipe((0, rxjs_1.tap)((message) => this.options.transport.send(this.addEnvelope({
55
+ });
56
+ return (0, rxjs_1.of)(null).pipe((0, rxjs_1.delay)(1000), (0, rxjs_1.mergeMap)(() => this.agent.run$(context).pipe((0, rxjs_1.tap)((message) => this.protocol.send({
81
57
  ...message,
82
58
  provider: context.provider,
83
- }))))));
59
+ })))));
84
60
  }
85
61
  switch (message.kind) {
86
62
  // handle commands
@@ -158,11 +134,13 @@ class AgentApp {
158
134
  };
159
135
  this.process$ = () => {
160
136
  const registration$ = this.options.transport.connected$.pipe((0, rxjs_1.switchMap)((connected) => connected
161
- ? this.getReply$('register-rs', {
137
+ ? this.protocol
138
+ .getReply$({
162
139
  kind: 'register',
163
140
  providers: this.options.providers,
164
141
  accessControlProviders: this.options.accessControlProviders,
165
- }).pipe((0, rxjs_1.retry)({ delay: 3000 }))
142
+ })
143
+ .pipe((0, rxjs_1.retry)({ delay: 3000 }))
166
144
  : rxjs_1.EMPTY));
167
145
  const startStop$ = this.options.transport.messages$.pipe((0, rxjs_1.filter)((message) => message.kind === 'start' || message.kind === 'stop'), (0, rxjs_1.switchMap)((message) => message.kind === 'start'
168
146
  ? this.agent
@@ -180,10 +158,10 @@ class AgentApp {
180
158
  deviceCatalog,
181
159
  startRequestId: message.id,
182
160
  })), (0, rxjs_1.mergeMap)((context) => this.runProvider$(context)))
183
- : (0, rxjs_1.of)(message).pipe((0, rxjs_1.tap)(() => this.options.transport.send(this.addEnvelope({
161
+ : (0, rxjs_1.of)(message).pipe((0, rxjs_1.tap)(() => this.protocol.send({
184
162
  kind: 'stop-rs',
185
163
  requestId: message.id,
186
- }))))));
164
+ })))));
187
165
  const validateConfig$ = this.options.transport.messages$.pipe((0, rxjs_1.filter)((message) => message.kind === 'validate-config'), (0, rxjs_1.mergeMap)((message) => {
188
166
  const provider = message.provider;
189
167
  const config = message.config;
@@ -202,6 +180,10 @@ class AgentApp {
202
180
  }));
203
181
  return (0, rxjs_1.merge)(registration$, startStop$, validateConfig$);
204
182
  };
183
+ this.protocol = new agent_protocol_1.AgentProtocol(this.options.transport, {
184
+ id: this.options.agentId,
185
+ replyTimeout: this.options.replyTimeout,
186
+ });
205
187
  }
206
188
  start() {
207
189
  this.sub = this.process$().subscribe();
@@ -214,5 +196,3 @@ class AgentApp {
214
196
  }
215
197
  }
216
198
  exports.AgentApp = AgentApp;
217
- AgentApp.id = 0;
218
- AgentApp.nextId = () => `${++AgentApp.id}`;
@@ -0,0 +1,32 @@
1
+ import { FromAgent, FromServer, Message, PayloadByKind, ReplyKindByRequestKind } from '@awarevue/api-types';
2
+ import { DuplexTransport } from './transport_types';
3
+ import { Observable } from 'rxjs';
4
+ export type Direction = 'server' | 'agent';
5
+ export type RequestKind = keyof typeof ReplyKindByRequestKind;
6
+ export type ResponseKind<K extends RequestKind> = (typeof ReplyKindByRequestKind)[K];
7
+ /**
8
+ * Directional wire types:
9
+ * - If you are SERVER: you receive FromAgent, send FromServer
10
+ * - If you are AGENT: you receive FromServer, send FromAgent
11
+ */
12
+ export type Inbound<D extends Direction> = D extends 'server' ? FromAgent : FromServer;
13
+ export type Outbound<D extends Direction> = D extends 'server' ? FromServer : FromAgent;
14
+ export type InMsg<D extends Direction> = Message<Inbound<D>>;
15
+ export type OutMsg<D extends Direction> = Message<Outbound<D>>;
16
+ export interface AgentProtocolOptions {
17
+ /** Timeout for awaiting replies to sent messages (default 10s) */
18
+ replyTimeout?: number;
19
+ id: string;
20
+ }
21
+ export declare class AgentProtocol<D extends Direction> {
22
+ private readonly transport;
23
+ private readonly options;
24
+ private static id;
25
+ private static nextId;
26
+ private addEnvelope;
27
+ constructor(transport: DuplexTransport<Message<Inbound<D>>, Message<Outbound<D>>>, options: AgentProtocolOptions);
28
+ getReply$: <K extends RequestKind>(payload: Extract<Outbound<D>, {
29
+ kind: K;
30
+ }>) => Observable<Message<PayloadByKind[ResponseKind<K>]>>;
31
+ send: (payload: Outbound<D>) => void;
32
+ }
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AgentProtocol = void 0;
4
+ const api_types_1 = require("@awarevue/api-types");
5
+ const rxjs_1 = require("rxjs");
6
+ const constants_1 = require("./constants");
7
+ class AgentProtocol {
8
+ constructor(transport, options) {
9
+ this.transport = transport;
10
+ this.options = options;
11
+ this.addEnvelope = (payload) => ({
12
+ ...payload,
13
+ id: AgentProtocol.nextId(),
14
+ from: this.options.id,
15
+ version: constants_1.AGENT_PROTOCOL_VERSION,
16
+ on: Date.now(),
17
+ });
18
+ this.getReply$ = (payload) => {
19
+ const responseKind = api_types_1.ReplyKindByRequestKind[payload.kind];
20
+ const reply$ = (id) => this.transport.messages$.pipe((0, rxjs_1.mergeMap)((message) => {
21
+ if (message.kind === 'error-rs' && message.requestId === id) {
22
+ const error = message.error;
23
+ return (0, rxjs_1.throwError)(() => new Error(`Server failed to process message ${payload.kind}: ${error}`));
24
+ }
25
+ return (0, rxjs_1.of)(message);
26
+ }), (0, rxjs_1.filter)((message) => message.kind === responseKind &&
27
+ 'requestId' in message &&
28
+ message.requestId === id), (0, rxjs_1.take)(1), (0, rxjs_1.timeout)(this.options.replyTimeout || 10000));
29
+ return (0, rxjs_1.of)(this.addEnvelope({ ...payload, id: AgentProtocol.nextId() })).pipe(
30
+ // send the message to the agent
31
+ (0, rxjs_1.tap)((p) => this.transport.send(p)),
32
+ // wait for the agent to reply
33
+ (0, rxjs_1.mergeMap)(({ id }) => reply$(id)));
34
+ };
35
+ this.send = (payload) => {
36
+ const message = this.addEnvelope(payload);
37
+ this.transport.send(message);
38
+ };
39
+ }
40
+ }
41
+ exports.AgentProtocol = AgentProtocol;
42
+ AgentProtocol.id = 0;
43
+ AgentProtocol.nextId = () => `${++AgentProtocol.id}`;
@@ -1,10 +1,10 @@
1
1
  import { AccessValidateChangeRq } from '@awarevue/api-types';
2
2
  import { Agent, Context } from './agent';
3
3
  export declare const createValidator: <T extends Agent>(agent: T) => (context: Context, change: AccessValidateChangeRq) => import("rxjs").Observable<readonly [{
4
- index?: number | undefined;
5
4
  code?: "BAD_REFERENCE" | "NOT_SUPPORTED" | "NOT_FOUND" | "NOT_UNIQUE" | "INVALID" | undefined;
6
- message?: string | undefined;
7
5
  path?: string | undefined;
6
+ message?: string | undefined;
8
7
  objectKind?: "person" | "zone" | "schedule" | "device" | "accessRule" | undefined;
9
8
  objectId?: string | undefined;
9
+ index?: number | undefined;
10
10
  }[], Record<"accessRule" | "schedule" | "person" | "device" | "zone", Record<string, Record<string, unknown>>>]>;
@@ -0,0 +1,19 @@
1
+ import { PeerId, HubTransport, PeerEvent, DuplexTransport } from '../transport_types';
2
+ export declare class InMemoryHub<TIn, TOut, TPeer extends PeerId> implements HubTransport<TIn, TOut, TPeer> {
3
+ private readonly peerEventsSubject;
4
+ private readonly messagesSubject;
5
+ private readonly peers;
6
+ private readonly peerSubs;
7
+ readonly peerEvents$: import("rxjs").Observable<PeerEvent<TPeer>>;
8
+ readonly messages$: import("rxjs").Observable<{
9
+ peer: TPeer;
10
+ msg: TIn;
11
+ }>;
12
+ addPeer(peerId: TPeer, conn: DuplexTransport<TIn, TOut>): void;
13
+ removePeer(peerId: TPeer, reason?: unknown): void;
14
+ closePeer(peerId: TPeer): void;
15
+ close(): void;
16
+ broadcast(msg: TOut): void;
17
+ connection(peerId: TPeer): DuplexTransport<TIn, TOut> | null;
18
+ sendTo(msg: TOut, peerId: TPeer): void;
19
+ }
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.InMemoryHub = void 0;
4
+ const rxjs_1 = require("rxjs");
5
+ const operators_1 = require("rxjs/operators");
6
+ class InMemoryHub {
7
+ constructor() {
8
+ this.peerEventsSubject = new rxjs_1.Subject();
9
+ this.messagesSubject = new rxjs_1.Subject();
10
+ this.peers = new Map();
11
+ this.peerSubs = new Map();
12
+ this.peerEvents$ = this.peerEventsSubject.asObservable().pipe((0, operators_1.share)());
13
+ this.messages$ = this.messagesSubject.asObservable().pipe((0, operators_1.share)());
14
+ }
15
+ addPeer(peerId, conn) {
16
+ // defensive: if peer already exists, clean it first
17
+ if (this.peers.has(peerId))
18
+ this.removePeer(peerId, 'replaced');
19
+ this.peers.set(peerId, conn);
20
+ this.peerEventsSubject.next({ type: 'join', peer: peerId });
21
+ const sub = new rxjs_1.Subscription();
22
+ sub.add(conn.messages$.subscribe((msg) => {
23
+ this.messagesSubject.next({ peer: peerId, msg });
24
+ }));
25
+ sub.add(conn.connected$.subscribe((connected) => {
26
+ if (!connected)
27
+ this.removePeer(peerId, 'disconnect');
28
+ }));
29
+ this.peerSubs.set(peerId, sub);
30
+ }
31
+ removePeer(peerId, reason) {
32
+ var _a;
33
+ if (!this.peers.has(peerId))
34
+ return;
35
+ // tear down subscriptions FIRST
36
+ (_a = this.peerSubs.get(peerId)) === null || _a === void 0 ? void 0 : _a.unsubscribe();
37
+ this.peerSubs.delete(peerId);
38
+ this.peers.delete(peerId);
39
+ this.peerEventsSubject.next({ type: 'leave', peer: peerId, reason });
40
+ }
41
+ closePeer(peerId) {
42
+ var _a;
43
+ (_a = this.peers.get(peerId)) === null || _a === void 0 ? void 0 : _a.close();
44
+ this.removePeer(peerId, 'closed');
45
+ }
46
+ close() {
47
+ var _a;
48
+ for (const [peerId, conn] of this.peers) {
49
+ conn.close();
50
+ (_a = this.peerSubs.get(peerId)) === null || _a === void 0 ? void 0 : _a.unsubscribe();
51
+ }
52
+ this.peerSubs.clear();
53
+ this.peers.clear();
54
+ // optional: if the hub itself is done
55
+ this.peerEventsSubject.complete();
56
+ this.messagesSubject.complete();
57
+ }
58
+ broadcast(msg) {
59
+ for (const [, conn] of this.peers)
60
+ conn.send(msg);
61
+ }
62
+ connection(peerId) {
63
+ var _a;
64
+ return (_a = this.peers.get(peerId)) !== null && _a !== void 0 ? _a : null;
65
+ }
66
+ sendTo(msg, peerId) {
67
+ var _a;
68
+ (_a = this.peers.get(peerId)) === null || _a === void 0 ? void 0 : _a.send(msg);
69
+ }
70
+ }
71
+ exports.InMemoryHub = InMemoryHub;
@@ -0,0 +1 @@
1
+ export * from './in-memory';
@@ -0,0 +1,17 @@
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("./in-memory"), exports);
package/dist/index.d.ts CHANGED
@@ -1,6 +1,9 @@
1
1
  export * from './agent-app';
2
2
  export * from './agent';
3
- export * from './ws-agent-transport';
4
- export * from './transport-logger-decorator';
5
- export * from './transport';
6
3
  export * from './constants';
4
+ export * from './agent-protocol';
5
+ export * from './agent-error';
6
+ export * from './transport_types';
7
+ export * from './utils';
8
+ export * from './transports';
9
+ export * from './hubs';
package/dist/index.js CHANGED
@@ -16,7 +16,10 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
16
16
  Object.defineProperty(exports, "__esModule", { value: true });
17
17
  __exportStar(require("./agent-app"), exports);
18
18
  __exportStar(require("./agent"), exports);
19
- __exportStar(require("./ws-agent-transport"), exports);
20
- __exportStar(require("./transport-logger-decorator"), exports);
21
- __exportStar(require("./transport"), exports);
22
19
  __exportStar(require("./constants"), exports);
20
+ __exportStar(require("./agent-protocol"), exports);
21
+ __exportStar(require("./agent-error"), exports);
22
+ __exportStar(require("./transport_types"), exports);
23
+ __exportStar(require("./utils"), exports);
24
+ __exportStar(require("./transports"), exports);
25
+ __exportStar(require("./hubs"), exports);
package/dist/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "git+https://github.com/Linc-Security-Systems/aware-essentials.git"
6
6
  },
7
- "version": "2.0.53",
7
+ "version": "2.0.55",
8
8
  "description": "SDK for building Agent implementations that speak the agent protocol.",
9
9
  "author": "Yaser Awajan",
10
10
  "license": "MIT",
@@ -31,12 +31,12 @@
31
31
  "lint:fix": "yarn lint --fix"
32
32
  },
33
33
  "peerDependencies": {
34
- "@awarevue/api-types": "2.0.53",
34
+ "@awarevue/api-types": "2.0.55",
35
35
  "rxjs": "^7.8.2",
36
36
  "ws": "^8"
37
37
  },
38
38
  "devDependencies": {
39
- "@awarevue/api-types": "2.0.53",
39
+ "@awarevue/api-types": "2.0.55",
40
40
  "@types/node": "^20.12.7",
41
41
  "@types/ws": "^8.18.1",
42
42
  "@typescript-eslint/eslint-plugin": "^8.31.1",
@@ -0,0 +1,50 @@
1
+ import { Observable } from 'rxjs';
2
+ /**
3
+ * Coherent model:
4
+ * - DuplexTransport: single logical connection (agent-friendly)
5
+ * - HubTransport: multiplexed routing over many peers (server-friendly)
6
+ * - HubTransport.connection(peer): projects a peer into a DuplexTransport
7
+ *
8
+ * This avoids "parity" traps: agent does not pretend it can manage peers.
9
+ */
10
+ export interface DuplexTransport<TIn, TOut> {
11
+ /** True when this logical connection is up */
12
+ readonly connected$: Observable<boolean>;
13
+ /** Incoming messages on this connection */
14
+ readonly messages$: Observable<TIn>;
15
+ /** Send on this connection */
16
+ send(msg: TOut): void;
17
+ /** Close this connection */
18
+ close(): void;
19
+ }
20
+ export type PeerId = string | number;
21
+ export type PeerEvent<TPeer> = {
22
+ type: 'join';
23
+ peer: TPeer;
24
+ } | {
25
+ type: 'leave';
26
+ peer: TPeer;
27
+ reason?: unknown;
28
+ };
29
+ export interface HubTransport<TIn, TOut, TPeer> {
30
+ /** Peer join/leave stream */
31
+ readonly peerEvents$: Observable<PeerEvent<TPeer>>;
32
+ /** Incoming messages with peer attribution */
33
+ readonly messages$: Observable<{
34
+ msg: TIn;
35
+ peer: TPeer;
36
+ }>;
37
+ /** Send to one peer */
38
+ sendTo(msg: TOut, peer: TPeer): void;
39
+ /** Broadcast to all currently connected peers */
40
+ broadcast(msg: TOut): void;
41
+ /** Disconnect one peer */
42
+ closePeer(peer: TPeer): void;
43
+ /** Close hub transport */
44
+ close(): void;
45
+ /**
46
+ * Project a peer into a single-connection view.
47
+ * This is the key to reuse agent-side SDK logic against a hub peer.
48
+ */
49
+ connection(peer: TPeer): DuplexTransport<TIn, TOut> | null;
50
+ }
@@ -0,0 +1,2 @@
1
+ export * from './ws';
2
+ export * from './logging';
@@ -0,0 +1,18 @@
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("./ws"), exports);
18
+ __exportStar(require("./logging"), exports);
@@ -0,0 +1,24 @@
1
+ import { Observable } from 'rxjs';
2
+ import { DuplexTransport } from '../transport_types';
3
+ export interface LoggingTransportOptions {
4
+ /**
5
+ * Label prepended to each log line so multiple transports can be
6
+ * distinguished in the same process (default: `'transport'`).
7
+ */
8
+ label?: string;
9
+ /**
10
+ * Custom log sink. When omitted, messages are written to
11
+ * `console.log` as pretty-printed JSON.
12
+ */
13
+ logger?: (direction: 'IN' | 'OUT', label: string, msg: unknown) => void;
14
+ }
15
+ export declare class LoggingDuplexTransport<TIn, TOut> implements DuplexTransport<TIn, TOut> {
16
+ private readonly inner;
17
+ readonly connected$: Observable<boolean>;
18
+ readonly messages$: Observable<TIn>;
19
+ private readonly label;
20
+ private readonly sink;
21
+ constructor(inner: DuplexTransport<TIn, TOut>, opts?: LoggingTransportOptions);
22
+ send(msg: TOut): void;
23
+ close(): void;
24
+ }
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ // transports/logging.ts
3
+ // ----------------------------------------------------------------
4
+ // Logging decorator for DuplexTransport.
5
+ // Wraps any DuplexTransport and prints inbound / outbound messages.
6
+ // ----------------------------------------------------------------
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.LoggingDuplexTransport = void 0;
9
+ const operators_1 = require("rxjs/operators");
10
+ /* ---------------------------------------------------------------- */
11
+ /* Implementation */
12
+ /* ---------------------------------------------------------------- */
13
+ class LoggingDuplexTransport {
14
+ constructor(inner, opts = {}) {
15
+ var _a, _b;
16
+ this.inner = inner;
17
+ this.label = (_a = opts.label) !== null && _a !== void 0 ? _a : 'transport';
18
+ this.sink =
19
+ (_b = opts.logger) !== null && _b !== void 0 ? _b : ((direction, label, msg) => console.log(`[${label}] ${direction}`, JSON.stringify(msg, null, 2)));
20
+ this.connected$ = inner.connected$;
21
+ // Tap into the incoming stream before exposing it to consumers.
22
+ this.messages$ = inner.messages$.pipe((0, operators_1.tap)((msg) => this.sink('IN', this.label, msg)));
23
+ }
24
+ send(msg) {
25
+ this.sink('OUT', this.label, msg);
26
+ this.inner.send(msg);
27
+ }
28
+ close() {
29
+ this.inner.close();
30
+ }
31
+ }
32
+ exports.LoggingDuplexTransport = LoggingDuplexTransport;
@@ -0,0 +1,56 @@
1
+ import { Observable } from 'rxjs';
2
+ import { DuplexTransport } from '../transport_types';
3
+ export interface WsDuplexTransportOptions {
4
+ /** WebSocket endpoint URL (ws:// or wss://) */
5
+ url: string;
6
+ /**
7
+ * Optional headers sent during the WebSocket handshake
8
+ * (e.g. Authorization, User-Agent).
9
+ */
10
+ headers?: Record<string, string>;
11
+ /**
12
+ * Serialise an outbound message to a string or Buffer.
13
+ * Defaults to `JSON.stringify`.
14
+ */
15
+ serialise?: (msg: unknown) => string;
16
+ /**
17
+ * Deserialise an inbound raw payload to a message object.
18
+ * Defaults to `JSON.parse`.
19
+ */
20
+ deserialise?: (raw: string) => unknown;
21
+ /** Initial reconnect delay in ms (default 1 000). */
22
+ reconnectDelay?: number;
23
+ /** Maximum reconnect delay in ms (default 30 000). */
24
+ maxReconnectDelay?: number;
25
+ /** When false, no automatic reconnect is attempted (default true). */
26
+ autoReconnect?: boolean;
27
+ }
28
+ export declare class WsDuplexTransport<TIn, TOut> implements DuplexTransport<TIn, TOut> {
29
+ private readonly opts;
30
+ readonly connected$: Observable<boolean>;
31
+ readonly messages$: Observable<TIn>;
32
+ readonly errors$: Observable<Error>;
33
+ private readonly _connected$;
34
+ private readonly _messages$;
35
+ private readonly _errors$;
36
+ private readonly outbound$;
37
+ private readonly destroy$;
38
+ private ws;
39
+ private reconnectDelay;
40
+ private readonly maxDelay;
41
+ private readonly autoReconnect;
42
+ private destroyed;
43
+ private readonly serialise;
44
+ private readonly deserialise;
45
+ private defaultDeserializer;
46
+ private defaultSerializer;
47
+ constructor(opts: WsDuplexTransportOptions);
48
+ send(msg: TOut): void;
49
+ close(): void;
50
+ /** Single subscription that drains the outbound queue when connected. */
51
+ private setupSender;
52
+ /** Establish (or re-establish) the WebSocket connection. */
53
+ private connect;
54
+ /** Exponential back-off reconnect, cancelled via destroy$. */
55
+ private scheduleReconnect;
56
+ }
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ // transports/ws.ts
3
+ // ----------------------------------------------------------------
4
+ // Generic WebSocket-backed DuplexTransport with auto-reconnect.
5
+ // Unlike WsAgentTransport (which is protocol-aware), this is a
6
+ // reusable building block for any TIn / TOut message pair.
7
+ // ----------------------------------------------------------------
8
+ Object.defineProperty(exports, "__esModule", { value: true });
9
+ exports.WsDuplexTransport = void 0;
10
+ const ws_1 = require("ws");
11
+ const rxjs_1 = require("rxjs");
12
+ /* ---------------------------------------------------------------- */
13
+ /* Implementation */
14
+ /* ---------------------------------------------------------------- */
15
+ class WsDuplexTransport {
16
+ constructor(opts) {
17
+ var _a, _b, _c, _d, _e;
18
+ this.opts = opts;
19
+ // ---- internal subjects ----
20
+ this._connected$ = new rxjs_1.BehaviorSubject(false);
21
+ this._messages$ = new rxjs_1.Subject();
22
+ this._errors$ = new rxjs_1.Subject();
23
+ this.outbound$ = new rxjs_1.Subject();
24
+ this.destroy$ = new rxjs_1.Subject();
25
+ this.destroyed = false;
26
+ this.defaultDeserializer = (raw) => {
27
+ const env = JSON.parse(raw);
28
+ return {
29
+ kind: env.event,
30
+ ...env.data,
31
+ };
32
+ };
33
+ this.defaultSerializer = (msg) => {
34
+ if (typeof msg === 'object' && 'kind' in (msg || {})) {
35
+ const { kind, ...data } = msg;
36
+ return JSON.stringify({ event: kind, data });
37
+ }
38
+ return JSON.stringify(msg);
39
+ };
40
+ this.connected$ = this._connected$.asObservable();
41
+ this.messages$ = this._messages$.asObservable();
42
+ this.errors$ = this._errors$.asObservable();
43
+ this.reconnectDelay = (_a = opts.reconnectDelay) !== null && _a !== void 0 ? _a : 1000;
44
+ this.maxDelay = (_b = opts.maxReconnectDelay) !== null && _b !== void 0 ? _b : 30000;
45
+ this.autoReconnect = (_c = opts.autoReconnect) !== null && _c !== void 0 ? _c : true;
46
+ this.serialise = ((_d = opts.serialise) !== null && _d !== void 0 ? _d : this.defaultSerializer);
47
+ this.deserialise = ((_e = opts.deserialise) !== null && _e !== void 0 ? _e : this.defaultDeserializer);
48
+ this.connect();
49
+ this.setupSender();
50
+ }
51
+ /* -------------------------------------------------------------- */
52
+ /* Public API */
53
+ /* -------------------------------------------------------------- */
54
+ send(msg) {
55
+ this.outbound$.next(this.serialise(msg));
56
+ }
57
+ close() {
58
+ var _a;
59
+ if (this.destroyed)
60
+ return;
61
+ this.destroyed = true;
62
+ this.destroy$.next();
63
+ this.destroy$.complete();
64
+ this._connected$.complete();
65
+ this._messages$.complete();
66
+ this._errors$.complete();
67
+ (_a = this.ws) === null || _a === void 0 ? void 0 : _a.terminate();
68
+ }
69
+ /* -------------------------------------------------------------- */
70
+ /* Private helpers */
71
+ /* -------------------------------------------------------------- */
72
+ /** Single subscription that drains the outbound queue when connected. */
73
+ setupSender() {
74
+ this.outbound$
75
+ .pipe((0, rxjs_1.concatMap)((payload) => this._connected$.pipe((0, rxjs_1.filter)(Boolean), (0, rxjs_1.take)(1), (0, rxjs_1.takeUntil)(this.destroy$), (0, rxjs_1.map)(() => payload))), (0, rxjs_1.takeUntil)(this.destroy$))
76
+ .subscribe((payload) => {
77
+ this.ws.send(payload);
78
+ });
79
+ }
80
+ /** Establish (or re-establish) the WebSocket connection. */
81
+ connect() {
82
+ this.ws = new ws_1.WebSocket(this.opts.url, {
83
+ headers: this.opts.headers,
84
+ });
85
+ this.ws.on('open', () => {
86
+ var _a;
87
+ this.reconnectDelay = (_a = this.opts.reconnectDelay) !== null && _a !== void 0 ? _a : 1000;
88
+ this._connected$.next(true);
89
+ });
90
+ this.ws.on('message', (data) => {
91
+ try {
92
+ const msg = this.deserialise(data.toString());
93
+ this._messages$.next(msg);
94
+ }
95
+ catch (err) {
96
+ this._errors$.next(err instanceof Error ? err : new Error(String(err)));
97
+ }
98
+ });
99
+ this.ws.on('error', (err) => {
100
+ this._errors$.next(err);
101
+ });
102
+ this.ws.on('close', () => {
103
+ this._connected$.next(false);
104
+ if (!this.destroyed && this.autoReconnect) {
105
+ this.scheduleReconnect();
106
+ }
107
+ });
108
+ }
109
+ /** Exponential back-off reconnect, cancelled via destroy$. */
110
+ scheduleReconnect() {
111
+ (0, rxjs_1.timer)(this.reconnectDelay)
112
+ .pipe((0, rxjs_1.takeUntil)(this.destroy$))
113
+ .subscribe(() => this.connect());
114
+ this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxDelay);
115
+ }
116
+ }
117
+ exports.WsDuplexTransport = WsDuplexTransport;
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "git",
5
5
  "url": "git+https://github.com/Linc-Security-Systems/aware-essentials.git"
6
6
  },
7
- "version": "2.0.53",
7
+ "version": "2.0.55",
8
8
  "description": "SDK for building Agent implementations that speak the agent protocol.",
9
9
  "author": "Yaser Awajan",
10
10
  "license": "MIT",
@@ -31,12 +31,12 @@
31
31
  "lint:fix": "yarn lint --fix"
32
32
  },
33
33
  "peerDependencies": {
34
- "@awarevue/api-types": "2.0.53",
34
+ "@awarevue/api-types": "2.0.55",
35
35
  "rxjs": "^7.8.2",
36
36
  "ws": "^8"
37
37
  },
38
38
  "devDependencies": {
39
- "@awarevue/api-types": "2.0.53",
39
+ "@awarevue/api-types": "2.0.55",
40
40
  "@types/node": "^20.12.7",
41
41
  "@types/ws": "^8.18.1",
42
42
  "@typescript-eslint/eslint-plugin": "^8.31.1",
@@ -1,11 +0,0 @@
1
- import { Message, FromAgent } from '@awarevue/api-types';
2
- import { Transport } from './transport';
3
- export declare class TransportWithLogs implements Transport {
4
- private readonly decoratee;
5
- constructor(decoratee: Transport);
6
- get connected$(): import("rxjs").Observable<boolean>;
7
- get messages$(): import("rxjs").Observable<Message<import("@awarevue/api-types").FromServer>>;
8
- get errors$(): import("rxjs").Observable<Error>;
9
- close(): void;
10
- send(msg: Message<FromAgent>): void;
11
- }
@@ -1,35 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.TransportWithLogs = void 0;
4
- class TransportWithLogs {
5
- constructor(decoratee) {
6
- this.decoratee = decoratee;
7
- this.decoratee.connected$.subscribe((connected) => {
8
- console.log(`[${new Date()}] - Transport connected: ${connected}`);
9
- });
10
- this.decoratee.messages$.subscribe((msg) => {
11
- console.log(`[${new Date()}] - Transport message: ${JSON.stringify(msg)}`);
12
- });
13
- this.decoratee.errors$.subscribe((err) => {
14
- console.error(`[${new Date()}] - Transport error: ${err}`);
15
- });
16
- }
17
- get connected$() {
18
- return this.decoratee.connected$;
19
- }
20
- get messages$() {
21
- return this.decoratee.messages$;
22
- }
23
- get errors$() {
24
- return this.decoratee.errors$;
25
- }
26
- close() {
27
- console.log(`Closing transport`);
28
- this.decoratee.close();
29
- }
30
- send(msg) {
31
- console.log(`[${new Date()}] - Sending message: ${JSON.stringify(msg)}`);
32
- this.decoratee.send(msg);
33
- }
34
- }
35
- exports.TransportWithLogs = TransportWithLogs;
@@ -1,10 +0,0 @@
1
- import { FromAgent, FromServer, Message } from '@awarevue/api-types';
2
- import { Observable } from 'rxjs';
3
- /** What the SDK expects from a transport */
4
- export interface Transport {
5
- readonly connected$: Observable<boolean>;
6
- readonly messages$: Observable<Message<FromServer>>;
7
- readonly errors$: Observable<Error>;
8
- send(msg: Message<FromAgent>): void;
9
- close(): void;
10
- }
@@ -1,29 +0,0 @@
1
- import { Observable } from 'rxjs';
2
- import { FromAgent, FromServer, Message } from '@awarevue/api-types';
3
- import { Transport } from './transport';
4
- export declare class WsAgentTransport implements Transport {
5
- private readonly url;
6
- private readonly apiKey;
7
- readonly connected$: Observable<boolean>;
8
- readonly messages$: Observable<Message<FromServer>>;
9
- readonly errors$: Observable<Error>;
10
- private ws;
11
- private readonly _connected$;
12
- private readonly _messages$;
13
- private readonly _errors$;
14
- private readonly outbound$;
15
- private readonly destroy$;
16
- private reconnectDelay;
17
- private readonly maxDelay;
18
- private destroyed;
19
- constructor(url: string, apiKey: string);
20
- send(msg: Message<FromAgent>): void;
21
- /** Call once when your container/module shuts down. */
22
- close(): void;
23
- /** One subscription for all outbound traffic. */
24
- private setupSender;
25
- /** Establish or re-establish a WebSocket connection. */
26
- private connect;
27
- /** Exponential back-off reconnect, cancelled via destroy$. */
28
- private scheduleReconnect;
29
- }
@@ -1,104 +0,0 @@
1
- "use strict";
2
- // ws-agent-transport.ts
3
- //--------------------------------------------------------------
4
- // A robust, RxJS-friendly WebSocket transport for the Aware SDK
5
- // • automatic exponential back-off reconnect
6
- // • single outbound queue, no per-call subscriptions
7
- // • clean shutdown (no leaks)
8
- //--------------------------------------------------------------
9
- Object.defineProperty(exports, "__esModule", { value: true });
10
- exports.WsAgentTransport = void 0;
11
- const ws_1 = require("ws");
12
- const rxjs_1 = require("rxjs");
13
- class WsAgentTransport {
14
- constructor(url, apiKey) {
15
- this.url = url;
16
- this.apiKey = apiKey;
17
- this._connected$ = new rxjs_1.BehaviorSubject(false);
18
- this._messages$ = new rxjs_1.Subject();
19
- this._errors$ = new rxjs_1.Subject();
20
- this.outbound$ = new rxjs_1.Subject();
21
- this.destroy$ = new rxjs_1.Subject();
22
- this.reconnectDelay = 1000; // start at 1 s
23
- this.maxDelay = 30000; // cap at 30 s
24
- this.destroyed = false;
25
- this.connected$ = this._connected$.asObservable();
26
- this.messages$ = this._messages$.asObservable();
27
- this.errors$ = this._errors$.asObservable();
28
- this.connect(); // first connection
29
- this.setupSender(); // single outbound subscription
30
- }
31
- //----------------------------------------
32
- // Public API
33
- //----------------------------------------
34
- send(msg) {
35
- const { kind, ...data } = msg;
36
- const payload = JSON.stringify({ event: kind, data });
37
- this.outbound$.next(payload);
38
- }
39
- /** Call once when your container/module shuts down. */
40
- close() {
41
- var _a;
42
- if (this.destroyed)
43
- return;
44
- this.destroyed = true;
45
- this.destroy$.next();
46
- this.destroy$.complete();
47
- this._connected$.complete();
48
- this._messages$.complete();
49
- this._errors$.complete();
50
- /** terminate() is immediate; skip the close handshake intentionally */
51
- (_a = this.ws) === null || _a === void 0 ? void 0 : _a.terminate();
52
- }
53
- //----------------------------------------
54
- // Private helpers
55
- //----------------------------------------
56
- /** One subscription for all outbound traffic. */
57
- setupSender() {
58
- this.outbound$
59
- .pipe((0, rxjs_1.concatMap)((payload) => this._connected$.pipe((0, rxjs_1.filter)(Boolean), // wait until we’re actually connected
60
- (0, rxjs_1.take)(1), // just the next open
61
- (0, rxjs_1.takeUntil)(this.destroy$), (0, rxjs_1.map)(() => payload))), (0, rxjs_1.takeUntil)(this.destroy$))
62
- .subscribe((payload) => {
63
- /* readyState **must** be OPEN here by construction */
64
- this.ws.send(payload);
65
- });
66
- }
67
- /** Establish or re-establish a WebSocket connection. */
68
- connect() {
69
- this.ws = new ws_1.WebSocket(this.url, {
70
- headers: {
71
- 'User-Agent': 'Aware Agent SDK',
72
- Authorization: `ApiKey ${this.apiKey}`,
73
- },
74
- });
75
- this.ws.on('open', () => {
76
- this.reconnectDelay = 1000; // reset back-off
77
- this._connected$.next(true);
78
- });
79
- this.ws.on('message', (data) => {
80
- const raw = JSON.parse(data.toString());
81
- this._messages$.next({
82
- kind: raw.event,
83
- ...raw.data,
84
- });
85
- });
86
- this.ws.on('error', (err) => {
87
- this._errors$.next(err);
88
- // the 'close' handler will schedule reconnect
89
- });
90
- this.ws.on('close', () => {
91
- this._connected$.next(false);
92
- if (!this.destroyed)
93
- this.scheduleReconnect();
94
- });
95
- }
96
- /** Exponential back-off reconnect, cancelled via destroy$. */
97
- scheduleReconnect() {
98
- (0, rxjs_1.timer)(this.reconnectDelay)
99
- .pipe((0, rxjs_1.takeUntil)(this.destroy$))
100
- .subscribe(() => this.connect());
101
- this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxDelay);
102
- }
103
- }
104
- exports.WsAgentTransport = WsAgentTransport;
File without changes