@mcp-ts/sdk 2.4.0 → 2.4.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/README.md +58 -19
- package/dist/adapters/agui-adapter.d.mts +3 -4
- package/dist/adapters/agui-adapter.d.ts +3 -4
- package/dist/adapters/agui-middleware.d.mts +3 -4
- package/dist/adapters/agui-middleware.d.ts +3 -4
- package/dist/adapters/ai-adapter.d.mts +3 -4
- package/dist/adapters/ai-adapter.d.ts +3 -4
- package/dist/adapters/langchain-adapter.d.mts +3 -4
- package/dist/adapters/langchain-adapter.d.ts +3 -4
- package/dist/adapters/mastra-adapter.d.mts +2 -2
- package/dist/adapters/mastra-adapter.d.ts +2 -2
- package/dist/client/index.d.mts +2 -3
- package/dist/client/index.d.ts +2 -3
- package/dist/client/react.d.mts +4 -6
- package/dist/client/react.d.ts +4 -6
- package/dist/client/vue.d.mts +4 -6
- package/dist/client/vue.d.ts +4 -6
- package/dist/{index-CtXvKl8N.d.ts → index-B8kJSrBJ.d.ts} +1 -2
- package/dist/{index-ByIjEReo.d.mts → index-DiJsm_lK.d.mts} +1 -2
- package/dist/index.d.mts +5 -6
- package/dist/index.d.ts +5 -6
- package/dist/index.js +149 -90
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +149 -90
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-CnvZEGPY.d.ts → multi-session-client-BluyCPo9.d.ts} +219 -66
- package/dist/{multi-session-client-CIMUGF8S.d.mts → multi-session-client-CWs-AE78.d.mts} +219 -66
- package/dist/server/index.d.mts +6 -129
- package/dist/server/index.d.ts +6 -129
- package/dist/server/index.js +149 -90
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +149 -90
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.d.mts +4 -5
- package/dist/shared/index.d.ts +4 -5
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs.map +1 -1
- package/dist/{tool-router-CuApsDiV.d.mts → tool-router-CbG4Tum6.d.mts} +1 -1
- package/dist/{tool-router-CZMrOG8J.d.ts → tool-router-ChIhPwgP.d.ts} +1 -1
- package/dist/{types-DCk_IF4L.d.ts → types-CjczQwNX.d.mts} +122 -1
- package/dist/{types-DCk_IF4L.d.mts → types-CjczQwNX.d.ts} +122 -1
- package/migrations/v2.3.4/neon/20260513010000_install_mcp_sessions.sql +69 -0
- package/migrations/v2.3.4/neon/20260513020000_add_session_cleanup_cron.sql +35 -0
- package/migrations/v2.3.4/supabase/20260330195700_install_mcp_sessions.sql +82 -0
- package/migrations/v2.3.4/supabase/20260421010000_add_session_cleanup_cron.sql +32 -0
- package/package.json +1 -1
- package/src/server/handlers/sse-handler.ts +7 -5
- package/src/server/mcp/multi-session-client.ts +186 -100
- package/src/server/mcp/oauth-client.ts +60 -10
- package/src/shared/events.ts +0 -1
- package/dist/events-CK3N--3g.d.mts +0 -123
- package/dist/events-CK3N--3g.d.ts +0 -123
package/dist/index.js
CHANGED
|
@@ -2363,7 +2363,7 @@ var MCPClient = class {
|
|
|
2363
2363
|
* Observation: SDK 1.24.0+ connections may hang indefinitely in some environments.
|
|
2364
2364
|
* This wrapper enforces a timeout and properly uses AbortController to unblock the request.
|
|
2365
2365
|
*/
|
|
2366
|
-
fetch: (url, init) => {
|
|
2366
|
+
fetch: async (url, init) => {
|
|
2367
2367
|
const timeout = 3e4;
|
|
2368
2368
|
const controller = new AbortController();
|
|
2369
2369
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
@@ -2371,7 +2371,17 @@ var MCPClient = class {
|
|
|
2371
2371
|
// @ts-ignore: AbortSignal.any is available in Node 20+
|
|
2372
2372
|
AbortSignal.any ? AbortSignal.any([init.signal, controller.signal]) : controller.signal
|
|
2373
2373
|
) : controller.signal;
|
|
2374
|
-
|
|
2374
|
+
try {
|
|
2375
|
+
const response = await fetch(url, { ...init, signal });
|
|
2376
|
+
const hasSessionHeader = init?.headers && new Headers(init.headers).has("mcp-session-id");
|
|
2377
|
+
if (response.status === 404 && hasSessionHeader) {
|
|
2378
|
+
this.client = null;
|
|
2379
|
+
throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
|
|
2380
|
+
}
|
|
2381
|
+
return response;
|
|
2382
|
+
} finally {
|
|
2383
|
+
clearTimeout(timeoutId);
|
|
2384
|
+
}
|
|
2375
2385
|
}
|
|
2376
2386
|
};
|
|
2377
2387
|
if (type === "sse") {
|
|
@@ -2563,6 +2573,14 @@ var MCPClient = class {
|
|
|
2563
2573
|
* @throws {Error} When connection fails for other reasons
|
|
2564
2574
|
*/
|
|
2565
2575
|
async connect() {
|
|
2576
|
+
if (this.client?.transport) {
|
|
2577
|
+
this.transport = null;
|
|
2578
|
+
try {
|
|
2579
|
+
await this.client.close();
|
|
2580
|
+
} catch {
|
|
2581
|
+
}
|
|
2582
|
+
this.client = null;
|
|
2583
|
+
}
|
|
2566
2584
|
await this.initialize();
|
|
2567
2585
|
if (!this.client || !this.oauthProvider) {
|
|
2568
2586
|
const error = "Client or OAuth provider not initialized";
|
|
@@ -2941,7 +2959,7 @@ var MCPClient = class {
|
|
|
2941
2959
|
await this.oauthProvider.invalidateCredentials("all");
|
|
2942
2960
|
}
|
|
2943
2961
|
await sessions.delete(this.userId, this.sessionId);
|
|
2944
|
-
this.disconnect();
|
|
2962
|
+
await this.disconnect();
|
|
2945
2963
|
}
|
|
2946
2964
|
/**
|
|
2947
2965
|
* Checks if the client is currently connected to an MCP server
|
|
@@ -2951,10 +2969,21 @@ var MCPClient = class {
|
|
|
2951
2969
|
return this.client !== null;
|
|
2952
2970
|
}
|
|
2953
2971
|
/**
|
|
2954
|
-
* Disconnects from the MCP server and cleans up resources
|
|
2955
|
-
* Does not remove session from Redis
|
|
2972
|
+
* Disconnects from the MCP server and cleans up resources.
|
|
2973
|
+
* Does not remove session from Redis — use clearSession() for that.
|
|
2974
|
+
*
|
|
2975
|
+
* For Streamable HTTP sessions, sends an HTTP DELETE to the MCP endpoint
|
|
2976
|
+
* before closing, as recommended by the MCP Streamable HTTP spec
|
|
2977
|
+
* (section "Session Management", rule 5). This is best-effort — errors
|
|
2978
|
+
* (e.g. server already restarted, 404/405 responses) are silently ignored.
|
|
2956
2979
|
*/
|
|
2957
|
-
disconnect(
|
|
2980
|
+
async disconnect() {
|
|
2981
|
+
if (this.transport instanceof streamableHttp_js.StreamableHTTPClientTransport) {
|
|
2982
|
+
try {
|
|
2983
|
+
await this.transport.terminateSession();
|
|
2984
|
+
} catch {
|
|
2985
|
+
}
|
|
2986
|
+
}
|
|
2958
2987
|
if (this.client) {
|
|
2959
2988
|
this.client.close();
|
|
2960
2989
|
}
|
|
@@ -2966,7 +2995,6 @@ var MCPClient = class {
|
|
|
2966
2995
|
type: "disconnected",
|
|
2967
2996
|
sessionId: this.sessionId,
|
|
2968
2997
|
serverId: this.serverId,
|
|
2969
|
-
reason,
|
|
2970
2998
|
timestamp: Date.now()
|
|
2971
2999
|
});
|
|
2972
3000
|
this._onObservabilityEvent.fire({
|
|
@@ -2975,9 +3003,7 @@ var MCPClient = class {
|
|
|
2975
3003
|
message: `Disconnected from ${this.serverId}`,
|
|
2976
3004
|
sessionId: this.sessionId,
|
|
2977
3005
|
serverId: this.serverId,
|
|
2978
|
-
payload: {
|
|
2979
|
-
reason: reason || "unknown"
|
|
2980
|
-
},
|
|
3006
|
+
payload: {},
|
|
2981
3007
|
timestamp: Date.now(),
|
|
2982
3008
|
id: nanoid.nanoid()
|
|
2983
3009
|
});
|
|
@@ -3044,17 +3070,14 @@ var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
|
3044
3070
|
var CONNECTION_BATCH_SIZE = 5;
|
|
3045
3071
|
var MultiSessionClient = class {
|
|
3046
3072
|
/**
|
|
3047
|
-
*
|
|
3048
|
-
*
|
|
3049
|
-
* @param userId - A unique string identifying the user (e.g. user ID or email).
|
|
3050
|
-
* @param options - Optional tuning for connection timeout, retry count, and retry delay.
|
|
3051
|
-
* Falls back to sensible defaults if not provided.
|
|
3073
|
+
* @param userId - Unique identifier for the user (e.g. user ID or email).
|
|
3074
|
+
* @param options - Optional tuning and lifecycle hooks.
|
|
3052
3075
|
*/
|
|
3053
3076
|
constructor(userId, options = {}) {
|
|
3054
3077
|
__publicField(this, "clients", []);
|
|
3078
|
+
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
3055
3079
|
__publicField(this, "userId");
|
|
3056
3080
|
__publicField(this, "options");
|
|
3057
|
-
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
3058
3081
|
this.userId = userId;
|
|
3059
3082
|
this.options = {
|
|
3060
3083
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
@@ -3063,28 +3086,85 @@ var MultiSessionClient = class {
|
|
|
3063
3086
|
...options
|
|
3064
3087
|
};
|
|
3065
3088
|
}
|
|
3089
|
+
// -----------------------------------------------------------------------
|
|
3090
|
+
// Public API
|
|
3091
|
+
// -----------------------------------------------------------------------
|
|
3066
3092
|
/**
|
|
3067
|
-
* Fetches
|
|
3068
|
-
* ones that are ready to connect.
|
|
3093
|
+
* Fetches active sessions and establishes connections to all of them.
|
|
3069
3094
|
*
|
|
3070
|
-
*
|
|
3071
|
-
*
|
|
3072
|
-
*
|
|
3073
|
-
* and let the OAuth flow complete separately before we try to reconnect them.
|
|
3095
|
+
* Call this once after creating the client. On long-running servers you
|
|
3096
|
+
* can cache the `MultiSessionClient` instance and call `connect()` on
|
|
3097
|
+
* each request — already-connected sessions are skipped internally.
|
|
3074
3098
|
*/
|
|
3075
|
-
async
|
|
3076
|
-
const
|
|
3077
|
-
const
|
|
3099
|
+
async connect() {
|
|
3100
|
+
const sessions2 = await this.fetchActiveSessions();
|
|
3101
|
+
const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
|
|
3102
|
+
for (const client of this.clients) {
|
|
3103
|
+
if (!activeSessionIds.has(client.getSessionId())) {
|
|
3104
|
+
this.options.onSessionEvicted?.(client.getSessionId());
|
|
3105
|
+
}
|
|
3106
|
+
}
|
|
3107
|
+
this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
|
|
3108
|
+
await this.connectInBatches(sessions2);
|
|
3109
|
+
}
|
|
3110
|
+
/**
|
|
3111
|
+
* Drops all cached `MCPClient` instances and reconnects fresh from storage.
|
|
3112
|
+
*
|
|
3113
|
+
* Call this when downstream MCP servers have expired their transport sessions
|
|
3114
|
+
* (e.g. after a remote server restart) and subsequent tool calls return
|
|
3115
|
+
* "Session not found. Reconnect without session header." errors.
|
|
3116
|
+
*
|
|
3117
|
+
* OAuth tokens are preserved in the storage backend — no re-authentication
|
|
3118
|
+
* is required. Only the in-memory transport sessions are cleared.
|
|
3119
|
+
*/
|
|
3120
|
+
async reconnect() {
|
|
3121
|
+
await this.disconnect();
|
|
3122
|
+
await this.connect();
|
|
3123
|
+
}
|
|
3124
|
+
/**
|
|
3125
|
+
* Returns all currently connected `MCPClient` instances.
|
|
3126
|
+
*
|
|
3127
|
+
* Use this to enumerate available tools across all connected servers,
|
|
3128
|
+
* or to route a tool call to the right client by `serverId`.
|
|
3129
|
+
*/
|
|
3130
|
+
getClients() {
|
|
3131
|
+
return this.clients;
|
|
3132
|
+
}
|
|
3133
|
+
/**
|
|
3134
|
+
* Gracefully disconnects all active MCP clients and clears the internal list.
|
|
3135
|
+
*
|
|
3136
|
+
* For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
|
|
3137
|
+
* endpoint per the spec before closing locally. All disconnects run in
|
|
3138
|
+
* parallel so shutdown is not serialised across many sessions.
|
|
3139
|
+
*
|
|
3140
|
+
* Call this during server shutdown or when a user logs out to free up
|
|
3141
|
+
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3142
|
+
*/
|
|
3143
|
+
async disconnect() {
|
|
3144
|
+
await Promise.all(this.clients.map((client) => client.disconnect()));
|
|
3145
|
+
this.clients = [];
|
|
3146
|
+
}
|
|
3147
|
+
// -----------------------------------------------------------------------
|
|
3148
|
+
// Internals
|
|
3149
|
+
// -----------------------------------------------------------------------
|
|
3150
|
+
/**
|
|
3151
|
+
* Resolves the list of sessions to connect.
|
|
3152
|
+
*
|
|
3153
|
+
* Uses the custom `sessionProvider` when provided, otherwise falls back
|
|
3154
|
+
* to querying the storage backend via `sessions.list(userId)`.
|
|
3155
|
+
*/
|
|
3156
|
+
async fetchActiveSessions() {
|
|
3157
|
+
const sessionList = this.options.sessionProvider ? await this.options.sessionProvider() : await sessions.list(this.userId);
|
|
3158
|
+
return sessionList.filter(
|
|
3078
3159
|
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.status === "active"
|
|
3079
3160
|
);
|
|
3080
|
-
return valid;
|
|
3081
3161
|
}
|
|
3082
3162
|
/**
|
|
3083
|
-
* Connects
|
|
3163
|
+
* Connects a list of sessions in controlled batches.
|
|
3084
3164
|
*
|
|
3085
|
-
* Batching prevents overwhelming the event loop or external servers when
|
|
3086
|
-
* has many active MCP sessions
|
|
3087
|
-
*
|
|
3165
|
+
* Batching prevents overwhelming the event loop or external servers when
|
|
3166
|
+
* a user has many active MCP sessions. Within each batch, sessions are
|
|
3167
|
+
* connected concurrently using `Promise.all`.
|
|
3088
3168
|
*/
|
|
3089
3169
|
async connectInBatches(sessions2) {
|
|
3090
3170
|
for (let i = 0; i < sessions2.length; i += CONNECTION_BATCH_SIZE) {
|
|
@@ -3093,19 +3173,25 @@ var MultiSessionClient = class {
|
|
|
3093
3173
|
}
|
|
3094
3174
|
}
|
|
3095
3175
|
/**
|
|
3096
|
-
* Connects a single session, with
|
|
3176
|
+
* Connects a single session, with deduplication to prevent race conditions.
|
|
3097
3177
|
*
|
|
3098
|
-
* - If a client for this session already exists and is connected, returns
|
|
3099
|
-
*
|
|
3100
|
-
*
|
|
3101
|
-
*
|
|
3102
|
-
*
|
|
3103
|
-
*
|
|
3178
|
+
* - If a client for this session already exists and is connected, returns
|
|
3179
|
+
* immediately.
|
|
3180
|
+
* - If the existing client entry is no longer connected (e.g. explicit
|
|
3181
|
+
* disconnect), it is evicted so a fresh transport is created.
|
|
3182
|
+
* - If a connection attempt for this session is already in-flight, the
|
|
3183
|
+
* existing promise is reused as a per-session mutex.
|
|
3184
|
+
* - On completion (success or failure), the promise is cleaned up from
|
|
3185
|
+
* the connectionPromises map.
|
|
3104
3186
|
*/
|
|
3105
3187
|
async connectSession(session) {
|
|
3106
|
-
const
|
|
3107
|
-
if (
|
|
3108
|
-
|
|
3188
|
+
const existing = this.clients.find((c) => c.getSessionId() === session.sessionId);
|
|
3189
|
+
if (existing) {
|
|
3190
|
+
if (existing.isConnected()) {
|
|
3191
|
+
return;
|
|
3192
|
+
}
|
|
3193
|
+
this.options.onSessionEvicted?.(existing.getSessionId());
|
|
3194
|
+
this.clients = this.clients.filter((c) => c !== existing);
|
|
3109
3195
|
}
|
|
3110
3196
|
if (this.connectionPromises.has(session.sessionId)) {
|
|
3111
3197
|
return this.connectionPromises.get(session.sessionId);
|
|
@@ -3119,22 +3205,19 @@ var MultiSessionClient = class {
|
|
|
3119
3205
|
}
|
|
3120
3206
|
}
|
|
3121
3207
|
/**
|
|
3122
|
-
*
|
|
3208
|
+
* Core connection loop for a single session with retry logic.
|
|
3123
3209
|
*
|
|
3124
|
-
*
|
|
3125
|
-
*
|
|
3126
|
-
*
|
|
3127
|
-
* 2. Races the connect call against a timeout promise — if the server doesn't respond
|
|
3128
|
-
* within `timeoutMs`, the attempt is aborted and counted as a failure.
|
|
3129
|
-
* 3. On success, replaces any stale client entry for this session in the `clients` array.
|
|
3210
|
+
* 1. Creates a fresh `MCPClient` from the session data.
|
|
3211
|
+
* 2. Races `client.connect()` against a timeout.
|
|
3212
|
+
* 3. On success, replaces any stale entry and fires `onSessionConnected`.
|
|
3130
3213
|
* 4. On failure, waits `retryDelay` ms before the next attempt.
|
|
3131
3214
|
*
|
|
3132
|
-
* If all attempts are exhausted, logs an error and returns silently
|
|
3133
|
-
*
|
|
3215
|
+
* If all attempts are exhausted, logs an error and returns silently so
|
|
3216
|
+
* a single bad server doesn't block the rest of the batch.
|
|
3134
3217
|
*/
|
|
3135
3218
|
async establishConnectionWithRetries(session) {
|
|
3136
|
-
const maxRetries = this.options.maxRetries
|
|
3137
|
-
const retryDelay = this.options.retryDelay
|
|
3219
|
+
const maxRetries = this.options.maxRetries;
|
|
3220
|
+
const retryDelay = this.options.retryDelay;
|
|
3138
3221
|
let lastError;
|
|
3139
3222
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3140
3223
|
try {
|
|
@@ -3148,10 +3231,13 @@ var MultiSessionClient = class {
|
|
|
3148
3231
|
transportType: session.transportType,
|
|
3149
3232
|
headers: session.headers
|
|
3150
3233
|
});
|
|
3151
|
-
const timeoutMs = this.options.timeout
|
|
3234
|
+
const timeoutMs = this.options.timeout;
|
|
3152
3235
|
let timeoutTimer;
|
|
3153
3236
|
const timeoutPromise = new Promise((_, reject) => {
|
|
3154
|
-
timeoutTimer = setTimeout(
|
|
3237
|
+
timeoutTimer = setTimeout(
|
|
3238
|
+
() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)),
|
|
3239
|
+
timeoutMs
|
|
3240
|
+
);
|
|
3155
3241
|
});
|
|
3156
3242
|
try {
|
|
3157
3243
|
await Promise.race([client.connect(), timeoutPromise]);
|
|
@@ -3160,6 +3246,7 @@ var MultiSessionClient = class {
|
|
|
3160
3246
|
}
|
|
3161
3247
|
this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
|
|
3162
3248
|
this.clients.push(client);
|
|
3249
|
+
this.options.onSessionConnected?.(session.sessionId, client);
|
|
3163
3250
|
return;
|
|
3164
3251
|
} catch (error) {
|
|
3165
3252
|
lastError = error;
|
|
@@ -3168,38 +3255,11 @@ var MultiSessionClient = class {
|
|
|
3168
3255
|
}
|
|
3169
3256
|
}
|
|
3170
3257
|
}
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
*
|
|
3177
|
-
* Call this once after creating the client. On traditional servers, you can
|
|
3178
|
-
* cache the `MultiSessionClient` instance after calling `connect()` to avoid
|
|
3179
|
-
* re-fetching and re-connecting on every request.
|
|
3180
|
-
*/
|
|
3181
|
-
async connect() {
|
|
3182
|
-
const sessions2 = await this.getActiveSessions();
|
|
3183
|
-
await this.connectInBatches(sessions2);
|
|
3184
|
-
}
|
|
3185
|
-
/**
|
|
3186
|
-
* Returns all currently connected `MCPClient` instances.
|
|
3187
|
-
*
|
|
3188
|
-
* Use this to enumerate available tools across all connected servers,
|
|
3189
|
-
* or to route a tool call to the right client by `serverId`.
|
|
3190
|
-
*/
|
|
3191
|
-
getClients() {
|
|
3192
|
-
return this.clients;
|
|
3193
|
-
}
|
|
3194
|
-
/**
|
|
3195
|
-
* Gracefully disconnects all active MCP clients and clears the internal client list.
|
|
3196
|
-
*
|
|
3197
|
-
* Call this during server shutdown or when a user logs out to free up
|
|
3198
|
-
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3199
|
-
*/
|
|
3200
|
-
disconnect() {
|
|
3201
|
-
this.clients.forEach((client) => client.disconnect());
|
|
3202
|
-
this.clients = [];
|
|
3258
|
+
this.options.onSessionFailed?.(session.sessionId, lastError);
|
|
3259
|
+
console.error(
|
|
3260
|
+
`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`,
|
|
3261
|
+
lastError
|
|
3262
|
+
);
|
|
3203
3263
|
}
|
|
3204
3264
|
};
|
|
3205
3265
|
|
|
@@ -3432,7 +3492,6 @@ var SSEConnectionManager = class {
|
|
|
3432
3492
|
const client = this.clients.get(sessionId);
|
|
3433
3493
|
if (client) {
|
|
3434
3494
|
await client.clearSession();
|
|
3435
|
-
client.disconnect();
|
|
3436
3495
|
this.clients.delete(sessionId);
|
|
3437
3496
|
} else {
|
|
3438
3497
|
await sessions.delete(this.userId, sessionId);
|
|
@@ -3641,14 +3700,14 @@ var SSEConnectionManager = class {
|
|
|
3641
3700
|
/**
|
|
3642
3701
|
* Cleanup and close all connections
|
|
3643
3702
|
*/
|
|
3644
|
-
dispose() {
|
|
3703
|
+
async dispose() {
|
|
3645
3704
|
this.isActive = false;
|
|
3646
3705
|
if (this.heartbeatTimer) {
|
|
3647
3706
|
clearInterval(this.heartbeatTimer);
|
|
3648
3707
|
}
|
|
3649
|
-
|
|
3650
|
-
client.disconnect()
|
|
3651
|
-
|
|
3708
|
+
await Promise.all(
|
|
3709
|
+
Array.from(this.clients.values()).map((client) => client.disconnect())
|
|
3710
|
+
);
|
|
3652
3711
|
this.clients.clear();
|
|
3653
3712
|
}
|
|
3654
3713
|
};
|