@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 +62 -111
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +18 -28
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +62 -111
- package/dist/index.js.map +1 -1
- package/package.json +4 -3
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
|
-
|
|
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
|
+
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
|
-
|
|
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
|
|
35
|
-
|
|
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.
|
|
53
|
-
|
|
54
|
-
|
|
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
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
this.
|
|
105
|
-
}
|
|
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.
|
|
106
|
+
if (this.pendingBatches.length === 0) return;
|
|
135
107
|
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
|
-
}
|
|
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.
|
|
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;
|
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","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
|
|
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,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
|
-
|
|
31
|
-
private
|
|
32
|
-
private
|
|
33
|
-
private
|
|
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
|
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,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
|
-
|
|
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
|
+
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
|
-
|
|
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
|
|
33
|
-
|
|
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.
|
|
51
|
-
|
|
52
|
-
|
|
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
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
this.
|
|
103
|
-
}
|
|
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.
|
|
104
|
+
if (this.pendingBatches.length === 0) return;
|
|
133
105
|
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
|
-
}
|
|
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.
|
|
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.
|
|
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": "^
|
|
36
|
+
"@cnstra/devtools-dto": "^2.0.0"
|
|
37
37
|
},
|
|
38
38
|
"peerDependencies": {
|
|
39
|
-
"@cnstra/devtools": "^
|
|
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",
|