@cnstra/devtools-transport-ws 1.1.1 → 1.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.
package/dist/index.cjs CHANGED
@@ -1,85 +1,76 @@
1
1
  'use strict';
2
2
 
3
+ var devtoolsDto = require('@cnstra/devtools-dto');
4
+
3
5
  // src/index.ts
4
6
  var CNSDevToolsTransportWs = class {
5
7
  constructor(opts) {
6
8
  this.opts = opts;
7
9
  this.connecting = false;
8
10
  this.closed = false;
9
- this.buffer = [];
10
- this.hasConnectedOnce = false;
11
11
  this.reconnectAttempts = 0;
12
+ this.hasConnectedOnce = false;
13
+ this.pendingBatches = [];
12
14
  }
13
- get reconnectDelayMs() {
14
- return this.opts.reconnectDelayMs ?? 1e3;
15
+ // ─── ICNSDevToolsTransport ────────────────────────────────────────────────────
16
+ async sendBatch(message) {
17
+ if (message.items.some((i) => i.type === "topology")) {
18
+ this.lastTopologyBatch = message;
19
+ }
20
+ this.pendingBatches.push(message);
21
+ await this.flush();
15
22
  }
16
- get bufferMaxSize() {
17
- return this.opts.bufferMaxSize ?? 100;
23
+ onReplayStart(handler) {
24
+ this.onReplayHandler = handler;
25
+ return () => {
26
+ this.onReplayHandler = void 0;
27
+ };
18
28
  }
19
- get maxReconnectAttempts() {
20
- return this.opts.maxReconnectAttempts ?? Infinity;
29
+ onStimulationCommand(handler) {
30
+ this.onCommandHandler = handler;
31
+ return () => {
32
+ this.onCommandHandler = void 0;
33
+ };
34
+ }
35
+ // ─── Connection management ────────────────────────────────────────────────────
36
+ get WS() {
37
+ const impl = this.opts.webSocketImpl ?? (typeof WebSocket !== "undefined" ? WebSocket : void 0);
38
+ if (!impl) throw new Error("No WebSocket implementation. Pass webSocketImpl option.");
39
+ return impl;
21
40
  }
22
41
  ensureSocket() {
23
- const WS = this.opts.webSocketImpl || (typeof WebSocket !== "undefined" ? WebSocket : void 0);
24
- if (!WS)
25
- return Promise.reject(
26
- new Error(
27
- "WebSocket implementation is not available. Provide webSocketImpl."
28
- )
29
- );
30
- if (this.ws && this.ws.readyState === 1)
31
- return Promise.resolve();
42
+ if (this.ws?.readyState === 1) return Promise.resolve();
32
43
  if (this.connecting) {
33
44
  return new Promise((resolve) => {
34
- const check = () => {
35
- if (this.ws && this.ws.readyState === 1)
36
- return resolve();
37
- setTimeout(check, 50);
38
- };
39
- check();
45
+ const poll = () => this.ws?.readyState === 1 ? resolve() : setTimeout(poll, 50);
46
+ poll();
40
47
  });
41
48
  }
42
49
  this.connecting = true;
43
50
  return new Promise((resolve) => {
44
- const ws = new WS(this.opts.url, this.opts.protocols);
51
+ const ws = new this.WS(this.opts.url, this.opts.protocols);
45
52
  this.ws = ws;
46
53
  ws.onopen = () => {
47
- if (this.opts.consoleLogEnabled) {
48
- console.log("DevTools connected to server");
49
- }
50
54
  this.connecting = false;
51
55
  this.reconnectAttempts = 0;
52
- if (this.hasConnectedOnce && this.lastInitMessage) {
53
- if (this.opts.consoleLogEnabled) {
54
- console.log("DevTools reconnected - resending init message");
55
- }
56
- this.buffer.unshift({
57
- type: "init",
58
- payload: this.lastInitMessage
59
- });
56
+ if (this.opts.consoleLogEnabled) console.log("[DevTools] Connected");
57
+ if (this.hasConnectedOnce && this.lastTopologyBatch) {
58
+ this.pendingBatches.unshift(this.lastTopologyBatch);
60
59
  }
61
60
  this.hasConnectedOnce = true;
62
- this.flush();
63
- resolve();
61
+ this.flush().then(resolve);
64
62
  };
65
63
  ws.onclose = () => {
66
64
  this.connecting = false;
67
- if (!this.closed && this.reconnectAttempts < this.maxReconnectAttempts) {
65
+ if (!this.closed && this.reconnectAttempts < (this.opts.maxReconnectAttempts ?? Infinity)) {
68
66
  this.reconnectAttempts++;
69
67
  if (this.opts.consoleLogEnabled) {
70
- console.log(
71
- `DevTools disconnected, reconnecting... (attempt ${this.reconnectAttempts})`
72
- );
73
- }
74
- setTimeout(
75
- () => this.ensureSocket().catch(() => {
76
- }),
77
- this.reconnectDelayMs
78
- );
79
- } else if (this.reconnectAttempts >= this.maxReconnectAttempts) {
80
- if (this.opts.consoleLogEnabled) {
81
- console.log("DevTools max reconnect attempts reached");
68
+ console.log(`[DevTools] Disconnected, reconnecting (attempt ${this.reconnectAttempts})`);
82
69
  }
70
+ setTimeout(() => this.ensureSocket().catch(() => {
71
+ }), this.opts.reconnectDelayMs ?? 1e3);
72
+ } else if (this.reconnectAttempts >= (this.opts.maxReconnectAttempts ?? Infinity)) {
73
+ if (this.opts.consoleLogEnabled) console.log("[DevTools] Max reconnect attempts reached");
83
74
  }
84
75
  };
85
76
  ws.onerror = () => {
@@ -90,76 +81,41 @@ var CNSDevToolsTransportWs = class {
90
81
  };
91
82
  ws.onmessage = (ev) => {
92
83
  try {
93
- const data = typeof ev.data === "string" ? ev.data : "" + ev.data;
94
- const msg = JSON.parse(data);
95
- if (msg && msg.type === "stimulate") {
96
- const cmd = msg;
97
- const ack = {
98
- type: "stimulate-accepted",
99
- stimulationCommandId: cmd.stimulationCommandId,
100
- stimulationId: cmd.stimulationCommandId,
101
- appId: cmd.appId
102
- };
103
- try {
104
- this.ws?.send(JSON.stringify(ack));
105
- } catch {
84
+ const msg = JSON.parse(typeof ev.data === "string" ? ev.data : String(ev.data));
85
+ if (msg?.type === "replay.start") {
86
+ const result = devtoolsDto.CNSDTOReplayStartMessageSchema.safeParse(msg);
87
+ if (result.success) {
88
+ this.onReplayHandler?.(result.data);
89
+ } else if (this.opts.consoleLogEnabled) {
90
+ console.warn("[DevTools] Invalid replay.start message:", result.error.message);
91
+ }
92
+ } else if (msg?.type === "cns.stimulation.resume" || msg?.type === "cns.stimulation.launch") {
93
+ const result = devtoolsDto.CNSDTOAppCommandSchema.safeParse(msg);
94
+ if (result.success) {
95
+ this.onCommandHandler?.(result.data);
96
+ } else if (this.opts.consoleLogEnabled) {
97
+ console.warn("[DevTools] Invalid stimulation command:", result.error.message);
106
98
  }
107
- this.onStimulate?.(cmd);
108
99
  }
109
100
  } catch {
110
101
  }
111
102
  };
112
103
  });
113
104
  }
114
- async sendInitMessage(message) {
115
- this.lastInitMessage = message;
116
- this.buffer.push({ type: "init", payload: message });
117
- if (this.buffer.length >= this.bufferMaxSize) await this.flush();
118
- else void this.flush();
119
- }
120
- async sendNeuronResponseMessage(message) {
121
- this.buffer.push({ type: "response", payload: message });
122
- if (this.buffer.length >= this.bufferMaxSize) await this.flush();
123
- else void this.flush();
124
- }
125
- async sendStimulationMessage(message) {
126
- await this.ensureSocket();
127
- if (!this.ws || this.ws.readyState !== 1) return;
128
- try {
129
- this.ws.send(JSON.stringify(message));
130
- } catch {
131
- }
132
- }
133
105
  async flush() {
134
- if (this.buffer.length === 0) return;
106
+ if (this.pendingBatches.length === 0) return;
135
107
  await this.ensureSocket();
136
- if (!this.ws || this.ws.readyState !== 1) return;
137
- const items = this.buffer.splice(0, this.buffer.length);
138
- const replayResponses = items.filter((item) => item.type === "response").filter((item) => {
139
- const stimId = item.payload?.stimulationId || "";
140
- return typeof stimId === "string" && stimId.includes("-replay-");
141
- });
142
- if (replayResponses.length > 0 && this.opts.consoleLogEnabled) {
143
- console.log("[Transport] Flushing REPLAY responses batch:", {
144
- totalItems: items.length,
145
- replayResponsesCount: replayResponses.length,
146
- replayStimIds: replayResponses.slice(0, 3).map((r) => r.payload?.stimulationId),
147
- replayAppIds: replayResponses.slice(0, 3).map((r) => r.payload?.appId)
148
- });
149
- }
108
+ if (this.ws?.readyState !== 1) return;
109
+ const batches = this.pendingBatches.splice(0, this.pendingBatches.length);
110
+ if (batches.length === 0) return;
150
111
  try {
112
+ const items = batches.flatMap((b) => b.items);
113
+ if (items.length === 0) return;
151
114
  this.ws.send(JSON.stringify({ type: "batch", items }));
152
115
  } catch {
153
- this.buffer.unshift(...items);
116
+ this.pendingBatches.unshift(...batches);
154
117
  }
155
118
  }
156
- onStimulateCommand(handler) {
157
- this.onStimulate = handler;
158
- return () => {
159
- this.onStimulate = void 0;
160
- };
161
- }
162
- /** Close the connection and stop reconnecting */
163
119
  close() {
164
120
  this.closed = true;
165
121
  try {
@@ -168,14 +124,9 @@ var CNSDevToolsTransportWs = class {
168
124
  }
169
125
  this.ws = void 0;
170
126
  }
171
- /** Get connection status */
172
127
  get isConnected() {
173
128
  return this.ws?.readyState === 1;
174
129
  }
175
- /** Get buffered message count */
176
- get bufferSize() {
177
- return this.buffer.length;
178
- }
179
130
  };
180
131
 
181
132
  exports.CNSDevToolsTransportWs = CNSDevToolsTransportWs;
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ICNSDevToolsTransport } from '@cnstra/devtools';\nimport type {\n InitMessage,\n NeuronResponseMessage,\n StimulateCommand,\n StimulateAccepted,\n StimulateRejected,\n StimulationMessage,\n} from '@cnstra/devtools-dto';\n\nexport type TWSOpts = {\n /** ws://localhost:7777 or wss://... */\n url: string;\n /** Optional protocols */\n protocols?: string | string[];\n /** Reconnect delay in ms (default 1000) */\n reconnectDelayMs?: number;\n /** Max buffered messages before force flush (default 100) */\n bufferMaxSize?: number;\n /** Optional WebSocket implementation for Node (e.g., globalThis.WebSocket or ws) */\n webSocketImpl?: typeof WebSocket;\n /** Auto-connect on first message (default true) */\n autoConnect?: boolean;\n /** Max reconnect attempts (default Infinity) */\n maxReconnectAttempts?: number;\n /** When true, print transport-level console logs */\n consoleLogEnabled?: boolean;\n};\n\ntype WSLike = WebSocket;\n\nexport class CNSDevToolsTransportWs implements ICNSDevToolsTransport {\n private ws?: WSLike;\n private connecting = false;\n private closed = false;\n private readonly buffer: Array<{\n type: 'init' | 'response';\n payload: any;\n }> = [];\n\n // Store the latest init message for reconnect\n private lastInitMessage?: InitMessage;\n private hasConnectedOnce = false;\n private reconnectAttempts = 0;\n\n constructor(private readonly opts: TWSOpts) {}\n\n private get reconnectDelayMs() {\n return this.opts.reconnectDelayMs ?? 1000;\n }\n private get bufferMaxSize() {\n return this.opts.bufferMaxSize ?? 100;\n }\n private get maxReconnectAttempts() {\n return this.opts.maxReconnectAttempts ?? Infinity;\n }\n\n private ensureSocket(): Promise<void> {\n const WS: typeof WebSocket | undefined =\n this.opts.webSocketImpl ||\n (typeof WebSocket !== 'undefined' ? WebSocket : undefined);\n if (!WS)\n return Promise.reject(\n new Error(\n 'WebSocket implementation is not available. Provide webSocketImpl.'\n )\n );\n if (this.ws && this.ws.readyState === 1)\n // OPEN = 1\n return Promise.resolve();\n if (this.connecting) {\n return new Promise(resolve => {\n const check = () => {\n if (this.ws && this.ws.readyState === 1)\n // OPEN = 1\n return resolve();\n setTimeout(check, 50);\n };\n check();\n });\n }\n this.connecting = true;\n return new Promise(resolve => {\n const ws = new WS(this.opts.url, this.opts.protocols) as WSLike;\n this.ws = ws;\n ws.onopen = () => {\n if (this.opts.consoleLogEnabled) {\n console.log('DevTools connected to server');\n }\n this.connecting = false;\n this.reconnectAttempts = 0; // Reset on successful connection\n\n // On reconnect, resend init message first\n if (this.hasConnectedOnce && this.lastInitMessage) {\n if (this.opts.consoleLogEnabled) {\n console.log('DevTools reconnected - resending init message');\n }\n this.buffer.unshift({\n type: 'init',\n payload: this.lastInitMessage,\n });\n }\n\n this.hasConnectedOnce = true;\n this.flush();\n resolve();\n };\n ws.onclose = () => {\n this.connecting = false;\n if (\n !this.closed &&\n this.reconnectAttempts < this.maxReconnectAttempts\n ) {\n this.reconnectAttempts++;\n if (this.opts.consoleLogEnabled) {\n console.log(\n `DevTools disconnected, reconnecting... (attempt ${this.reconnectAttempts})`\n );\n }\n setTimeout(\n () => this.ensureSocket().catch(() => {}),\n this.reconnectDelayMs\n );\n } else if (\n this.reconnectAttempts >= this.maxReconnectAttempts\n ) {\n if (this.opts.consoleLogEnabled) {\n console.log('DevTools max reconnect attempts reached');\n }\n }\n };\n ws.onerror = () => {\n try {\n ws.close();\n } catch {}\n };\n ws.onmessage = (ev: MessageEvent) => {\n try {\n const data =\n typeof ev.data === 'string' ? ev.data : '' + ev.data;\n const msg = JSON.parse(data);\n if (msg && msg.type === 'stimulate') {\n const cmd = msg as StimulateCommand & {\n appId?: string;\n cnsId?: string;\n };\n const ack: StimulateAccepted = {\n type: 'stimulate-accepted',\n stimulationCommandId: cmd.stimulationCommandId,\n stimulationId: cmd.stimulationCommandId,\n appId: cmd.appId,\n };\n try {\n this.ws?.send(JSON.stringify(ack));\n } catch {}\n this.onStimulate?.(cmd);\n }\n } catch {\n // ignore invalid messages\n }\n };\n });\n }\n\n async sendInitMessage(message: InitMessage): Promise<void> {\n // Store the latest init message for reconnects\n this.lastInitMessage = message;\n\n this.buffer.push({ type: 'init', payload: message });\n if (this.buffer.length >= this.bufferMaxSize) await this.flush();\n else void this.flush();\n }\n\n async sendNeuronResponseMessage(\n message: NeuronResponseMessage\n ): Promise<void> {\n this.buffer.push({ type: 'response', payload: message });\n if (this.buffer.length >= this.bufferMaxSize) await this.flush();\n else void this.flush();\n }\n\n async sendStimulationMessage(message: StimulationMessage): Promise<void> {\n await this.ensureSocket();\n if (!this.ws || this.ws.readyState !== 1) return; // OPEN = 1\n try {\n this.ws.send(JSON.stringify(message));\n } catch {\n // ignore send errors\n }\n }\n\n private async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n await this.ensureSocket();\n /* istanbul ignore next -- ts-jest source-map occasionally misattributes coverage for this guard */\n if (!this.ws || this.ws.readyState !== 1) return; // OPEN = 1\n /* istanbul ignore next -- see note above */\n const items = this.buffer.splice(0, this.buffer.length);\n\n // Check for replay responses in the batch\n /* istanbul ignore next -- see note above */\n const replayResponses = items\n .filter(item => item.type === 'response')\n .filter(item => {\n const stimId = item.payload?.stimulationId || '';\n return (\n typeof stimId === 'string' && stimId.includes('-replay-')\n );\n });\n\n if (replayResponses.length > 0 && this.opts.consoleLogEnabled) {\n console.log('[Transport] Flushing REPLAY responses batch:', {\n totalItems: items.length,\n replayResponsesCount: replayResponses.length,\n replayStimIds: replayResponses\n .slice(0, 3)\n .map(r => r.payload?.stimulationId),\n replayAppIds: replayResponses\n .slice(0, 3)\n .map(r => r.payload?.appId),\n });\n }\n\n try {\n this.ws.send(JSON.stringify({ type: 'batch', items }));\n } catch {\n // requeue if send fails\n this.buffer.unshift(...items);\n }\n }\n\n private onStimulate?: (cmd: StimulateCommand) => void;\n onStimulateCommand(handler: (cmd: StimulateCommand) => void): () => void {\n this.onStimulate = handler;\n return () => {\n this.onStimulate = undefined;\n };\n }\n\n /** Close the connection and stop reconnecting */\n close(): void {\n this.closed = true;\n try {\n this.ws?.close();\n } catch {}\n this.ws = undefined;\n }\n\n /** Get connection status */\n get isConnected(): boolean {\n return this.ws?.readyState === 1; // OPEN = 1\n }\n\n /** Get buffered message count */\n get bufferSize(): number {\n return this.buffer.length;\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":["CNSDTOReplayStartMessageSchema","CNSDTOAppCommandSchema"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ICNSDevToolsTransport } from '@cnstra/devtools';\nimport {\n CNSDTOReplayStartMessageSchema,\n CNSDTOAppCommandSchema,\n type CNSDTOAppBatchMessage,\n} from '@cnstra/devtools-dto';\n\nexport type CNSDevToolsTransportWsOptions = {\n /** WebSocket URL, e.g. ws://localhost:3141 */\n url: string;\n protocols?: string | string[];\n /** Reconnect delay in ms (default: 1000) */\n reconnectDelayMs?: number;\n /** Max buffered batch items before force flush (default: 100) */\n batchMaxSize?: number;\n /** Provide a WebSocket implementation for Node.js (e.g. `ws`) */\n webSocketImpl?: typeof WebSocket;\n /** Max reconnect attempts (default: Infinity) */\n maxReconnectAttempts?: number;\n consoleLogEnabled?: boolean;\n};\n\nexport class CNSDevToolsTransportWs implements ICNSDevToolsTransport {\n private ws?: WebSocket;\n private connecting = false;\n private closed = false;\n private reconnectAttempts = 0;\n private hasConnectedOnce = false;\n\n private readonly pendingBatches: CNSDTOAppBatchMessage[] = [];\n private lastTopologyBatch?: CNSDTOAppBatchMessage;\n private onReplayHandler?: (cmd: any) => void;\n private onCommandHandler?: (cmd: any) => void;\n\n constructor(private readonly opts: CNSDevToolsTransportWsOptions) {}\n\n // ─── ICNSDevToolsTransport ────────────────────────────────────────────────────\n\n async sendBatch(message: CNSDTOAppBatchMessage): Promise<void> {\n if (message.items.some(i => i.type === 'topology')) {\n this.lastTopologyBatch = message;\n }\n this.pendingBatches.push(message);\n await this.flush();\n }\n\n onReplayStart(handler: (cmd: any) => void): () => void {\n this.onReplayHandler = handler;\n return () => { this.onReplayHandler = undefined; };\n }\n\n onStimulationCommand(handler: (cmd: any) => void): () => void {\n this.onCommandHandler = handler;\n return () => { this.onCommandHandler = undefined; };\n }\n\n // ─── Connection management ────────────────────────────────────────────────────\n\n private get WS(): typeof WebSocket {\n const impl = this.opts.webSocketImpl\n ?? (typeof WebSocket !== 'undefined' ? WebSocket : undefined);\n if (!impl) throw new Error('No WebSocket implementation. Pass webSocketImpl option.');\n return impl;\n }\n\n private ensureSocket(): Promise<void> {\n if (this.ws?.readyState === 1) return Promise.resolve();\n if (this.connecting) {\n return new Promise(resolve => {\n const poll = () => this.ws?.readyState === 1 ? resolve() : setTimeout(poll, 50);\n poll();\n });\n }\n this.connecting = true;\n return new Promise(resolve => {\n const ws = new this.WS(this.opts.url, this.opts.protocols) as WebSocket;\n this.ws = ws;\n\n ws.onopen = () => {\n this.connecting = false;\n this.reconnectAttempts = 0;\n if (this.opts.consoleLogEnabled) console.log('[DevTools] Connected');\n\n if (this.hasConnectedOnce && this.lastTopologyBatch) {\n this.pendingBatches.unshift(this.lastTopologyBatch);\n }\n this.hasConnectedOnce = true;\n this.flush().then(resolve);\n };\n\n ws.onclose = () => {\n this.connecting = false;\n if (!this.closed && this.reconnectAttempts < (this.opts.maxReconnectAttempts ?? Infinity)) {\n this.reconnectAttempts++;\n if (this.opts.consoleLogEnabled) {\n console.log(`[DevTools] Disconnected, reconnecting (attempt ${this.reconnectAttempts})`);\n }\n setTimeout(() => this.ensureSocket().catch(() => {}), this.opts.reconnectDelayMs ?? 1000);\n } else if (this.reconnectAttempts >= (this.opts.maxReconnectAttempts ?? Infinity)) {\n if (this.opts.consoleLogEnabled) console.log('[DevTools] Max reconnect attempts reached');\n }\n };\n\n ws.onerror = () => { try { ws.close(); } catch {} };\n\n ws.onmessage = (ev: MessageEvent) => {\n try {\n const msg = JSON.parse(typeof ev.data === 'string' ? ev.data : String(ev.data));\n if (msg?.type === 'replay.start') {\n const result = CNSDTOReplayStartMessageSchema.safeParse(msg);\n if (result.success) {\n this.onReplayHandler?.(result.data);\n } else if (this.opts.consoleLogEnabled) {\n console.warn('[DevTools] Invalid replay.start message:', result.error.message);\n }\n } else if (\n msg?.type === 'cns.stimulation.resume' ||\n msg?.type === 'cns.stimulation.launch'\n ) {\n const result = CNSDTOAppCommandSchema.safeParse(msg);\n if (result.success) {\n this.onCommandHandler?.(result.data);\n } else if (this.opts.consoleLogEnabled) {\n console.warn('[DevTools] Invalid stimulation command:', result.error.message);\n }\n }\n } catch {}\n };\n });\n }\n\n private async flush(): Promise<void> {\n if (this.pendingBatches.length === 0) return;\n await this.ensureSocket();\n if (this.ws?.readyState !== 1) return;\n\n const batches = this.pendingBatches.splice(0, this.pendingBatches.length);\n if (batches.length === 0) return;\n try {\n const items = batches.flatMap(b => b.items);\n if (items.length === 0) return;\n this.ws.send(JSON.stringify({ type: 'batch', items }));\n } catch {\n this.pendingBatches.unshift(...batches);\n }\n }\n\n close(): void {\n this.closed = true;\n try { this.ws?.close(); } catch {}\n this.ws = undefined;\n }\n\n get isConnected(): boolean {\n return this.ws?.readyState === 1;\n }\n}\n"]}
package/dist/index.d.ts CHANGED
@@ -1,21 +1,17 @@
1
1
  import type { ICNSDevToolsTransport } from '@cnstra/devtools';
2
- import type { InitMessage, NeuronResponseMessage, StimulateCommand, StimulationMessage } from '@cnstra/devtools-dto';
3
- export type TWSOpts = {
4
- /** ws://localhost:7777 or wss://... */
2
+ import { type CNSDTOAppBatchMessage } from '@cnstra/devtools-dto';
3
+ export type CNSDevToolsTransportWsOptions = {
4
+ /** WebSocket URL, e.g. ws://localhost:3141 */
5
5
  url: string;
6
- /** Optional protocols */
7
6
  protocols?: string | string[];
8
- /** Reconnect delay in ms (default 1000) */
7
+ /** Reconnect delay in ms (default: 1000) */
9
8
  reconnectDelayMs?: number;
10
- /** Max buffered messages before force flush (default 100) */
11
- bufferMaxSize?: number;
12
- /** Optional WebSocket implementation for Node (e.g., globalThis.WebSocket or ws) */
9
+ /** Max buffered batch items before force flush (default: 100) */
10
+ batchMaxSize?: number;
11
+ /** Provide a WebSocket implementation for Node.js (e.g. `ws`) */
13
12
  webSocketImpl?: typeof WebSocket;
14
- /** Auto-connect on first message (default true) */
15
- autoConnect?: boolean;
16
- /** Max reconnect attempts (default Infinity) */
13
+ /** Max reconnect attempts (default: Infinity) */
17
14
  maxReconnectAttempts?: number;
18
- /** When true, print transport-level console logs */
19
15
  consoleLogEnabled?: boolean;
20
16
  };
21
17
  export declare class CNSDevToolsTransportWs implements ICNSDevToolsTransport {
@@ -23,26 +19,20 @@ export declare class CNSDevToolsTransportWs implements ICNSDevToolsTransport {
23
19
  private ws?;
24
20
  private connecting;
25
21
  private closed;
26
- private readonly buffer;
27
- private lastInitMessage?;
28
- private hasConnectedOnce;
29
22
  private reconnectAttempts;
30
- constructor(opts: TWSOpts);
31
- private get reconnectDelayMs();
32
- private get bufferMaxSize();
33
- private get maxReconnectAttempts();
23
+ private hasConnectedOnce;
24
+ private readonly pendingBatches;
25
+ private lastTopologyBatch?;
26
+ private onReplayHandler?;
27
+ private onCommandHandler?;
28
+ constructor(opts: CNSDevToolsTransportWsOptions);
29
+ sendBatch(message: CNSDTOAppBatchMessage): Promise<void>;
30
+ onReplayStart(handler: (cmd: any) => void): () => void;
31
+ onStimulationCommand(handler: (cmd: any) => void): () => void;
32
+ private get WS();
34
33
  private ensureSocket;
35
- sendInitMessage(message: InitMessage): Promise<void>;
36
- sendNeuronResponseMessage(message: NeuronResponseMessage): Promise<void>;
37
- sendStimulationMessage(message: StimulationMessage): Promise<void>;
38
34
  private flush;
39
- private onStimulate?;
40
- onStimulateCommand(handler: (cmd: StimulateCommand) => void): () => void;
41
- /** Close the connection and stop reconnecting */
42
35
  close(): void;
43
- /** Get connection status */
44
36
  get isConnected(): boolean;
45
- /** Get buffered message count */
46
- get bufferSize(): number;
47
37
  }
48
38
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,KAAK,EACR,WAAW,EACX,qBAAqB,EACrB,gBAAgB,EAGhB,kBAAkB,EACrB,MAAM,sBAAsB,CAAC;AAE9B,MAAM,MAAM,OAAO,GAAG;IAClB,uCAAuC;IACvC,GAAG,EAAE,MAAM,CAAC;IACZ,yBAAyB;IACzB,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC9B,2CAA2C;IAC3C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6DAA6D;IAC7D,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,oFAAoF;IACpF,aAAa,CAAC,EAAE,OAAO,SAAS,CAAC;IACjC,mDAAmD;IACnD,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,gDAAgD;IAChD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,oDAAoD;IACpD,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC/B,CAAC;AAIF,qBAAa,sBAAuB,YAAW,qBAAqB;IAcpD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAbjC,OAAO,CAAC,EAAE,CAAC,CAAS;IACpB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,QAAQ,CAAC,MAAM,CAGf;IAGR,OAAO,CAAC,eAAe,CAAC,CAAc;IACtC,OAAO,CAAC,gBAAgB,CAAS;IACjC,OAAO,CAAC,iBAAiB,CAAK;gBAED,IAAI,EAAE,OAAO;IAE1C,OAAO,KAAK,gBAAgB,GAE3B;IACD,OAAO,KAAK,aAAa,GAExB;IACD,OAAO,KAAK,oBAAoB,GAE/B;IAED,OAAO,CAAC,YAAY;IA2Gd,eAAe,CAAC,OAAO,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IASpD,yBAAyB,CAC3B,OAAO,EAAE,qBAAqB,GAC/B,OAAO,CAAC,IAAI,CAAC;IAMV,sBAAsB,CAAC,OAAO,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC;YAU1D,KAAK;IAwCnB,OAAO,CAAC,WAAW,CAAC,CAAkC;IACtD,kBAAkB,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,gBAAgB,KAAK,IAAI,GAAG,MAAM,IAAI;IAOxE,iDAAiD;IACjD,KAAK,IAAI,IAAI;IAQb,4BAA4B;IAC5B,IAAI,WAAW,IAAI,OAAO,CAEzB;IAED,iCAAiC;IACjC,IAAI,UAAU,IAAI,MAAM,CAEvB;CACJ"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,qBAAqB,EAAE,MAAM,kBAAkB,CAAC;AAC9D,OAAO,EAGH,KAAK,qBAAqB,EAC7B,MAAM,sBAAsB,CAAC;AAE9B,MAAM,MAAM,6BAA6B,GAAG;IACxC,8CAA8C;IAC9C,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IAC9B,4CAA4C;IAC5C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iEAAiE;IACjE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,iEAAiE;IACjE,aAAa,CAAC,EAAE,OAAO,SAAS,CAAC;IACjC,iDAAiD;IACjD,oBAAoB,CAAC,EAAE,MAAM,CAAC;IAC9B,iBAAiB,CAAC,EAAE,OAAO,CAAC;CAC/B,CAAC;AAEF,qBAAa,sBAAuB,YAAW,qBAAqB;IAYpD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAXjC,OAAO,CAAC,EAAE,CAAC,CAAY;IACvB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,MAAM,CAAS;IACvB,OAAO,CAAC,iBAAiB,CAAK;IAC9B,OAAO,CAAC,gBAAgB,CAAS;IAEjC,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA+B;IAC9D,OAAO,CAAC,iBAAiB,CAAC,CAAwB;IAClD,OAAO,CAAC,eAAe,CAAC,CAAqB;IAC7C,OAAO,CAAC,gBAAgB,CAAC,CAAqB;gBAEjB,IAAI,EAAE,6BAA6B;IAI1D,SAAS,CAAC,OAAO,EAAE,qBAAqB,GAAG,OAAO,CAAC,IAAI,CAAC;IAQ9D,aAAa,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,GAAG,MAAM,IAAI;IAKtD,oBAAoB,CAAC,OAAO,EAAE,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,GAAG,MAAM,IAAI;IAO7D,OAAO,KAAK,EAAE,GAKb;IAED,OAAO,CAAC,YAAY;YAkEN,KAAK;IAgBnB,KAAK,IAAI,IAAI;IAMb,IAAI,WAAW,IAAI,OAAO,CAEzB;CACJ"}
package/dist/index.js CHANGED
@@ -1,83 +1,74 @@
1
+ import { CNSDTOReplayStartMessageSchema, CNSDTOAppCommandSchema } from '@cnstra/devtools-dto';
2
+
1
3
  // src/index.ts
2
4
  var CNSDevToolsTransportWs = class {
3
5
  constructor(opts) {
4
6
  this.opts = opts;
5
7
  this.connecting = false;
6
8
  this.closed = false;
7
- this.buffer = [];
8
- this.hasConnectedOnce = false;
9
9
  this.reconnectAttempts = 0;
10
+ this.hasConnectedOnce = false;
11
+ this.pendingBatches = [];
10
12
  }
11
- get reconnectDelayMs() {
12
- return this.opts.reconnectDelayMs ?? 1e3;
13
+ // ─── ICNSDevToolsTransport ────────────────────────────────────────────────────
14
+ async sendBatch(message) {
15
+ if (message.items.some((i) => i.type === "topology")) {
16
+ this.lastTopologyBatch = message;
17
+ }
18
+ this.pendingBatches.push(message);
19
+ await this.flush();
13
20
  }
14
- get bufferMaxSize() {
15
- return this.opts.bufferMaxSize ?? 100;
21
+ onReplayStart(handler) {
22
+ this.onReplayHandler = handler;
23
+ return () => {
24
+ this.onReplayHandler = void 0;
25
+ };
16
26
  }
17
- get maxReconnectAttempts() {
18
- return this.opts.maxReconnectAttempts ?? Infinity;
27
+ onStimulationCommand(handler) {
28
+ this.onCommandHandler = handler;
29
+ return () => {
30
+ this.onCommandHandler = void 0;
31
+ };
32
+ }
33
+ // ─── Connection management ────────────────────────────────────────────────────
34
+ get WS() {
35
+ const impl = this.opts.webSocketImpl ?? (typeof WebSocket !== "undefined" ? WebSocket : void 0);
36
+ if (!impl) throw new Error("No WebSocket implementation. Pass webSocketImpl option.");
37
+ return impl;
19
38
  }
20
39
  ensureSocket() {
21
- const WS = this.opts.webSocketImpl || (typeof WebSocket !== "undefined" ? WebSocket : void 0);
22
- if (!WS)
23
- return Promise.reject(
24
- new Error(
25
- "WebSocket implementation is not available. Provide webSocketImpl."
26
- )
27
- );
28
- if (this.ws && this.ws.readyState === 1)
29
- return Promise.resolve();
40
+ if (this.ws?.readyState === 1) return Promise.resolve();
30
41
  if (this.connecting) {
31
42
  return new Promise((resolve) => {
32
- const check = () => {
33
- if (this.ws && this.ws.readyState === 1)
34
- return resolve();
35
- setTimeout(check, 50);
36
- };
37
- check();
43
+ const poll = () => this.ws?.readyState === 1 ? resolve() : setTimeout(poll, 50);
44
+ poll();
38
45
  });
39
46
  }
40
47
  this.connecting = true;
41
48
  return new Promise((resolve) => {
42
- const ws = new WS(this.opts.url, this.opts.protocols);
49
+ const ws = new this.WS(this.opts.url, this.opts.protocols);
43
50
  this.ws = ws;
44
51
  ws.onopen = () => {
45
- if (this.opts.consoleLogEnabled) {
46
- console.log("DevTools connected to server");
47
- }
48
52
  this.connecting = false;
49
53
  this.reconnectAttempts = 0;
50
- if (this.hasConnectedOnce && this.lastInitMessage) {
51
- if (this.opts.consoleLogEnabled) {
52
- console.log("DevTools reconnected - resending init message");
53
- }
54
- this.buffer.unshift({
55
- type: "init",
56
- payload: this.lastInitMessage
57
- });
54
+ if (this.opts.consoleLogEnabled) console.log("[DevTools] Connected");
55
+ if (this.hasConnectedOnce && this.lastTopologyBatch) {
56
+ this.pendingBatches.unshift(this.lastTopologyBatch);
58
57
  }
59
58
  this.hasConnectedOnce = true;
60
- this.flush();
61
- resolve();
59
+ this.flush().then(resolve);
62
60
  };
63
61
  ws.onclose = () => {
64
62
  this.connecting = false;
65
- if (!this.closed && this.reconnectAttempts < this.maxReconnectAttempts) {
63
+ if (!this.closed && this.reconnectAttempts < (this.opts.maxReconnectAttempts ?? Infinity)) {
66
64
  this.reconnectAttempts++;
67
65
  if (this.opts.consoleLogEnabled) {
68
- console.log(
69
- `DevTools disconnected, reconnecting... (attempt ${this.reconnectAttempts})`
70
- );
71
- }
72
- setTimeout(
73
- () => this.ensureSocket().catch(() => {
74
- }),
75
- this.reconnectDelayMs
76
- );
77
- } else if (this.reconnectAttempts >= this.maxReconnectAttempts) {
78
- if (this.opts.consoleLogEnabled) {
79
- console.log("DevTools max reconnect attempts reached");
66
+ console.log(`[DevTools] Disconnected, reconnecting (attempt ${this.reconnectAttempts})`);
80
67
  }
68
+ setTimeout(() => this.ensureSocket().catch(() => {
69
+ }), this.opts.reconnectDelayMs ?? 1e3);
70
+ } else if (this.reconnectAttempts >= (this.opts.maxReconnectAttempts ?? Infinity)) {
71
+ if (this.opts.consoleLogEnabled) console.log("[DevTools] Max reconnect attempts reached");
81
72
  }
82
73
  };
83
74
  ws.onerror = () => {
@@ -88,76 +79,41 @@ var CNSDevToolsTransportWs = class {
88
79
  };
89
80
  ws.onmessage = (ev) => {
90
81
  try {
91
- const data = typeof ev.data === "string" ? ev.data : "" + ev.data;
92
- const msg = JSON.parse(data);
93
- if (msg && msg.type === "stimulate") {
94
- const cmd = msg;
95
- const ack = {
96
- type: "stimulate-accepted",
97
- stimulationCommandId: cmd.stimulationCommandId,
98
- stimulationId: cmd.stimulationCommandId,
99
- appId: cmd.appId
100
- };
101
- try {
102
- this.ws?.send(JSON.stringify(ack));
103
- } catch {
82
+ const msg = JSON.parse(typeof ev.data === "string" ? ev.data : String(ev.data));
83
+ if (msg?.type === "replay.start") {
84
+ const result = CNSDTOReplayStartMessageSchema.safeParse(msg);
85
+ if (result.success) {
86
+ this.onReplayHandler?.(result.data);
87
+ } else if (this.opts.consoleLogEnabled) {
88
+ console.warn("[DevTools] Invalid replay.start message:", result.error.message);
89
+ }
90
+ } else if (msg?.type === "cns.stimulation.resume" || msg?.type === "cns.stimulation.launch") {
91
+ const result = CNSDTOAppCommandSchema.safeParse(msg);
92
+ if (result.success) {
93
+ this.onCommandHandler?.(result.data);
94
+ } else if (this.opts.consoleLogEnabled) {
95
+ console.warn("[DevTools] Invalid stimulation command:", result.error.message);
104
96
  }
105
- this.onStimulate?.(cmd);
106
97
  }
107
98
  } catch {
108
99
  }
109
100
  };
110
101
  });
111
102
  }
112
- async sendInitMessage(message) {
113
- this.lastInitMessage = message;
114
- this.buffer.push({ type: "init", payload: message });
115
- if (this.buffer.length >= this.bufferMaxSize) await this.flush();
116
- else void this.flush();
117
- }
118
- async sendNeuronResponseMessage(message) {
119
- this.buffer.push({ type: "response", payload: message });
120
- if (this.buffer.length >= this.bufferMaxSize) await this.flush();
121
- else void this.flush();
122
- }
123
- async sendStimulationMessage(message) {
124
- await this.ensureSocket();
125
- if (!this.ws || this.ws.readyState !== 1) return;
126
- try {
127
- this.ws.send(JSON.stringify(message));
128
- } catch {
129
- }
130
- }
131
103
  async flush() {
132
- if (this.buffer.length === 0) return;
104
+ if (this.pendingBatches.length === 0) return;
133
105
  await this.ensureSocket();
134
- if (!this.ws || this.ws.readyState !== 1) return;
135
- const items = this.buffer.splice(0, this.buffer.length);
136
- const replayResponses = items.filter((item) => item.type === "response").filter((item) => {
137
- const stimId = item.payload?.stimulationId || "";
138
- return typeof stimId === "string" && stimId.includes("-replay-");
139
- });
140
- if (replayResponses.length > 0 && this.opts.consoleLogEnabled) {
141
- console.log("[Transport] Flushing REPLAY responses batch:", {
142
- totalItems: items.length,
143
- replayResponsesCount: replayResponses.length,
144
- replayStimIds: replayResponses.slice(0, 3).map((r) => r.payload?.stimulationId),
145
- replayAppIds: replayResponses.slice(0, 3).map((r) => r.payload?.appId)
146
- });
147
- }
106
+ if (this.ws?.readyState !== 1) return;
107
+ const batches = this.pendingBatches.splice(0, this.pendingBatches.length);
108
+ if (batches.length === 0) return;
148
109
  try {
110
+ const items = batches.flatMap((b) => b.items);
111
+ if (items.length === 0) return;
149
112
  this.ws.send(JSON.stringify({ type: "batch", items }));
150
113
  } catch {
151
- this.buffer.unshift(...items);
114
+ this.pendingBatches.unshift(...batches);
152
115
  }
153
116
  }
154
- onStimulateCommand(handler) {
155
- this.onStimulate = handler;
156
- return () => {
157
- this.onStimulate = void 0;
158
- };
159
- }
160
- /** Close the connection and stop reconnecting */
161
117
  close() {
162
118
  this.closed = true;
163
119
  try {
@@ -166,14 +122,9 @@ var CNSDevToolsTransportWs = class {
166
122
  }
167
123
  this.ws = void 0;
168
124
  }
169
- /** Get connection status */
170
125
  get isConnected() {
171
126
  return this.ws?.readyState === 1;
172
127
  }
173
- /** Get buffered message count */
174
- get bufferSize() {
175
- return this.buffer.length;
176
- }
177
128
  };
178
129
 
179
130
  export { CNSDevToolsTransportWs };
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ICNSDevToolsTransport } from '@cnstra/devtools';\nimport type {\n InitMessage,\n NeuronResponseMessage,\n StimulateCommand,\n StimulateAccepted,\n StimulateRejected,\n StimulationMessage,\n} from '@cnstra/devtools-dto';\n\nexport type TWSOpts = {\n /** ws://localhost:7777 or wss://... */\n url: string;\n /** Optional protocols */\n protocols?: string | string[];\n /** Reconnect delay in ms (default 1000) */\n reconnectDelayMs?: number;\n /** Max buffered messages before force flush (default 100) */\n bufferMaxSize?: number;\n /** Optional WebSocket implementation for Node (e.g., globalThis.WebSocket or ws) */\n webSocketImpl?: typeof WebSocket;\n /** Auto-connect on first message (default true) */\n autoConnect?: boolean;\n /** Max reconnect attempts (default Infinity) */\n maxReconnectAttempts?: number;\n /** When true, print transport-level console logs */\n consoleLogEnabled?: boolean;\n};\n\ntype WSLike = WebSocket;\n\nexport class CNSDevToolsTransportWs implements ICNSDevToolsTransport {\n private ws?: WSLike;\n private connecting = false;\n private closed = false;\n private readonly buffer: Array<{\n type: 'init' | 'response';\n payload: any;\n }> = [];\n\n // Store the latest init message for reconnect\n private lastInitMessage?: InitMessage;\n private hasConnectedOnce = false;\n private reconnectAttempts = 0;\n\n constructor(private readonly opts: TWSOpts) {}\n\n private get reconnectDelayMs() {\n return this.opts.reconnectDelayMs ?? 1000;\n }\n private get bufferMaxSize() {\n return this.opts.bufferMaxSize ?? 100;\n }\n private get maxReconnectAttempts() {\n return this.opts.maxReconnectAttempts ?? Infinity;\n }\n\n private ensureSocket(): Promise<void> {\n const WS: typeof WebSocket | undefined =\n this.opts.webSocketImpl ||\n (typeof WebSocket !== 'undefined' ? WebSocket : undefined);\n if (!WS)\n return Promise.reject(\n new Error(\n 'WebSocket implementation is not available. Provide webSocketImpl.'\n )\n );\n if (this.ws && this.ws.readyState === 1)\n // OPEN = 1\n return Promise.resolve();\n if (this.connecting) {\n return new Promise(resolve => {\n const check = () => {\n if (this.ws && this.ws.readyState === 1)\n // OPEN = 1\n return resolve();\n setTimeout(check, 50);\n };\n check();\n });\n }\n this.connecting = true;\n return new Promise(resolve => {\n const ws = new WS(this.opts.url, this.opts.protocols) as WSLike;\n this.ws = ws;\n ws.onopen = () => {\n if (this.opts.consoleLogEnabled) {\n console.log('DevTools connected to server');\n }\n this.connecting = false;\n this.reconnectAttempts = 0; // Reset on successful connection\n\n // On reconnect, resend init message first\n if (this.hasConnectedOnce && this.lastInitMessage) {\n if (this.opts.consoleLogEnabled) {\n console.log('DevTools reconnected - resending init message');\n }\n this.buffer.unshift({\n type: 'init',\n payload: this.lastInitMessage,\n });\n }\n\n this.hasConnectedOnce = true;\n this.flush();\n resolve();\n };\n ws.onclose = () => {\n this.connecting = false;\n if (\n !this.closed &&\n this.reconnectAttempts < this.maxReconnectAttempts\n ) {\n this.reconnectAttempts++;\n if (this.opts.consoleLogEnabled) {\n console.log(\n `DevTools disconnected, reconnecting... (attempt ${this.reconnectAttempts})`\n );\n }\n setTimeout(\n () => this.ensureSocket().catch(() => {}),\n this.reconnectDelayMs\n );\n } else if (\n this.reconnectAttempts >= this.maxReconnectAttempts\n ) {\n if (this.opts.consoleLogEnabled) {\n console.log('DevTools max reconnect attempts reached');\n }\n }\n };\n ws.onerror = () => {\n try {\n ws.close();\n } catch {}\n };\n ws.onmessage = (ev: MessageEvent) => {\n try {\n const data =\n typeof ev.data === 'string' ? ev.data : '' + ev.data;\n const msg = JSON.parse(data);\n if (msg && msg.type === 'stimulate') {\n const cmd = msg as StimulateCommand & {\n appId?: string;\n cnsId?: string;\n };\n const ack: StimulateAccepted = {\n type: 'stimulate-accepted',\n stimulationCommandId: cmd.stimulationCommandId,\n stimulationId: cmd.stimulationCommandId,\n appId: cmd.appId,\n };\n try {\n this.ws?.send(JSON.stringify(ack));\n } catch {}\n this.onStimulate?.(cmd);\n }\n } catch {\n // ignore invalid messages\n }\n };\n });\n }\n\n async sendInitMessage(message: InitMessage): Promise<void> {\n // Store the latest init message for reconnects\n this.lastInitMessage = message;\n\n this.buffer.push({ type: 'init', payload: message });\n if (this.buffer.length >= this.bufferMaxSize) await this.flush();\n else void this.flush();\n }\n\n async sendNeuronResponseMessage(\n message: NeuronResponseMessage\n ): Promise<void> {\n this.buffer.push({ type: 'response', payload: message });\n if (this.buffer.length >= this.bufferMaxSize) await this.flush();\n else void this.flush();\n }\n\n async sendStimulationMessage(message: StimulationMessage): Promise<void> {\n await this.ensureSocket();\n if (!this.ws || this.ws.readyState !== 1) return; // OPEN = 1\n try {\n this.ws.send(JSON.stringify(message));\n } catch {\n // ignore send errors\n }\n }\n\n private async flush(): Promise<void> {\n if (this.buffer.length === 0) return;\n await this.ensureSocket();\n /* istanbul ignore next -- ts-jest source-map occasionally misattributes coverage for this guard */\n if (!this.ws || this.ws.readyState !== 1) return; // OPEN = 1\n /* istanbul ignore next -- see note above */\n const items = this.buffer.splice(0, this.buffer.length);\n\n // Check for replay responses in the batch\n /* istanbul ignore next -- see note above */\n const replayResponses = items\n .filter(item => item.type === 'response')\n .filter(item => {\n const stimId = item.payload?.stimulationId || '';\n return (\n typeof stimId === 'string' && stimId.includes('-replay-')\n );\n });\n\n if (replayResponses.length > 0 && this.opts.consoleLogEnabled) {\n console.log('[Transport] Flushing REPLAY responses batch:', {\n totalItems: items.length,\n replayResponsesCount: replayResponses.length,\n replayStimIds: replayResponses\n .slice(0, 3)\n .map(r => r.payload?.stimulationId),\n replayAppIds: replayResponses\n .slice(0, 3)\n .map(r => r.payload?.appId),\n });\n }\n\n try {\n this.ws.send(JSON.stringify({ type: 'batch', items }));\n } catch {\n // requeue if send fails\n this.buffer.unshift(...items);\n }\n }\n\n private onStimulate?: (cmd: StimulateCommand) => void;\n onStimulateCommand(handler: (cmd: StimulateCommand) => void): () => void {\n this.onStimulate = handler;\n return () => {\n this.onStimulate = undefined;\n };\n }\n\n /** Close the connection and stop reconnecting */\n close(): void {\n this.closed = true;\n try {\n this.ws?.close();\n } catch {}\n this.ws = undefined;\n }\n\n /** Get connection status */\n get isConnected(): boolean {\n return this.ws?.readyState === 1; // OPEN = 1\n }\n\n /** Get buffered message count */\n get bufferSize(): number {\n return this.buffer.length;\n }\n}\n"]}
1
+ {"version":3,"sources":["../src/index.ts"],"names":[],"mappings":"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","file":"index.js","sourcesContent":["import type { ICNSDevToolsTransport } from '@cnstra/devtools';\nimport {\n CNSDTOReplayStartMessageSchema,\n CNSDTOAppCommandSchema,\n type CNSDTOAppBatchMessage,\n} from '@cnstra/devtools-dto';\n\nexport type CNSDevToolsTransportWsOptions = {\n /** WebSocket URL, e.g. ws://localhost:3141 */\n url: string;\n protocols?: string | string[];\n /** Reconnect delay in ms (default: 1000) */\n reconnectDelayMs?: number;\n /** Max buffered batch items before force flush (default: 100) */\n batchMaxSize?: number;\n /** Provide a WebSocket implementation for Node.js (e.g. `ws`) */\n webSocketImpl?: typeof WebSocket;\n /** Max reconnect attempts (default: Infinity) */\n maxReconnectAttempts?: number;\n consoleLogEnabled?: boolean;\n};\n\nexport class CNSDevToolsTransportWs implements ICNSDevToolsTransport {\n private ws?: WebSocket;\n private connecting = false;\n private closed = false;\n private reconnectAttempts = 0;\n private hasConnectedOnce = false;\n\n private readonly pendingBatches: CNSDTOAppBatchMessage[] = [];\n private lastTopologyBatch?: CNSDTOAppBatchMessage;\n private onReplayHandler?: (cmd: any) => void;\n private onCommandHandler?: (cmd: any) => void;\n\n constructor(private readonly opts: CNSDevToolsTransportWsOptions) {}\n\n // ─── ICNSDevToolsTransport ────────────────────────────────────────────────────\n\n async sendBatch(message: CNSDTOAppBatchMessage): Promise<void> {\n if (message.items.some(i => i.type === 'topology')) {\n this.lastTopologyBatch = message;\n }\n this.pendingBatches.push(message);\n await this.flush();\n }\n\n onReplayStart(handler: (cmd: any) => void): () => void {\n this.onReplayHandler = handler;\n return () => { this.onReplayHandler = undefined; };\n }\n\n onStimulationCommand(handler: (cmd: any) => void): () => void {\n this.onCommandHandler = handler;\n return () => { this.onCommandHandler = undefined; };\n }\n\n // ─── Connection management ────────────────────────────────────────────────────\n\n private get WS(): typeof WebSocket {\n const impl = this.opts.webSocketImpl\n ?? (typeof WebSocket !== 'undefined' ? WebSocket : undefined);\n if (!impl) throw new Error('No WebSocket implementation. Pass webSocketImpl option.');\n return impl;\n }\n\n private ensureSocket(): Promise<void> {\n if (this.ws?.readyState === 1) return Promise.resolve();\n if (this.connecting) {\n return new Promise(resolve => {\n const poll = () => this.ws?.readyState === 1 ? resolve() : setTimeout(poll, 50);\n poll();\n });\n }\n this.connecting = true;\n return new Promise(resolve => {\n const ws = new this.WS(this.opts.url, this.opts.protocols) as WebSocket;\n this.ws = ws;\n\n ws.onopen = () => {\n this.connecting = false;\n this.reconnectAttempts = 0;\n if (this.opts.consoleLogEnabled) console.log('[DevTools] Connected');\n\n if (this.hasConnectedOnce && this.lastTopologyBatch) {\n this.pendingBatches.unshift(this.lastTopologyBatch);\n }\n this.hasConnectedOnce = true;\n this.flush().then(resolve);\n };\n\n ws.onclose = () => {\n this.connecting = false;\n if (!this.closed && this.reconnectAttempts < (this.opts.maxReconnectAttempts ?? Infinity)) {\n this.reconnectAttempts++;\n if (this.opts.consoleLogEnabled) {\n console.log(`[DevTools] Disconnected, reconnecting (attempt ${this.reconnectAttempts})`);\n }\n setTimeout(() => this.ensureSocket().catch(() => {}), this.opts.reconnectDelayMs ?? 1000);\n } else if (this.reconnectAttempts >= (this.opts.maxReconnectAttempts ?? Infinity)) {\n if (this.opts.consoleLogEnabled) console.log('[DevTools] Max reconnect attempts reached');\n }\n };\n\n ws.onerror = () => { try { ws.close(); } catch {} };\n\n ws.onmessage = (ev: MessageEvent) => {\n try {\n const msg = JSON.parse(typeof ev.data === 'string' ? ev.data : String(ev.data));\n if (msg?.type === 'replay.start') {\n const result = CNSDTOReplayStartMessageSchema.safeParse(msg);\n if (result.success) {\n this.onReplayHandler?.(result.data);\n } else if (this.opts.consoleLogEnabled) {\n console.warn('[DevTools] Invalid replay.start message:', result.error.message);\n }\n } else if (\n msg?.type === 'cns.stimulation.resume' ||\n msg?.type === 'cns.stimulation.launch'\n ) {\n const result = CNSDTOAppCommandSchema.safeParse(msg);\n if (result.success) {\n this.onCommandHandler?.(result.data);\n } else if (this.opts.consoleLogEnabled) {\n console.warn('[DevTools] Invalid stimulation command:', result.error.message);\n }\n }\n } catch {}\n };\n });\n }\n\n private async flush(): Promise<void> {\n if (this.pendingBatches.length === 0) return;\n await this.ensureSocket();\n if (this.ws?.readyState !== 1) return;\n\n const batches = this.pendingBatches.splice(0, this.pendingBatches.length);\n if (batches.length === 0) return;\n try {\n const items = batches.flatMap(b => b.items);\n if (items.length === 0) return;\n this.ws.send(JSON.stringify({ type: 'batch', items }));\n } catch {\n this.pendingBatches.unshift(...batches);\n }\n }\n\n close(): void {\n this.closed = true;\n try { this.ws?.close(); } catch {}\n this.ws = undefined;\n }\n\n get isConnected(): boolean {\n return this.ws?.readyState === 1;\n }\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cnstra/devtools-transport-ws",
3
- "version": "1.1.1",
3
+ "version": "1.2.0",
4
4
  "description": "WebSocket transport for CNStra DevTools",
5
5
  "keywords": [
6
6
  "devtools",
@@ -33,12 +33,13 @@
33
33
  "dist"
34
34
  ],
35
35
  "dependencies": {
36
- "@cnstra/devtools-dto": "^1.0.0"
36
+ "@cnstra/devtools-dto": "^2.0.0"
37
37
  },
38
38
  "peerDependencies": {
39
- "@cnstra/devtools": "^1.0.1"
39
+ "@cnstra/devtools": "^2.0.0"
40
40
  },
41
41
  "devDependencies": {
42
+ "@cnstra/devtools": "^2.0.0",
42
43
  "@types/jest": "^29.5.5",
43
44
  "jest": "^29.7.0",
44
45
  "jest-environment-jsdom": "^29.7.0",