@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.mjs
CHANGED
|
@@ -2218,7 +2218,7 @@ var MCPClient = class {
|
|
|
2218
2218
|
* Observation: SDK 1.24.0+ connections may hang indefinitely in some environments.
|
|
2219
2219
|
* This wrapper enforces a timeout and properly uses AbortController to unblock the request.
|
|
2220
2220
|
*/
|
|
2221
|
-
fetch: (url, init) => {
|
|
2221
|
+
fetch: async (url, init) => {
|
|
2222
2222
|
const timeout = 3e4;
|
|
2223
2223
|
const controller = new AbortController();
|
|
2224
2224
|
const timeoutId = setTimeout(() => controller.abort(), timeout);
|
|
@@ -2226,7 +2226,17 @@ var MCPClient = class {
|
|
|
2226
2226
|
// @ts-ignore: AbortSignal.any is available in Node 20+
|
|
2227
2227
|
AbortSignal.any ? AbortSignal.any([init.signal, controller.signal]) : controller.signal
|
|
2228
2228
|
) : controller.signal;
|
|
2229
|
-
|
|
2229
|
+
try {
|
|
2230
|
+
const response = await fetch(url, { ...init, signal });
|
|
2231
|
+
const hasSessionHeader = init?.headers && new Headers(init.headers).has("mcp-session-id");
|
|
2232
|
+
if (response.status === 404 && hasSessionHeader) {
|
|
2233
|
+
this.client = null;
|
|
2234
|
+
throw new Error("MCP_SESSION_EXPIRED: Downstream session was not found on the server.");
|
|
2235
|
+
}
|
|
2236
|
+
return response;
|
|
2237
|
+
} finally {
|
|
2238
|
+
clearTimeout(timeoutId);
|
|
2239
|
+
}
|
|
2230
2240
|
}
|
|
2231
2241
|
};
|
|
2232
2242
|
if (type === "sse") {
|
|
@@ -2418,6 +2428,14 @@ var MCPClient = class {
|
|
|
2418
2428
|
* @throws {Error} When connection fails for other reasons
|
|
2419
2429
|
*/
|
|
2420
2430
|
async connect() {
|
|
2431
|
+
if (this.client?.transport) {
|
|
2432
|
+
this.transport = null;
|
|
2433
|
+
try {
|
|
2434
|
+
await this.client.close();
|
|
2435
|
+
} catch {
|
|
2436
|
+
}
|
|
2437
|
+
this.client = null;
|
|
2438
|
+
}
|
|
2421
2439
|
await this.initialize();
|
|
2422
2440
|
if (!this.client || !this.oauthProvider) {
|
|
2423
2441
|
const error = "Client or OAuth provider not initialized";
|
|
@@ -2796,7 +2814,7 @@ var MCPClient = class {
|
|
|
2796
2814
|
await this.oauthProvider.invalidateCredentials("all");
|
|
2797
2815
|
}
|
|
2798
2816
|
await sessions.delete(this.userId, this.sessionId);
|
|
2799
|
-
this.disconnect();
|
|
2817
|
+
await this.disconnect();
|
|
2800
2818
|
}
|
|
2801
2819
|
/**
|
|
2802
2820
|
* Checks if the client is currently connected to an MCP server
|
|
@@ -2806,10 +2824,21 @@ var MCPClient = class {
|
|
|
2806
2824
|
return this.client !== null;
|
|
2807
2825
|
}
|
|
2808
2826
|
/**
|
|
2809
|
-
* Disconnects from the MCP server and cleans up resources
|
|
2810
|
-
* Does not remove session from Redis
|
|
2827
|
+
* Disconnects from the MCP server and cleans up resources.
|
|
2828
|
+
* Does not remove session from Redis — use clearSession() for that.
|
|
2829
|
+
*
|
|
2830
|
+
* For Streamable HTTP sessions, sends an HTTP DELETE to the MCP endpoint
|
|
2831
|
+
* before closing, as recommended by the MCP Streamable HTTP spec
|
|
2832
|
+
* (section "Session Management", rule 5). This is best-effort — errors
|
|
2833
|
+
* (e.g. server already restarted, 404/405 responses) are silently ignored.
|
|
2811
2834
|
*/
|
|
2812
|
-
disconnect(
|
|
2835
|
+
async disconnect() {
|
|
2836
|
+
if (this.transport instanceof StreamableHTTPClientTransport) {
|
|
2837
|
+
try {
|
|
2838
|
+
await this.transport.terminateSession();
|
|
2839
|
+
} catch {
|
|
2840
|
+
}
|
|
2841
|
+
}
|
|
2813
2842
|
if (this.client) {
|
|
2814
2843
|
this.client.close();
|
|
2815
2844
|
}
|
|
@@ -2821,7 +2850,6 @@ var MCPClient = class {
|
|
|
2821
2850
|
type: "disconnected",
|
|
2822
2851
|
sessionId: this.sessionId,
|
|
2823
2852
|
serverId: this.serverId,
|
|
2824
|
-
reason,
|
|
2825
2853
|
timestamp: Date.now()
|
|
2826
2854
|
});
|
|
2827
2855
|
this._onObservabilityEvent.fire({
|
|
@@ -2830,9 +2858,7 @@ var MCPClient = class {
|
|
|
2830
2858
|
message: `Disconnected from ${this.serverId}`,
|
|
2831
2859
|
sessionId: this.sessionId,
|
|
2832
2860
|
serverId: this.serverId,
|
|
2833
|
-
payload: {
|
|
2834
|
-
reason: reason || "unknown"
|
|
2835
|
-
},
|
|
2861
|
+
payload: {},
|
|
2836
2862
|
timestamp: Date.now(),
|
|
2837
2863
|
id: nanoid()
|
|
2838
2864
|
});
|
|
@@ -2898,17 +2924,14 @@ var DEFAULT_RETRY_DELAY_MS = 1e3;
|
|
|
2898
2924
|
var CONNECTION_BATCH_SIZE = 5;
|
|
2899
2925
|
var MultiSessionClient = class {
|
|
2900
2926
|
/**
|
|
2901
|
-
*
|
|
2902
|
-
*
|
|
2903
|
-
* @param userId - A unique string identifying the user (e.g. user ID or email).
|
|
2904
|
-
* @param options - Optional tuning for connection timeout, retry count, and retry delay.
|
|
2905
|
-
* Falls back to sensible defaults if not provided.
|
|
2927
|
+
* @param userId - Unique identifier for the user (e.g. user ID or email).
|
|
2928
|
+
* @param options - Optional tuning and lifecycle hooks.
|
|
2906
2929
|
*/
|
|
2907
2930
|
constructor(userId, options = {}) {
|
|
2908
2931
|
__publicField(this, "clients", []);
|
|
2932
|
+
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
2909
2933
|
__publicField(this, "userId");
|
|
2910
2934
|
__publicField(this, "options");
|
|
2911
|
-
__publicField(this, "connectionPromises", /* @__PURE__ */ new Map());
|
|
2912
2935
|
this.userId = userId;
|
|
2913
2936
|
this.options = {
|
|
2914
2937
|
timeout: DEFAULT_TIMEOUT_MS,
|
|
@@ -2917,28 +2940,85 @@ var MultiSessionClient = class {
|
|
|
2917
2940
|
...options
|
|
2918
2941
|
};
|
|
2919
2942
|
}
|
|
2943
|
+
// -----------------------------------------------------------------------
|
|
2944
|
+
// Public API
|
|
2945
|
+
// -----------------------------------------------------------------------
|
|
2920
2946
|
/**
|
|
2921
|
-
* Fetches
|
|
2922
|
-
* ones that are ready to connect.
|
|
2947
|
+
* Fetches active sessions and establishes connections to all of them.
|
|
2923
2948
|
*
|
|
2924
|
-
*
|
|
2925
|
-
*
|
|
2926
|
-
*
|
|
2927
|
-
* and let the OAuth flow complete separately before we try to reconnect them.
|
|
2949
|
+
* Call this once after creating the client. On long-running servers you
|
|
2950
|
+
* can cache the `MultiSessionClient` instance and call `connect()` on
|
|
2951
|
+
* each request — already-connected sessions are skipped internally.
|
|
2928
2952
|
*/
|
|
2929
|
-
async
|
|
2930
|
-
const
|
|
2931
|
-
const
|
|
2953
|
+
async connect() {
|
|
2954
|
+
const sessions2 = await this.fetchActiveSessions();
|
|
2955
|
+
const activeSessionIds = new Set(sessions2.map((s) => s.sessionId));
|
|
2956
|
+
for (const client of this.clients) {
|
|
2957
|
+
if (!activeSessionIds.has(client.getSessionId())) {
|
|
2958
|
+
this.options.onSessionEvicted?.(client.getSessionId());
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
this.clients = this.clients.filter((c) => activeSessionIds.has(c.getSessionId()));
|
|
2962
|
+
await this.connectInBatches(sessions2);
|
|
2963
|
+
}
|
|
2964
|
+
/**
|
|
2965
|
+
* Drops all cached `MCPClient` instances and reconnects fresh from storage.
|
|
2966
|
+
*
|
|
2967
|
+
* Call this when downstream MCP servers have expired their transport sessions
|
|
2968
|
+
* (e.g. after a remote server restart) and subsequent tool calls return
|
|
2969
|
+
* "Session not found. Reconnect without session header." errors.
|
|
2970
|
+
*
|
|
2971
|
+
* OAuth tokens are preserved in the storage backend — no re-authentication
|
|
2972
|
+
* is required. Only the in-memory transport sessions are cleared.
|
|
2973
|
+
*/
|
|
2974
|
+
async reconnect() {
|
|
2975
|
+
await this.disconnect();
|
|
2976
|
+
await this.connect();
|
|
2977
|
+
}
|
|
2978
|
+
/**
|
|
2979
|
+
* Returns all currently connected `MCPClient` instances.
|
|
2980
|
+
*
|
|
2981
|
+
* Use this to enumerate available tools across all connected servers,
|
|
2982
|
+
* or to route a tool call to the right client by `serverId`.
|
|
2983
|
+
*/
|
|
2984
|
+
getClients() {
|
|
2985
|
+
return this.clients;
|
|
2986
|
+
}
|
|
2987
|
+
/**
|
|
2988
|
+
* Gracefully disconnects all active MCP clients and clears the internal list.
|
|
2989
|
+
*
|
|
2990
|
+
* For Streamable HTTP sessions, each client sends an HTTP DELETE to its MCP
|
|
2991
|
+
* endpoint per the spec before closing locally. All disconnects run in
|
|
2992
|
+
* parallel so shutdown is not serialised across many sessions.
|
|
2993
|
+
*
|
|
2994
|
+
* Call this during server shutdown or when a user logs out to free up
|
|
2995
|
+
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
2996
|
+
*/
|
|
2997
|
+
async disconnect() {
|
|
2998
|
+
await Promise.all(this.clients.map((client) => client.disconnect()));
|
|
2999
|
+
this.clients = [];
|
|
3000
|
+
}
|
|
3001
|
+
// -----------------------------------------------------------------------
|
|
3002
|
+
// Internals
|
|
3003
|
+
// -----------------------------------------------------------------------
|
|
3004
|
+
/**
|
|
3005
|
+
* Resolves the list of sessions to connect.
|
|
3006
|
+
*
|
|
3007
|
+
* Uses the custom `sessionProvider` when provided, otherwise falls back
|
|
3008
|
+
* to querying the storage backend via `sessions.list(userId)`.
|
|
3009
|
+
*/
|
|
3010
|
+
async fetchActiveSessions() {
|
|
3011
|
+
const sessionList = this.options.sessionProvider ? await this.options.sessionProvider() : await sessions.list(this.userId);
|
|
3012
|
+
return sessionList.filter(
|
|
2932
3013
|
(s) => s.serverId && s.serverUrl && s.callbackUrl && s.status === "active"
|
|
2933
3014
|
);
|
|
2934
|
-
return valid;
|
|
2935
3015
|
}
|
|
2936
3016
|
/**
|
|
2937
|
-
* Connects
|
|
3017
|
+
* Connects a list of sessions in controlled batches.
|
|
2938
3018
|
*
|
|
2939
|
-
* Batching prevents overwhelming the event loop or external servers when
|
|
2940
|
-
* has many active MCP sessions
|
|
2941
|
-
*
|
|
3019
|
+
* Batching prevents overwhelming the event loop or external servers when
|
|
3020
|
+
* a user has many active MCP sessions. Within each batch, sessions are
|
|
3021
|
+
* connected concurrently using `Promise.all`.
|
|
2942
3022
|
*/
|
|
2943
3023
|
async connectInBatches(sessions2) {
|
|
2944
3024
|
for (let i = 0; i < sessions2.length; i += CONNECTION_BATCH_SIZE) {
|
|
@@ -2947,19 +3027,25 @@ var MultiSessionClient = class {
|
|
|
2947
3027
|
}
|
|
2948
3028
|
}
|
|
2949
3029
|
/**
|
|
2950
|
-
* Connects a single session, with
|
|
3030
|
+
* Connects a single session, with deduplication to prevent race conditions.
|
|
2951
3031
|
*
|
|
2952
|
-
* - If a client for this session already exists and is connected, returns
|
|
2953
|
-
*
|
|
2954
|
-
*
|
|
2955
|
-
*
|
|
2956
|
-
*
|
|
2957
|
-
*
|
|
3032
|
+
* - If a client for this session already exists and is connected, returns
|
|
3033
|
+
* immediately.
|
|
3034
|
+
* - If the existing client entry is no longer connected (e.g. explicit
|
|
3035
|
+
* disconnect), it is evicted so a fresh transport is created.
|
|
3036
|
+
* - If a connection attempt for this session is already in-flight, the
|
|
3037
|
+
* existing promise is reused as a per-session mutex.
|
|
3038
|
+
* - On completion (success or failure), the promise is cleaned up from
|
|
3039
|
+
* the connectionPromises map.
|
|
2958
3040
|
*/
|
|
2959
3041
|
async connectSession(session) {
|
|
2960
|
-
const
|
|
2961
|
-
if (
|
|
2962
|
-
|
|
3042
|
+
const existing = this.clients.find((c) => c.getSessionId() === session.sessionId);
|
|
3043
|
+
if (existing) {
|
|
3044
|
+
if (existing.isConnected()) {
|
|
3045
|
+
return;
|
|
3046
|
+
}
|
|
3047
|
+
this.options.onSessionEvicted?.(existing.getSessionId());
|
|
3048
|
+
this.clients = this.clients.filter((c) => c !== existing);
|
|
2963
3049
|
}
|
|
2964
3050
|
if (this.connectionPromises.has(session.sessionId)) {
|
|
2965
3051
|
return this.connectionPromises.get(session.sessionId);
|
|
@@ -2973,22 +3059,19 @@ var MultiSessionClient = class {
|
|
|
2973
3059
|
}
|
|
2974
3060
|
}
|
|
2975
3061
|
/**
|
|
2976
|
-
*
|
|
3062
|
+
* Core connection loop for a single session with retry logic.
|
|
2977
3063
|
*
|
|
2978
|
-
*
|
|
2979
|
-
*
|
|
2980
|
-
*
|
|
2981
|
-
* 2. Races the connect call against a timeout promise — if the server doesn't respond
|
|
2982
|
-
* within `timeoutMs`, the attempt is aborted and counted as a failure.
|
|
2983
|
-
* 3. On success, replaces any stale client entry for this session in the `clients` array.
|
|
3064
|
+
* 1. Creates a fresh `MCPClient` from the session data.
|
|
3065
|
+
* 2. Races `client.connect()` against a timeout.
|
|
3066
|
+
* 3. On success, replaces any stale entry and fires `onSessionConnected`.
|
|
2984
3067
|
* 4. On failure, waits `retryDelay` ms before the next attempt.
|
|
2985
3068
|
*
|
|
2986
|
-
* If all attempts are exhausted, logs an error and returns silently
|
|
2987
|
-
*
|
|
3069
|
+
* If all attempts are exhausted, logs an error and returns silently so
|
|
3070
|
+
* a single bad server doesn't block the rest of the batch.
|
|
2988
3071
|
*/
|
|
2989
3072
|
async establishConnectionWithRetries(session) {
|
|
2990
|
-
const maxRetries = this.options.maxRetries
|
|
2991
|
-
const retryDelay = this.options.retryDelay
|
|
3073
|
+
const maxRetries = this.options.maxRetries;
|
|
3074
|
+
const retryDelay = this.options.retryDelay;
|
|
2992
3075
|
let lastError;
|
|
2993
3076
|
for (let attempt = 0; attempt <= maxRetries; attempt++) {
|
|
2994
3077
|
try {
|
|
@@ -3002,10 +3085,13 @@ var MultiSessionClient = class {
|
|
|
3002
3085
|
transportType: session.transportType,
|
|
3003
3086
|
headers: session.headers
|
|
3004
3087
|
});
|
|
3005
|
-
const timeoutMs = this.options.timeout
|
|
3088
|
+
const timeoutMs = this.options.timeout;
|
|
3006
3089
|
let timeoutTimer;
|
|
3007
3090
|
const timeoutPromise = new Promise((_, reject) => {
|
|
3008
|
-
timeoutTimer = setTimeout(
|
|
3091
|
+
timeoutTimer = setTimeout(
|
|
3092
|
+
() => reject(new Error(`Connection timed out after ${timeoutMs}ms`)),
|
|
3093
|
+
timeoutMs
|
|
3094
|
+
);
|
|
3009
3095
|
});
|
|
3010
3096
|
try {
|
|
3011
3097
|
await Promise.race([client.connect(), timeoutPromise]);
|
|
@@ -3014,6 +3100,7 @@ var MultiSessionClient = class {
|
|
|
3014
3100
|
}
|
|
3015
3101
|
this.clients = this.clients.filter((c) => c.getSessionId() !== session.sessionId);
|
|
3016
3102
|
this.clients.push(client);
|
|
3103
|
+
this.options.onSessionConnected?.(session.sessionId, client);
|
|
3017
3104
|
return;
|
|
3018
3105
|
} catch (error) {
|
|
3019
3106
|
lastError = error;
|
|
@@ -3022,38 +3109,11 @@ var MultiSessionClient = class {
|
|
|
3022
3109
|
}
|
|
3023
3110
|
}
|
|
3024
3111
|
}
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
*
|
|
3031
|
-
* Call this once after creating the client. On traditional servers, you can
|
|
3032
|
-
* cache the `MultiSessionClient` instance after calling `connect()` to avoid
|
|
3033
|
-
* re-fetching and re-connecting on every request.
|
|
3034
|
-
*/
|
|
3035
|
-
async connect() {
|
|
3036
|
-
const sessions2 = await this.getActiveSessions();
|
|
3037
|
-
await this.connectInBatches(sessions2);
|
|
3038
|
-
}
|
|
3039
|
-
/**
|
|
3040
|
-
* Returns all currently connected `MCPClient` instances.
|
|
3041
|
-
*
|
|
3042
|
-
* Use this to enumerate available tools across all connected servers,
|
|
3043
|
-
* or to route a tool call to the right client by `serverId`.
|
|
3044
|
-
*/
|
|
3045
|
-
getClients() {
|
|
3046
|
-
return this.clients;
|
|
3047
|
-
}
|
|
3048
|
-
/**
|
|
3049
|
-
* Gracefully disconnects all active MCP clients and clears the internal client list.
|
|
3050
|
-
*
|
|
3051
|
-
* Call this during server shutdown or when a user logs out to free up
|
|
3052
|
-
* underlying transport resources (SSE streams, HTTP connections, etc.).
|
|
3053
|
-
*/
|
|
3054
|
-
disconnect() {
|
|
3055
|
-
this.clients.forEach((client) => client.disconnect());
|
|
3056
|
-
this.clients = [];
|
|
3112
|
+
this.options.onSessionFailed?.(session.sessionId, lastError);
|
|
3113
|
+
console.error(
|
|
3114
|
+
`[MultiSessionClient] Failed to connect to session ${session.sessionId} after ${maxRetries + 1} attempts:`,
|
|
3115
|
+
lastError
|
|
3116
|
+
);
|
|
3057
3117
|
}
|
|
3058
3118
|
};
|
|
3059
3119
|
|
|
@@ -3282,7 +3342,6 @@ var SSEConnectionManager = class {
|
|
|
3282
3342
|
const client = this.clients.get(sessionId);
|
|
3283
3343
|
if (client) {
|
|
3284
3344
|
await client.clearSession();
|
|
3285
|
-
client.disconnect();
|
|
3286
3345
|
this.clients.delete(sessionId);
|
|
3287
3346
|
} else {
|
|
3288
3347
|
await sessions.delete(this.userId, sessionId);
|
|
@@ -3491,14 +3550,14 @@ var SSEConnectionManager = class {
|
|
|
3491
3550
|
/**
|
|
3492
3551
|
* Cleanup and close all connections
|
|
3493
3552
|
*/
|
|
3494
|
-
dispose() {
|
|
3553
|
+
async dispose() {
|
|
3495
3554
|
this.isActive = false;
|
|
3496
3555
|
if (this.heartbeatTimer) {
|
|
3497
3556
|
clearInterval(this.heartbeatTimer);
|
|
3498
3557
|
}
|
|
3499
|
-
|
|
3500
|
-
client.disconnect()
|
|
3501
|
-
|
|
3558
|
+
await Promise.all(
|
|
3559
|
+
Array.from(this.clients.values()).map((client) => client.disconnect())
|
|
3560
|
+
);
|
|
3502
3561
|
this.clients.clear();
|
|
3503
3562
|
}
|
|
3504
3563
|
};
|