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.
- package/CHANGELOG.md +72 -0
- package/LICENSE +21 -0
- package/README.md +174 -0
- package/bin/ec-ts.js +18 -0
- package/dist/buffer/disk-queue.d.ts +140 -0
- package/dist/buffer/disk-queue.js +370 -0
- package/dist/cli/devices.d.ts +1 -0
- package/dist/cli/devices.js +61 -0
- package/dist/cli/enroll.d.ts +2 -0
- package/dist/cli/enroll.js +89 -0
- package/dist/cli/index.d.ts +10 -0
- package/dist/cli/index.js +116 -0
- package/dist/cli/messages.d.ts +1 -0
- package/dist/cli/messages.js +59 -0
- package/dist/cli/run.d.ts +5 -0
- package/dist/cli/run.js +112 -0
- package/dist/cli/status.d.ts +1 -0
- package/dist/cli/status.js +56 -0
- package/dist/cli/whoami.d.ts +2 -0
- package/dist/cli/whoami.js +41 -0
- package/dist/config/cmd-gate.d.ts +65 -0
- package/dist/config/cmd-gate.js +128 -0
- package/dist/config/settings.d.ts +209 -0
- package/dist/config/settings.js +627 -0
- package/dist/crypto/aes-gcm.d.ts +38 -0
- package/dist/crypto/aes-gcm.js +90 -0
- package/dist/crypto/hmac.d.ts +31 -0
- package/dist/crypto/hmac.js +52 -0
- package/dist/crypto/tls-guard.d.ts +36 -0
- package/dist/crypto/tls-guard.js +54 -0
- package/dist/daemon/manager.d.ts +82 -0
- package/dist/daemon/manager.js +461 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.js +63 -0
- package/dist/logging/file-logger.d.ts +21 -0
- package/dist/logging/file-logger.js +71 -0
- package/dist/network/ws-client.d.ts +221 -0
- package/dist/network/ws-client.js +1134 -0
- package/dist/session/fail-fast.d.ts +70 -0
- package/dist/session/fail-fast.js +122 -0
- package/dist/session/manager.d.ts +136 -0
- package/dist/session/manager.js +291 -0
- package/dist/session/persistence.d.ts +103 -0
- package/dist/session/persistence.js +194 -0
- package/dist/session/pi-rpc.d.ts +164 -0
- package/dist/session/pi-rpc.js +412 -0
- package/dist/session/sftp.d.ts +64 -0
- package/dist/session/sftp.js +335 -0
- package/dist/session/shell-frame.d.ts +77 -0
- package/dist/session/shell-frame.js +199 -0
- package/dist/session/shell.d.ts +124 -0
- package/dist/session/shell.js +300 -0
- package/docs/CONFIGURATION.md +169 -0
- package/docs/INSTALLATION.md +164 -0
- package/docs/PROTOCOL.md +248 -0
- package/docs/TROUBLESHOOTING.md +177 -0
- 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;
|