n8n-mcp 2.19.5 → 2.19.6

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.
@@ -35,7 +35,7 @@ function extractMultiTenantHeaders(req) {
35
35
  };
36
36
  }
37
37
  class SingleSessionHTTPServer {
38
- constructor(options = {}) {
38
+ constructor() {
39
39
  this.transports = {};
40
40
  this.servers = {};
41
41
  this.sessionMetadata = {};
@@ -46,17 +46,7 @@ class SingleSessionHTTPServer {
46
46
  this.sessionTimeout = 30 * 60 * 1000;
47
47
  this.authToken = null;
48
48
  this.cleanupTimer = null;
49
- this.cleanupInProgress = new Set();
50
- this.isShuttingDown = false;
51
49
  this.validateEnvironment();
52
- this.onSessionNotFound = options.onSessionNotFound;
53
- this.sessionRestorationTimeout = options.sessionRestorationTimeout || 5000;
54
- this.sessionEvents = options.sessionEvents;
55
- this.sessionRestorationRetries = options.sessionRestorationRetries ?? 0;
56
- this.sessionRestorationRetryDelay = options.sessionRestorationRetryDelay || 100;
57
- if (options.sessionTimeout) {
58
- this.sessionTimeout = options.sessionTimeout;
59
- }
60
50
  this.startSessionCleanup();
61
51
  }
62
52
  startSessionCleanup() {
@@ -74,7 +64,7 @@ class SingleSessionHTTPServer {
74
64
  sessionTimeout: this.sessionTimeout / 1000 / 60
75
65
  });
76
66
  }
77
- async cleanupExpiredSessions() {
67
+ cleanupExpiredSessions() {
78
68
  const now = Date.now();
79
69
  const expiredSessions = [];
80
70
  for (const sessionId in this.sessionMetadata) {
@@ -89,100 +79,29 @@ class SingleSessionHTTPServer {
89
79
  logger_1.logger.debug('Cleaned orphaned session context', { sessionId });
90
80
  }
91
81
  }
92
- for (const sessionId in this.transports) {
93
- if (!this.sessionMetadata[sessionId]) {
94
- logger_1.logger.warn('Orphaned transport detected, cleaning up', { sessionId });
95
- try {
96
- await this.removeSession(sessionId, 'orphaned_transport');
97
- }
98
- catch (err) {
99
- logger_1.logger.error('Error cleaning orphaned transport', {
100
- sessionId,
101
- error: err instanceof Error ? err.message : String(err)
102
- });
103
- }
104
- }
105
- }
106
- for (const sessionId in this.servers) {
107
- if (!this.sessionMetadata[sessionId]) {
108
- logger_1.logger.warn('Orphaned server detected, cleaning up', { sessionId });
109
- delete this.servers[sessionId];
110
- logger_1.logger.debug('Cleaned orphaned server', { sessionId });
111
- }
112
- }
113
- let successCount = 0;
114
- let failureCount = 0;
115
82
  for (const sessionId of expiredSessions) {
116
- try {
117
- await this.emitEvent('onSessionExpired', sessionId);
118
- await this.removeSession(sessionId, 'expired');
119
- successCount++;
120
- }
121
- catch (error) {
122
- failureCount++;
123
- logger_1.logger.error('Failed to cleanup expired session (isolated)', {
124
- sessionId,
125
- error: error instanceof Error ? error.message : String(error),
126
- stack: error instanceof Error ? error.stack : undefined
127
- });
128
- }
83
+ this.removeSession(sessionId, 'expired');
129
84
  }
130
85
  if (expiredSessions.length > 0) {
131
- logger_1.logger.info('Expired session cleanup completed', {
132
- total: expiredSessions.length,
133
- successful: successCount,
134
- failed: failureCount,
86
+ logger_1.logger.info('Cleaned up expired sessions', {
87
+ removed: expiredSessions.length,
135
88
  remaining: this.getActiveSessionCount()
136
89
  });
137
90
  }
138
91
  }
139
- async safeCloseTransport(sessionId) {
140
- const transport = this.transports[sessionId];
141
- if (!transport)
142
- return;
143
- try {
144
- transport.onclose = undefined;
145
- transport.onerror = undefined;
146
- const closePromise = transport.close();
147
- const timeoutPromise = new Promise((_, reject) => setTimeout(() => reject(new Error('Transport close timeout')), 3000));
148
- await Promise.race([closePromise, timeoutPromise]);
149
- logger_1.logger.debug('Transport closed safely', { sessionId });
150
- }
151
- catch (error) {
152
- logger_1.logger.warn('Transport close error (non-fatal)', {
153
- sessionId,
154
- error: error instanceof Error ? error.message : String(error)
155
- });
156
- }
157
- }
158
92
  async removeSession(sessionId, reason) {
159
- if (this.cleanupInProgress.has(sessionId)) {
160
- logger_1.logger.debug('Cleanup already in progress, skipping duplicate', {
161
- sessionId,
162
- reason
163
- });
164
- return;
165
- }
166
- this.cleanupInProgress.add(sessionId);
167
93
  try {
168
94
  if (this.transports[sessionId]) {
169
- await this.safeCloseTransport(sessionId);
95
+ await this.transports[sessionId].close();
170
96
  delete this.transports[sessionId];
171
97
  }
172
98
  delete this.servers[sessionId];
173
99
  delete this.sessionMetadata[sessionId];
174
100
  delete this.sessionContexts[sessionId];
175
- logger_1.logger.info('Session removed successfully', { sessionId, reason });
101
+ logger_1.logger.info('Session removed', { sessionId, reason });
176
102
  }
177
103
  catch (error) {
178
- logger_1.logger.warn('Error during session removal', {
179
- sessionId,
180
- reason,
181
- error: error instanceof Error ? error.message : String(error)
182
- });
183
- }
184
- finally {
185
- this.cleanupInProgress.delete(sessionId);
104
+ logger_1.logger.warn('Error removing session', { sessionId, reason, error });
186
105
  }
187
106
  }
188
107
  getActiveSessionCount() {
@@ -192,16 +111,7 @@ class SingleSessionHTTPServer {
192
111
  return this.getActiveSessionCount() < MAX_SESSIONS;
193
112
  }
194
113
  isValidSessionId(sessionId) {
195
- if (!sessionId || typeof sessionId !== 'string') {
196
- return false;
197
- }
198
- if (!/^[a-zA-Z0-9_-]+$/.test(sessionId)) {
199
- return false;
200
- }
201
- if (sessionId.length > 100) {
202
- return false;
203
- }
204
- return true;
114
+ return Boolean(sessionId && sessionId.length > 0);
205
115
  }
206
116
  sanitizeErrorForClient(error) {
207
117
  const isProduction = process.env.NODE_ENV === 'production';
@@ -228,12 +138,6 @@ class SingleSessionHTTPServer {
228
138
  updateSessionAccess(sessionId) {
229
139
  if (this.sessionMetadata[sessionId]) {
230
140
  this.sessionMetadata[sessionId].lastAccess = new Date();
231
- this.emitEvent('onSessionAccessed', sessionId).catch(err => {
232
- logger_1.logger.error('Failed to emit onSessionAccessed event (non-blocking)', {
233
- sessionId,
234
- error: err instanceof Error ? err.message : String(err)
235
- });
236
- });
237
141
  }
238
142
  }
239
143
  async switchSessionContext(sessionId, newContext) {
@@ -265,223 +169,6 @@ class SingleSessionHTTPServer {
265
169
  }
266
170
  }
267
171
  }
268
- timeout(ms) {
269
- return new Promise((_, reject) => {
270
- setTimeout(() => {
271
- const error = new Error(`Operation timed out after ${ms}ms`);
272
- error.name = 'TimeoutError';
273
- reject(error);
274
- }, ms);
275
- });
276
- }
277
- async emitEvent(eventName, ...args) {
278
- const handler = this.sessionEvents?.[eventName];
279
- if (!handler)
280
- return;
281
- try {
282
- await Promise.resolve(handler(...args));
283
- }
284
- catch (error) {
285
- logger_1.logger.error(`Session event handler failed: ${eventName}`, {
286
- error: error instanceof Error ? error.message : String(error),
287
- sessionId: args[0]
288
- });
289
- }
290
- }
291
- async restoreSessionWithRetry(sessionId) {
292
- if (!this.onSessionNotFound) {
293
- throw new Error('onSessionNotFound hook not configured');
294
- }
295
- const maxRetries = this.sessionRestorationRetries;
296
- const retryDelay = this.sessionRestorationRetryDelay;
297
- const overallTimeout = this.sessionRestorationTimeout;
298
- const startTime = Date.now();
299
- for (let attempt = 0; attempt <= maxRetries; attempt++) {
300
- try {
301
- const remainingTime = overallTimeout - (Date.now() - startTime);
302
- if (remainingTime <= 0) {
303
- const error = new Error(`Session restoration timed out after ${overallTimeout}ms`);
304
- error.name = 'TimeoutError';
305
- throw error;
306
- }
307
- if (attempt > 0) {
308
- logger_1.logger.debug('Retrying session restoration', {
309
- sessionId,
310
- attempt: attempt,
311
- maxRetries: maxRetries,
312
- remainingTime: remainingTime + 'ms'
313
- });
314
- }
315
- const context = await Promise.race([
316
- this.onSessionNotFound(sessionId),
317
- this.timeout(remainingTime)
318
- ]);
319
- if (attempt > 0) {
320
- logger_1.logger.info('Session restoration succeeded after retry', {
321
- sessionId,
322
- attempts: attempt + 1
323
- });
324
- }
325
- return context;
326
- }
327
- catch (error) {
328
- if (error instanceof Error && error.name === 'TimeoutError') {
329
- logger_1.logger.error('Session restoration timeout (no retry)', {
330
- sessionId,
331
- timeout: overallTimeout
332
- });
333
- throw error;
334
- }
335
- if (attempt === maxRetries) {
336
- logger_1.logger.error('Session restoration failed after all retries', {
337
- sessionId,
338
- attempts: attempt + 1,
339
- error: error instanceof Error ? error.message : String(error)
340
- });
341
- throw error;
342
- }
343
- logger_1.logger.warn('Session restoration failed, will retry', {
344
- sessionId,
345
- attempt: attempt + 1,
346
- maxRetries: maxRetries,
347
- error: error instanceof Error ? error.message : String(error),
348
- nextRetryIn: retryDelay + 'ms'
349
- });
350
- await new Promise(resolve => setTimeout(resolve, retryDelay));
351
- }
352
- }
353
- throw new Error('Unexpected state in restoreSessionWithRetry');
354
- }
355
- createSession(instanceContext, sessionId, waitForConnection = false) {
356
- const id = sessionId || this.generateSessionId(instanceContext);
357
- if (this.transports[id]) {
358
- logger_1.logger.debug('Session already exists, skipping creation (idempotent)', {
359
- sessionId: id
360
- });
361
- return waitForConnection ? Promise.resolve(id) : id;
362
- }
363
- if (sessionId && !this.isValidSessionId(sessionId)) {
364
- logger_1.logger.error('Invalid session ID format during creation', { sessionId });
365
- throw new Error('Invalid session ID format');
366
- }
367
- if (!this.sessionMetadata[id]) {
368
- this.sessionMetadata[id] = {
369
- lastAccess: new Date(),
370
- createdAt: new Date()
371
- };
372
- this.sessionContexts[id] = instanceContext;
373
- }
374
- const server = new server_1.N8NDocumentationMCPServer(instanceContext);
375
- const transport = new streamableHttp_js_1.StreamableHTTPServerTransport({
376
- sessionIdGenerator: () => id,
377
- onsessioninitialized: (initializedSessionId) => {
378
- logger_1.logger.info('Session initialized during explicit creation', {
379
- sessionId: initializedSessionId
380
- });
381
- }
382
- });
383
- this.transports[id] = transport;
384
- this.servers[id] = server;
385
- transport.onclose = () => {
386
- if (transport.sessionId) {
387
- if (this.isShuttingDown) {
388
- logger_1.logger.debug('Ignoring transport close event during shutdown', {
389
- sessionId: transport.sessionId
390
- });
391
- return;
392
- }
393
- logger_1.logger.info('Transport closed during createSession, cleaning up', {
394
- sessionId: transport.sessionId
395
- });
396
- this.removeSession(transport.sessionId, 'transport_closed').catch(err => {
397
- logger_1.logger.error('Error during transport close cleanup', {
398
- sessionId: transport.sessionId,
399
- error: err instanceof Error ? err.message : String(err)
400
- });
401
- });
402
- }
403
- };
404
- transport.onerror = (error) => {
405
- if (transport.sessionId) {
406
- if (this.isShuttingDown) {
407
- logger_1.logger.debug('Ignoring transport error event during shutdown', {
408
- sessionId: transport.sessionId
409
- });
410
- return;
411
- }
412
- logger_1.logger.error('Transport error during createSession', {
413
- sessionId: transport.sessionId,
414
- error: error.message
415
- });
416
- this.removeSession(transport.sessionId, 'transport_error').catch(err => {
417
- logger_1.logger.error('Error during transport error cleanup', { error: err });
418
- });
419
- }
420
- };
421
- const initializeSession = async () => {
422
- try {
423
- await server.initialized;
424
- await server.connect(transport);
425
- if (waitForConnection) {
426
- logger_1.logger.info('Session created and connected successfully', {
427
- sessionId: id,
428
- hasInstanceContext: !!instanceContext,
429
- instanceId: instanceContext?.instanceId
430
- });
431
- }
432
- else {
433
- logger_1.logger.info('Session created successfully (connecting server to transport)', {
434
- sessionId: id,
435
- hasInstanceContext: !!instanceContext,
436
- instanceId: instanceContext?.instanceId
437
- });
438
- }
439
- }
440
- catch (err) {
441
- logger_1.logger.error('Failed to connect server to transport in createSession', {
442
- sessionId: id,
443
- error: err instanceof Error ? err.message : String(err),
444
- waitForConnection
445
- });
446
- await this.removeSession(id, 'connection_failed').catch(cleanupErr => {
447
- logger_1.logger.error('Error during connection failure cleanup', { error: cleanupErr });
448
- });
449
- throw err;
450
- }
451
- this.emitEvent('onSessionCreated', id, instanceContext).catch(eventErr => {
452
- logger_1.logger.error('Failed to emit onSessionCreated event (non-blocking)', {
453
- sessionId: id,
454
- error: eventErr instanceof Error ? eventErr.message : String(eventErr)
455
- });
456
- });
457
- return id;
458
- };
459
- if (waitForConnection) {
460
- return initializeSession();
461
- }
462
- initializeSession().catch(error => {
463
- logger_1.logger.error('Async session creation failed in manual restore flow', {
464
- sessionId: id,
465
- error: error instanceof Error ? error.message : String(error)
466
- });
467
- });
468
- return id;
469
- }
470
- generateSessionId(instanceContext) {
471
- const isMultiTenantEnabled = process.env.ENABLE_MULTI_TENANT === 'true';
472
- const sessionStrategy = process.env.MULTI_TENANT_SESSION_STRATEGY || 'instance';
473
- if (isMultiTenantEnabled && sessionStrategy === 'instance' && instanceContext?.instanceId) {
474
- const configHash = (0, crypto_1.createHash)('sha256')
475
- .update(JSON.stringify({
476
- url: instanceContext.n8nApiUrl,
477
- instanceId: instanceContext.instanceId
478
- }))
479
- .digest('hex')
480
- .substring(0, 8);
481
- return `instance-${instanceContext.instanceId}-${configHash}-${(0, uuid_1.v4)()}`;
482
- }
483
- return (0, uuid_1.v4)();
484
- }
485
172
  getSessionMetrics() {
486
173
  const now = Date.now();
487
174
  let expiredCount = 0;
@@ -624,27 +311,14 @@ class SingleSessionHTTPServer {
624
311
  transport.onclose = () => {
625
312
  const sid = transport.sessionId;
626
313
  if (sid) {
627
- if (this.isShuttingDown) {
628
- logger_1.logger.debug('Ignoring transport close event during shutdown', { sessionId: sid });
629
- return;
630
- }
631
314
  logger_1.logger.info('handleRequest: Transport closed, cleaning up', { sessionId: sid });
632
- this.removeSession(sid, 'transport_closed').catch(err => {
633
- logger_1.logger.error('Error during transport close cleanup', {
634
- sessionId: sid,
635
- error: err instanceof Error ? err.message : String(err)
636
- });
637
- });
315
+ this.removeSession(sid, 'transport_closed');
638
316
  }
639
317
  };
640
318
  transport.onerror = (error) => {
641
319
  const sid = transport.sessionId;
320
+ logger_1.logger.error('Transport error', { sessionId: sid, error: error.message });
642
321
  if (sid) {
643
- if (this.isShuttingDown) {
644
- logger_1.logger.debug('Ignoring transport error event during shutdown', { sessionId: sid });
645
- return;
646
- }
647
- logger_1.logger.error('Transport error', { sessionId: sid, error: error.message });
648
322
  this.removeSession(sid, 'transport_error').catch(err => {
649
323
  logger_1.logger.error('Error during transport error cleanup', { error: err });
650
324
  });
@@ -652,12 +326,6 @@ class SingleSessionHTTPServer {
652
326
  };
653
327
  logger_1.logger.info('handleRequest: Connecting server to new transport');
654
328
  await server.connect(transport);
655
- this.emitEvent('onSessionCreated', sessionIdToUse, instanceContext).catch(eventErr => {
656
- logger_1.logger.error('Failed to emit onSessionCreated event (non-blocking)', {
657
- sessionId: sessionIdToUse,
658
- error: eventErr instanceof Error ? eventErr.message : String(eventErr)
659
- });
660
- });
661
329
  }
662
330
  else if (sessionId && this.transports[sessionId]) {
663
331
  if (!this.isValidSessionId(sessionId)) {
@@ -682,150 +350,29 @@ class SingleSessionHTTPServer {
682
350
  this.updateSessionAccess(sessionId);
683
351
  }
684
352
  else {
685
- if (sessionId) {
686
- if (!this.isValidSessionId(sessionId)) {
687
- logger_1.logger.warn('handleRequest: Invalid session ID format rejected', {
688
- sessionId: sessionId.substring(0, 20)
689
- });
690
- res.status(400).json({
691
- jsonrpc: '2.0',
692
- error: {
693
- code: -32602,
694
- message: 'Invalid session ID format'
695
- },
696
- id: req.body?.id || null
697
- });
698
- return;
699
- }
700
- if (this.onSessionNotFound) {
701
- logger_1.logger.info('Attempting session restoration', { sessionId });
702
- try {
703
- const restoredContext = await this.restoreSessionWithRetry(sessionId);
704
- if (restoredContext === null || restoredContext === undefined) {
705
- logger_1.logger.info('Session restoration declined by hook', {
706
- sessionId,
707
- returnValue: restoredContext === null ? 'null' : 'undefined'
708
- });
709
- res.status(400).json({
710
- jsonrpc: '2.0',
711
- error: {
712
- code: -32000,
713
- message: 'Session not found or expired'
714
- },
715
- id: req.body?.id || null
716
- });
717
- return;
718
- }
719
- const validation = (0, instance_context_1.validateInstanceContext)(restoredContext);
720
- if (!validation.valid) {
721
- logger_1.logger.error('Invalid context returned from restoration hook', {
722
- sessionId,
723
- errors: validation.errors
724
- });
725
- res.status(400).json({
726
- jsonrpc: '2.0',
727
- error: {
728
- code: -32000,
729
- message: 'Invalid session context'
730
- },
731
- id: req.body?.id || null
732
- });
733
- return;
734
- }
735
- if (this.transports[sessionId]) {
736
- logger_1.logger.info('Session already restored by concurrent request', { sessionId });
737
- transport = this.transports[sessionId];
738
- }
739
- else {
740
- logger_1.logger.info('Session restoration successful, creating session', {
741
- sessionId,
742
- instanceId: restoredContext.instanceId
743
- });
744
- this.createSession(restoredContext, sessionId, false);
745
- transport = this.transports[sessionId];
746
- if (!transport) {
747
- throw new Error('Transport not found after session creation');
748
- }
749
- }
750
- this.emitEvent('onSessionRestored', sessionId, restoredContext).catch(err => {
751
- logger_1.logger.error('Failed to emit onSessionRestored event (non-blocking)', {
752
- sessionId,
753
- error: err instanceof Error ? err.message : String(err)
754
- });
755
- });
756
- logger_1.logger.info('Handling request through restored session transport', { sessionId });
757
- await transport.handleRequest(req, res, req.body);
758
- return;
759
- }
760
- catch (error) {
761
- if (this.transports[sessionId]) {
762
- logger_1.logger.info('Cleaning up failed session restoration', { sessionId });
763
- await this.removeSession(sessionId, 'restoration_failed').catch(cleanupErr => {
764
- logger_1.logger.error('Error during restoration failure cleanup', {
765
- sessionId,
766
- error: cleanupErr instanceof Error ? cleanupErr.message : String(cleanupErr)
767
- });
768
- });
769
- }
770
- if (error instanceof Error && error.name === 'TimeoutError') {
771
- logger_1.logger.error('Session restoration timeout', {
772
- sessionId,
773
- timeout: this.sessionRestorationTimeout
774
- });
775
- res.status(408).json({
776
- jsonrpc: '2.0',
777
- error: {
778
- code: -32000,
779
- message: 'Session restoration timeout'
780
- },
781
- id: req.body?.id || null
782
- });
783
- return;
784
- }
785
- logger_1.logger.error('Session restoration failed', {
786
- sessionId,
787
- error: error instanceof Error ? error.message : String(error)
788
- });
789
- res.status(500).json({
790
- jsonrpc: '2.0',
791
- error: {
792
- code: -32603,
793
- message: 'Session restoration failed'
794
- },
795
- id: req.body?.id || null
796
- });
797
- return;
798
- }
799
- }
800
- else {
801
- logger_1.logger.warn('Session not found and no restoration hook configured', {
802
- sessionId
803
- });
804
- res.status(400).json({
805
- jsonrpc: '2.0',
806
- error: {
807
- code: -32000,
808
- message: 'Session not found or expired'
809
- },
810
- id: req.body?.id || null
811
- });
812
- return;
813
- }
353
+ const errorDetails = {
354
+ hasSessionId: !!sessionId,
355
+ isInitialize: isInitialize,
356
+ sessionIdValid: sessionId ? this.isValidSessionId(sessionId) : false,
357
+ sessionExists: sessionId ? !!this.transports[sessionId] : false
358
+ };
359
+ logger_1.logger.warn('handleRequest: Invalid request - no session ID and not initialize', errorDetails);
360
+ let errorMessage = 'Bad Request: No valid session ID provided and not an initialize request';
361
+ if (sessionId && !this.isValidSessionId(sessionId)) {
362
+ errorMessage = 'Bad Request: Invalid session ID format';
814
363
  }
815
- else {
816
- logger_1.logger.warn('handleRequest: Invalid request - no session ID and not initialize', {
817
- isInitialize
818
- });
819
- res.status(400).json({
820
- jsonrpc: '2.0',
821
- error: {
822
- code: -32000,
823
- message: 'Bad Request: No valid session ID provided and not an initialize request'
824
- },
825
- id: req.body?.id || null
826
- });
827
- return;
364
+ else if (sessionId && !this.transports[sessionId]) {
365
+ errorMessage = 'Bad Request: Session not found or expired';
828
366
  }
367
+ res.status(400).json({
368
+ jsonrpc: '2.0',
369
+ error: {
370
+ code: -32000,
371
+ message: errorMessage
372
+ },
373
+ id: req.body?.id || null
374
+ });
375
+ return;
829
376
  }
830
377
  logger_1.logger.info('handleRequest: Handling request with transport', {
831
378
  sessionId: isInitialize ? 'new' : sessionId,
@@ -1408,8 +955,6 @@ class SingleSessionHTTPServer {
1408
955
  }
1409
956
  async shutdown() {
1410
957
  logger_1.logger.info('Shutting down Single-Session HTTP server...');
1411
- this.isShuttingDown = true;
1412
- logger_1.logger.info('Shutdown flag set - recursive cleanup prevention enabled');
1413
958
  if (this.cleanupTimer) {
1414
959
  clearInterval(this.cleanupTimer);
1415
960
  this.cleanupTimer = null;
@@ -1417,28 +962,15 @@ class SingleSessionHTTPServer {
1417
962
  }
1418
963
  const sessionIds = Object.keys(this.transports);
1419
964
  logger_1.logger.info(`Closing ${sessionIds.length} active sessions`);
1420
- let successCount = 0;
1421
- let failureCount = 0;
1422
965
  for (const sessionId of sessionIds) {
1423
966
  try {
1424
967
  logger_1.logger.info(`Closing transport for session ${sessionId}`);
1425
968
  await this.removeSession(sessionId, 'server_shutdown');
1426
- successCount++;
1427
969
  }
1428
970
  catch (error) {
1429
- failureCount++;
1430
- logger_1.logger.warn(`Error closing transport for session ${sessionId}:`, {
1431
- error: error instanceof Error ? error.message : String(error)
1432
- });
971
+ logger_1.logger.warn(`Error closing transport for session ${sessionId}:`, error);
1433
972
  }
1434
973
  }
1435
- if (sessionIds.length > 0) {
1436
- logger_1.logger.info('Session shutdown completed', {
1437
- total: sessionIds.length,
1438
- successful: successCount,
1439
- failed: failureCount
1440
- });
1441
- }
1442
974
  if (this.session) {
1443
975
  try {
1444
976
  await this.session.transport.close();
@@ -1486,117 +1018,6 @@ class SingleSessionHTTPServer {
1486
1018
  }
1487
1019
  };
1488
1020
  }
1489
- getActiveSessions() {
1490
- return Object.keys(this.sessionMetadata);
1491
- }
1492
- getSessionState(sessionId) {
1493
- const metadata = this.sessionMetadata[sessionId];
1494
- if (!metadata) {
1495
- return null;
1496
- }
1497
- const instanceContext = this.sessionContexts[sessionId];
1498
- const expiresAt = new Date(metadata.lastAccess.getTime() + this.sessionTimeout);
1499
- return {
1500
- sessionId,
1501
- instanceContext: instanceContext || {
1502
- n8nApiUrl: process.env.N8N_API_URL,
1503
- n8nApiKey: process.env.N8N_API_KEY,
1504
- instanceId: process.env.N8N_INSTANCE_ID
1505
- },
1506
- createdAt: metadata.createdAt,
1507
- lastAccess: metadata.lastAccess,
1508
- expiresAt,
1509
- metadata: instanceContext?.metadata
1510
- };
1511
- }
1512
- getAllSessionStates() {
1513
- const sessionIds = this.getActiveSessions();
1514
- const states = [];
1515
- for (const sessionId of sessionIds) {
1516
- const state = this.getSessionState(sessionId);
1517
- if (state) {
1518
- states.push(state);
1519
- }
1520
- }
1521
- return states;
1522
- }
1523
- manuallyRestoreSession(sessionId, instanceContext) {
1524
- try {
1525
- if (!this.isValidSessionId(sessionId)) {
1526
- logger_1.logger.error('Invalid session ID format in manual restoration', { sessionId });
1527
- return false;
1528
- }
1529
- const validation = (0, instance_context_1.validateInstanceContext)(instanceContext);
1530
- if (!validation.valid) {
1531
- logger_1.logger.error('Invalid instance context in manual restoration', {
1532
- sessionId,
1533
- errors: validation.errors
1534
- });
1535
- return false;
1536
- }
1537
- this.sessionMetadata[sessionId] = {
1538
- lastAccess: new Date(),
1539
- createdAt: new Date()
1540
- };
1541
- this.sessionContexts[sessionId] = instanceContext;
1542
- const creationResult = this.createSession(instanceContext, sessionId, false);
1543
- Promise.resolve(creationResult).catch(error => {
1544
- logger_1.logger.error('Async session creation failed in manual restoration', {
1545
- sessionId,
1546
- error: error instanceof Error ? error.message : String(error)
1547
- });
1548
- delete this.sessionMetadata[sessionId];
1549
- delete this.sessionContexts[sessionId];
1550
- });
1551
- logger_1.logger.info('Session manually restored', {
1552
- sessionId,
1553
- instanceId: instanceContext.instanceId
1554
- });
1555
- return true;
1556
- }
1557
- catch (error) {
1558
- logger_1.logger.error('Failed to manually restore session', {
1559
- sessionId,
1560
- error: error instanceof Error ? error.message : String(error)
1561
- });
1562
- return false;
1563
- }
1564
- }
1565
- manuallyDeleteSession(sessionId) {
1566
- if (!this.sessionMetadata[sessionId]) {
1567
- logger_1.logger.debug('Session not found for manual deletion', { sessionId });
1568
- return false;
1569
- }
1570
- try {
1571
- if (this.transports[sessionId]) {
1572
- this.transports[sessionId].close().catch(error => {
1573
- logger_1.logger.warn('Error closing transport during manual deletion', {
1574
- sessionId,
1575
- error: error instanceof Error ? error.message : String(error)
1576
- });
1577
- });
1578
- }
1579
- this.emitEvent('onSessionDeleted', sessionId).catch(err => {
1580
- logger_1.logger.error('Failed to emit onSessionDeleted event (non-blocking)', {
1581
- sessionId,
1582
- error: err instanceof Error ? err.message : String(err)
1583
- });
1584
- });
1585
- delete this.transports[sessionId];
1586
- delete this.servers[sessionId];
1587
- delete this.sessionMetadata[sessionId];
1588
- delete this.sessionContexts[sessionId];
1589
- logger_1.logger.info('Session manually deleted', { sessionId });
1590
- return true;
1591
- }
1592
- catch (error) {
1593
- logger_1.logger.error('Error during manual session deletion', {
1594
- sessionId,
1595
- error: error instanceof Error ? error.message : String(error)
1596
- });
1597
- return false;
1598
- }
1599
- }
1600
1021
  }
1601
1022
  exports.SingleSessionHTTPServer = SingleSessionHTTPServer;
1602
1023
  if (require.main === module) {