@cnstra/devtools-transport-ws 1.1.1 → 1.1.2
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 +49 -111
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +16 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +49 -111
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -1,85 +1,70 @@
|
|
|
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
|
-
|
|
14
|
-
|
|
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
|
-
|
|
17
|
-
|
|
23
|
+
onReplayStart(handler) {
|
|
24
|
+
this.onReplayHandler = handler;
|
|
25
|
+
return () => {
|
|
26
|
+
this.onReplayHandler = void 0;
|
|
27
|
+
};
|
|
18
28
|
}
|
|
19
|
-
|
|
20
|
-
|
|
29
|
+
// ─── Connection management ────────────────────────────────────────────────────
|
|
30
|
+
get WS() {
|
|
31
|
+
const impl = this.opts.webSocketImpl ?? (typeof WebSocket !== "undefined" ? WebSocket : void 0);
|
|
32
|
+
if (!impl) throw new Error("No WebSocket implementation. Pass webSocketImpl option.");
|
|
33
|
+
return impl;
|
|
21
34
|
}
|
|
22
35
|
ensureSocket() {
|
|
23
|
-
|
|
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();
|
|
36
|
+
if (this.ws?.readyState === 1) return Promise.resolve();
|
|
32
37
|
if (this.connecting) {
|
|
33
38
|
return new Promise((resolve) => {
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
return resolve();
|
|
37
|
-
setTimeout(check, 50);
|
|
38
|
-
};
|
|
39
|
-
check();
|
|
39
|
+
const poll = () => this.ws?.readyState === 1 ? resolve() : setTimeout(poll, 50);
|
|
40
|
+
poll();
|
|
40
41
|
});
|
|
41
42
|
}
|
|
42
43
|
this.connecting = true;
|
|
43
44
|
return new Promise((resolve) => {
|
|
44
|
-
const ws = new WS(this.opts.url, this.opts.protocols);
|
|
45
|
+
const ws = new this.WS(this.opts.url, this.opts.protocols);
|
|
45
46
|
this.ws = ws;
|
|
46
47
|
ws.onopen = () => {
|
|
47
|
-
if (this.opts.consoleLogEnabled) {
|
|
48
|
-
console.log("DevTools connected to server");
|
|
49
|
-
}
|
|
50
48
|
this.connecting = false;
|
|
51
49
|
this.reconnectAttempts = 0;
|
|
52
|
-
if (this.
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
this.buffer.unshift({
|
|
57
|
-
type: "init",
|
|
58
|
-
payload: this.lastInitMessage
|
|
59
|
-
});
|
|
50
|
+
if (this.opts.consoleLogEnabled) console.log("[DevTools] Connected");
|
|
51
|
+
if (this.hasConnectedOnce && this.lastTopologyBatch) {
|
|
52
|
+
this.pendingBatches.unshift(this.lastTopologyBatch);
|
|
60
53
|
}
|
|
61
54
|
this.hasConnectedOnce = true;
|
|
62
|
-
this.flush();
|
|
63
|
-
resolve();
|
|
55
|
+
this.flush().then(resolve);
|
|
64
56
|
};
|
|
65
57
|
ws.onclose = () => {
|
|
66
58
|
this.connecting = false;
|
|
67
|
-
if (!this.closed && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
59
|
+
if (!this.closed && this.reconnectAttempts < (this.opts.maxReconnectAttempts ?? Infinity)) {
|
|
68
60
|
this.reconnectAttempts++;
|
|
69
61
|
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");
|
|
62
|
+
console.log(`[DevTools] Disconnected, reconnecting (attempt ${this.reconnectAttempts})`);
|
|
82
63
|
}
|
|
64
|
+
setTimeout(() => this.ensureSocket().catch(() => {
|
|
65
|
+
}), this.opts.reconnectDelayMs ?? 1e3);
|
|
66
|
+
} else if (this.reconnectAttempts >= (this.opts.maxReconnectAttempts ?? Infinity)) {
|
|
67
|
+
if (this.opts.consoleLogEnabled) console.log("[DevTools] Max reconnect attempts reached");
|
|
83
68
|
}
|
|
84
69
|
};
|
|
85
70
|
ws.onerror = () => {
|
|
@@ -90,76 +75,34 @@ var CNSDevToolsTransportWs = class {
|
|
|
90
75
|
};
|
|
91
76
|
ws.onmessage = (ev) => {
|
|
92
77
|
try {
|
|
93
|
-
const
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
stimulationId: cmd.stimulationCommandId,
|
|
101
|
-
appId: cmd.appId
|
|
102
|
-
};
|
|
103
|
-
try {
|
|
104
|
-
this.ws?.send(JSON.stringify(ack));
|
|
105
|
-
} catch {
|
|
78
|
+
const msg = JSON.parse(typeof ev.data === "string" ? ev.data : String(ev.data));
|
|
79
|
+
if (msg?.type === "replay.start") {
|
|
80
|
+
const result = devtoolsDto.CNSDTOReplayStartMessageSchema.safeParse(msg);
|
|
81
|
+
if (result.success) {
|
|
82
|
+
this.onReplayHandler?.(result.data);
|
|
83
|
+
} else if (this.opts.consoleLogEnabled) {
|
|
84
|
+
console.warn("[DevTools] Invalid replay.start message:", result.error.message);
|
|
106
85
|
}
|
|
107
|
-
this.onStimulate?.(cmd);
|
|
108
86
|
}
|
|
109
87
|
} catch {
|
|
110
88
|
}
|
|
111
89
|
};
|
|
112
90
|
});
|
|
113
91
|
}
|
|
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
92
|
async flush() {
|
|
134
|
-
if (this.
|
|
93
|
+
if (this.pendingBatches.length === 0) return;
|
|
135
94
|
await this.ensureSocket();
|
|
136
|
-
if (
|
|
137
|
-
const
|
|
138
|
-
|
|
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
|
-
}
|
|
95
|
+
if (this.ws?.readyState !== 1) return;
|
|
96
|
+
const batches = this.pendingBatches.splice(0, this.pendingBatches.length);
|
|
97
|
+
if (batches.length === 0) return;
|
|
150
98
|
try {
|
|
99
|
+
const items = batches.flatMap((b) => b.items);
|
|
100
|
+
if (items.length === 0) return;
|
|
151
101
|
this.ws.send(JSON.stringify({ type: "batch", items }));
|
|
152
102
|
} catch {
|
|
153
|
-
this.
|
|
103
|
+
this.pendingBatches.unshift(...batches);
|
|
154
104
|
}
|
|
155
105
|
}
|
|
156
|
-
onStimulateCommand(handler) {
|
|
157
|
-
this.onStimulate = handler;
|
|
158
|
-
return () => {
|
|
159
|
-
this.onStimulate = void 0;
|
|
160
|
-
};
|
|
161
|
-
}
|
|
162
|
-
/** Close the connection and stop reconnecting */
|
|
163
106
|
close() {
|
|
164
107
|
this.closed = true;
|
|
165
108
|
try {
|
|
@@ -168,14 +111,9 @@ var CNSDevToolsTransportWs = class {
|
|
|
168
111
|
}
|
|
169
112
|
this.ws = void 0;
|
|
170
113
|
}
|
|
171
|
-
/** Get connection status */
|
|
172
114
|
get isConnected() {
|
|
173
115
|
return this.ws?.readyState === 1;
|
|
174
116
|
}
|
|
175
|
-
/** Get buffered message count */
|
|
176
|
-
get bufferSize() {
|
|
177
|
-
return this.buffer.length;
|
|
178
|
-
}
|
|
179
117
|
};
|
|
180
118
|
|
|
181
119
|
exports.CNSDevToolsTransportWs = CNSDevToolsTransportWs;
|
package/dist/index.cjs.map
CHANGED
|
@@ -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"],"mappings":"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","file":"index.cjs","sourcesContent":["import type { ICNSDevToolsTransport } from '@cnstra/devtools';\nimport { CNSDTOReplayStartMessageSchema, type CNSDTOAppBatchMessage } 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\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 // ─── 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 }\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
|
|
3
|
-
export type
|
|
4
|
-
/** ws://localhost:
|
|
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
|
|
11
|
-
|
|
12
|
-
/**
|
|
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
|
-
/**
|
|
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,18 @@ 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
|
-
|
|
31
|
-
private
|
|
32
|
-
private
|
|
33
|
-
private
|
|
23
|
+
private hasConnectedOnce;
|
|
24
|
+
private readonly pendingBatches;
|
|
25
|
+
private lastTopologyBatch?;
|
|
26
|
+
private onReplayHandler?;
|
|
27
|
+
constructor(opts: CNSDevToolsTransportWsOptions);
|
|
28
|
+
sendBatch(message: CNSDTOAppBatchMessage): Promise<void>;
|
|
29
|
+
onReplayStart(handler: (cmd: any) => void): () => void;
|
|
30
|
+
private get WS();
|
|
34
31
|
private ensureSocket;
|
|
35
|
-
sendInitMessage(message: InitMessage): Promise<void>;
|
|
36
|
-
sendNeuronResponseMessage(message: NeuronResponseMessage): Promise<void>;
|
|
37
|
-
sendStimulationMessage(message: StimulationMessage): Promise<void>;
|
|
38
32
|
private flush;
|
|
39
|
-
private onStimulate?;
|
|
40
|
-
onStimulateCommand(handler: (cmd: StimulateCommand) => void): () => void;
|
|
41
|
-
/** Close the connection and stop reconnecting */
|
|
42
33
|
close(): void;
|
|
43
|
-
/** Get connection status */
|
|
44
34
|
get isConnected(): boolean;
|
|
45
|
-
/** Get buffered message count */
|
|
46
|
-
get bufferSize(): number;
|
|
47
35
|
}
|
|
48
36
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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,EAAkC,KAAK,qBAAqB,EAAE,MAAM,sBAAsB,CAAC;AAElG,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;IAWpD,OAAO,CAAC,QAAQ,CAAC,IAAI;IAVjC,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;gBAEhB,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;IAOtD,OAAO,KAAK,EAAE,GAKb;IAED,OAAO,CAAC,YAAY;YAwDN,KAAK;IAgBnB,KAAK,IAAI,IAAI;IAMb,IAAI,WAAW,IAAI,OAAO,CAEzB;CACJ"}
|
package/dist/index.js
CHANGED
|
@@ -1,83 +1,68 @@
|
|
|
1
|
+
import { CNSDTOReplayStartMessageSchema } 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
|
-
|
|
12
|
-
|
|
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
|
-
|
|
15
|
-
|
|
21
|
+
onReplayStart(handler) {
|
|
22
|
+
this.onReplayHandler = handler;
|
|
23
|
+
return () => {
|
|
24
|
+
this.onReplayHandler = void 0;
|
|
25
|
+
};
|
|
16
26
|
}
|
|
17
|
-
|
|
18
|
-
|
|
27
|
+
// ─── Connection management ────────────────────────────────────────────────────
|
|
28
|
+
get WS() {
|
|
29
|
+
const impl = this.opts.webSocketImpl ?? (typeof WebSocket !== "undefined" ? WebSocket : void 0);
|
|
30
|
+
if (!impl) throw new Error("No WebSocket implementation. Pass webSocketImpl option.");
|
|
31
|
+
return impl;
|
|
19
32
|
}
|
|
20
33
|
ensureSocket() {
|
|
21
|
-
|
|
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();
|
|
34
|
+
if (this.ws?.readyState === 1) return Promise.resolve();
|
|
30
35
|
if (this.connecting) {
|
|
31
36
|
return new Promise((resolve) => {
|
|
32
|
-
const
|
|
33
|
-
|
|
34
|
-
return resolve();
|
|
35
|
-
setTimeout(check, 50);
|
|
36
|
-
};
|
|
37
|
-
check();
|
|
37
|
+
const poll = () => this.ws?.readyState === 1 ? resolve() : setTimeout(poll, 50);
|
|
38
|
+
poll();
|
|
38
39
|
});
|
|
39
40
|
}
|
|
40
41
|
this.connecting = true;
|
|
41
42
|
return new Promise((resolve) => {
|
|
42
|
-
const ws = new WS(this.opts.url, this.opts.protocols);
|
|
43
|
+
const ws = new this.WS(this.opts.url, this.opts.protocols);
|
|
43
44
|
this.ws = ws;
|
|
44
45
|
ws.onopen = () => {
|
|
45
|
-
if (this.opts.consoleLogEnabled) {
|
|
46
|
-
console.log("DevTools connected to server");
|
|
47
|
-
}
|
|
48
46
|
this.connecting = false;
|
|
49
47
|
this.reconnectAttempts = 0;
|
|
50
|
-
if (this.
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
this.buffer.unshift({
|
|
55
|
-
type: "init",
|
|
56
|
-
payload: this.lastInitMessage
|
|
57
|
-
});
|
|
48
|
+
if (this.opts.consoleLogEnabled) console.log("[DevTools] Connected");
|
|
49
|
+
if (this.hasConnectedOnce && this.lastTopologyBatch) {
|
|
50
|
+
this.pendingBatches.unshift(this.lastTopologyBatch);
|
|
58
51
|
}
|
|
59
52
|
this.hasConnectedOnce = true;
|
|
60
|
-
this.flush();
|
|
61
|
-
resolve();
|
|
53
|
+
this.flush().then(resolve);
|
|
62
54
|
};
|
|
63
55
|
ws.onclose = () => {
|
|
64
56
|
this.connecting = false;
|
|
65
|
-
if (!this.closed && this.reconnectAttempts < this.maxReconnectAttempts) {
|
|
57
|
+
if (!this.closed && this.reconnectAttempts < (this.opts.maxReconnectAttempts ?? Infinity)) {
|
|
66
58
|
this.reconnectAttempts++;
|
|
67
59
|
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");
|
|
60
|
+
console.log(`[DevTools] Disconnected, reconnecting (attempt ${this.reconnectAttempts})`);
|
|
80
61
|
}
|
|
62
|
+
setTimeout(() => this.ensureSocket().catch(() => {
|
|
63
|
+
}), this.opts.reconnectDelayMs ?? 1e3);
|
|
64
|
+
} else if (this.reconnectAttempts >= (this.opts.maxReconnectAttempts ?? Infinity)) {
|
|
65
|
+
if (this.opts.consoleLogEnabled) console.log("[DevTools] Max reconnect attempts reached");
|
|
81
66
|
}
|
|
82
67
|
};
|
|
83
68
|
ws.onerror = () => {
|
|
@@ -88,76 +73,34 @@ var CNSDevToolsTransportWs = class {
|
|
|
88
73
|
};
|
|
89
74
|
ws.onmessage = (ev) => {
|
|
90
75
|
try {
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
stimulationId: cmd.stimulationCommandId,
|
|
99
|
-
appId: cmd.appId
|
|
100
|
-
};
|
|
101
|
-
try {
|
|
102
|
-
this.ws?.send(JSON.stringify(ack));
|
|
103
|
-
} catch {
|
|
76
|
+
const msg = JSON.parse(typeof ev.data === "string" ? ev.data : String(ev.data));
|
|
77
|
+
if (msg?.type === "replay.start") {
|
|
78
|
+
const result = CNSDTOReplayStartMessageSchema.safeParse(msg);
|
|
79
|
+
if (result.success) {
|
|
80
|
+
this.onReplayHandler?.(result.data);
|
|
81
|
+
} else if (this.opts.consoleLogEnabled) {
|
|
82
|
+
console.warn("[DevTools] Invalid replay.start message:", result.error.message);
|
|
104
83
|
}
|
|
105
|
-
this.onStimulate?.(cmd);
|
|
106
84
|
}
|
|
107
85
|
} catch {
|
|
108
86
|
}
|
|
109
87
|
};
|
|
110
88
|
});
|
|
111
89
|
}
|
|
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
90
|
async flush() {
|
|
132
|
-
if (this.
|
|
91
|
+
if (this.pendingBatches.length === 0) return;
|
|
133
92
|
await this.ensureSocket();
|
|
134
|
-
if (
|
|
135
|
-
const
|
|
136
|
-
|
|
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
|
-
}
|
|
93
|
+
if (this.ws?.readyState !== 1) return;
|
|
94
|
+
const batches = this.pendingBatches.splice(0, this.pendingBatches.length);
|
|
95
|
+
if (batches.length === 0) return;
|
|
148
96
|
try {
|
|
97
|
+
const items = batches.flatMap((b) => b.items);
|
|
98
|
+
if (items.length === 0) return;
|
|
149
99
|
this.ws.send(JSON.stringify({ type: "batch", items }));
|
|
150
100
|
} catch {
|
|
151
|
-
this.
|
|
101
|
+
this.pendingBatches.unshift(...batches);
|
|
152
102
|
}
|
|
153
103
|
}
|
|
154
|
-
onStimulateCommand(handler) {
|
|
155
|
-
this.onStimulate = handler;
|
|
156
|
-
return () => {
|
|
157
|
-
this.onStimulate = void 0;
|
|
158
|
-
};
|
|
159
|
-
}
|
|
160
|
-
/** Close the connection and stop reconnecting */
|
|
161
104
|
close() {
|
|
162
105
|
this.closed = true;
|
|
163
106
|
try {
|
|
@@ -166,14 +109,9 @@ var CNSDevToolsTransportWs = class {
|
|
|
166
109
|
}
|
|
167
110
|
this.ws = void 0;
|
|
168
111
|
}
|
|
169
|
-
/** Get connection status */
|
|
170
112
|
get isConnected() {
|
|
171
113
|
return this.ws?.readyState === 1;
|
|
172
114
|
}
|
|
173
|
-
/** Get buffered message count */
|
|
174
|
-
get bufferSize() {
|
|
175
|
-
return this.buffer.length;
|
|
176
|
-
}
|
|
177
115
|
};
|
|
178
116
|
|
|
179
117
|
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 { CNSDTOReplayStartMessageSchema, type CNSDTOAppBatchMessage } 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\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 // ─── 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 }\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"]}
|