@spfn/core 0.2.0-beta.50 → 0.2.0-beta.52
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/dist/env/index.d.ts +24 -1
- package/dist/env/index.js +9 -1
- package/dist/env/index.js.map +1 -1
- package/dist/event/index.d.ts +3 -3
- package/dist/event/index.js +7 -2
- package/dist/event/index.js.map +1 -1
- package/dist/event/sse/client.d.ts +2 -2
- package/dist/event/sse/index.d.ts +4 -4
- package/dist/event/sse/index.js +71 -25
- package/dist/event/sse/index.js.map +1 -1
- package/dist/event/ws/client.d.ts +2 -2
- package/dist/event/ws/index.d.ts +3 -3
- package/dist/server/index.d.ts +3 -3
- package/dist/server/index.js +342 -28
- package/dist/server/index.js.map +1 -1
- package/dist/{token-manager-CyG7la3p.d.ts → token-manager-C2Ag5-s8.d.ts} +6 -0
- package/dist/{types-Cfj--lfr.d.ts → types-CKsmzaB8.d.ts} +19 -1
- package/dist/{types-C1jMLGwK.d.ts → types-VpVQIsyB.d.ts} +27 -2
- package/package.json +1 -1
package/dist/server/index.js
CHANGED
|
@@ -398,11 +398,62 @@ function loadEnvFiles() {
|
|
|
398
398
|
}
|
|
399
399
|
loadEnv();
|
|
400
400
|
}
|
|
401
|
+
|
|
402
|
+
// src/event/sse/bounded-writer.ts
|
|
403
|
+
function createBoundedWriter(stream, maxQueue, onClose) {
|
|
404
|
+
const pending = [];
|
|
405
|
+
let flushing = false;
|
|
406
|
+
let closed = false;
|
|
407
|
+
const flush = async () => {
|
|
408
|
+
if (flushing || closed) {
|
|
409
|
+
return;
|
|
410
|
+
}
|
|
411
|
+
flushing = true;
|
|
412
|
+
while (pending.length > 0 && !closed) {
|
|
413
|
+
const frame = pending.shift();
|
|
414
|
+
try {
|
|
415
|
+
await stream.writeSSE(frame);
|
|
416
|
+
} catch (err) {
|
|
417
|
+
flushing = false;
|
|
418
|
+
onClose(err instanceof Error ? err.message : String(err));
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
flushing = false;
|
|
423
|
+
};
|
|
424
|
+
return {
|
|
425
|
+
enqueue(frame) {
|
|
426
|
+
if (closed) {
|
|
427
|
+
return;
|
|
428
|
+
}
|
|
429
|
+
pending.push(frame);
|
|
430
|
+
if (pending.length > maxQueue) {
|
|
431
|
+
onClose("outbound queue overflow (slow consumer)");
|
|
432
|
+
return;
|
|
433
|
+
}
|
|
434
|
+
void flush();
|
|
435
|
+
},
|
|
436
|
+
close() {
|
|
437
|
+
closed = true;
|
|
438
|
+
pending.length = 0;
|
|
439
|
+
},
|
|
440
|
+
get queued() {
|
|
441
|
+
return pending.length;
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
// src/event/sse/handler.ts
|
|
401
447
|
var sseLogger = logger.child("@spfn/core:sse");
|
|
448
|
+
var DEFAULT_MAX_QUEUE = 1e3;
|
|
402
449
|
function createSSEHandler(router, config = {}, tokenManager) {
|
|
403
450
|
const {
|
|
404
|
-
|
|
405
|
-
|
|
451
|
+
// 10s — keep-alive가 중간 프록시(GCP LB/nginx)의 idle 타임아웃(흔히 ~15-30s)보다 자주 와야
|
|
452
|
+
// 첫 ping 전 침묵 구간에 연결이 끊기지 않는다. 30s 기본은 idle 타임아웃보다 길어 SSE가
|
|
453
|
+
// 데이터 없는 동안 끊기던 문제가 있었다.
|
|
454
|
+
pingInterval = 1e4,
|
|
455
|
+
auth: authConfig,
|
|
456
|
+
maxQueue = DEFAULT_MAX_QUEUE
|
|
406
457
|
} = config;
|
|
407
458
|
return async (c) => {
|
|
408
459
|
const subject = await authenticateToken(c, tokenManager);
|
|
@@ -443,15 +494,29 @@ function createSSEHandler(router, config = {}, tokenManager) {
|
|
|
443
494
|
let messageId = 0;
|
|
444
495
|
let connectionDead = false;
|
|
445
496
|
let pingTimer;
|
|
446
|
-
|
|
497
|
+
let writer;
|
|
498
|
+
const cleanup = (reason) => {
|
|
447
499
|
if (connectionDead) return;
|
|
448
500
|
connectionDead = true;
|
|
449
501
|
clearInterval(pingTimer);
|
|
450
502
|
unsubscribes.forEach((fn) => fn());
|
|
503
|
+
writer?.close();
|
|
451
504
|
sseLogger.info("SSE dead connection cleaned up", {
|
|
452
|
-
events: allowedEvents
|
|
505
|
+
events: allowedEvents,
|
|
506
|
+
reason
|
|
453
507
|
});
|
|
454
508
|
};
|
|
509
|
+
writer = createBoundedWriter(stream, maxQueue, (reason) => {
|
|
510
|
+
sseLogger.warn("SSE connection closed", { events: allowedEvents, reason });
|
|
511
|
+
cleanup(reason);
|
|
512
|
+
});
|
|
513
|
+
writer.enqueue({
|
|
514
|
+
event: "connected",
|
|
515
|
+
data: JSON.stringify({
|
|
516
|
+
subscribedEvents: allowedEvents,
|
|
517
|
+
timestamp: Date.now()
|
|
518
|
+
})
|
|
519
|
+
});
|
|
455
520
|
for (const eventName of allowedEvents) {
|
|
456
521
|
const eventDef = router.events[eventName];
|
|
457
522
|
if (!eventDef) {
|
|
@@ -473,17 +538,10 @@ function createSSEHandler(router, config = {}, tokenManager) {
|
|
|
473
538
|
event: eventName,
|
|
474
539
|
messageId
|
|
475
540
|
});
|
|
476
|
-
|
|
541
|
+
writer.enqueue({
|
|
477
542
|
id: String(messageId),
|
|
478
543
|
event: eventName,
|
|
479
544
|
data: JSON.stringify(message)
|
|
480
|
-
}).catch((err) => {
|
|
481
|
-
sseLogger.warn("SSE write failed", {
|
|
482
|
-
event: eventName,
|
|
483
|
-
messageId,
|
|
484
|
-
error: err.message
|
|
485
|
-
});
|
|
486
|
-
cleanup();
|
|
487
545
|
});
|
|
488
546
|
});
|
|
489
547
|
unsubscribes.push(unsubscribe);
|
|
@@ -492,23 +550,11 @@ function createSSEHandler(router, config = {}, tokenManager) {
|
|
|
492
550
|
events: allowedEvents,
|
|
493
551
|
subscriptionCount: unsubscribes.length
|
|
494
552
|
});
|
|
495
|
-
await stream.writeSSE({
|
|
496
|
-
event: "connected",
|
|
497
|
-
data: JSON.stringify({
|
|
498
|
-
subscribedEvents: allowedEvents,
|
|
499
|
-
timestamp: Date.now()
|
|
500
|
-
})
|
|
501
|
-
});
|
|
502
553
|
pingTimer = setInterval(() => {
|
|
503
554
|
if (connectionDead) return;
|
|
504
|
-
|
|
555
|
+
writer.enqueue({
|
|
505
556
|
event: "ping",
|
|
506
557
|
data: JSON.stringify({ timestamp: Date.now() })
|
|
507
|
-
}).catch((err) => {
|
|
508
|
-
sseLogger.warn("SSE ping failed", {
|
|
509
|
-
error: err.message
|
|
510
|
-
});
|
|
511
|
-
cleanup();
|
|
512
558
|
});
|
|
513
559
|
}, pingInterval);
|
|
514
560
|
const abortSignal = c.req.raw.signal;
|
|
@@ -649,6 +695,263 @@ var SSETokenManager = class {
|
|
|
649
695
|
}
|
|
650
696
|
}
|
|
651
697
|
};
|
|
698
|
+
var transportLogger = logger.child("@spfn/core:event-transport");
|
|
699
|
+
var DEFAULT_CHANNEL_PREFIX = "spfn:sse:";
|
|
700
|
+
var SUBSCRIBER_QUIT_TIMEOUT_MS = 5e3;
|
|
701
|
+
var PUBLISH_TIMEOUT_MS = 5e3;
|
|
702
|
+
var SUBSCRIBE_TIMEOUT_MS = 5e3;
|
|
703
|
+
var WARN_THROTTLE_MS = 3e4;
|
|
704
|
+
var PUBLISH_BREAKER_COOLDOWN_MS = 1e4;
|
|
705
|
+
function withTimeout(promise, ms, label) {
|
|
706
|
+
return new Promise((resolve2, reject) => {
|
|
707
|
+
const timer = setTimeout(() => {
|
|
708
|
+
reject(new Error(`${label} timed out after ${ms}ms`));
|
|
709
|
+
}, ms);
|
|
710
|
+
timer.unref?.();
|
|
711
|
+
promise.then(
|
|
712
|
+
(value) => {
|
|
713
|
+
clearTimeout(timer);
|
|
714
|
+
resolve2(value);
|
|
715
|
+
},
|
|
716
|
+
(err) => {
|
|
717
|
+
clearTimeout(timer);
|
|
718
|
+
reject(err);
|
|
719
|
+
}
|
|
720
|
+
);
|
|
721
|
+
});
|
|
722
|
+
}
|
|
723
|
+
var warnThrottle = /* @__PURE__ */ new Map();
|
|
724
|
+
function throttledWarn(key, message, fields) {
|
|
725
|
+
const now = Date.now();
|
|
726
|
+
const entry = warnThrottle.get(key);
|
|
727
|
+
if (entry && now - entry.last < WARN_THROTTLE_MS) {
|
|
728
|
+
entry.suppressed++;
|
|
729
|
+
return;
|
|
730
|
+
}
|
|
731
|
+
transportLogger.warn(message, entry && entry.suppressed > 0 ? { ...fields, suppressedSinceLast: entry.suppressed } : fields);
|
|
732
|
+
warnThrottle.set(key, { last: now, suppressed: 0 });
|
|
733
|
+
}
|
|
734
|
+
var STATE_KEY = Symbol.for("@spfn/core:event-transport");
|
|
735
|
+
var state = globalThis[STATE_KEY] ??= {
|
|
736
|
+
pubSubCache: void 0,
|
|
737
|
+
subscriber: void 0,
|
|
738
|
+
channelPrefix: void 0,
|
|
739
|
+
wired: /* @__PURE__ */ new Map()
|
|
740
|
+
};
|
|
741
|
+
function resolveChannelPrefix(override) {
|
|
742
|
+
return override ?? process.env.SPFN_SSE_CHANNEL_PREFIX ?? DEFAULT_CHANNEL_PREFIX;
|
|
743
|
+
}
|
|
744
|
+
function createRedisPubSubCache(client, channelPrefix, onSubscriber) {
|
|
745
|
+
let subscriber;
|
|
746
|
+
const handlers = /* @__PURE__ */ new Map();
|
|
747
|
+
let degradedUntil = 0;
|
|
748
|
+
const ensureSubscriber = () => {
|
|
749
|
+
if (subscriber) {
|
|
750
|
+
return subscriber;
|
|
751
|
+
}
|
|
752
|
+
subscriber = client.duplicate();
|
|
753
|
+
onSubscriber?.(subscriber);
|
|
754
|
+
subscriber.on("message", (channel, raw) => {
|
|
755
|
+
const handler = handlers.get(channel);
|
|
756
|
+
if (!handler) {
|
|
757
|
+
return;
|
|
758
|
+
}
|
|
759
|
+
let payload;
|
|
760
|
+
try {
|
|
761
|
+
payload = JSON.parse(raw);
|
|
762
|
+
} catch {
|
|
763
|
+
transportLogger.warn("Pub/sub message parse failed \u2014 dropped", { channel });
|
|
764
|
+
return;
|
|
765
|
+
}
|
|
766
|
+
void handler(payload);
|
|
767
|
+
});
|
|
768
|
+
subscriber.on("error", (err) => {
|
|
769
|
+
throttledWarn("subscriber-error", "Pub/sub subscriber error", { error: err.message });
|
|
770
|
+
});
|
|
771
|
+
return subscriber;
|
|
772
|
+
};
|
|
773
|
+
const subscriberHealthy = () => !!subscriber && (subscriber.status === void 0 || subscriber.status === "ready");
|
|
774
|
+
const deliverLocal = (channel, message) => {
|
|
775
|
+
const handler = handlers.get(channel);
|
|
776
|
+
if (handler) {
|
|
777
|
+
void handler(message);
|
|
778
|
+
}
|
|
779
|
+
};
|
|
780
|
+
return {
|
|
781
|
+
publish: async (name, message) => {
|
|
782
|
+
const channel = channelPrefix + name;
|
|
783
|
+
let serialized;
|
|
784
|
+
try {
|
|
785
|
+
serialized = JSON.stringify(message ?? null);
|
|
786
|
+
} catch (err) {
|
|
787
|
+
transportLogger.warn("Pub/sub payload serialization failed \u2014 local delivery only", {
|
|
788
|
+
channel,
|
|
789
|
+
error: err instanceof Error ? err.message : String(err)
|
|
790
|
+
});
|
|
791
|
+
deliverLocal(channel, message ?? null);
|
|
792
|
+
return;
|
|
793
|
+
}
|
|
794
|
+
if (serialized === void 0) {
|
|
795
|
+
transportLogger.warn("Pub/sub payload not serializable (function/symbol) \u2014 local delivery only", {
|
|
796
|
+
channel
|
|
797
|
+
});
|
|
798
|
+
deliverLocal(channel, message ?? null);
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
const ready = client.status === void 0 || client.status === "ready";
|
|
802
|
+
const now = Date.now();
|
|
803
|
+
if (!ready || degradedUntil > 0 && now < degradedUntil) {
|
|
804
|
+
deliverLocal(channel, JSON.parse(serialized));
|
|
805
|
+
return;
|
|
806
|
+
}
|
|
807
|
+
if (degradedUntil > 0) {
|
|
808
|
+
degradedUntil = now + PUBLISH_TIMEOUT_MS;
|
|
809
|
+
}
|
|
810
|
+
try {
|
|
811
|
+
await withTimeout(client.publish(channel, serialized), PUBLISH_TIMEOUT_MS, "pub/sub publish");
|
|
812
|
+
degradedUntil = 0;
|
|
813
|
+
if (!subscriberHealthy()) {
|
|
814
|
+
throttledWarn("subscriber-down", "Subscriber connection down \u2014 same-pod local fallback", {
|
|
815
|
+
channel
|
|
816
|
+
});
|
|
817
|
+
deliverLocal(channel, JSON.parse(serialized));
|
|
818
|
+
}
|
|
819
|
+
} catch (err) {
|
|
820
|
+
degradedUntil = Date.now() + PUBLISH_BREAKER_COOLDOWN_MS;
|
|
821
|
+
throttledWarn("publish-failed", "Pub/sub publish failed \u2014 local delivery only", {
|
|
822
|
+
channel,
|
|
823
|
+
error: err instanceof Error ? err.message : String(err)
|
|
824
|
+
});
|
|
825
|
+
deliverLocal(channel, JSON.parse(serialized));
|
|
826
|
+
}
|
|
827
|
+
},
|
|
828
|
+
subscribe: async (name, handler) => {
|
|
829
|
+
const channel = channelPrefix + name;
|
|
830
|
+
const sub = ensureSubscriber();
|
|
831
|
+
try {
|
|
832
|
+
await withTimeout(sub.subscribe(channel), SUBSCRIBE_TIMEOUT_MS, "pub/sub subscribe");
|
|
833
|
+
} catch (err) {
|
|
834
|
+
if (handlers.size === 0 && subscriber === sub) {
|
|
835
|
+
subscriber = void 0;
|
|
836
|
+
void sub.quit?.().catch(() => void 0);
|
|
837
|
+
}
|
|
838
|
+
throw err;
|
|
839
|
+
}
|
|
840
|
+
handlers.set(channel, handler);
|
|
841
|
+
}
|
|
842
|
+
};
|
|
843
|
+
}
|
|
844
|
+
async function wireEventRouterCache(router, options = {}) {
|
|
845
|
+
if (options.multiInstance === false) {
|
|
846
|
+
const events2 = router.events;
|
|
847
|
+
const stillRedis = Object.keys(events2).filter((k) => state.wired.has(events2[k].name)).length;
|
|
848
|
+
if (stillRedis > 0) {
|
|
849
|
+
transportLogger.warn("multiInstance:false has no effect on events already wired to redis by another router", {
|
|
850
|
+
redisWired: stillRedis
|
|
851
|
+
});
|
|
852
|
+
} else {
|
|
853
|
+
transportLogger.info("Event transport: in-process (multiInstance disabled)");
|
|
854
|
+
}
|
|
855
|
+
return "in-process";
|
|
856
|
+
}
|
|
857
|
+
const pubSubCache = await resolvePubSubCache(options.channelPrefix);
|
|
858
|
+
if (!pubSubCache) {
|
|
859
|
+
transportLogger.info("Event transport: in-process (no cache configured)");
|
|
860
|
+
return "in-process";
|
|
861
|
+
}
|
|
862
|
+
const events = router.events;
|
|
863
|
+
let wired = 0;
|
|
864
|
+
let degraded = 0;
|
|
865
|
+
let alreadyWired = 0;
|
|
866
|
+
for (const key of Object.keys(events)) {
|
|
867
|
+
const event = events[key];
|
|
868
|
+
if (state.wired.has(event.name)) {
|
|
869
|
+
alreadyWired++;
|
|
870
|
+
continue;
|
|
871
|
+
}
|
|
872
|
+
try {
|
|
873
|
+
await event.useCache(pubSubCache);
|
|
874
|
+
state.wired.set(event.name, event);
|
|
875
|
+
wired++;
|
|
876
|
+
} catch (err) {
|
|
877
|
+
degraded++;
|
|
878
|
+
transportLogger.warn("Event cache wiring failed \u2014 staying in-process for this event", {
|
|
879
|
+
event: event.name,
|
|
880
|
+
error: err instanceof Error ? err.message : String(err)
|
|
881
|
+
});
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
if (wired + alreadyWired === 0) {
|
|
885
|
+
transportLogger.warn("Event transport: redis configured but no event wired \u2014 running in-process", {
|
|
886
|
+
total: Object.keys(events).length,
|
|
887
|
+
degraded
|
|
888
|
+
});
|
|
889
|
+
return "in-process";
|
|
890
|
+
}
|
|
891
|
+
transportLogger.info("Event transport: redis (cross-pod fan-out)", {
|
|
892
|
+
total: Object.keys(events).length,
|
|
893
|
+
wired,
|
|
894
|
+
degraded,
|
|
895
|
+
alreadyWired
|
|
896
|
+
});
|
|
897
|
+
return "redis";
|
|
898
|
+
}
|
|
899
|
+
async function resolvePubSubCache(channelPrefix) {
|
|
900
|
+
const resolvedPrefix = resolveChannelPrefix(channelPrefix);
|
|
901
|
+
if (state.pubSubCache) {
|
|
902
|
+
if (state.channelPrefix !== resolvedPrefix) {
|
|
903
|
+
transportLogger.warn("Conflicting channelPrefix ignored \u2014 the transport prefix is process-global (first wiring wins).", {
|
|
904
|
+
active: state.channelPrefix,
|
|
905
|
+
ignored: resolvedPrefix
|
|
906
|
+
});
|
|
907
|
+
}
|
|
908
|
+
return state.pubSubCache;
|
|
909
|
+
}
|
|
910
|
+
let client;
|
|
911
|
+
try {
|
|
912
|
+
const { getCache: getCache2 } = await import('@spfn/core/cache');
|
|
913
|
+
client = getCache2();
|
|
914
|
+
} catch {
|
|
915
|
+
return void 0;
|
|
916
|
+
}
|
|
917
|
+
if (!client) {
|
|
918
|
+
return void 0;
|
|
919
|
+
}
|
|
920
|
+
if (!channelPrefix && !process.env.SPFN_SSE_CHANNEL_PREFIX) {
|
|
921
|
+
transportLogger.warn(
|
|
922
|
+
"Event fan-out is on the default channel prefix (spfn:sse:). Apps sharing one Redis must set channelPrefix or SPFN_SSE_CHANNEL_PREFIX to avoid cross-app leakage."
|
|
923
|
+
);
|
|
924
|
+
}
|
|
925
|
+
state.channelPrefix = resolvedPrefix;
|
|
926
|
+
state.pubSubCache = createRedisPubSubCache(
|
|
927
|
+
client,
|
|
928
|
+
resolvedPrefix,
|
|
929
|
+
(connection) => {
|
|
930
|
+
state.subscriber = connection;
|
|
931
|
+
}
|
|
932
|
+
);
|
|
933
|
+
return state.pubSubCache;
|
|
934
|
+
}
|
|
935
|
+
async function closeEventTransport() {
|
|
936
|
+
const subscriber = state.subscriber;
|
|
937
|
+
for (const event of state.wired.values()) {
|
|
938
|
+
event._resetCache?.();
|
|
939
|
+
}
|
|
940
|
+
state.subscriber = void 0;
|
|
941
|
+
state.pubSubCache = void 0;
|
|
942
|
+
state.channelPrefix = void 0;
|
|
943
|
+
state.wired.clear();
|
|
944
|
+
if (!subscriber?.quit) {
|
|
945
|
+
return;
|
|
946
|
+
}
|
|
947
|
+
await Promise.race([
|
|
948
|
+
subscriber.quit().catch(() => void 0),
|
|
949
|
+
new Promise((resolve2) => {
|
|
950
|
+
const timer = setTimeout(resolve2, SUBSCRIBER_QUIT_TIMEOUT_MS);
|
|
951
|
+
timer.unref?.();
|
|
952
|
+
})
|
|
953
|
+
]);
|
|
954
|
+
}
|
|
652
955
|
var serverLogger = logger.child("@spfn/core:server");
|
|
653
956
|
|
|
654
957
|
// src/server/shutdown-manager.ts
|
|
@@ -822,7 +1125,7 @@ var ShutdownManager = class {
|
|
|
822
1125
|
for (const hook of this.hooks) {
|
|
823
1126
|
serverLogger.debug(`Shutdown hook [${hook.name}] starting (timeout: ${hook.timeout}ms)`);
|
|
824
1127
|
try {
|
|
825
|
-
await
|
|
1128
|
+
await withTimeout2(
|
|
826
1129
|
hook.handler(),
|
|
827
1130
|
hook.timeout,
|
|
828
1131
|
`Shutdown hook '${hook.name}' timeout after ${hook.timeout}ms`
|
|
@@ -850,7 +1153,7 @@ function resetShutdownManager() {
|
|
|
850
1153
|
function sleep(ms) {
|
|
851
1154
|
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
852
1155
|
}
|
|
853
|
-
async function
|
|
1156
|
+
async function withTimeout2(promise, timeout, message) {
|
|
854
1157
|
let timeoutId;
|
|
855
1158
|
return Promise.race([
|
|
856
1159
|
promise.finally(() => {
|
|
@@ -1150,12 +1453,17 @@ async function registerSSEEndpoint(app, config) {
|
|
|
1150
1453
|
serverLogger.info(`\u2713 SSE token endpoint registered at POST ${tokenPath}`);
|
|
1151
1454
|
}
|
|
1152
1455
|
}
|
|
1456
|
+
const transport = await wireEventRouterCache(config.events, {
|
|
1457
|
+
multiInstance: eventsConfig.multiInstance,
|
|
1458
|
+
channelPrefix: eventsConfig.channelPrefix
|
|
1459
|
+
});
|
|
1153
1460
|
app.get(streamPath, createSSEHandler(config.events, eventsConfig, tokenManager));
|
|
1154
1461
|
if (debug) {
|
|
1155
1462
|
const eventNames = config.events.eventNames;
|
|
1156
1463
|
serverLogger.info(`\u2713 SSE endpoint registered at ${streamPath}`, {
|
|
1157
1464
|
events: eventNames,
|
|
1158
|
-
auth: !!authConfig?.enabled
|
|
1465
|
+
auth: !!authConfig?.enabled,
|
|
1466
|
+
transport
|
|
1159
1467
|
});
|
|
1160
1468
|
}
|
|
1161
1469
|
}
|
|
@@ -1890,6 +2198,10 @@ async function initializeWebSocket(server, app, config) {
|
|
|
1890
2198
|
});
|
|
1891
2199
|
if (debug) serverLogger.info(`\u2713 WS token endpoint registered at POST ${tokenPath}`);
|
|
1892
2200
|
}
|
|
2201
|
+
await wireEventRouterCache(wsRouter, {
|
|
2202
|
+
multiInstance: wsConfig.multiInstance,
|
|
2203
|
+
channelPrefix: wsConfig.channelPrefix
|
|
2204
|
+
});
|
|
1893
2205
|
return await attachWSHandler(server, wsRouter, wsConfig, tokenManager);
|
|
1894
2206
|
}
|
|
1895
2207
|
function logMiddlewareOrder(config) {
|
|
@@ -1961,6 +2273,7 @@ function createShutdownHandler(server, config, shutdownState, wsCleanup) {
|
|
|
1961
2273
|
await closeInfrastructure(closeDatabase, "Database", TIMEOUTS.DATABASE_CLOSE);
|
|
1962
2274
|
}
|
|
1963
2275
|
if (infraConfig.redis) {
|
|
2276
|
+
await closeEventTransport();
|
|
1964
2277
|
await closeInfrastructure(closeCache, "Redis", TIMEOUTS.REDIS_CLOSE);
|
|
1965
2278
|
}
|
|
1966
2279
|
serverLogger.info("Server shutdown completed");
|
|
@@ -2111,6 +2424,7 @@ async function cleanupOnFailure(config) {
|
|
|
2111
2424
|
await closeInfrastructure(closeDatabase, "Database", TIMEOUTS.DATABASE_CLOSE);
|
|
2112
2425
|
}
|
|
2113
2426
|
if (infraConfig.redis) {
|
|
2427
|
+
await closeEventTransport();
|
|
2114
2428
|
await closeInfrastructure(closeCache, "Redis", TIMEOUTS.REDIS_CLOSE);
|
|
2115
2429
|
}
|
|
2116
2430
|
resetShutdownManager();
|