edge-ai-client-ts 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/CHANGELOG.md +72 -0
  2. package/LICENSE +21 -0
  3. package/README.md +174 -0
  4. package/bin/ec-ts.js +18 -0
  5. package/dist/buffer/disk-queue.d.ts +140 -0
  6. package/dist/buffer/disk-queue.js +370 -0
  7. package/dist/cli/devices.d.ts +1 -0
  8. package/dist/cli/devices.js +61 -0
  9. package/dist/cli/enroll.d.ts +2 -0
  10. package/dist/cli/enroll.js +89 -0
  11. package/dist/cli/index.d.ts +10 -0
  12. package/dist/cli/index.js +116 -0
  13. package/dist/cli/messages.d.ts +1 -0
  14. package/dist/cli/messages.js +59 -0
  15. package/dist/cli/run.d.ts +5 -0
  16. package/dist/cli/run.js +112 -0
  17. package/dist/cli/status.d.ts +1 -0
  18. package/dist/cli/status.js +56 -0
  19. package/dist/cli/whoami.d.ts +2 -0
  20. package/dist/cli/whoami.js +41 -0
  21. package/dist/config/cmd-gate.d.ts +65 -0
  22. package/dist/config/cmd-gate.js +128 -0
  23. package/dist/config/settings.d.ts +209 -0
  24. package/dist/config/settings.js +627 -0
  25. package/dist/crypto/aes-gcm.d.ts +38 -0
  26. package/dist/crypto/aes-gcm.js +90 -0
  27. package/dist/crypto/hmac.d.ts +31 -0
  28. package/dist/crypto/hmac.js +52 -0
  29. package/dist/crypto/tls-guard.d.ts +36 -0
  30. package/dist/crypto/tls-guard.js +54 -0
  31. package/dist/daemon/manager.d.ts +82 -0
  32. package/dist/daemon/manager.js +461 -0
  33. package/dist/index.d.ts +39 -0
  34. package/dist/index.js +63 -0
  35. package/dist/logging/file-logger.d.ts +21 -0
  36. package/dist/logging/file-logger.js +71 -0
  37. package/dist/network/ws-client.d.ts +221 -0
  38. package/dist/network/ws-client.js +1134 -0
  39. package/dist/session/fail-fast.d.ts +70 -0
  40. package/dist/session/fail-fast.js +122 -0
  41. package/dist/session/manager.d.ts +136 -0
  42. package/dist/session/manager.js +291 -0
  43. package/dist/session/persistence.d.ts +103 -0
  44. package/dist/session/persistence.js +194 -0
  45. package/dist/session/pi-rpc.d.ts +164 -0
  46. package/dist/session/pi-rpc.js +412 -0
  47. package/dist/session/sftp.d.ts +64 -0
  48. package/dist/session/sftp.js +335 -0
  49. package/dist/session/shell-frame.d.ts +77 -0
  50. package/dist/session/shell-frame.js +199 -0
  51. package/dist/session/shell.d.ts +124 -0
  52. package/dist/session/shell.js +300 -0
  53. package/docs/CONFIGURATION.md +169 -0
  54. package/docs/INSTALLATION.md +164 -0
  55. package/docs/PROTOCOL.md +248 -0
  56. package/docs/TROUBLESHOOTING.md +177 -0
  57. package/package.json +79 -0
@@ -0,0 +1,221 @@
1
+ /**
2
+ * JSON `/edge` WebSocket client for the TypeScript edge-client.
3
+ *
4
+ * Mirrors `edge-client/src/network/ws_client.rs` for wire shapes, URL auth,
5
+ * registration, heartbeat metadata, ACK correlation, and reconnect backoff. The
6
+ * binary `/shell-edge` channel is deliberately out of scope for this module.
7
+ */
8
+ import { EventEmitter } from 'node:events';
9
+ import { type Settings } from '../config/settings.js';
10
+ export declare const SUBPROTOCOL_V2 = "edgeai-v2";
11
+ export declare const DEFAULT_HEARTBEAT_INTERVAL_MS = 30000;
12
+ export declare const DEFAULT_MAX_PAYLOAD_BYTES: number;
13
+ export type JsonPrimitive = string | number | boolean | null;
14
+ export type JsonValue = JsonPrimitive | JsonObject | JsonValue[];
15
+ export interface JsonObject {
16
+ [key: string]: JsonValue;
17
+ }
18
+ export interface RegisterMessage {
19
+ type: 'register';
20
+ machine_id: string;
21
+ agent_id: string;
22
+ }
23
+ export interface StreamMessage {
24
+ msg_id: string;
25
+ machine_id: string;
26
+ agent_id: string;
27
+ type: string;
28
+ payload: JsonValue;
29
+ timestamp: number;
30
+ }
31
+ export interface AckMessage {
32
+ action: string;
33
+ msg_id: string;
34
+ }
35
+ export interface SystemMetrics {
36
+ cpu_cores: number | null;
37
+ cpu_speed_mhz: number | null;
38
+ load_avg_1m: number | null;
39
+ load_avg_5m: number | null;
40
+ cpu_usage_pct: number | null;
41
+ mem_total_mb: number | null;
42
+ mem_available_mb: number | null;
43
+ disk_total_gb: number | null;
44
+ disk_free_gb: number | null;
45
+ process_count: number | null;
46
+ }
47
+ export interface HeartbeatMessage {
48
+ type: 'heartbeat';
49
+ machine_id: string;
50
+ platform?: string;
51
+ hostname?: string;
52
+ agent_version?: string;
53
+ system?: SystemMetrics;
54
+ operator_id?: string;
55
+ }
56
+ export interface BufferDrainable {
57
+ enqueue(frame: StreamMessage): Promise<void>;
58
+ drain(): AsyncIterable<StreamMessage>;
59
+ }
60
+ export interface ReconnectOptions {
61
+ initialDelayMs: number;
62
+ maxDelayMs: number;
63
+ multiplier: number;
64
+ }
65
+ export interface EdgeWsClientOptions {
66
+ settings: Settings;
67
+ edgeToken?: string;
68
+ deviceSecret?: string;
69
+ pinnedFingerprint?: string;
70
+ networkMode?: string;
71
+ buffer?: BufferDrainable;
72
+ heartbeatIntervalMs?: number;
73
+ requestTimeoutMs?: number;
74
+ reconnect?: Partial<ReconnectOptions>;
75
+ packageVersion?: string;
76
+ }
77
+ export interface EdgeWsClientEventMap {
78
+ registered: [message: JsonObject];
79
+ inbound: [message: JsonObject];
80
+ online: [];
81
+ offline: [];
82
+ error: [error: Error];
83
+ agent_list: [message: JsonObject];
84
+ audit_event: [message: JsonObject];
85
+ heartbeat: [message: JsonObject];
86
+ config: [config: JsonObject | null];
87
+ }
88
+ export type EdgeAuthDecision = {
89
+ kind: 'Connect';
90
+ url: string;
91
+ } | {
92
+ kind: 'ConnectUnauthenticated';
93
+ url: string;
94
+ } | {
95
+ kind: 'Refuse';
96
+ };
97
+ export interface StreamMessageInput {
98
+ machine_id: string;
99
+ agent_id: string;
100
+ type: string;
101
+ payload: JsonValue;
102
+ timestamp?: number;
103
+ }
104
+ export declare function createRegisterMessage(machineId: string, agentId: string): RegisterMessage;
105
+ export declare function createStreamMessage(input: StreamMessageInput): StreamMessage;
106
+ export declare function createAckMessage(msgId: string): AckMessage;
107
+ export declare function encodeStreamMessage(message: StreamMessage): string;
108
+ export declare function decodeStreamMessage(text: string): StreamMessage;
109
+ export declare function isRegisterMessage(value: unknown): value is RegisterMessage;
110
+ export declare function isStreamMessage(value: unknown): value is StreamMessage;
111
+ export declare function isAckMessage(value: unknown): value is AckMessage;
112
+ export declare function isHeartbeatMessage(value: unknown): value is HeartbeatMessage;
113
+ export declare function selectRelayUrl(mode: string | undefined, lanUrl: string | undefined, wanUrl: string | undefined, defaultUrl: string): string;
114
+ export declare function percentEncodeToken(token: string): string;
115
+ export declare function appendEdgeToken(url: string, token: string | undefined): string;
116
+ export declare function appendDeviceSecret(url: string, secret: string | undefined): string;
117
+ /**
118
+ * Build the AUTH-TOKEN half of the `Sec-WebSocket-Protocol` header. Caller
119
+ * passes this to `ws` as a separate element in the `protocols` array
120
+ * alongside SUBPROTOCOL_V2 — the resulting HTTP header reads
121
+ * `Sec-WebSocket-Protocol: edgeai-v2, <returned-value>`.
122
+ *
123
+ * Edge-2-edge: the `deviceSecret` historically travelled INSIDE the
124
+ * subprotocol header (so reverse-proxy logs could never see it), but for
125
+ * v2 we omit it — we send only the opaque edge_token. Returns `undefined`
126
+ * when there is no token to send, OR when the token is NOT a v2 opaque
127
+ * token (a raw edge_token would trigger `malformed_opaque` 401 on the
128
+ * server's subprotocol channel; raw tokens should travel via the URL
129
+ * query string instead — which `decideEdgeAuth` already wires up).
130
+ */
131
+ export declare function buildEdgeSubprotocolHeader(edgeToken: string | undefined, deviceSecret: string | undefined): string | undefined;
132
+ /** True iff the token LOOKS like a v2 opaque token (`o.<exp>.<nonce>.<sig>`). */
133
+ declare function isOpaqueToken(token: string): boolean;
134
+ export { isOpaqueToken as _isOpaqueTokenForTests };
135
+ export declare function decideEdgeAuth(baseUrl: string, token: string | undefined, escapeActive: boolean): EdgeAuthDecision;
136
+ /**
137
+ * FIX 2026-07-12 (3.5 + 3.6 from review): wrap any Error before emitting
138
+ * it from EdgeWsClient so the URL (and other sensitive tokens) can never
139
+ * leak to listeners that log the raw error. EventEmitter's `emit('error', err)`
140
+ * propagates the error verbatim — if `err.message` contains a URL with
141
+ * `?edge_token=...&device_secret=...`, a single missed redaction in a
142
+ * listener leaks credentials to the log.
143
+ *
144
+ * This helper produces a NEW Error whose `.message` has `redactUrl()`
145
+ * applied. The original `.stack` is preserved (so debugging still works)
146
+ * but we strip the message portion of the stack (the first line) so the
147
+ * URL isn't in the stack either. Listeners that destructure
148
+ * `{ message, code }` are safe; listeners that log `err.toString()` are
149
+ * also safe (toString uses message).
150
+ */
151
+ export declare function redactErrorUrl(err: Error): Error;
152
+ export declare function redactUrl(url: string): string;
153
+ export declare function buildHeartbeatMessage(settings: Settings, packageVersion?: string): HeartbeatMessage;
154
+ export declare class EdgeWsClient extends EventEmitter {
155
+ private readonly settings;
156
+ private readonly edgeToken;
157
+ private readonly deviceSecret;
158
+ private readonly pinnedFingerprint;
159
+ private readonly buffer;
160
+ private readonly heartbeatIntervalMs;
161
+ private readonly requestTimeoutMs;
162
+ private readonly reconnect;
163
+ private readonly packageVersion;
164
+ private readonly localQueue;
165
+ private readonly pending;
166
+ private ws;
167
+ private started;
168
+ private stopping;
169
+ private registered;
170
+ private online;
171
+ private flushing;
172
+ private networkMode;
173
+ private reconnectAttempts;
174
+ private heartbeatTimer;
175
+ private reconnectTimer;
176
+ private lastRelayHeartbeatMs;
177
+ constructor(options: EdgeWsClientOptions);
178
+ on<K extends keyof EdgeWsClientEventMap>(event: K, listener: (...args: EdgeWsClientEventMap[K]) => void): this;
179
+ once<K extends keyof EdgeWsClientEventMap>(event: K, listener: (...args: EdgeWsClientEventMap[K]) => void): this;
180
+ off<K extends keyof EdgeWsClientEventMap>(event: K, listener: (...args: EdgeWsClientEventMap[K]) => void): this;
181
+ emit<K extends keyof EdgeWsClientEventMap>(event: K, ...args: EdgeWsClientEventMap[K]): boolean;
182
+ start(): Promise<void>;
183
+ stop(): Promise<void>;
184
+ send(message: StreamMessage): Promise<void>;
185
+ request(message: StreamMessage, timeoutMs?: number): Promise<JsonObject>;
186
+ sendHeartbeat(): Promise<void>;
187
+ isOnline(): boolean;
188
+ isRegistered(): boolean;
189
+ lastRelayHeartbeat(): number | undefined;
190
+ currentUrl(): string;
191
+ private connect;
192
+ private buildWebSocketOptions;
193
+ private createPinnedTlsConnection;
194
+ private resolveCurrentUrl;
195
+ private handleRawMessage;
196
+ private dispatchAction;
197
+ private dispatchAckOrType;
198
+ private handleRegistered;
199
+ private applyConfigMessage;
200
+ private handleNetworkModeChange;
201
+ private ackConfigMessage;
202
+ private requestConfigSnapshot;
203
+ private replyWithConfigSnapshot;
204
+ private resolvePending;
205
+ private canSendNow;
206
+ private sendStreamNow;
207
+ private sendJsonObject;
208
+ private sendText;
209
+ private enqueueOffline;
210
+ private flushOutbound;
211
+ private startHeartbeat;
212
+ private restartHeartbeat;
213
+ private stopHeartbeat;
214
+ private handleClose;
215
+ private markOffline;
216
+ private forceReconnect;
217
+ private scheduleReconnect;
218
+ private clearReconnectTimer;
219
+ private rejectAllPending;
220
+ }
221
+ export declare function reconnectDelayMs(attempt: number, options: ReconnectOptions): number;