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