@minion-stack/shared 0.1.0 → 0.2.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.
@@ -0,0 +1,73 @@
1
+ import type { EventFrame } from './types.js';
2
+ export declare const PROTOCOL_VERSION = 3;
3
+ export interface GatewayClientOptions {
4
+ /** WebSocket URL to connect to. */
5
+ url: string;
6
+ /**
7
+ * Optional WebSocket constructor injection (defaults to globalThis.WebSocket).
8
+ * Pass ws's WebSocket class when running in Node.
9
+ */
10
+ WebSocketImpl?: any;
11
+ /** Second arg to WebSocket constructor (Node ws accepts headers/maxPayload record). */
12
+ wsConstructorArgs?: [] | [Record<string, unknown>];
13
+ /**
14
+ * Called when server sends connect.challenge.
15
+ * Must return the params object to send with the 'connect' request.
16
+ */
17
+ onChallenge: (nonce: string) => Promise<Record<string, unknown>>;
18
+ /** Called for every inbound event frame except connect.challenge. */
19
+ onEvent?: (frame: EventFrame) => void | Promise<void>;
20
+ /** Called when the socket opens (before challenge handshake completes). */
21
+ onOpen?: () => void;
22
+ /** Called when the socket closes. */
23
+ onClose?: (code: number, reason: string) => void;
24
+ /** Called just before a reconnect delay starts (useful for UI toasts). */
25
+ onReconnectScheduled?: (delayMs: number) => void;
26
+ /**
27
+ * true = exponential backoff auto-reconnect (browser UX).
28
+ * false = single-shot; close() is final (Node adapter default).
29
+ * Default: false.
30
+ */
31
+ autoReconnect?: boolean;
32
+ /** Timeout for the connect() promise (ms). Default: 10000. */
33
+ connectTimeoutMs?: number;
34
+ /** Default timeout for request<T>() (ms). Default: 15000. */
35
+ requestTimeoutMs?: number;
36
+ }
37
+ export declare class GatewayClient {
38
+ private readonly opts;
39
+ private ws;
40
+ /** Increments per connect() call to fence stale socket event handlers. */
41
+ private generation;
42
+ private pending;
43
+ private connectNonce;
44
+ private connectSent;
45
+ private reconnectTimer;
46
+ private backoffMs;
47
+ private closed;
48
+ private helloResolve;
49
+ private helloReject;
50
+ constructor(opts: GatewayClientOptions);
51
+ /**
52
+ * Open the WebSocket and complete the connect.challenge handshake.
53
+ * Resolves with the HelloOk payload from the server.
54
+ */
55
+ connect(): Promise<unknown>;
56
+ /**
57
+ * Send a gateway request and resolve with the response payload.
58
+ * Rejects if not connected or if the request times out.
59
+ */
60
+ request<T>(method: string, params?: unknown, opts?: {
61
+ timeoutMs?: number;
62
+ }): Promise<T>;
63
+ /**
64
+ * Gracefully close the connection and cancel any pending reconnect timers.
65
+ * All pending requests are rejected.
66
+ */
67
+ close(code?: number, reason?: string): void;
68
+ private wireEvents;
69
+ private handleMessage;
70
+ private sendConnect;
71
+ private scheduleReconnect;
72
+ }
73
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/gateway/client.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAG7C,eAAO,MAAM,gBAAgB,IAAI,CAAC;AAElC,MAAM,WAAW,oBAAoB;IACnC,mCAAmC;IACnC,GAAG,EAAE,MAAM,CAAC;IACZ;;;OAGG;IAEH,aAAa,CAAC,EAAE,GAAG,CAAC;IACpB,uFAAuF;IACvF,iBAAiB,CAAC,EAAE,EAAE,GAAG,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACnD;;;OAGG;IACH,WAAW,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAC;IACjE,qEAAqE;IACrE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,2EAA2E;IAC3E,MAAM,CAAC,EAAE,MAAM,IAAI,CAAC;IACpB,qCAAqC;IACrC,OAAO,CAAC,EAAE,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD,0EAA0E;IAC1E,oBAAoB,CAAC,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAC;IACjD;;;;OAIG;IACH,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,8DAA8D;IAC9D,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,qBAAa,aAAa;IAcZ,OAAO,CAAC,QAAQ,CAAC,IAAI;IAZjC,OAAO,CAAC,EAAE,CAAa;IACvB,0EAA0E;IAC1E,OAAO,CAAC,UAAU,CAAK;IACvB,OAAO,CAAC,OAAO,CAAqC;IACpD,OAAO,CAAC,YAAY,CAAuB;IAC3C,OAAO,CAAC,WAAW,CAAS;IAC5B,OAAO,CAAC,cAAc,CAA8C;IACpE,OAAO,CAAC,SAAS,CAAO;IACxB,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,YAAY,CAA2C;IAC/D,OAAO,CAAC,WAAW,CAAuC;gBAE7B,IAAI,EAAE,oBAAoB;IAEvD;;;OAGG;IACG,OAAO,IAAI,OAAO,CAAC,OAAO,CAAC;IAgDjC;;;OAGG;IACG,OAAO,CAAC,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE;QAAE,SAAS,CAAC,EAAE,MAAM,CAAA;KAAE,GAAG,OAAO,CAAC,CAAC,CAAC;IAmB7F;;;OAGG;IACH,KAAK,CAAC,IAAI,SAAO,EAAE,MAAM,SAAiB,GAAG,IAAI;IAkBjD,OAAO,CAAC,UAAU;IAwElB,OAAO,CAAC,aAAa;YAyBP,WAAW;IAkBzB,OAAO,CAAC,iBAAiB;CAW1B"}
@@ -0,0 +1,244 @@
1
+ // packages/shared/src/gateway/client.ts
2
+ // Runtime-agnostic GatewayClient — browser uses globalThis.WebSocket, Node uses ws via ./node subpath.
3
+ import { flushPending, handleResponseFrame } from './protocol.js';
4
+ import { uuid } from '../utils/uuid.js';
5
+ export const PROTOCOL_VERSION = 3;
6
+ export class GatewayClient {
7
+ opts;
8
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
9
+ ws = null;
10
+ /** Increments per connect() call to fence stale socket event handlers. */
11
+ generation = 0;
12
+ pending = new Map();
13
+ connectNonce = null;
14
+ connectSent = false;
15
+ reconnectTimer = null;
16
+ backoffMs = 800;
17
+ closed = false;
18
+ helloResolve = null;
19
+ helloReject = null;
20
+ constructor(opts) {
21
+ this.opts = opts;
22
+ }
23
+ /**
24
+ * Open the WebSocket and complete the connect.challenge handshake.
25
+ * Resolves with the HelloOk payload from the server.
26
+ */
27
+ async connect() {
28
+ this.closed = false;
29
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
30
+ const Impl = this.opts.WebSocketImpl ?? globalThis.WebSocket;
31
+ if (!Impl)
32
+ throw new Error('No WebSocket implementation available. Pass WebSocketImpl or run in a browser.');
33
+ const args = this.opts.wsConstructorArgs ?? [];
34
+ const gen = ++this.generation;
35
+ this.connectSent = false;
36
+ this.connectNonce = null;
37
+ return new Promise((resolve, reject) => {
38
+ this.helloResolve = resolve;
39
+ this.helloReject = reject;
40
+ let connectTimer = null;
41
+ const connectTimeoutMs = this.opts.connectTimeoutMs ?? 10000;
42
+ connectTimer = setTimeout(() => {
43
+ connectTimer = null;
44
+ if (this.generation === gen) {
45
+ this.helloReject?.(new Error(`connect timed out after ${connectTimeoutMs}ms`));
46
+ this.helloResolve = this.helloReject = null;
47
+ this.ws?.close();
48
+ }
49
+ }, connectTimeoutMs);
50
+ const origReject = reject;
51
+ this.helloReject = (err) => {
52
+ if (connectTimer) {
53
+ clearTimeout(connectTimer);
54
+ connectTimer = null;
55
+ }
56
+ origReject(err);
57
+ };
58
+ this.helloResolve = (value) => {
59
+ if (connectTimer) {
60
+ clearTimeout(connectTimer);
61
+ connectTimer = null;
62
+ }
63
+ resolve(value);
64
+ };
65
+ try {
66
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
+ this.ws = new Impl(this.opts.url, ...args);
68
+ this.wireEvents(gen);
69
+ }
70
+ catch (err) {
71
+ if (connectTimer) {
72
+ clearTimeout(connectTimer);
73
+ connectTimer = null;
74
+ }
75
+ this.helloResolve = this.helloReject = null;
76
+ reject(err instanceof Error ? err : new Error(String(err)));
77
+ }
78
+ });
79
+ }
80
+ /**
81
+ * Send a gateway request and resolve with the response payload.
82
+ * Rejects if not connected or if the request times out.
83
+ */
84
+ async request(method, params, opts) {
85
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
86
+ const ws = this.ws;
87
+ if (!ws || ws.readyState !== 1 /* OPEN */)
88
+ throw new Error('not connected');
89
+ const id = uuid();
90
+ const timeoutMs = opts?.timeoutMs ?? this.opts.requestTimeoutMs ?? 15000;
91
+ return new Promise((resolve, reject) => {
92
+ const timer = setTimeout(() => {
93
+ this.pending.delete(id);
94
+ reject(new Error(`request '${method}' timed out after ${timeoutMs}ms`));
95
+ }, timeoutMs);
96
+ this.pending.set(id, {
97
+ resolve: (v) => { clearTimeout(timer); resolve(v); },
98
+ reject: (e) => { clearTimeout(timer); reject(e); },
99
+ });
100
+ ws.send(JSON.stringify({ type: 'req', id, method, params }));
101
+ });
102
+ }
103
+ /**
104
+ * Gracefully close the connection and cancel any pending reconnect timers.
105
+ * All pending requests are rejected.
106
+ */
107
+ close(code = 1000, reason = 'client close') {
108
+ this.closed = true;
109
+ if (this.reconnectTimer) {
110
+ clearTimeout(this.reconnectTimer);
111
+ this.reconnectTimer = null;
112
+ }
113
+ if (this.ws) {
114
+ this.ws.close(code, reason);
115
+ this.ws = null;
116
+ }
117
+ flushPending(this.pending, new Error('disconnected'));
118
+ this.backoffMs = 800;
119
+ }
120
+ // ---------------------------------------------------------------------------
121
+ // Private helpers
122
+ // ---------------------------------------------------------------------------
123
+ wireEvents(gen) {
124
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
125
+ const ws = this.ws;
126
+ // Normalize Node ws (.on) vs browser WebSocket (addEventListener).
127
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
128
+ const on = (ev, fn) => {
129
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
130
+ if (typeof ws.on === 'function') {
131
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
132
+ ws.on(ev, fn);
133
+ }
134
+ else {
135
+ ws.addEventListener(ev, (e) => fn(e));
136
+ }
137
+ };
138
+ on('open', () => {
139
+ if (this.generation !== gen)
140
+ return;
141
+ this.opts.onOpen?.();
142
+ });
143
+ on('message', (evOrData) => {
144
+ if (this.generation !== gen)
145
+ return;
146
+ // Node ws fires message(data, isBinary); browser fires MessageEvent.
147
+ let raw;
148
+ if (typeof evOrData === 'object' &&
149
+ evOrData !== null &&
150
+ 'data' in evOrData) {
151
+ raw = String(evOrData.data ?? '');
152
+ }
153
+ else {
154
+ raw = String(evOrData ?? '');
155
+ }
156
+ this.handleMessage(raw);
157
+ });
158
+ on('close', (evOrCode, reasonBuf) => {
159
+ if (this.generation !== gen)
160
+ return;
161
+ // Node ws close(code, reason: Buffer); browser fires CloseEvent.
162
+ let code;
163
+ let reason;
164
+ if (typeof evOrCode === 'object' && evOrCode !== null && 'code' in evOrCode) {
165
+ const ev = evOrCode;
166
+ code = Number(ev.code);
167
+ reason = ev.reason != null ? String(ev.reason) : '';
168
+ }
169
+ else {
170
+ code = Number(evOrCode ?? 1006);
171
+ reason = reasonBuf != null ? String(reasonBuf) : '';
172
+ }
173
+ this.ws = null;
174
+ flushPending(this.pending, new Error(`closed (${code}): ${reason}`));
175
+ this.opts.onClose?.(code, reason);
176
+ // If connect() promise is still pending (closed before hello completed):
177
+ if (this.helloReject) {
178
+ this.helloReject(new Error(`closed before hello (${code})`));
179
+ this.helloResolve = this.helloReject = null;
180
+ }
181
+ if (this.opts.autoReconnect && !this.closed) {
182
+ this.scheduleReconnect();
183
+ }
184
+ });
185
+ on('error', () => {
186
+ // close handler fires next — no action needed here.
187
+ });
188
+ }
189
+ handleMessage(raw) {
190
+ let frame;
191
+ try {
192
+ frame = JSON.parse(raw);
193
+ }
194
+ catch {
195
+ // Malformed JSON — silently discard (T-07-02 mitigation).
196
+ return;
197
+ }
198
+ if (frame['type'] === 'event') {
199
+ if (frame['event'] === 'connect.challenge') {
200
+ const payload = frame['payload'];
201
+ const nonce = payload && typeof payload.nonce === 'string' ? payload.nonce : null;
202
+ if (nonce) {
203
+ void this.sendConnect(nonce);
204
+ }
205
+ return;
206
+ }
207
+ void Promise.resolve(this.opts.onEvent?.(frame)).catch(() => { });
208
+ return;
209
+ }
210
+ handleResponseFrame(frame, this.pending);
211
+ }
212
+ async sendConnect(nonce) {
213
+ if (this.connectSent)
214
+ return;
215
+ this.connectSent = true;
216
+ try {
217
+ const params = await this.opts.onChallenge(nonce);
218
+ const hello = await this.request('connect', params);
219
+ // Successful connect — reset backoff.
220
+ this.backoffMs = 800;
221
+ this.helloResolve?.(hello);
222
+ this.helloResolve = this.helloReject = null;
223
+ }
224
+ catch (err) {
225
+ this.helloReject?.(err instanceof Error ? err : new Error(String(err)));
226
+ this.helloResolve = this.helloReject = null;
227
+ // Close the socket to trigger reconnect logic if autoReconnect is true.
228
+ this.ws?.close(4008, 'connect failed');
229
+ }
230
+ }
231
+ scheduleReconnect() {
232
+ if (this.closed)
233
+ return;
234
+ const delay = this.backoffMs;
235
+ // Exponential backoff capped at 15000ms (T-07-04 mitigation).
236
+ this.backoffMs = Math.min(this.backoffMs * 1.7, 15000);
237
+ this.opts.onReconnectScheduled?.(delay);
238
+ this.reconnectTimer = setTimeout(() => {
239
+ this.reconnectTimer = null;
240
+ void this.connect().catch(() => { });
241
+ }, delay);
242
+ }
243
+ }
244
+ //# sourceMappingURL=client.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../../src/gateway/client.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=client.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.test.d.ts","sourceRoot":"","sources":["../../src/gateway/client.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,235 @@
1
+ // packages/shared/src/gateway/client.test.ts
2
+ // Unit tests for GatewayClient using a hand-rolled mock WebSocket.
3
+ import { describe, it, expect, vi, beforeEach, afterEach } from 'vitest';
4
+ import { GatewayClient, PROTOCOL_VERSION } from './client.js';
5
+ // ---------------------------------------------------------------------------
6
+ // Mock WebSocket
7
+ // Supports Node ws style (.on()) — that's what the client uses for the mock.
8
+ // ---------------------------------------------------------------------------
9
+ class MockWebSocket {
10
+ static CONNECTING = 0;
11
+ static OPEN = 1;
12
+ static CLOSING = 2;
13
+ static CLOSED = 3;
14
+ readyState = MockWebSocket.OPEN; // Start as OPEN so send() works immediately after construction
15
+ sentMessages = [];
16
+ listeners = {};
17
+ // Node ws API
18
+ on(event, fn) {
19
+ (this.listeners[event] ??= []).push(fn);
20
+ return this;
21
+ }
22
+ send(data) {
23
+ this.sentMessages.push(data);
24
+ }
25
+ close(code, reason) {
26
+ this.readyState = MockWebSocket.CLOSED;
27
+ this.__emit('close', code ?? 1000, reason ?? '');
28
+ }
29
+ __emit(event, ...args) {
30
+ for (const fn of this.listeners[event] ?? [])
31
+ fn(...args);
32
+ }
33
+ // Simulation helpers
34
+ __simulateOpen() {
35
+ this.readyState = MockWebSocket.OPEN;
36
+ this.__emit('open');
37
+ }
38
+ __simulateMessage(data) {
39
+ this.__emit('message', data);
40
+ }
41
+ __simulateClose(code, reason) {
42
+ this.readyState = MockWebSocket.CLOSED;
43
+ this.__emit('close', code, reason);
44
+ }
45
+ }
46
+ // ---------------------------------------------------------------------------
47
+ // Factory: build a GatewayClient with injected MockWebSocket
48
+ // connectTimeoutMs set very high so fake timers don't fire the connect timeout.
49
+ // requestTimeoutMs set high for the connect sub-request too.
50
+ // ---------------------------------------------------------------------------
51
+ function makeMockImpl(instance) {
52
+ return function MockImpl(_url, ..._args) {
53
+ return instance;
54
+ };
55
+ }
56
+ function makeClient(mockWs, opts = {}) {
57
+ return new GatewayClient({
58
+ url: 'ws://mock-host/gateway',
59
+ onChallenge: async (_nonce) => ({ token: 'test-token', minProtocol: 3, maxProtocol: 3 }),
60
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
61
+ WebSocketImpl: makeMockImpl(mockWs),
62
+ connectTimeoutMs: 999_999,
63
+ requestTimeoutMs: 999_999,
64
+ ...opts,
65
+ });
66
+ }
67
+ // ---------------------------------------------------------------------------
68
+ // Helper: perform the full connect handshake synchronously with fake timers
69
+ // ---------------------------------------------------------------------------
70
+ async function performConnect(client, mockWs) {
71
+ const connectPromise = client.connect();
72
+ // Simulate open + challenge
73
+ mockWs.__simulateOpen();
74
+ mockWs.__simulateMessage(JSON.stringify({ type: 'event', event: 'connect.challenge', payload: { nonce: 'test-nonce' } }));
75
+ // Let the onChallenge async callback resolve
76
+ await Promise.resolve();
77
+ await Promise.resolve();
78
+ // Find the connect request id from sent messages
79
+ const connectMsg = mockWs.sentMessages.find((m) => {
80
+ try {
81
+ return JSON.parse(m).method === 'connect';
82
+ }
83
+ catch {
84
+ return false;
85
+ }
86
+ });
87
+ if (!connectMsg)
88
+ throw new Error('connect request not sent');
89
+ const connectReq = JSON.parse(connectMsg);
90
+ const helloPayload = { type: 'hello-ok', protocol: 3, server: { connId: 'conn-1' } };
91
+ mockWs.__simulateMessage(JSON.stringify({ type: 'res', id: connectReq.id, ok: true, payload: helloPayload }));
92
+ return connectPromise;
93
+ }
94
+ // ---------------------------------------------------------------------------
95
+ // Tests
96
+ // ---------------------------------------------------------------------------
97
+ describe('GatewayClient', () => {
98
+ let mockWs;
99
+ beforeEach(() => {
100
+ mockWs = new MockWebSocket();
101
+ vi.useFakeTimers();
102
+ });
103
+ afterEach(() => {
104
+ vi.useRealTimers();
105
+ vi.restoreAllMocks();
106
+ });
107
+ it('exports PROTOCOL_VERSION = 3', () => {
108
+ expect(PROTOCOL_VERSION).toBe(3);
109
+ });
110
+ it('resolves connect() with hello payload after challenge handshake', async () => {
111
+ const client = makeClient(mockWs);
112
+ const connectPromise = client.connect();
113
+ // Socket opens → server sends connect.challenge
114
+ mockWs.__simulateOpen();
115
+ mockWs.__simulateMessage(JSON.stringify({ type: 'event', event: 'connect.challenge', payload: { nonce: 'abc-nonce' } }));
116
+ // Let async onChallenge resolve
117
+ await Promise.resolve();
118
+ await Promise.resolve();
119
+ // The client should have sent the 'connect' request
120
+ const connectMsg = mockWs.sentMessages.find((m) => {
121
+ try {
122
+ return JSON.parse(m).method === 'connect';
123
+ }
124
+ catch {
125
+ return false;
126
+ }
127
+ });
128
+ expect(connectMsg).toBeDefined();
129
+ const connectReq = JSON.parse(connectMsg);
130
+ const helloPayload = { type: 'hello-ok', protocol: 3, server: { connId: 'conn-1' } };
131
+ mockWs.__simulateMessage(JSON.stringify({ type: 'res', id: connectReq.id, ok: true, payload: helloPayload }));
132
+ const result = await connectPromise;
133
+ expect(result).toEqual(helloPayload);
134
+ });
135
+ it('request<T>() matches response by id and resolves', async () => {
136
+ const client = makeClient(mockWs);
137
+ await performConnect(client, mockWs);
138
+ // Now send a real request
139
+ const reqPromise = client.request('agents.list', { page: 1 });
140
+ const lastMsg = mockWs.sentMessages.at(-1);
141
+ const sentReq = JSON.parse(lastMsg);
142
+ expect(sentReq.method).toBe('agents.list');
143
+ // Simulate matching response
144
+ mockWs.__simulateMessage(JSON.stringify({ type: 'res', id: sentReq.id, ok: true, payload: { value: 42 } }));
145
+ const result = await reqPromise;
146
+ expect(result).toEqual({ value: 42 });
147
+ });
148
+ it('request<T>() rejects after requestTimeoutMs', async () => {
149
+ // Use a short requestTimeoutMs for this specific test
150
+ const client = makeClient(mockWs, { requestTimeoutMs: 500 });
151
+ await performConnect(client, mockWs);
152
+ const reqPromise = client.request('slow.method');
153
+ // Advance timers past the request timeout (not connect timeout — that's 999999)
154
+ vi.advanceTimersByTime(600);
155
+ await expect(reqPromise).rejects.toThrow("request 'slow.method' timed out after 500ms");
156
+ });
157
+ it('close() flushes pending requests with disconnect error', async () => {
158
+ const client = makeClient(mockWs);
159
+ await performConnect(client, mockWs);
160
+ // Queue a request (no response will come)
161
+ const reqPromise = client.request('pending.method');
162
+ // close() should flush it immediately
163
+ client.close();
164
+ // Pending requests are flushed by the close event handler with the close code/reason.
165
+ // (MockWebSocket.close() fires synchronously, so the close handler runs before our explicit flush.)
166
+ await expect(reqPromise).rejects.toThrow(/closed|disconnected/);
167
+ });
168
+ it('does not reconnect when autoReconnect is false (default)', async () => {
169
+ const client = makeClient(mockWs); // autoReconnect not set → defaults to false
170
+ await performConnect(client, mockWs);
171
+ const sentCountBefore = mockWs.sentMessages.length;
172
+ // Simulate unexpected server close
173
+ mockWs.__simulateClose(1006, 'network gone');
174
+ // Advance timers — no reconnect should fire
175
+ vi.advanceTimersByTime(5000);
176
+ // No new messages on the same socket (reconnect would make a new socket)
177
+ expect(mockWs.sentMessages.length).toBe(sentCountBefore);
178
+ });
179
+ it('schedules reconnect with exponential backoff when autoReconnect is true', async () => {
180
+ const instances = [];
181
+ let instanceIdx = 0;
182
+ const ws1 = new MockWebSocket();
183
+ const ws2 = new MockWebSocket();
184
+ instances.push(ws1, ws2);
185
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
186
+ const MultiImpl = function (_url, ..._args) {
187
+ return instances[instanceIdx++];
188
+ };
189
+ const reconnectDelays = [];
190
+ const client = new GatewayClient({
191
+ url: 'ws://mock-host/gateway',
192
+ onChallenge: async (_nonce) => ({ token: 'x' }),
193
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
194
+ WebSocketImpl: MultiImpl,
195
+ autoReconnect: true,
196
+ connectTimeoutMs: 999_999,
197
+ requestTimeoutMs: 999_999,
198
+ onReconnectScheduled: (delay) => { reconnectDelays.push(delay); },
199
+ });
200
+ // --- First connect ---
201
+ await performConnect(client, ws1);
202
+ // --- Trigger close (unexpected) → should schedule reconnect at 800ms ---
203
+ ws1.__simulateClose(1006, 'gone');
204
+ expect(reconnectDelays).toEqual([800]);
205
+ // Advance 800ms → second connect() fires on ws2
206
+ vi.advanceTimersByTime(800);
207
+ // Let the reconnect's connect() promise start and its async onChallenge run
208
+ await Promise.resolve();
209
+ await Promise.resolve();
210
+ // ws2 should have been opened and gotten the challenge
211
+ ws2.__simulateOpen();
212
+ ws2.__simulateMessage(JSON.stringify({ type: 'event', event: 'connect.challenge', payload: { nonce: 'n2' } }));
213
+ await Promise.resolve();
214
+ await Promise.resolve();
215
+ const connectMsg2 = ws2.sentMessages.find((m) => {
216
+ try {
217
+ return JSON.parse(m).method === 'connect';
218
+ }
219
+ catch {
220
+ return false;
221
+ }
222
+ });
223
+ if (connectMsg2) {
224
+ const req2 = JSON.parse(connectMsg2);
225
+ ws2.__simulateMessage(JSON.stringify({ type: 'res', id: req2.id, ok: true, payload: { type: 'hello-ok' } }));
226
+ await Promise.resolve();
227
+ }
228
+ // --- Trigger close again → backoff should be ~1360ms (800 * 1.7) ---
229
+ ws2.__simulateClose(1006, 'gone again');
230
+ // The second reconnect delay should be 1360 (800 * 1.7)
231
+ expect(reconnectDelays.length).toBeGreaterThanOrEqual(2);
232
+ expect(reconnectDelays[1]).toBeCloseTo(1360, -1);
233
+ });
234
+ });
235
+ //# sourceMappingURL=client.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.test.js","sourceRoot":"","sources":["../../src/gateway/client.test.ts"],"names":[],"mappings":"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"}
@@ -1,4 +1,5 @@
1
1
  export * from './types.js';
2
2
  export * from './protocol.js';
3
3
  export * from './connection.js';
4
+ export * from './client.js';
4
5
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/gateway/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/gateway/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC"}
@@ -1,4 +1,5 @@
1
1
  export * from './types.js';
2
2
  export * from './protocol.js';
3
3
  export * from './connection.js';
4
+ export * from './client.js';
4
5
  //# sourceMappingURL=index.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gateway/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/gateway/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAC;AAC3B,cAAc,eAAe,CAAC;AAC9B,cAAc,iBAAiB,CAAC;AAChC,cAAc,aAAa,CAAC"}
@@ -0,0 +1,20 @@
1
+ import { GatewayClient, type GatewayClientOptions } from '../gateway/client.js';
2
+ export interface NodeGatewayClientOptions extends Omit<GatewayClientOptions, 'WebSocketImpl' | 'wsConstructorArgs'> {
3
+ /** Additional HTTP headers to send during the WebSocket upgrade (Node ws feature). */
4
+ headers?: Record<string, string>;
5
+ /** Max message payload in bytes. Default: 25 MiB. */
6
+ maxPayload?: number;
7
+ }
8
+ /**
9
+ * Create a GatewayClient instance backed by the `ws` npm package.
10
+ *
11
+ * Use this in Node.js environments (paperclip adapter, scripts).
12
+ * Hub and site use `new GatewayClient(opts)` directly (browser globalThis.WebSocket).
13
+ */
14
+ export declare function createNodeGatewayClient(options: NodeGatewayClientOptions): GatewayClient;
15
+ export { GatewayClient, PROTOCOL_VERSION } from '../gateway/client.js';
16
+ export type { GatewayClientOptions } from '../gateway/client.js';
17
+ export * from '../gateway/types.js';
18
+ export * from '../gateway/protocol.js';
19
+ export * from '../utils/index.js';
20
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,aAAa,EAAE,KAAK,oBAAoB,EAAoB,MAAM,sBAAsB,CAAC;AAElG,MAAM,WAAW,wBACf,SAAQ,IAAI,CAAC,oBAAoB,EAAE,eAAe,GAAG,mBAAmB,CAAC;IACzE,sFAAsF;IACtF,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,qDAAqD;IACrD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED;;;;;GAKG;AACH,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,wBAAwB,GAAG,aAAa,CAQxF;AAGD,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AACvE,YAAY,EAAE,oBAAoB,EAAE,MAAM,sBAAsB,CAAC;AACjE,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,28 @@
1
+ // packages/shared/src/node/index.ts
2
+ // Node-runtime entry point for the gateway client.
3
+ // Consumers: paperclip-minion adapter (pnpm, Node 22+).
4
+ // Browser consumers (hub, site) import from '@minion-stack/shared' (main entry) — NOT this file.
5
+ // This file is the ONLY entry point in this package that imports 'ws'.
6
+ import { WebSocket } from 'ws';
7
+ import { GatewayClient } from '../gateway/client.js';
8
+ /**
9
+ * Create a GatewayClient instance backed by the `ws` npm package.
10
+ *
11
+ * Use this in Node.js environments (paperclip adapter, scripts).
12
+ * Hub and site use `new GatewayClient(opts)` directly (browser globalThis.WebSocket).
13
+ */
14
+ export function createNodeGatewayClient(options) {
15
+ const { headers, maxPayload, ...rest } = options;
16
+ return new GatewayClient({
17
+ ...rest,
18
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
19
+ WebSocketImpl: WebSocket,
20
+ wsConstructorArgs: [{ headers: headers ?? {}, maxPayload: maxPayload ?? 25 * 1024 * 1024 }],
21
+ });
22
+ }
23
+ // Re-export shared surface so Node consumers can import everything from one subpath.
24
+ export { GatewayClient, PROTOCOL_VERSION } from '../gateway/client.js';
25
+ export * from '../gateway/types.js';
26
+ export * from '../gateway/protocol.js';
27
+ export * from '../utils/index.js';
28
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/node/index.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,mDAAmD;AACnD,wDAAwD;AACxD,iGAAiG;AACjG,uEAAuE;AACvE,OAAO,EAAE,SAAS,EAAE,MAAM,IAAI,CAAC;AAC/B,OAAO,EAAE,aAAa,EAA+C,MAAM,sBAAsB,CAAC;AAUlG;;;;;GAKG;AACH,MAAM,UAAU,uBAAuB,CAAC,OAAiC;IACvE,MAAM,EAAE,OAAO,EAAE,UAAU,EAAE,GAAG,IAAI,EAAE,GAAG,OAAO,CAAC;IACjD,OAAO,IAAI,aAAa,CAAC;QACvB,GAAG,IAAI;QACP,8DAA8D;QAC9D,aAAa,EAAE,SAA2B;QAC1C,iBAAiB,EAAE,CAAC,EAAE,OAAO,EAAE,OAAO,IAAI,EAAE,EAAE,UAAU,EAAE,UAAU,IAAI,EAAE,GAAG,IAAI,GAAG,IAAI,EAAE,CAAC;KAC5F,CAAC,CAAC;AACL,CAAC;AAED,qFAAqF;AACrF,OAAO,EAAE,aAAa,EAAE,gBAAgB,EAAE,MAAM,sBAAsB,CAAC;AAEvE,cAAc,qBAAqB,CAAC;AACpC,cAAc,wBAAwB,CAAC;AACvC,cAAc,mBAAmB,CAAC"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=index.test.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.d.ts","sourceRoot":"","sources":["../../src/node/index.test.ts"],"names":[],"mappings":""}
@@ -0,0 +1,92 @@
1
+ // packages/shared/src/node/index.test.ts
2
+ // Integration smoke test: spin up a real ws WebSocketServer, connect via createNodeGatewayClient,
3
+ // assert connect() resolves with canned helloOk payload.
4
+ import { describe, it, expect, afterAll } from 'vitest';
5
+ import { WebSocketServer } from 'ws';
6
+ import { createNodeGatewayClient, PROTOCOL_VERSION, GatewayClient } from './index.js';
7
+ let server;
8
+ let port;
9
+ // Spin up a minimal gateway mock server before tests.
10
+ function startMockServer() {
11
+ return new Promise((resolve) => {
12
+ server = new WebSocketServer({ port: 0 }, () => {
13
+ const addr = server.address();
14
+ port = typeof addr === 'object' && addr !== null ? addr.port : 0;
15
+ resolve();
16
+ });
17
+ server.on('connection', (ws) => {
18
+ // 1. Send connect.challenge immediately after connection.
19
+ ws.send(JSON.stringify({
20
+ type: 'event',
21
+ event: 'connect.challenge',
22
+ payload: { nonce: 'server-nonce-123' },
23
+ }));
24
+ // 2. Listen for client's 'connect' request and respond with helloOk.
25
+ ws.on('message', (data) => {
26
+ let frame;
27
+ try {
28
+ frame = JSON.parse(data.toString());
29
+ }
30
+ catch {
31
+ return;
32
+ }
33
+ if (frame['method'] === 'connect') {
34
+ ws.send(JSON.stringify({
35
+ type: 'res',
36
+ id: frame['id'],
37
+ ok: true,
38
+ payload: {
39
+ type: 'hello-ok',
40
+ protocol: 3,
41
+ server: { version: '1.0.0', connId: 'smoke-conn-1' },
42
+ features: { methods: ['connect'], events: ['connect.challenge'] },
43
+ snapshot: { presence: [], health: {}, stateVersion: { presence: 0, health: 0 }, uptimeMs: 0 },
44
+ policy: { maxPayload: 1048576, maxBufferedBytes: 1048576, tickIntervalMs: 1000 },
45
+ },
46
+ }));
47
+ }
48
+ });
49
+ });
50
+ });
51
+ }
52
+ // Start server once for all tests in this file.
53
+ await startMockServer();
54
+ afterAll(() => {
55
+ server?.close();
56
+ });
57
+ describe('createNodeGatewayClient (integration smoke)', () => {
58
+ it('exports createNodeGatewayClient as a function', () => {
59
+ expect(typeof createNodeGatewayClient).toBe('function');
60
+ });
61
+ it('exports GatewayClient class', () => {
62
+ expect(typeof GatewayClient).toBe('function');
63
+ });
64
+ it('re-exports PROTOCOL_VERSION = 3', () => {
65
+ expect(PROTOCOL_VERSION).toBe(3);
66
+ });
67
+ it('connect() resolves with hello-ok payload from a real WebSocketServer', async () => {
68
+ const client = createNodeGatewayClient({
69
+ url: `ws://127.0.0.1:${port}`,
70
+ onChallenge: async (_nonce) => ({
71
+ token: 'smoke-token',
72
+ minProtocol: 3,
73
+ maxProtocol: 3,
74
+ client: { id: 'smoke-test', mode: 'backend' },
75
+ }),
76
+ autoReconnect: false,
77
+ });
78
+ const result = await client.connect();
79
+ expect(result).toMatchObject({ type: 'hello-ok', protocol: 3 });
80
+ client.close();
81
+ });
82
+ it('createNodeGatewayClient injects ws as WebSocketImpl (returns GatewayClient instance)', () => {
83
+ const client = createNodeGatewayClient({
84
+ url: `ws://127.0.0.1:${port}`,
85
+ onChallenge: async (_nonce) => ({}),
86
+ autoReconnect: false,
87
+ });
88
+ expect(client).toBeInstanceOf(GatewayClient);
89
+ client.close();
90
+ });
91
+ });
92
+ //# sourceMappingURL=index.test.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../../src/node/index.test.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@minion-stack/shared",
3
- "version": "0.1.0",
3
+ "version": "0.2.0",
4
4
  "description": "Shared gateway protocol types and utilities for the Minion platform.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -23,6 +23,18 @@
23
23
  "./utils": {
24
24
  "types": "./dist/utils/index.d.ts",
25
25
  "import": "./dist/utils/index.js"
26
+ },
27
+ "./node": {
28
+ "types": "./dist/node/index.d.ts",
29
+ "import": "./dist/node/index.js"
30
+ }
31
+ },
32
+ "peerDependencies": {
33
+ "ws": "^8.0.0"
34
+ },
35
+ "peerDependenciesMeta": {
36
+ "ws": {
37
+ "optional": true
26
38
  }
27
39
  },
28
40
  "publishConfig": {
@@ -34,10 +46,15 @@
34
46
  ],
35
47
  "scripts": {
36
48
  "build": "tsc",
37
- "prepublishOnly": "tsc"
49
+ "prepublishOnly": "tsc",
50
+ "test": "vitest run",
51
+ "test:watch": "vitest"
38
52
  },
39
53
  "devDependencies": {
40
54
  "@minion-stack/tsconfig": "workspace:*",
41
- "typescript": "^5.7.0"
55
+ "@types/ws": "^8.18.1",
56
+ "typescript": "^5.7.0",
57
+ "vitest": "^2.1.9",
58
+ "ws": "^8.19.0"
42
59
  }
43
60
  }