@trops/dash-core 0.1.162 → 0.1.164
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/electron/index.js +381 -60
- package/dist/electron/index.js.map +1 -1
- package/dist/index.esm.js +23 -0
- package/dist/index.esm.js.map +1 -1
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/electron/index.js
CHANGED
|
@@ -44949,6 +44949,10 @@ var ws = WebSocket$1;
|
|
|
44949
44949
|
* - pendingConnects Map for in-flight deduplication
|
|
44950
44950
|
* - Status constants matching MCP pattern
|
|
44951
44951
|
*
|
|
44952
|
+
* Features:
|
|
44953
|
+
* - Auto-reconnect with exponential backoff (1s → 2s → 4s → 8s → 16s, max 30s, 5 retries)
|
|
44954
|
+
* - Heartbeat ping/pong keepalive (30s interval, 10s pong timeout)
|
|
44955
|
+
*
|
|
44952
44956
|
* Uses the `ws` package (installed in dash-electron) for WebSocket clients.
|
|
44953
44957
|
* Multiple widgets referencing the same provider share a single socket.
|
|
44954
44958
|
*/
|
|
@@ -44959,7 +44963,10 @@ const WebSocket = ws;
|
|
|
44959
44963
|
* Active WebSocket connections
|
|
44960
44964
|
* Map<string, { socket: WebSocket, status: string, config: object,
|
|
44961
44965
|
* consumers: Set<number>, messageCount: number,
|
|
44962
|
-
* connectedAt: number|null, lastMessageAt: number|null
|
|
44966
|
+
* connectedAt: number|null, lastMessageAt: number|null,
|
|
44967
|
+
* retryCount: number, retryTimer: NodeJS.Timeout|null,
|
|
44968
|
+
* heartbeatTimer: NodeJS.Timeout|null, pongTimer: NodeJS.Timeout|null,
|
|
44969
|
+
* intentionalClose: boolean }>
|
|
44963
44970
|
*/
|
|
44964
44971
|
const activeConnections = new Map();
|
|
44965
44972
|
|
|
@@ -44981,6 +44988,24 @@ const STATUS = {
|
|
|
44981
44988
|
ERROR: "error",
|
|
44982
44989
|
};
|
|
44983
44990
|
|
|
44991
|
+
/**
|
|
44992
|
+
* Reconnect configuration
|
|
44993
|
+
*/
|
|
44994
|
+
const RECONNECT = {
|
|
44995
|
+
BASE_DELAY: 1000, // 1 second
|
|
44996
|
+
MULTIPLIER: 2,
|
|
44997
|
+
MAX_DELAY: 30000, // 30 seconds
|
|
44998
|
+
MAX_RETRIES: 5,
|
|
44999
|
+
};
|
|
45000
|
+
|
|
45001
|
+
/**
|
|
45002
|
+
* Heartbeat configuration
|
|
45003
|
+
*/
|
|
45004
|
+
const HEARTBEAT = {
|
|
45005
|
+
PING_INTERVAL: 30000, // 30 seconds
|
|
45006
|
+
PONG_TIMEOUT: 10000, // 10 seconds
|
|
45007
|
+
};
|
|
45008
|
+
|
|
44984
45009
|
/**
|
|
44985
45010
|
* Interpolate {{fieldName}} placeholders in a string with credential values.
|
|
44986
45011
|
* Reuses the same pattern as mcpController for URL and header templates.
|
|
@@ -45001,7 +45026,7 @@ function interpolate(template, credentials) {
|
|
|
45001
45026
|
*
|
|
45002
45027
|
* @param {string} providerName - The provider whose status changed
|
|
45003
45028
|
* @param {string} status - New status value
|
|
45004
|
-
* @param {object} extra - Additional fields (error, etc.)
|
|
45029
|
+
* @param {object} extra - Additional fields (error, retryCount, retryIn, etc.)
|
|
45005
45030
|
*/
|
|
45006
45031
|
function broadcastStatusChange(providerName, status, extra = {}) {
|
|
45007
45032
|
const { WS_STATUS_CHANGE } = webSocketEvents$1;
|
|
@@ -45037,6 +45062,320 @@ function broadcastMessage(providerName, data) {
|
|
|
45037
45062
|
}
|
|
45038
45063
|
}
|
|
45039
45064
|
|
|
45065
|
+
/**
|
|
45066
|
+
* Calculate the backoff delay for a given retry attempt.
|
|
45067
|
+
*
|
|
45068
|
+
* @param {number} retryCount - Current retry attempt (0-based)
|
|
45069
|
+
* @returns {number} Delay in milliseconds
|
|
45070
|
+
*/
|
|
45071
|
+
function getBackoffDelay(retryCount) {
|
|
45072
|
+
const delay =
|
|
45073
|
+
RECONNECT.BASE_DELAY * Math.pow(RECONNECT.MULTIPLIER, retryCount);
|
|
45074
|
+
return Math.min(delay, RECONNECT.MAX_DELAY);
|
|
45075
|
+
}
|
|
45076
|
+
|
|
45077
|
+
/**
|
|
45078
|
+
* Clear heartbeat and pong timers for a connection.
|
|
45079
|
+
*
|
|
45080
|
+
* @param {object} conn - The connection object from activeConnections
|
|
45081
|
+
*/
|
|
45082
|
+
function clearHeartbeatTimers(conn) {
|
|
45083
|
+
if (conn.heartbeatTimer) {
|
|
45084
|
+
clearInterval(conn.heartbeatTimer);
|
|
45085
|
+
conn.heartbeatTimer = null;
|
|
45086
|
+
}
|
|
45087
|
+
if (conn.pongTimer) {
|
|
45088
|
+
clearTimeout(conn.pongTimer);
|
|
45089
|
+
conn.pongTimer = null;
|
|
45090
|
+
}
|
|
45091
|
+
}
|
|
45092
|
+
|
|
45093
|
+
/**
|
|
45094
|
+
* Clear the reconnect timer for a connection.
|
|
45095
|
+
*
|
|
45096
|
+
* @param {object} conn - The connection object from activeConnections
|
|
45097
|
+
*/
|
|
45098
|
+
function clearReconnectTimer(conn) {
|
|
45099
|
+
if (conn.retryTimer) {
|
|
45100
|
+
clearTimeout(conn.retryTimer);
|
|
45101
|
+
conn.retryTimer = null;
|
|
45102
|
+
}
|
|
45103
|
+
}
|
|
45104
|
+
|
|
45105
|
+
/**
|
|
45106
|
+
* Start heartbeat ping/pong for an active connection.
|
|
45107
|
+
* Sends a WebSocket ping frame every HEARTBEAT.PING_INTERVAL ms.
|
|
45108
|
+
* If no pong is received within HEARTBEAT.PONG_TIMEOUT ms, the connection
|
|
45109
|
+
* is considered stale and a reconnect is triggered.
|
|
45110
|
+
*
|
|
45111
|
+
* @param {string} providerName - The provider name
|
|
45112
|
+
*/
|
|
45113
|
+
function startHeartbeat(providerName) {
|
|
45114
|
+
const conn = activeConnections.get(providerName);
|
|
45115
|
+
if (!conn || !conn.socket) return;
|
|
45116
|
+
|
|
45117
|
+
// Clear any existing heartbeat timers
|
|
45118
|
+
clearHeartbeatTimers(conn);
|
|
45119
|
+
|
|
45120
|
+
conn.heartbeatTimer = setInterval(() => {
|
|
45121
|
+
const current = activeConnections.get(providerName);
|
|
45122
|
+
if (
|
|
45123
|
+
!current ||
|
|
45124
|
+
!current.socket ||
|
|
45125
|
+
current.socket.readyState !== WebSocket.OPEN
|
|
45126
|
+
) {
|
|
45127
|
+
clearHeartbeatTimers(current || conn);
|
|
45128
|
+
return;
|
|
45129
|
+
}
|
|
45130
|
+
|
|
45131
|
+
// Send ping
|
|
45132
|
+
try {
|
|
45133
|
+
current.socket.ping();
|
|
45134
|
+
} catch {
|
|
45135
|
+
// Socket errored during ping — will be caught by error/close handlers
|
|
45136
|
+
return;
|
|
45137
|
+
}
|
|
45138
|
+
|
|
45139
|
+
// Start pong timeout
|
|
45140
|
+
current.pongTimer = setTimeout(() => {
|
|
45141
|
+
const staleConn = activeConnections.get(providerName);
|
|
45142
|
+
if (!staleConn || staleConn.intentionalClose) return;
|
|
45143
|
+
|
|
45144
|
+
console.log(
|
|
45145
|
+
`[webSocketController] Heartbeat timeout (no pong): ${providerName}`,
|
|
45146
|
+
);
|
|
45147
|
+
|
|
45148
|
+
// Clear heartbeat before triggering reconnect
|
|
45149
|
+
clearHeartbeatTimers(staleConn);
|
|
45150
|
+
|
|
45151
|
+
// Close the stale socket to trigger the close handler → reconnect
|
|
45152
|
+
if (staleConn.socket) {
|
|
45153
|
+
try {
|
|
45154
|
+
staleConn.socket.terminate();
|
|
45155
|
+
} catch {
|
|
45156
|
+
/* already closing */
|
|
45157
|
+
}
|
|
45158
|
+
}
|
|
45159
|
+
}, HEARTBEAT.PONG_TIMEOUT);
|
|
45160
|
+
}, HEARTBEAT.PING_INTERVAL);
|
|
45161
|
+
}
|
|
45162
|
+
|
|
45163
|
+
/**
|
|
45164
|
+
* Attempt to reconnect a provider with exponential backoff.
|
|
45165
|
+
* Called from the socket close handler when the close was unexpected.
|
|
45166
|
+
*
|
|
45167
|
+
* @param {string} providerName - The provider to reconnect
|
|
45168
|
+
*/
|
|
45169
|
+
function scheduleReconnect(providerName) {
|
|
45170
|
+
const conn = activeConnections.get(providerName);
|
|
45171
|
+
if (!conn) return;
|
|
45172
|
+
|
|
45173
|
+
if (conn.retryCount >= RECONNECT.MAX_RETRIES) {
|
|
45174
|
+
console.log(
|
|
45175
|
+
`[webSocketController] Max retries (${RECONNECT.MAX_RETRIES}) reached for ${providerName}`,
|
|
45176
|
+
);
|
|
45177
|
+
activeConnections.set(providerName, {
|
|
45178
|
+
...conn,
|
|
45179
|
+
socket: null,
|
|
45180
|
+
status: STATUS.ERROR,
|
|
45181
|
+
error: `Reconnect failed after ${RECONNECT.MAX_RETRIES} attempts`,
|
|
45182
|
+
});
|
|
45183
|
+
broadcastStatusChange(providerName, STATUS.ERROR, {
|
|
45184
|
+
error: `Reconnect failed after ${RECONNECT.MAX_RETRIES} attempts`,
|
|
45185
|
+
});
|
|
45186
|
+
return;
|
|
45187
|
+
}
|
|
45188
|
+
|
|
45189
|
+
const delay = getBackoffDelay(conn.retryCount);
|
|
45190
|
+
console.log(
|
|
45191
|
+
`[webSocketController] Reconnecting ${providerName} in ${delay}ms (attempt ${conn.retryCount + 1}/${RECONNECT.MAX_RETRIES})`,
|
|
45192
|
+
);
|
|
45193
|
+
|
|
45194
|
+
// Broadcast connecting status with retry info
|
|
45195
|
+
broadcastStatusChange(providerName, STATUS.CONNECTING, {
|
|
45196
|
+
retryCount: conn.retryCount + 1,
|
|
45197
|
+
retryIn: delay,
|
|
45198
|
+
});
|
|
45199
|
+
|
|
45200
|
+
conn.retryTimer = setTimeout(async () => {
|
|
45201
|
+
const current = activeConnections.get(providerName);
|
|
45202
|
+
if (!current || current.intentionalClose) return;
|
|
45203
|
+
|
|
45204
|
+
// Update retry count before attempting
|
|
45205
|
+
current.retryCount++;
|
|
45206
|
+
|
|
45207
|
+
try {
|
|
45208
|
+
// Use the stored config to reconnect
|
|
45209
|
+
const config = current.config;
|
|
45210
|
+
const url = config.credentials
|
|
45211
|
+
? interpolate(config.url, config.credentials)
|
|
45212
|
+
: config.url;
|
|
45213
|
+
|
|
45214
|
+
const wsOptions = {};
|
|
45215
|
+
if (config.headers) {
|
|
45216
|
+
const headers = {};
|
|
45217
|
+
if (config.credentials) {
|
|
45218
|
+
Object.entries(config.headers).forEach(([headerName, template]) => {
|
|
45219
|
+
headers[headerName] = interpolate(template, config.credentials);
|
|
45220
|
+
});
|
|
45221
|
+
} else {
|
|
45222
|
+
Object.assign(headers, config.headers);
|
|
45223
|
+
}
|
|
45224
|
+
wsOptions.headers = headers;
|
|
45225
|
+
}
|
|
45226
|
+
|
|
45227
|
+
console.log(
|
|
45228
|
+
`[webSocketController] Reconnect attempt ${current.retryCount}/${RECONNECT.MAX_RETRIES}: ${providerName}`,
|
|
45229
|
+
);
|
|
45230
|
+
|
|
45231
|
+
const socket = new WebSocket(url, config.subprotocols || [], wsOptions);
|
|
45232
|
+
|
|
45233
|
+
// Wait for open or error
|
|
45234
|
+
await new Promise((resolve, reject) => {
|
|
45235
|
+
const onOpen = () => {
|
|
45236
|
+
socket.removeListener("error", onError);
|
|
45237
|
+
resolve();
|
|
45238
|
+
};
|
|
45239
|
+
const onError = (err) => {
|
|
45240
|
+
socket.removeListener("open", onOpen);
|
|
45241
|
+
reject(err);
|
|
45242
|
+
};
|
|
45243
|
+
socket.once("open", onOpen);
|
|
45244
|
+
socket.once("error", onError);
|
|
45245
|
+
});
|
|
45246
|
+
|
|
45247
|
+
// Reconnect succeeded — reset retry count, update connection
|
|
45248
|
+
const reconnected = activeConnections.get(providerName);
|
|
45249
|
+
if (!reconnected || reconnected.intentionalClose) {
|
|
45250
|
+
// Was disconnected intentionally during reconnect
|
|
45251
|
+
socket.close(1000, "Client disconnect");
|
|
45252
|
+
return;
|
|
45253
|
+
}
|
|
45254
|
+
|
|
45255
|
+
activeConnections.set(providerName, {
|
|
45256
|
+
...reconnected,
|
|
45257
|
+
socket,
|
|
45258
|
+
status: STATUS.CONNECTED,
|
|
45259
|
+
connectedAt: Date.now(),
|
|
45260
|
+
retryCount: 0,
|
|
45261
|
+
retryTimer: null,
|
|
45262
|
+
error: undefined,
|
|
45263
|
+
});
|
|
45264
|
+
|
|
45265
|
+
// Wire up handlers on the new socket
|
|
45266
|
+
wireSocketHandlers(providerName, socket);
|
|
45267
|
+
|
|
45268
|
+
// Start heartbeat on the new socket
|
|
45269
|
+
startHeartbeat(providerName);
|
|
45270
|
+
|
|
45271
|
+
broadcastStatusChange(providerName, STATUS.CONNECTED);
|
|
45272
|
+
|
|
45273
|
+
console.log(
|
|
45274
|
+
`[webSocketController] Reconnected: ${providerName} (after ${current.retryCount} attempt(s))`,
|
|
45275
|
+
);
|
|
45276
|
+
} catch (err) {
|
|
45277
|
+
console.error(
|
|
45278
|
+
`[webSocketController] Reconnect attempt ${current.retryCount} failed for ${providerName}:`,
|
|
45279
|
+
err.message,
|
|
45280
|
+
);
|
|
45281
|
+
|
|
45282
|
+
// Schedule next retry
|
|
45283
|
+
scheduleReconnect(providerName);
|
|
45284
|
+
}
|
|
45285
|
+
}, delay);
|
|
45286
|
+
}
|
|
45287
|
+
|
|
45288
|
+
/**
|
|
45289
|
+
* Wire up message, close, error, and pong handlers on a WebSocket instance.
|
|
45290
|
+
* Extracted so both initial connect and reconnect use the same handlers.
|
|
45291
|
+
*
|
|
45292
|
+
* @param {string} providerName - The provider name
|
|
45293
|
+
* @param {WebSocket} socket - The WebSocket instance
|
|
45294
|
+
*/
|
|
45295
|
+
function wireSocketHandlers(providerName, socket) {
|
|
45296
|
+
// Message handler
|
|
45297
|
+
socket.on("message", (data) => {
|
|
45298
|
+
const conn = activeConnections.get(providerName);
|
|
45299
|
+
if (conn) {
|
|
45300
|
+
conn.messageCount++;
|
|
45301
|
+
conn.lastMessageAt = Date.now();
|
|
45302
|
+
}
|
|
45303
|
+
|
|
45304
|
+
let parsed;
|
|
45305
|
+
try {
|
|
45306
|
+
parsed = JSON.parse(data.toString());
|
|
45307
|
+
} catch {
|
|
45308
|
+
parsed = data.toString();
|
|
45309
|
+
}
|
|
45310
|
+
|
|
45311
|
+
broadcastMessage(providerName, parsed);
|
|
45312
|
+
});
|
|
45313
|
+
|
|
45314
|
+
// Close handler — triggers auto-reconnect for unexpected closes
|
|
45315
|
+
socket.on("close", (code, reason) => {
|
|
45316
|
+
console.log(
|
|
45317
|
+
`[webSocketController] Connection closed: ${providerName} (code: ${code})`,
|
|
45318
|
+
);
|
|
45319
|
+
const conn = activeConnections.get(providerName);
|
|
45320
|
+
if (!conn || conn.socket !== socket) return;
|
|
45321
|
+
|
|
45322
|
+
// Clean up heartbeat timers
|
|
45323
|
+
clearHeartbeatTimers(conn);
|
|
45324
|
+
|
|
45325
|
+
// Update status
|
|
45326
|
+
activeConnections.set(providerName, {
|
|
45327
|
+
...conn,
|
|
45328
|
+
socket: null,
|
|
45329
|
+
status: STATUS.DISCONNECTED,
|
|
45330
|
+
});
|
|
45331
|
+
|
|
45332
|
+
// Normal close (code 1000) or intentional disconnect — don't reconnect
|
|
45333
|
+
if (conn.intentionalClose || code === 1000) {
|
|
45334
|
+
broadcastStatusChange(providerName, STATUS.DISCONNECTED, {
|
|
45335
|
+
code,
|
|
45336
|
+
reason: reason?.toString(),
|
|
45337
|
+
});
|
|
45338
|
+
return;
|
|
45339
|
+
}
|
|
45340
|
+
|
|
45341
|
+
// Unexpected close — attempt auto-reconnect
|
|
45342
|
+
broadcastStatusChange(providerName, STATUS.DISCONNECTED, {
|
|
45343
|
+
code,
|
|
45344
|
+
reason: reason?.toString(),
|
|
45345
|
+
reconnecting: true,
|
|
45346
|
+
});
|
|
45347
|
+
scheduleReconnect(providerName);
|
|
45348
|
+
});
|
|
45349
|
+
|
|
45350
|
+
// Error handler
|
|
45351
|
+
socket.on("error", (err) => {
|
|
45352
|
+
console.error(
|
|
45353
|
+
`[webSocketController] Socket error for ${providerName}:`,
|
|
45354
|
+
err.message,
|
|
45355
|
+
);
|
|
45356
|
+
const conn = activeConnections.get(providerName);
|
|
45357
|
+
if (conn && conn.socket === socket) {
|
|
45358
|
+
activeConnections.set(providerName, {
|
|
45359
|
+
...conn,
|
|
45360
|
+
status: STATUS.ERROR,
|
|
45361
|
+
error: err.message,
|
|
45362
|
+
});
|
|
45363
|
+
broadcastStatusChange(providerName, STATUS.ERROR, {
|
|
45364
|
+
error: err.message,
|
|
45365
|
+
});
|
|
45366
|
+
}
|
|
45367
|
+
});
|
|
45368
|
+
|
|
45369
|
+
// Pong handler — clear the pong timeout when we receive a pong
|
|
45370
|
+
socket.on("pong", () => {
|
|
45371
|
+
const conn = activeConnections.get(providerName);
|
|
45372
|
+
if (conn && conn.pongTimer) {
|
|
45373
|
+
clearTimeout(conn.pongTimer);
|
|
45374
|
+
conn.pongTimer = null;
|
|
45375
|
+
}
|
|
45376
|
+
});
|
|
45377
|
+
}
|
|
45378
|
+
|
|
45040
45379
|
const webSocketController$1 = {
|
|
45041
45380
|
/**
|
|
45042
45381
|
* connect
|
|
@@ -45076,8 +45415,11 @@ const webSocketController$1 = {
|
|
|
45076
45415
|
// 3. Fresh connect — wrap in a promise and track it
|
|
45077
45416
|
const connectPromise = (async () => {
|
|
45078
45417
|
try {
|
|
45079
|
-
// Clean up stale/error state
|
|
45418
|
+
// Clean up stale/error state (including any pending reconnect timers)
|
|
45080
45419
|
if (activeConnections.has(providerName)) {
|
|
45420
|
+
const stale = activeConnections.get(providerName);
|
|
45421
|
+
clearReconnectTimer(stale);
|
|
45422
|
+
clearHeartbeatTimers(stale);
|
|
45081
45423
|
await webSocketController$1.disconnect(win, providerName);
|
|
45082
45424
|
}
|
|
45083
45425
|
|
|
@@ -45120,6 +45462,11 @@ const webSocketController$1 = {
|
|
|
45120
45462
|
messageCount: 0,
|
|
45121
45463
|
connectedAt: null,
|
|
45122
45464
|
lastMessageAt: null,
|
|
45465
|
+
retryCount: 0,
|
|
45466
|
+
retryTimer: null,
|
|
45467
|
+
heartbeatTimer: null,
|
|
45468
|
+
pongTimer: null,
|
|
45469
|
+
intentionalClose: false,
|
|
45123
45470
|
});
|
|
45124
45471
|
broadcastStatusChange(providerName, STATUS.CONNECTING);
|
|
45125
45472
|
|
|
@@ -45152,64 +45499,18 @@ const webSocketController$1 = {
|
|
|
45152
45499
|
messageCount: 0,
|
|
45153
45500
|
connectedAt: Date.now(),
|
|
45154
45501
|
lastMessageAt: null,
|
|
45502
|
+
retryCount: 0,
|
|
45503
|
+
retryTimer: null,
|
|
45504
|
+
heartbeatTimer: null,
|
|
45505
|
+
pongTimer: null,
|
|
45506
|
+
intentionalClose: false,
|
|
45155
45507
|
});
|
|
45156
45508
|
|
|
45157
|
-
// Wire up
|
|
45158
|
-
|
|
45159
|
-
const conn = activeConnections.get(providerName);
|
|
45160
|
-
if (conn) {
|
|
45161
|
-
conn.messageCount++;
|
|
45162
|
-
conn.lastMessageAt = Date.now();
|
|
45163
|
-
}
|
|
45164
|
-
|
|
45165
|
-
// Parse if JSON, otherwise pass as string
|
|
45166
|
-
let parsed;
|
|
45167
|
-
try {
|
|
45168
|
-
parsed = JSON.parse(data.toString());
|
|
45169
|
-
} catch {
|
|
45170
|
-
parsed = data.toString();
|
|
45171
|
-
}
|
|
45172
|
-
|
|
45173
|
-
broadcastMessage(providerName, parsed);
|
|
45174
|
-
});
|
|
45175
|
-
|
|
45176
|
-
// Wire up close handler
|
|
45177
|
-
socket.on("close", (code, reason) => {
|
|
45178
|
-
console.log(
|
|
45179
|
-
`[webSocketController] Connection closed: ${providerName} (code: ${code})`,
|
|
45180
|
-
);
|
|
45181
|
-
const conn = activeConnections.get(providerName);
|
|
45182
|
-
if (conn && conn.socket === socket) {
|
|
45183
|
-
activeConnections.set(providerName, {
|
|
45184
|
-
...conn,
|
|
45185
|
-
socket: null,
|
|
45186
|
-
status: STATUS.DISCONNECTED,
|
|
45187
|
-
});
|
|
45188
|
-
broadcastStatusChange(providerName, STATUS.DISCONNECTED, {
|
|
45189
|
-
code,
|
|
45190
|
-
reason: reason?.toString(),
|
|
45191
|
-
});
|
|
45192
|
-
}
|
|
45193
|
-
});
|
|
45509
|
+
// Wire up socket event handlers
|
|
45510
|
+
wireSocketHandlers(providerName, socket);
|
|
45194
45511
|
|
|
45195
|
-
//
|
|
45196
|
-
|
|
45197
|
-
console.error(
|
|
45198
|
-
`[webSocketController] Socket error for ${providerName}:`,
|
|
45199
|
-
err.message,
|
|
45200
|
-
);
|
|
45201
|
-
const conn = activeConnections.get(providerName);
|
|
45202
|
-
if (conn && conn.socket === socket) {
|
|
45203
|
-
activeConnections.set(providerName, {
|
|
45204
|
-
...conn,
|
|
45205
|
-
status: STATUS.ERROR,
|
|
45206
|
-
error: err.message,
|
|
45207
|
-
});
|
|
45208
|
-
broadcastStatusChange(providerName, STATUS.ERROR, {
|
|
45209
|
-
error: err.message,
|
|
45210
|
-
});
|
|
45211
|
-
}
|
|
45212
|
-
});
|
|
45512
|
+
// Start heartbeat
|
|
45513
|
+
startHeartbeat(providerName);
|
|
45213
45514
|
|
|
45214
45515
|
broadcastStatusChange(providerName, STATUS.CONNECTED);
|
|
45215
45516
|
|
|
@@ -45235,6 +45536,11 @@ const webSocketController$1 = {
|
|
|
45235
45536
|
messageCount: 0,
|
|
45236
45537
|
connectedAt: null,
|
|
45237
45538
|
lastMessageAt: null,
|
|
45539
|
+
retryCount: 0,
|
|
45540
|
+
retryTimer: null,
|
|
45541
|
+
heartbeatTimer: null,
|
|
45542
|
+
pongTimer: null,
|
|
45543
|
+
intentionalClose: false,
|
|
45238
45544
|
error: error.message,
|
|
45239
45545
|
});
|
|
45240
45546
|
broadcastStatusChange(providerName, STATUS.ERROR, {
|
|
@@ -45259,6 +45565,7 @@ const webSocketController$1 = {
|
|
|
45259
45565
|
/**
|
|
45260
45566
|
* disconnect
|
|
45261
45567
|
* Close a WebSocket connection and clean up.
|
|
45568
|
+
* Marks the close as intentional to suppress auto-reconnect.
|
|
45262
45569
|
*
|
|
45263
45570
|
* @param {BrowserWindow} win - the requesting window
|
|
45264
45571
|
* @param {string} providerName - the provider to disconnect
|
|
@@ -45286,6 +45593,13 @@ const webSocketController$1 = {
|
|
|
45286
45593
|
|
|
45287
45594
|
console.log(`[webSocketController] Disconnecting: ${providerName}`);
|
|
45288
45595
|
|
|
45596
|
+
// Mark as intentional so the close handler doesn't auto-reconnect
|
|
45597
|
+
conn.intentionalClose = true;
|
|
45598
|
+
|
|
45599
|
+
// Clear all timers
|
|
45600
|
+
clearHeartbeatTimers(conn);
|
|
45601
|
+
clearReconnectTimer(conn);
|
|
45602
|
+
|
|
45289
45603
|
// Close the socket
|
|
45290
45604
|
if (conn.socket) {
|
|
45291
45605
|
try {
|
|
@@ -45313,6 +45627,11 @@ const webSocketController$1 = {
|
|
|
45313
45627
|
error,
|
|
45314
45628
|
);
|
|
45315
45629
|
// Clean up anyway
|
|
45630
|
+
const conn = activeConnections.get(providerName);
|
|
45631
|
+
if (conn) {
|
|
45632
|
+
clearHeartbeatTimers(conn);
|
|
45633
|
+
clearReconnectTimer(conn);
|
|
45634
|
+
}
|
|
45316
45635
|
activeConnections.delete(providerName);
|
|
45317
45636
|
return {
|
|
45318
45637
|
error: true,
|
|
@@ -45367,7 +45686,7 @@ const webSocketController$1 = {
|
|
|
45367
45686
|
*
|
|
45368
45687
|
* @param {BrowserWindow} win - the requesting window
|
|
45369
45688
|
* @param {string} providerName - the provider name
|
|
45370
|
-
* @returns {{ providerName, status, messageCount, connectedAt, lastMessageAt, error }}
|
|
45689
|
+
* @returns {{ providerName, status, messageCount, connectedAt, lastMessageAt, error, retryCount }}
|
|
45371
45690
|
*/
|
|
45372
45691
|
getStatus: (win, providerName) => {
|
|
45373
45692
|
const conn = activeConnections.get(providerName);
|
|
@@ -45388,6 +45707,7 @@ const webSocketController$1 = {
|
|
|
45388
45707
|
connectedAt: conn.connectedAt || null,
|
|
45389
45708
|
lastMessageAt: conn.lastMessageAt || null,
|
|
45390
45709
|
error: conn.error || null,
|
|
45710
|
+
retryCount: conn.retryCount || 0,
|
|
45391
45711
|
};
|
|
45392
45712
|
},
|
|
45393
45713
|
|
|
@@ -45396,7 +45716,7 @@ const webSocketController$1 = {
|
|
|
45396
45716
|
* Returns all active connections with their status.
|
|
45397
45717
|
*
|
|
45398
45718
|
* @param {BrowserWindow} win - the requesting window
|
|
45399
|
-
* @returns {{ connections: Array<{ providerName, status, messageCount, connectedAt, lastMessageAt }> }}
|
|
45719
|
+
* @returns {{ connections: Array<{ providerName, status, messageCount, connectedAt, lastMessageAt, retryCount }> }}
|
|
45400
45720
|
*/
|
|
45401
45721
|
getAll: (win) => {
|
|
45402
45722
|
const connections = [];
|
|
@@ -45408,6 +45728,7 @@ const webSocketController$1 = {
|
|
|
45408
45728
|
connectedAt: conn.connectedAt || null,
|
|
45409
45729
|
lastMessageAt: conn.lastMessageAt || null,
|
|
45410
45730
|
error: conn.error || null,
|
|
45731
|
+
retryCount: conn.retryCount || 0,
|
|
45411
45732
|
});
|
|
45412
45733
|
}
|
|
45413
45734
|
return { success: true, connections };
|