@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.mjs
CHANGED
|
@@ -2290,7 +2290,7 @@ var MCPClient = class {
|
|
|
2290
2290
|
* Observation: SDK 1.24.0+ connections may hang indefinitely in some environments.
|
|
2291
2291
|
* This wrapper enforces a timeout and properly uses AbortController to unblock the request.
|
|
2292
2292
|
*/
|
|
2293
|
-
fetch: (url, init) => {
|
|
2293
|
+
fetch: async (url, init) => {
|
|
2294
2294
|
const timeout = 3e4;
|
|
2295
2295
|
const controller = new AbortController();
|
|
2296
2296
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
@@ -2298,7 +2298,17 @@ var MCPClient = class {
|
|
|
2298
2298
|
// @ts-ignore: AbortSignal.any is available in Node 20+
|
|
2299
2299
|
AbortSignal.any ? AbortSignal.any([init.signal, controller.signal]) : controller.signal
|
|
2300
2300
|
) : controller.signal;
|
|
2301
|
-
|
|
2301
|
+
try {
|
|
2302
|
+
const response = await fetch(url, { ...init, signal });
|
|
2303
|
+
const hasSessionHeader = init?.headers && new Headers(init.headers).has("mcp-session-id");
|
|
2304
|
+
if (response.status === 404 && hasSessionHeader) {
|
|
2305
|
+
this.client = null;
|
|
2306
|
+
throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
|
|
2307
|
+
}
|
|
2308
|
+
return response;
|
|
2309
|
+
} finally {
|
|
2310
|
+
clearTimeout(timeoutId);
|
|
2311
|
+
}
|
|
2302
2312
|
}
|
|
2303
2313
|
};
|
|
2304
2314
|
if (type === "sse") {
|
|
@@ -2490,6 +2500,14 @@ var MCPClient = class {
|
|
|
2490
2500
|
* @throws {Error} When connection fails for other reasons
|
|
2491
2501
|
*/
|
|
2492
2502
|
async connect() {
|
|
2503
|
+
if (this.client?.transport) {
|
|
2504
|
+
this.transport = null;
|
|
2505
|
+
try {
|
|
2506
|
+
await this.client.close();
|
|
2507
|
+
} catch {
|
|
2508
|
+
}
|
|
2509
|
+
this.client = null;
|
|
2510
|
+
}
|
|
2493
2511
|
await this.initialize();
|
|
2494
2512
|
if (!this.client || !this.oauthProvider) {
|
|
2495
2513
|
const error = "Client or OAuth provider not initialized";
|
|
@@ -2868,7 +2886,7 @@ var MCPClient = class {
|
|
|
2868
2886
|
await this.oauthProvider.invalidateCredentials("all");
|
|
2869
2887
|
}
|
|
2870
2888
|
await sessions.delete(this.userId, this.sessionId);
|
|
2871
|
-
this.disconnect();
|
|
2889
|
+
await this.disconnect();
|
|
2872
2890
|
}
|
|
2873
2891
|
/**
|
|
2874
2892
|
* Checks if the client is currently connected to an MCP server
|
|
@@ -2878,10 +2896,21 @@ var MCPClient = class {
|
|
|
2878
2896
|
return this.client !== null;
|
|
2879
2897
|
}
|
|
2880
2898
|
/**
|
|
2881
|
-
* Disconnects from the MCP server and cleans up resources
|
|
2882
|
-
* Does not remove session from Redis
|
|
2899
|
+
* Disconnects from the MCP server and cleans up resources.
|
|
2900
|
+
* Does not remove session from Redis — use clearSession() for that.
|
|
2901
|
+
*
|
|
2902
|
+
* For Streamable HTTP sessions, sends an HTTP DELETE to the MCP endpoint
|
|
2903
|
+
* before closing, as recommended by the MCP Streamable HTTP spec
|
|
2904
|
+
* (section "Session Management", rule 5). This is best-effort — errors
|
|
2905
|
+
* (e.g. server already restarted, 404/405 responses) are silently ignored.
|
|
2883
2906
|
*/
|
|
2884
|
-
disconnect(
|
|
2907
|
+
async disconnect() {
|
|
2908
|
+
if (this.transport instanceof StreamableHTTPClientTransport) {
|
|
2909
|
+
try {
|
|
2910
|
+
await this.transport.terminateSession();
|
|
2911
|
+
} catch {
|
|
2912
|
+
}
|
|
2913
|
+
}
|
|
2885
2914
|
if (this.client) {
|
|
2886
2915
|
this.client.close();
|
|
2887
2916
|
}
|
|
@@ -2893,7 +2922,6 @@ var MCPClient = class {
|
|
|
2893
2922
|
type: "disconnected",
|
|
2894
2923
|
sessionId: this.sessionId,
|
|
2895
2924
|
serverId: this.serverId,
|
|
2896
|
-
reason,
|
|
2897
2925
|
timestamp: Date.now()
|
|
2898
2926
|
});
|
|
2899
2927
|
this._onObservabilityEvent.fire({
|
|
@@ -2902,9 +2930,7 @@ var MCPClient = class {
|
|
|
2902
2930
|
message: `Disconnected from ${this.serverId}`,
|
|
2903
2931
|
sessionId: this.sessionId,
|
|
2904
2932
|
serverId: this.serverId,
|
|
2905
|
-
payload: {
|
|
2906
|
-
reason: reason || "unknown"
|
|
2907
|
-
},
|
|
2933
|
+
payload: {},
|
|
2908
2934
|
timestamp: Date.now(),
|
|
2909
2935
|
id: nanoid()
|
|
2910
2936
|
});
|
|
@@ -2970,17 +2996,14 @@ var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
|
2970
2996
|
var CONNECTION_BATCH_SIZE = 5;
|
|
2971
2997
|
var MultiSessionClient = class {
|
|
2972
2998
|
/**
|
|
2973
|
-
*
|
|
2974
|
-
*
|
|
2975
|
-
* @param userId - A unique string identifying the user (e.g. user ID or email).
|
|
2976
|
-
* @param options - Optional tuning for connection timeout, retry count, and retry delay.
|
|
2977
|
-
* Falls back to sensible defaults if not provided.
|
|
2999
|
+
* @param userId - Unique identifier for the user (e.g. user ID or email).
|
|
3000
|
+
* @param options - Optional tuning and lifecycle hooks.
|
|
2978
3001
|
*/
|
|
2979
3002
|
constructor(userId, options = {}) {
|
|
2980
3003
|
__publicField(this, "clients", []);
|
|
3004
|
+
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
2981
3005
|
__publicField(this, "userId");
|
|
2982
3006
|
__publicField(this, "options");
|
|
2983
|
-
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
2984
3007
|
this.userId = userId;
|
|
2985
3008
|
this.options = {
|
|
2986
3009
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
@@ -2989,28 +3012,85 @@ var MultiSessionClient = class {
|
|
|
2989
3012
|
...options
|
|
2990
3013
|
};
|
|
2991
3014
|
}
|
|
3015
|
+
// -----------------------------------------------------------------------
|
|
3016
|
+
// Public API
|
|
3017
|
+
// -----------------------------------------------------------------------
|
|
2992
3018
|
/**
|
|
2993
|
-
* Fetches
|
|
2994
|
-
* ones that are ready to connect.
|
|
3019
|
+
* Fetches active sessions and establishes connections to all of them.
|
|
2995
3020
|
*
|
|
2996
|
-
*
|
|
2997
|
-
*
|
|
2998
|
-
*
|
|
2999
|
-
* and let the OAuth flow complete separately before we try to reconnect them.
|
|
3021
|
+
* Call this once after creating the client. On long-running servers you
|
|
3022
|
+
* can cache the `MultiSessionClient` instance and call `connect()` on
|
|
3023
|
+
* each request — already-connected sessions are skipped internally.
|
|
3000
3024
|
*/
|
|
3001
|
-
async
|
|
3002
|
-
const
|
|
3003
|
-
const
|
|
3025
|
+
async connect() {
|
|
3026
|
+
const sessions2 = await this.fetchActiveSessions();
|
|
3027
|
+
const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
|
|
3028
|
+
for (const client of this.clients) {
|
|
3029
|
+
if (!activeSessionIds.has(client.getSessionId())) {
|
|
3030
|
+
this.options.onSessionEvicted?.(client.getSessionId());
|
|
3031
|
+
}
|
|
3032
|
+
}
|
|
3033
|
+
this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
|
|
3034
|
+
await this.connectInBatches(sessions2);
|
|
3035
|
+
}
|
|
3036
|
+
/**
|
|
3037
|
+
* Drops all cached `MCPClient` instances and reconnects fresh from storage.
|
|
3038
|
+
*
|
|
3039
|
+
* Call this when downstream MCP servers have expired their transport sessions
|
|
3040
|
+
* (e.g. after a remote server restart) and subsequent tool calls return
|
|
3041
|
+
* "Session not found. Reconnect without session header." errors.
|
|
3042
|
+
*
|
|
3043
|
+
* OAuth tokens are preserved in the storage backend — no re-authentication
|
|
3044
|
+
* is required. Only the in-memory transport sessions are cleared.
|
|
3045
|
+
*/
|
|
3046
|
+
async reconnect() {
|
|
3047
|
+
await this.disconnect();
|
|
3048
|
+
await this.connect();
|
|
3049
|
+
}
|
|
3050
|
+
/**
|
|
3051
|
+
* Returns all currently connected `MCPClient` instances.
|
|
3052
|
+
*
|
|
3053
|
+
* Use this to enumerate available tools across all connected servers,
|
|
3054
|
+
* or to route a tool call to the right client by `serverId`.
|
|
3055
|
+
*/
|
|
3056
|
+
getClients() {
|
|
3057
|
+
return this.clients;
|
|
3058
|
+
}
|
|
3059
|
+
/**
|
|
3060
|
+
* Gracefully disconnects all active MCP clients and clears the internal list.
|
|
3061
|
+
*
|
|
3062
|
+
* For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
|
|
3063
|
+
* endpoint per the spec before closing locally. All disconnects run in
|
|
3064
|
+
* parallel so shutdown is not serialised across many sessions.
|
|
3065
|
+
*
|
|
3066
|
+
* Call this during server shutdown or when a user logs out to free up
|
|
3067
|
+
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3068
|
+
*/
|
|
3069
|
+
async disconnect() {
|
|
3070
|
+
await Promise.all(this.clients.map((client) => client.disconnect()));
|
|
3071
|
+
this.clients = [];
|
|
3072
|
+
}
|
|
3073
|
+
// -----------------------------------------------------------------------
|
|
3074
|
+
// Internals
|
|
3075
|
+
// -----------------------------------------------------------------------
|
|
3076
|
+
/**
|
|
3077
|
+
* Resolves the list of sessions to connect.
|
|
3078
|
+
*
|
|
3079
|
+
* Uses the custom `sessionProvider` when provided, otherwise falls back
|
|
3080
|
+
* to querying the storage backend via `sessions.list(userId)`.
|
|
3081
|
+
*/
|
|
3082
|
+
async fetchActiveSessions() {
|
|
3083
|
+
const sessionList = this.options.sessionProvider ? await this.options.sessionProvider() : await sessions.list(this.userId);
|
|
3084
|
+
return sessionList.filter(
|
|
3004
3085
|
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.status === "active"
|
|
3005
3086
|
);
|
|
3006
|
-
return valid;
|
|
3007
3087
|
}
|
|
3008
3088
|
/**
|
|
3009
|
-
* Connects
|
|
3089
|
+
* Connects a list of sessions in controlled batches.
|
|
3010
3090
|
*
|
|
3011
|
-
* Batching prevents overwhelming the event loop or external servers when
|
|
3012
|
-
* has many active MCP sessions
|
|
3013
|
-
*
|
|
3091
|
+
* Batching prevents overwhelming the event loop or external servers when
|
|
3092
|
+
* a user has many active MCP sessions. Within each batch, sessions are
|
|
3093
|
+
* connected concurrently using `Promise.all`.
|
|
3014
3094
|
*/
|
|
3015
3095
|
async connectInBatches(sessions2) {
|
|
3016
3096
|
for (let i = 0; i < sessions2.length; i += CONNECTION_BATCH_SIZE) {
|
|
@@ -3019,19 +3099,25 @@ var MultiSessionClient = class {
|
|
|
3019
3099
|
}
|
|
3020
3100
|
}
|
|
3021
3101
|
/**
|
|
3022
|
-
* Connects a single session, with
|
|
3102
|
+
* Connects a single session, with deduplication to prevent race conditions.
|
|
3023
3103
|
*
|
|
3024
|
-
* - If a client for this session already exists and is connected, returns
|
|
3025
|
-
*
|
|
3026
|
-
*
|
|
3027
|
-
*
|
|
3028
|
-
*
|
|
3029
|
-
*
|
|
3104
|
+
* - If a client for this session already exists and is connected, returns
|
|
3105
|
+
* immediately.
|
|
3106
|
+
* - If the existing client entry is no longer connected (e.g. explicit
|
|
3107
|
+
* disconnect), it is evicted so a fresh transport is created.
|
|
3108
|
+
* - If a connection attempt for this session is already in-flight, the
|
|
3109
|
+
* existing promise is reused as a per-session mutex.
|
|
3110
|
+
* - On completion (success or failure), the promise is cleaned up from
|
|
3111
|
+
* the connectionPromises map.
|
|
3030
3112
|
*/
|
|
3031
3113
|
async connectSession(session) {
|
|
3032
|
-
const
|
|
3033
|
-
if (
|
|
3034
|
-
|
|
3114
|
+
const existing = this.clients.find((c) => c.getSessionId() === session.sessionId);
|
|
3115
|
+
if (existing) {
|
|
3116
|
+
if (existing.isConnected()) {
|
|
3117
|
+
return;
|
|
3118
|
+
}
|
|
3119
|
+
this.options.onSessionEvicted?.(existing.getSessionId());
|
|
3120
|
+
this.clients = this.clients.filter((c) => c !== existing);
|
|
3035
3121
|
}
|
|
3036
3122
|
if (this.connectionPromises.has(session.sessionId)) {
|
|
3037
3123
|
return this.connectionPromises.get(session.sessionId);
|
|
@@ -3045,22 +3131,19 @@ var MultiSessionClient = class {
|
|
|
3045
3131
|
}
|
|
3046
3132
|
}
|
|
3047
3133
|
/**
|
|
3048
|
-
*
|
|
3134
|
+
* Core connection loop for a single session with retry logic.
|
|
3049
3135
|
*
|
|
3050
|
-
*
|
|
3051
|
-
*
|
|
3052
|
-
*
|
|
3053
|
-
* 2. Races the connect call against a timeout promise — if the server doesn't respond
|
|
3054
|
-
* within `timeoutMs`, the attempt is aborted and counted as a failure.
|
|
3055
|
-
* 3. On success, replaces any stale client entry for this session in the `clients` array.
|
|
3136
|
+
* 1. Creates a fresh `MCPClient` from the session data.
|
|
3137
|
+
* 2. Races `client.connect()` against a timeout.
|
|
3138
|
+
* 3. On success, replaces any stale entry and fires `onSessionConnected`.
|
|
3056
3139
|
* 4. On failure, waits `retryDelay` ms before the next attempt.
|
|
3057
3140
|
*
|
|
3058
|
-
* If all attempts are exhausted, logs an error and returns silently
|
|
3059
|
-
*
|
|
3141
|
+
* If all attempts are exhausted, logs an error and returns silently so
|
|
3142
|
+
* a single bad server doesn't block the rest of the batch.
|
|
3060
3143
|
*/
|
|
3061
3144
|
async establishConnectionWithRetries(session) {
|
|
3062
|
-
const maxRetries = this.options.maxRetries
|
|
3063
|
-
const retryDelay = this.options.retryDelay
|
|
3145
|
+
const maxRetries = this.options.maxRetries;
|
|
3146
|
+
const retryDelay = this.options.retryDelay;
|
|
3064
3147
|
let lastError;
|
|
3065
3148
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
3066
3149
|
try {
|
|
@@ -3074,10 +3157,13 @@ var MultiSessionClient = class {
|
|
|
3074
3157
|
transportType: session.transportType,
|
|
3075
3158
|
headers: session.headers
|
|
3076
3159
|
});
|
|
3077
|
-
const timeoutMs = this.options.timeout
|
|
3160
|
+
const timeoutMs = this.options.timeout;
|
|
3078
3161
|
let timeoutTimer;
|
|
3079
3162
|
const timeoutPromise = new Promise((_, reject) => {
|
|
3080
|
-
timeoutTimer = setTimeout(
|
|
3163
|
+
timeoutTimer = setTimeout(
|
|
3164
|
+
() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)),
|
|
3165
|
+
timeoutMs
|
|
3166
|
+
);
|
|
3081
3167
|
});
|
|
3082
3168
|
try {
|
|
3083
3169
|
await Promise.race([client.connect(), timeoutPromise]);
|
|
@@ -3086,6 +3172,7 @@ var MultiSessionClient = class {
|
|
|
3086
3172
|
}
|
|
3087
3173
|
this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
|
|
3088
3174
|
this.clients.push(client);
|
|
3175
|
+
this.options.onSessionConnected?.(session.sessionId, client);
|
|
3089
3176
|
return;
|
|
3090
3177
|
} catch (error) {
|
|
3091
3178
|
lastError = error;
|
|
@@ -3094,38 +3181,11 @@ var MultiSessionClient = class {
|
|
|
3094
3181
|
}
|
|
3095
3182
|
}
|
|
3096
3183
|
}
|
|
3097
|
-
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
*
|
|
3103
|
-
* Call this once after creating the client. On traditional servers, you can
|
|
3104
|
-
* cache the `MultiSessionClient` instance after calling `connect()` to avoid
|
|
3105
|
-
* re-fetching and re-connecting on every request.
|
|
3106
|
-
*/
|
|
3107
|
-
async connect() {
|
|
3108
|
-
const sessions2 = await this.getActiveSessions();
|
|
3109
|
-
await this.connectInBatches(sessions2);
|
|
3110
|
-
}
|
|
3111
|
-
/**
|
|
3112
|
-
* Returns all currently connected `MCPClient` instances.
|
|
3113
|
-
*
|
|
3114
|
-
* Use this to enumerate available tools across all connected servers,
|
|
3115
|
-
* or to route a tool call to the right client by `serverId`.
|
|
3116
|
-
*/
|
|
3117
|
-
getClients() {
|
|
3118
|
-
return this.clients;
|
|
3119
|
-
}
|
|
3120
|
-
/**
|
|
3121
|
-
* Gracefully disconnects all active MCP clients and clears the internal client list.
|
|
3122
|
-
*
|
|
3123
|
-
* Call this during server shutdown or when a user logs out to free up
|
|
3124
|
-
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3125
|
-
*/
|
|
3126
|
-
disconnect() {
|
|
3127
|
-
this.clients.forEach((client) => client.disconnect());
|
|
3128
|
-
this.clients = [];
|
|
3184
|
+
this.options.onSessionFailed?.(session.sessionId, lastError);
|
|
3185
|
+
console.error(
|
|
3186
|
+
`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`,
|
|
3187
|
+
lastError
|
|
3188
|
+
);
|
|
3129
3189
|
}
|
|
3130
3190
|
};
|
|
3131
3191
|
|
|
@@ -3354,7 +3414,6 @@ var SSEConnectionManager = class {
|
|
|
3354
3414
|
const client = this.clients.get(sessionId);
|
|
3355
3415
|
if (client) {
|
|
3356
3416
|
await client.clearSession();
|
|
3357
|
-
client.disconnect();
|
|
3358
3417
|
this.clients.delete(sessionId);
|
|
3359
3418
|
} else {
|
|
3360
3419
|
await sessions.delete(this.userId, sessionId);
|
|
@@ -3563,14 +3622,14 @@ var SSEConnectionManager = class {
|
|
|
3563
3622
|
/**
|
|
3564
3623
|
* Cleanup and close all connections
|
|
3565
3624
|
*/
|
|
3566
|
-
dispose() {
|
|
3625
|
+
async dispose() {
|
|
3567
3626
|
this.isActive = false;
|
|
3568
3627
|
if (this.heartbeatTimer) {
|
|
3569
3628
|
clearInterval(this.heartbeatTimer);
|
|
3570
3629
|
}
|
|
3571
|
-
|
|
3572
|
-
client.disconnect()
|
|
3573
|
-
|
|
3630
|
+
await Promise.all(
|
|
3631
|
+
Array.from(this.clients.values()).map((client) => client.disconnect())
|
|
3632
|
+
);
|
|
3574
3633
|
this.clients.clear();
|
|
3575
3634
|
}
|
|
3576
3635
|
};
|