@mcp-ts/sdk 1.3.6 → 1.3.9
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/LICENSE +21 -21
- package/README.md +398 -404
- package/dist/adapters/agui-adapter.d.mts +1 -1
- package/dist/adapters/agui-adapter.d.ts +1 -1
- package/dist/adapters/agui-adapter.js +2 -2
- package/dist/adapters/agui-adapter.js.map +1 -1
- package/dist/adapters/agui-adapter.mjs +2 -2
- package/dist/adapters/agui-adapter.mjs.map +1 -1
- package/dist/adapters/agui-middleware.d.mts +1 -1
- package/dist/adapters/agui-middleware.d.ts +1 -1
- package/dist/adapters/agui-middleware.js.map +1 -1
- package/dist/adapters/agui-middleware.mjs.map +1 -1
- package/dist/adapters/ai-adapter.d.mts +1 -1
- package/dist/adapters/ai-adapter.d.ts +1 -1
- package/dist/adapters/ai-adapter.js +1 -1
- package/dist/adapters/ai-adapter.js.map +1 -1
- package/dist/adapters/ai-adapter.mjs +1 -1
- package/dist/adapters/ai-adapter.mjs.map +1 -1
- package/dist/adapters/langchain-adapter.d.mts +1 -1
- package/dist/adapters/langchain-adapter.d.ts +1 -1
- package/dist/adapters/langchain-adapter.js +1 -1
- package/dist/adapters/langchain-adapter.js.map +1 -1
- package/dist/adapters/langchain-adapter.mjs +1 -1
- package/dist/adapters/langchain-adapter.mjs.map +1 -1
- package/dist/adapters/mastra-adapter.d.mts +1 -1
- package/dist/adapters/mastra-adapter.d.ts +1 -1
- package/dist/adapters/mastra-adapter.js +1 -1
- package/dist/adapters/mastra-adapter.js.map +1 -1
- package/dist/adapters/mastra-adapter.mjs +1 -1
- package/dist/adapters/mastra-adapter.mjs.map +1 -1
- package/dist/bin/mcp-ts.js +0 -0
- package/dist/bin/mcp-ts.js.map +1 -1
- package/dist/bin/mcp-ts.mjs +0 -0
- package/dist/bin/mcp-ts.mjs.map +1 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/index.mjs.map +1 -1
- package/dist/client/react.d.mts +2 -2
- package/dist/client/react.d.ts +2 -2
- package/dist/client/react.js +25 -2
- package/dist/client/react.js.map +1 -1
- package/dist/client/react.mjs +26 -3
- package/dist/client/react.mjs.map +1 -1
- package/dist/client/vue.js.map +1 -1
- package/dist/client/vue.mjs.map +1 -1
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +134 -71
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +134 -71
- package/dist/index.mjs.map +1 -1
- package/dist/{multi-session-client-BYLarghq.d.ts → multi-session-client-CHE8QpVE.d.ts} +75 -5
- package/dist/{multi-session-client-CzhMkE0k.d.mts → multi-session-client-CQsRbxYI.d.mts} +75 -5
- package/dist/server/index.d.mts +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.js +134 -71
- package/dist/server/index.js.map +1 -1
- package/dist/server/index.mjs +134 -71
- package/dist/server/index.mjs.map +1 -1
- package/dist/shared/index.js +10 -2
- package/dist/shared/index.js.map +1 -1
- package/dist/shared/index.mjs +10 -2
- package/dist/shared/index.mjs.map +1 -1
- package/package.json +185 -185
- package/src/adapters/agui-adapter.ts +222 -222
- package/src/adapters/agui-middleware.ts +382 -382
- package/src/adapters/ai-adapter.ts +115 -115
- package/src/adapters/langchain-adapter.ts +127 -127
- package/src/adapters/mastra-adapter.ts +126 -126
- package/src/bin/mcp-ts.ts +102 -102
- package/src/client/core/app-host.ts +417 -417
- package/src/client/core/sse-client.ts +371 -371
- package/src/client/core/types.ts +31 -31
- package/src/client/index.ts +27 -27
- package/src/client/react/index.ts +16 -16
- package/src/client/react/use-app-host.ts +73 -73
- package/src/client/react/use-mcp-apps.tsx +247 -214
- package/src/client/react/use-mcp.ts +641 -641
- package/src/client/vue/index.ts +10 -10
- package/src/client/vue/use-mcp.ts +617 -617
- package/src/index.ts +11 -11
- package/src/server/handlers/nextjs-handler.ts +204 -204
- package/src/server/handlers/sse-handler.ts +631 -631
- package/src/server/index.ts +57 -57
- package/src/server/mcp/multi-session-client.ts +228 -132
- package/src/server/mcp/oauth-client.ts +1188 -1188
- package/src/server/mcp/storage-oauth-provider.ts +272 -272
- package/src/server/storage/file-backend.ts +157 -170
- package/src/server/storage/index.ts +176 -175
- package/src/server/storage/memory-backend.ts +123 -136
- package/src/server/storage/redis-backend.ts +276 -289
- package/src/server/storage/redis.ts +160 -160
- package/src/server/storage/sqlite-backend.ts +182 -186
- package/src/server/storage/supabase-backend.ts +228 -227
- package/src/server/storage/types.ts +116 -116
- package/src/shared/constants.ts +29 -29
- package/src/shared/errors.ts +133 -133
- package/src/shared/event-routing.ts +28 -28
- package/src/shared/events.ts +180 -180
- package/src/shared/index.ts +75 -75
- package/src/shared/tool-utils.ts +61 -61
- package/src/shared/types.ts +282 -282
- package/src/shared/utils.ts +38 -16
- package/supabase/migrations/20260330195700_install_mcp_sessions.sql +84 -84
|
@@ -1,289 +1,276 @@
|
|
|
1
|
-
|
|
2
|
-
import
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
sessionKey
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
return
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
} catch (error) {
|
|
194
|
-
console.error(
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
async
|
|
238
|
-
try {
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
const
|
|
242
|
-
|
|
243
|
-
await this.redis.
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
} catch (error) {
|
|
278
|
-
console.error('[RedisStorage] Failed to cleanup expired sessions:', error);
|
|
279
|
-
}
|
|
280
|
-
}
|
|
281
|
-
|
|
282
|
-
async disconnect(): Promise<void> {
|
|
283
|
-
try {
|
|
284
|
-
await this.redis.quit();
|
|
285
|
-
} catch (error) {
|
|
286
|
-
console.error('[RedisStorage] Failed to disconnect:', error);
|
|
287
|
-
}
|
|
288
|
-
}
|
|
289
|
-
}
|
|
1
|
+
import type { Redis } from 'ioredis';
|
|
2
|
+
import { StorageBackend, SessionData } from './types.js';
|
|
3
|
+
import { SESSION_TTL_SECONDS } from '../../shared/constants.js';
|
|
4
|
+
import { generateSessionId } from '../../shared/utils.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Redis implementation of StorageBackend
|
|
8
|
+
*/
|
|
9
|
+
export class RedisStorageBackend implements StorageBackend {
|
|
10
|
+
private readonly DEFAULT_TTL = SESSION_TTL_SECONDS;
|
|
11
|
+
private readonly KEY_PREFIX = 'mcp:session:';
|
|
12
|
+
private readonly IDENTITY_KEY_PREFIX = 'mcp:identity:';
|
|
13
|
+
private readonly IDENTITY_KEY_SUFFIX = ':sessions';
|
|
14
|
+
|
|
15
|
+
constructor(private redis: Redis) { }
|
|
16
|
+
|
|
17
|
+
async init(): Promise<void> {
|
|
18
|
+
try {
|
|
19
|
+
await this.redis.ping();
|
|
20
|
+
console.log('[mcp-ts][Storage] Redis: ✓ Connected to server.');
|
|
21
|
+
} catch (error: any) {
|
|
22
|
+
throw new Error(`[RedisStorage] Failed to connect to Redis: ${error.message}`);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Generates Redis key for a specific session
|
|
28
|
+
* @private
|
|
29
|
+
*/
|
|
30
|
+
private getSessionKey(identity: string, sessionId: string): string {
|
|
31
|
+
return `${this.KEY_PREFIX}${identity}:${sessionId}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Generates Redis key for tracking all sessions for an identity
|
|
36
|
+
* @private
|
|
37
|
+
*/
|
|
38
|
+
private getIdentityKey(identity: string): string {
|
|
39
|
+
return `${this.IDENTITY_KEY_PREFIX}${identity}${this.IDENTITY_KEY_SUFFIX}`;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
private parseIdentityFromKey(identityKey: string): string {
|
|
43
|
+
return identityKey.slice(
|
|
44
|
+
this.IDENTITY_KEY_PREFIX.length,
|
|
45
|
+
identityKey.length - this.IDENTITY_KEY_SUFFIX.length
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
private async scanKeys(pattern: string): Promise<string[]> {
|
|
50
|
+
const redis = this.redis as Redis & {
|
|
51
|
+
scan?: (cursor: string, ...args: Array<string | number>) => Promise<[string, string[]]>;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (typeof redis.scan !== 'function') {
|
|
55
|
+
return await this.redis.keys(pattern);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const keys = new Set<string>();
|
|
59
|
+
let cursor = '0';
|
|
60
|
+
|
|
61
|
+
try {
|
|
62
|
+
do {
|
|
63
|
+
const [nextCursor, batch] = await redis.scan(cursor, 'MATCH', pattern, 'COUNT', 100);
|
|
64
|
+
cursor = nextCursor;
|
|
65
|
+
for (const key of batch) {
|
|
66
|
+
keys.add(key);
|
|
67
|
+
}
|
|
68
|
+
} while (cursor !== '0');
|
|
69
|
+
} catch (error) {
|
|
70
|
+
console.warn('[RedisStorage] SCAN failed, falling back to KEYS:', error);
|
|
71
|
+
return await this.redis.keys(pattern);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return Array.from(keys);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
generateSessionId(): string {
|
|
78
|
+
return generateSessionId();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async createSession(session: SessionData, ttl?: number): Promise<void> {
|
|
82
|
+
const { sessionId, identity } = session;
|
|
83
|
+
if (!sessionId || !identity) throw new Error('identity and sessionId required');
|
|
84
|
+
|
|
85
|
+
const sessionKey = this.getSessionKey(identity, sessionId);
|
|
86
|
+
const identityKey = this.getIdentityKey(identity);
|
|
87
|
+
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
88
|
+
|
|
89
|
+
/** ioredis syntax: set(key, val, 'EX', ttl, 'NX') */
|
|
90
|
+
const result = await this.redis.set(
|
|
91
|
+
sessionKey,
|
|
92
|
+
JSON.stringify(session),
|
|
93
|
+
'EX',
|
|
94
|
+
effectiveTtl,
|
|
95
|
+
'NX'
|
|
96
|
+
);
|
|
97
|
+
|
|
98
|
+
if (result !== 'OK') {
|
|
99
|
+
throw new Error(`Session ${sessionId} already exists`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
await this.redis.sadd(identityKey, sessionId);
|
|
103
|
+
}
|
|
104
|
+
async updateSession(identity: string, sessionId: string, data: Partial<SessionData>, ttl?: number): Promise<void> {
|
|
105
|
+
const sessionKey = this.getSessionKey(identity, sessionId);
|
|
106
|
+
const effectiveTtl = ttl ?? this.DEFAULT_TTL;
|
|
107
|
+
|
|
108
|
+
/** Lua script for atomic parsing, merging, and saving */
|
|
109
|
+
const script = `
|
|
110
|
+
local currentStr = redis.call("GET", KEYS[1])
|
|
111
|
+
if not currentStr then
|
|
112
|
+
return 0
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
local current = cjson.decode(currentStr)
|
|
116
|
+
local updates = cjson.decode(ARGV[1])
|
|
117
|
+
|
|
118
|
+
for k,v in pairs(updates) do
|
|
119
|
+
current[k] = v
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
redis.call("SET", KEYS[1], cjson.encode(current), "EX", ARGV[2])
|
|
123
|
+
return 1
|
|
124
|
+
`;
|
|
125
|
+
|
|
126
|
+
const result = await this.redis.eval(
|
|
127
|
+
script,
|
|
128
|
+
1,
|
|
129
|
+
sessionKey,
|
|
130
|
+
JSON.stringify(data),
|
|
131
|
+
effectiveTtl
|
|
132
|
+
);
|
|
133
|
+
|
|
134
|
+
if (result === 0) {
|
|
135
|
+
throw new Error(`Session ${sessionId} not found for identity ${identity}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
async getSession(identity: string, sessionId: string): Promise<SessionData | null> {
|
|
140
|
+
try {
|
|
141
|
+
const sessionKey = this.getSessionKey(identity, sessionId);
|
|
142
|
+
const sessionDataStr = await this.redis.get(sessionKey);
|
|
143
|
+
|
|
144
|
+
if (!sessionDataStr) {
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const sessionData: SessionData = JSON.parse(sessionDataStr);
|
|
149
|
+
return sessionData;
|
|
150
|
+
} catch (error) {
|
|
151
|
+
console.error('[RedisStorage] Failed to get session:', error);
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
async getIdentityMcpSessions(identity: string): Promise<string[]> {
|
|
157
|
+
const sessions = await this.getIdentitySessionsData(identity);
|
|
158
|
+
return sessions.map((session) => session.sessionId);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
async getIdentitySessionsData(identity: string): Promise<SessionData[]> {
|
|
162
|
+
try {
|
|
163
|
+
const identityKey = this.getIdentityKey(identity);
|
|
164
|
+
const sessionIds = await this.redis.smembers(identityKey);
|
|
165
|
+
if (sessionIds.length === 0) return [];
|
|
166
|
+
|
|
167
|
+
const results = await Promise.all(
|
|
168
|
+
sessionIds.map(async (sessionId) => {
|
|
169
|
+
const data = await this.redis.get(this.getSessionKey(identity, sessionId));
|
|
170
|
+
return data ? (JSON.parse(data) as SessionData) : null;
|
|
171
|
+
})
|
|
172
|
+
);
|
|
173
|
+
|
|
174
|
+
const staleSessionIds = sessionIds.filter((_, index) => results[index] === null);
|
|
175
|
+
if (staleSessionIds.length > 0) {
|
|
176
|
+
await this.redis.srem(identityKey, ...staleSessionIds);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return results.filter((session): session is SessionData => session !== null);
|
|
180
|
+
} catch (error) {
|
|
181
|
+
console.error(`[RedisStorage] Failed to get session data for ${identity}:`, error);
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
async removeSession(identity: string, sessionId: string): Promise<void> {
|
|
187
|
+
try {
|
|
188
|
+
const sessionKey = this.getSessionKey(identity, sessionId);
|
|
189
|
+
const identityKey = this.getIdentityKey(identity);
|
|
190
|
+
|
|
191
|
+
await this.redis.srem(identityKey, sessionId);
|
|
192
|
+
await this.redis.del(sessionKey);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
console.error('[RedisStorage] Failed to remove session:', error);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async getAllSessionIds(): Promise<string[]> {
|
|
199
|
+
try {
|
|
200
|
+
const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
|
|
201
|
+
const sessions = await Promise.all(
|
|
202
|
+
keys.map(async (key) => {
|
|
203
|
+
const data = await this.redis.get(key);
|
|
204
|
+
if (!data) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
try {
|
|
209
|
+
return (JSON.parse(data) as SessionData).sessionId;
|
|
210
|
+
} catch (error) {
|
|
211
|
+
console.error('[RedisStorage] Failed to parse session while listing all session IDs:', error);
|
|
212
|
+
return null;
|
|
213
|
+
}
|
|
214
|
+
})
|
|
215
|
+
);
|
|
216
|
+
|
|
217
|
+
return sessions.filter((sessionId): sessionId is string => sessionId !== null);
|
|
218
|
+
} catch (error) {
|
|
219
|
+
console.error('[RedisStorage] Failed to get all sessions:', error);
|
|
220
|
+
return [];
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
async clearAll(): Promise<void> {
|
|
225
|
+
try {
|
|
226
|
+
const keys = await this.scanKeys(`${this.KEY_PREFIX}*`);
|
|
227
|
+
const identityKeys = await this.scanKeys(`${this.IDENTITY_KEY_PREFIX}*${this.IDENTITY_KEY_SUFFIX}`);
|
|
228
|
+
const allKeys = [...keys, ...identityKeys];
|
|
229
|
+
if (allKeys.length > 0) {
|
|
230
|
+
await this.redis.del(...allKeys);
|
|
231
|
+
}
|
|
232
|
+
} catch (error) {
|
|
233
|
+
console.error('[RedisStorage] Failed to clear sessions:', error);
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async cleanupExpiredSessions(): Promise<void> {
|
|
238
|
+
try {
|
|
239
|
+
const identityKeys = await this.scanKeys(`${this.IDENTITY_KEY_PREFIX}*${this.IDENTITY_KEY_SUFFIX}`);
|
|
240
|
+
|
|
241
|
+
for (const identityKey of identityKeys) {
|
|
242
|
+
const identity = this.parseIdentityFromKey(identityKey);
|
|
243
|
+
const sessionIds = await this.redis.smembers(identityKey);
|
|
244
|
+
|
|
245
|
+
if (sessionIds.length === 0) {
|
|
246
|
+
await this.redis.del(identityKey);
|
|
247
|
+
continue;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
const existenceChecks = await Promise.all(
|
|
251
|
+
sessionIds.map((sessionId) => this.redis.exists(this.getSessionKey(identity, sessionId)))
|
|
252
|
+
);
|
|
253
|
+
|
|
254
|
+
const staleSessionIds = sessionIds.filter((_, index) => existenceChecks[index] === 0);
|
|
255
|
+
if (staleSessionIds.length > 0) {
|
|
256
|
+
await this.redis.srem(identityKey, ...staleSessionIds);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
const remainingCount = await this.redis.scard(identityKey);
|
|
260
|
+
if (remainingCount === 0) {
|
|
261
|
+
await this.redis.del(identityKey);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
} catch (error) {
|
|
265
|
+
console.error('[RedisStorage] Failed to cleanup expired sessions:', error);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
async disconnect(): Promise<void> {
|
|
270
|
+
try {
|
|
271
|
+
await this.redis.quit();
|
|
272
|
+
} catch (error) {
|
|
273
|
+
console.error('[RedisStorage] Failed to disconnect:', error);
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|