@jskit-ai/realtime 0.1.155 → 0.1.157

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.
@@ -1,764 +0,0 @@
1
- import { normalizeRecordId, normalizeText } from "@jskit-ai/kernel/shared/support/normalize";
2
- import { createProviderLogger as createSharedProviderLogger } from "@jskit-ai/kernel/shared/support/providerLogger";
3
- import {
4
- registerDomainEventListener,
5
- resolveServiceRegistrations
6
- } from "@jskit-ai/kernel/server/runtime";
7
- import {
8
- createSocketIoServer,
9
- closeSocketIoServer,
10
- resolveRealtimeRedisUrl,
11
- resolveRealtimeRedisNamespace,
12
- configureSocketIoRedisAdapter,
13
- closeSocketIoRedisConnections
14
- } from "./runtime.js";
15
-
16
- const REALTIME_RUNTIME_SERVER_API = Object.freeze({
17
- createSocketIoServer,
18
- closeSocketIoServer
19
- });
20
-
21
- const REALTIME_ROOM_ALL_CLIENTS = "clients";
22
- const REALTIME_ROOM_ALL_USERS = "users";
23
-
24
- function normalizeArray(value) {
25
- const queue = Array.isArray(value) ? [...value] : [value];
26
- const list = [];
27
-
28
- while (queue.length > 0) {
29
- const entry = queue.shift();
30
- if (Array.isArray(entry)) {
31
- queue.push(...entry);
32
- continue;
33
- }
34
- if (entry == null) {
35
- continue;
36
- }
37
- list.push(entry);
38
- }
39
-
40
- return list;
41
- }
42
-
43
- function roomForUser(userId) {
44
- return `user:${String(userId || "").trim()}`;
45
- }
46
-
47
- function roomForWorkspace(workspaceId) {
48
- return `workspace:${String(workspaceId || "").trim()}`;
49
- }
50
-
51
- function roomForWorkspaceUser(workspaceId, userId) {
52
- return `workspace:${String(workspaceId || "").trim()}:user:${String(userId || "").trim()}`;
53
- }
54
-
55
- function parseCookieHeader(value = "") {
56
- const source = String(value || "").trim();
57
- if (!source) {
58
- return {};
59
- }
60
-
61
- return source.split(";").reduce((cookies, entry) => {
62
- const separator = entry.indexOf("=");
63
- if (separator < 1) {
64
- return cookies;
65
- }
66
- const key = entry.slice(0, separator).trim();
67
- const rawValue = entry.slice(separator + 1).trim();
68
- if (!key) {
69
- return cookies;
70
- }
71
-
72
- try {
73
- cookies[key] = decodeURIComponent(rawValue);
74
- } catch {
75
- cookies[key] = rawValue;
76
- }
77
- return cookies;
78
- }, {});
79
- }
80
-
81
- function createProviderLogger(scope, { debugEnabled = false } = {}) {
82
- const logger =
83
- scope && typeof scope.has === "function" && scope.has("jskit.logger") ? scope.make("jskit.logger") : null;
84
- return createSharedProviderLogger(logger, { debugEnabled });
85
- }
86
-
87
- function parseDebugFlag(value, fallback = null) {
88
- if (typeof value === "boolean") {
89
- return value;
90
- }
91
-
92
- const normalized = normalizeText(value).toLowerCase();
93
- if (!normalized) {
94
- return fallback;
95
- }
96
- if (normalized === "1" || normalized === "true" || normalized === "yes" || normalized === "on") {
97
- return true;
98
- }
99
- if (normalized === "0" || normalized === "false" || normalized === "no" || normalized === "off") {
100
- return false;
101
- }
102
-
103
- return fallback;
104
- }
105
-
106
- function resolveRealtimeServerDebugEnabled(scope) {
107
- const appConfig = scope && typeof scope.has === "function" && scope.has("appConfig") ? scope.make("appConfig") : {};
108
- const env = scope && typeof scope.has === "function" && scope.has("jskit.env") ? scope.make("jskit.env") : {};
109
- const realtime = appConfig && typeof appConfig === "object" ? appConfig.realtime : null;
110
-
111
- const envFlag = parseDebugFlag(env?.JSKIT_REALTIME_DEBUG);
112
- if (envFlag !== null) {
113
- return envFlag;
114
- }
115
-
116
- const configFlag = parseDebugFlag(realtime?.debug);
117
- if (configFlag !== null) {
118
- return configFlag;
119
- }
120
-
121
- return false;
122
- }
123
-
124
- function buildRealtimeDispatchIndex(registrations = []) {
125
- const index = new Map();
126
-
127
- for (const registration of registrations) {
128
- const serviceToken = String(registration?.serviceToken || "").trim();
129
- if (!serviceToken) {
130
- continue;
131
- }
132
-
133
- const eventsByMethod = registration?.metadata?.events;
134
- if (!eventsByMethod || typeof eventsByMethod !== "object" || Array.isArray(eventsByMethod)) {
135
- continue;
136
- }
137
-
138
- for (const [methodName, specs] of Object.entries(eventsByMethod)) {
139
- const normalizedMethodName = String(methodName || "").trim();
140
- if (!normalizedMethodName) {
141
- continue;
142
- }
143
- const key = `${serviceToken}:${normalizedMethodName}`;
144
- const list = [];
145
- const entries = Array.isArray(specs) ? specs : [];
146
-
147
- for (const spec of entries) {
148
- const realtime = spec?.realtime;
149
- const event = normalizeText(realtime?.event);
150
- if (!event) {
151
- continue;
152
- }
153
- list.push(
154
- Object.freeze({
155
- event,
156
- audience: realtime?.audience
157
- })
158
- );
159
- }
160
-
161
- if (list.length > 0) {
162
- index.set(key, Object.freeze(list));
163
- }
164
- }
165
- }
166
-
167
- return index;
168
- }
169
-
170
- function mergeRealtimePayload(event, payloadPatch) {
171
- const basePayload = event && typeof event === "object" && !Array.isArray(event) ? event : {};
172
- const action = normalizeText(basePayload?.meta?.action);
173
- const reason = normalizeText(basePayload?.meta?.reason);
174
- const semanticPatch = {};
175
- if (action) {
176
- semanticPatch.action = action;
177
- }
178
- if (reason) {
179
- semanticPatch.reason = reason;
180
- }
181
-
182
- if (payloadPatch == null) {
183
- return Object.keys(semanticPatch).length > 0
184
- ? {
185
- ...semanticPatch,
186
- ...basePayload
187
- }
188
- : basePayload;
189
- }
190
- if (!payloadPatch || typeof payloadPatch !== "object" || Array.isArray(payloadPatch)) {
191
- throw new TypeError("Realtime payload callback must return an object.");
192
- }
193
-
194
- // Keep canonical domain-event fields and standard lifecycle metadata authoritative.
195
- return {
196
- ...payloadPatch,
197
- ...semanticPatch,
198
- ...basePayload
199
- };
200
- }
201
-
202
- function resolveScopeWorkspaceId(scope = {}) {
203
- const source = scope && typeof scope === "object" && !Array.isArray(scope) ? scope : {};
204
- if (String(source.kind || "").trim().toLowerCase() === "workspace") {
205
- return normalizeRecordId(source.id, { fallback: null });
206
- }
207
- if (String(source.kind || "").trim().toLowerCase() === "workspace_user") {
208
- return normalizeRecordId(source.workspaceId || source.id, { fallback: null });
209
- }
210
- return normalizeRecordId(source.workspaceId, { fallback: null });
211
- }
212
-
213
- function resolveScopeUserId(scope = {}) {
214
- const source = scope && typeof scope === "object" && !Array.isArray(scope) ? scope : {};
215
- if (String(source.kind || "").trim().toLowerCase() === "user") {
216
- return normalizeRecordId(source.id, { fallback: null });
217
- }
218
- if (String(source.kind || "").trim().toLowerCase() === "workspace_user") {
219
- return normalizeRecordId(source.userId, { fallback: null });
220
- }
221
- return normalizeRecordId(source.userId, { fallback: null });
222
- }
223
-
224
- function applyAudiencePreset(preset, { event, rooms, flags, logger } = {}) {
225
- const normalizedPreset = normalizeText(preset).toLowerCase();
226
- if (!normalizedPreset || normalizedPreset === "none") {
227
- return;
228
- }
229
-
230
- if (normalizedPreset === "all_clients") {
231
- flags.broadcastAllClients = true;
232
- return;
233
- }
234
-
235
- if (normalizedPreset === "all_users") {
236
- rooms.add(REALTIME_ROOM_ALL_USERS);
237
- return;
238
- }
239
-
240
- if (normalizedPreset === "actor_user") {
241
- const actorId = normalizeRecordId(event?.actorId, { fallback: null });
242
- if (actorId) {
243
- rooms.add(roomForUser(actorId));
244
- }
245
- return;
246
- }
247
-
248
- if (normalizedPreset === "all_workspace_users") {
249
- const workspaceId = resolveScopeWorkspaceId(event?.scope);
250
- if (workspaceId) {
251
- rooms.add(roomForWorkspace(workspaceId));
252
- return;
253
- }
254
- logger.warn(
255
- {
256
- audience: normalizedPreset,
257
- scope: event?.scope || null
258
- },
259
- "Realtime audience preset requires a workspace scope."
260
- );
261
- return;
262
- }
263
-
264
- if (normalizedPreset === "event_scope") {
265
- const scopeKind = normalizeText(event?.scope?.kind).toLowerCase();
266
- if (scopeKind === "workspace") {
267
- const workspaceId = resolveScopeWorkspaceId(event?.scope);
268
- if (workspaceId) {
269
- rooms.add(roomForWorkspace(workspaceId));
270
- }
271
- return;
272
- }
273
- if (scopeKind === "workspace_user") {
274
- const workspaceId = resolveScopeWorkspaceId(event?.scope);
275
- const userId = resolveScopeUserId(event?.scope);
276
- if (workspaceId && userId) {
277
- rooms.add(roomForWorkspaceUser(workspaceId, userId));
278
- }
279
- return;
280
- }
281
- if (scopeKind === "user") {
282
- const userId = resolveScopeUserId(event?.scope);
283
- if (userId) {
284
- rooms.add(roomForUser(userId));
285
- }
286
- return;
287
- }
288
- rooms.add(REALTIME_ROOM_ALL_USERS);
289
- return;
290
- }
291
-
292
- logger.warn(
293
- {
294
- audience: normalizedPreset
295
- },
296
- "Realtime bridge ignored unknown audience preset."
297
- );
298
- }
299
-
300
- function addAudienceRoomsFromObject(selection, { event, rooms, flags, logger } = {}) {
301
- if (!selection || typeof selection !== "object" || Array.isArray(selection)) {
302
- return;
303
- }
304
-
305
- if (Object.hasOwn(selection, "preset")) {
306
- applyAudiencePreset(selection.preset, {
307
- event,
308
- rooms,
309
- flags,
310
- logger
311
- });
312
- }
313
- if (selection.broadcast === true) {
314
- flags.broadcastAllClients = true;
315
- }
316
-
317
- const room = normalizeText(selection.room);
318
- if (room) {
319
- rooms.add(room);
320
- }
321
- for (const entry of normalizeArray(selection.rooms)) {
322
- const normalizedRoom = normalizeText(entry);
323
- if (normalizedRoom) {
324
- rooms.add(normalizedRoom);
325
- }
326
- }
327
-
328
- const userId = normalizeRecordId(selection.userId, { fallback: null });
329
- if (userId) {
330
- rooms.add(roomForUser(userId));
331
- }
332
- for (const entry of normalizeArray(selection.userIds)) {
333
- const normalizedUserId = normalizeRecordId(entry, { fallback: null });
334
- if (normalizedUserId) {
335
- rooms.add(roomForUser(normalizedUserId));
336
- }
337
- }
338
-
339
- const workspaceId = normalizeRecordId(selection.workspaceId, { fallback: null });
340
- if (workspaceId) {
341
- rooms.add(roomForWorkspace(workspaceId));
342
- }
343
- for (const entry of normalizeArray(selection.workspaceIds)) {
344
- const normalizedWorkspaceId = normalizeRecordId(entry, { fallback: null });
345
- if (normalizedWorkspaceId) {
346
- rooms.add(roomForWorkspace(normalizedWorkspaceId));
347
- }
348
- }
349
-
350
- const workspaceUser = selection.workspaceUser;
351
- if (workspaceUser && typeof workspaceUser === "object") {
352
- const targetWorkspaceId = normalizeRecordId(workspaceUser.workspaceId, { fallback: null });
353
- const targetUserId = normalizeRecordId(workspaceUser.userId, { fallback: null });
354
- if (targetWorkspaceId && targetUserId) {
355
- rooms.add(roomForWorkspaceUser(targetWorkspaceId, targetUserId));
356
- }
357
- }
358
-
359
- for (const entry of normalizeArray(selection.workspaceUsers)) {
360
- if (!entry || typeof entry !== "object") {
361
- continue;
362
- }
363
- const targetWorkspaceId = normalizeRecordId(entry.workspaceId, { fallback: null });
364
- const targetUserId = normalizeRecordId(entry.userId, { fallback: null });
365
- if (targetWorkspaceId && targetUserId) {
366
- rooms.add(roomForWorkspaceUser(targetWorkspaceId, targetUserId));
367
- }
368
- }
369
- }
370
-
371
- function collectUserIdsFromQueryRows(rows = []) {
372
- const result = new Set();
373
- for (const row of normalizeArray(rows)) {
374
- if (typeof row === "number") {
375
- const directId = normalizeRecordId(row, { fallback: null });
376
- if (directId) {
377
- result.add(directId);
378
- }
379
- continue;
380
- }
381
- if (!row || typeof row !== "object" || Array.isArray(row)) {
382
- continue;
383
- }
384
- const userId = normalizeRecordId(row.userId || row.user_id || row.id, { fallback: null });
385
- if (userId) {
386
- result.add(userId);
387
- }
388
- }
389
- return [...result];
390
- }
391
-
392
- async function resolveAudienceQueryRooms(userQuery, { scope, event, logger } = {}) {
393
- if (typeof userQuery !== "function") {
394
- return [];
395
- }
396
-
397
- if (!scope || typeof scope.has !== "function" || typeof scope.make !== "function" || !scope.has("jskit.database.knex")) {
398
- logger.warn("Realtime audience userQuery requires runtime database token.");
399
- return [];
400
- }
401
-
402
- const knex = scope.make("jskit.database.knex");
403
- const queryResult = await userQuery({
404
- knex,
405
- event
406
- });
407
- const userIds = collectUserIdsFromQueryRows(await Promise.resolve(queryResult));
408
- return userIds.map((userId) => roomForUser(userId));
409
- }
410
-
411
- async function resolveAudienceTargets(dispatcher, event, { scope, logger } = {}) {
412
- const rooms = new Set();
413
- const flags = {
414
- broadcastAllClients: false
415
- };
416
-
417
- let selection = dispatcher?.audience;
418
- if (typeof selection === "function") {
419
- selection = await selection({
420
- event
421
- });
422
- }
423
-
424
- for (const entry of normalizeArray(selection)) {
425
- if (typeof entry === "string") {
426
- applyAudiencePreset(entry, {
427
- event,
428
- rooms,
429
- flags,
430
- logger
431
- });
432
- continue;
433
- }
434
-
435
- if (!entry || typeof entry !== "object" || Array.isArray(entry)) {
436
- continue;
437
- }
438
-
439
- addAudienceRoomsFromObject(entry, {
440
- event,
441
- rooms,
442
- flags,
443
- logger
444
- });
445
-
446
- if (typeof entry.userQuery === "function") {
447
- const queryRooms = await resolveAudienceQueryRooms(entry.userQuery, {
448
- scope,
449
- event,
450
- logger
451
- });
452
- for (const room of queryRooms) {
453
- rooms.add(room);
454
- }
455
- }
456
- }
457
-
458
- return Object.freeze({
459
- broadcastAllClients: flags.broadcastAllClients,
460
- rooms: Object.freeze([...rooms])
461
- });
462
- }
463
-
464
- async function resolveSocketActorId(authService, socket) {
465
- if (!authService || typeof authService.authenticateRequest !== "function") {
466
- return null;
467
- }
468
-
469
- const handshakeHeaders =
470
- socket?.handshake?.headers && typeof socket.handshake.headers === "object"
471
- ? socket.handshake.headers
472
- : {};
473
- const requestHeaders =
474
- socket?.request?.headers && typeof socket.request.headers === "object"
475
- ? socket.request.headers
476
- : {};
477
- const cookieHeader = normalizeText(
478
- handshakeHeaders.cookie ||
479
- requestHeaders.cookie
480
- );
481
- const cookies = parseCookieHeader(cookieHeader);
482
- const host = normalizeText(handshakeHeaders.host || requestHeaders.host);
483
- const remoteAddress = normalizeText(
484
- socket?.request?.socket?.remoteAddress ||
485
- socket?.conn?.remoteAddress ||
486
- socket?.handshake?.address
487
- );
488
- const authRequest = {
489
- cookies
490
- };
491
- if (host) {
492
- authRequest.headers = {
493
- host
494
- };
495
- }
496
- if (remoteAddress) {
497
- authRequest.socket = {
498
- remoteAddress
499
- };
500
- }
501
- const authResult = await authService.authenticateRequest({
502
- ...authRequest
503
- });
504
- if (!authResult || authResult.authenticated !== true) {
505
- return null;
506
- }
507
- return normalizeRecordId(authResult?.actor?.id, { fallback: null });
508
- }
509
-
510
- async function resolveActorWorkspaceIds(workspaceMembershipsRepository, actorId) {
511
- if (!workspaceMembershipsRepository || typeof workspaceMembershipsRepository.listActiveWorkspaceIdsByUserId !== "function") {
512
- return [];
513
- }
514
-
515
- const workspaceIds = await workspaceMembershipsRepository.listActiveWorkspaceIdsByUserId(actorId);
516
- return normalizeArray(workspaceIds)
517
- .map((entry) => normalizeRecordId(entry, { fallback: null }))
518
- .filter(Boolean);
519
- }
520
-
521
- function resolveOptionalScopeBinding(scope, token = "") {
522
- if (!scope || typeof scope.has !== "function" || typeof scope.make !== "function") {
523
- return null;
524
- }
525
-
526
- const normalizedToken = String(token || "").trim();
527
- if (!normalizedToken || scope.has(normalizedToken) !== true) {
528
- return null;
529
- }
530
-
531
- return scope.make(normalizedToken);
532
- }
533
-
534
- function registerRealtimeSocketAudienceBootstrap(scope, io, logger) {
535
- if (!io || typeof io.on !== "function") {
536
- return;
537
- }
538
-
539
- io.on("connection", async (socket) => {
540
- try {
541
- const authService = resolveOptionalScopeBinding(scope, "authService");
542
- const workspaceMembershipsRepository = resolveOptionalScopeBinding(scope, "internal.repository.workspace-memberships");
543
-
544
- socket.join(REALTIME_ROOM_ALL_CLIENTS);
545
- logger.debug(
546
- {
547
- listenerId: "runtime.realtime.domain-event-bridge",
548
- stage: "socket.connection",
549
- room: REALTIME_ROOM_ALL_CLIENTS
550
- },
551
- "Realtime socket joined default clients room."
552
- );
553
-
554
- const actorId = await resolveSocketActorId(authService, socket);
555
- if (!actorId) {
556
- logger.debug(
557
- {
558
- listenerId: "runtime.realtime.domain-event-bridge",
559
- stage: "socket.connection",
560
- authenticated: false
561
- },
562
- "Realtime socket connected without authenticated actor."
563
- );
564
- return;
565
- }
566
-
567
- socket.data = socket.data && typeof socket.data === "object" ? socket.data : {};
568
- socket.data.actorId = actorId;
569
-
570
- socket.join(REALTIME_ROOM_ALL_USERS);
571
- socket.join(roomForUser(actorId));
572
- logger.debug(
573
- {
574
- listenerId: "runtime.realtime.domain-event-bridge",
575
- stage: "socket.connection",
576
- actorId,
577
- joinedRooms: [REALTIME_ROOM_ALL_USERS, roomForUser(actorId)]
578
- },
579
- "Realtime socket joined actor/user rooms."
580
- );
581
-
582
- const workspaceIds = await resolveActorWorkspaceIds(workspaceMembershipsRepository, actorId);
583
- for (const workspaceId of workspaceIds) {
584
- socket.join(roomForWorkspace(workspaceId));
585
- socket.join(roomForWorkspaceUser(workspaceId, actorId));
586
- }
587
- logger.debug(
588
- {
589
- listenerId: "runtime.realtime.domain-event-bridge",
590
- stage: "socket.connection",
591
- actorId,
592
- workspaceIds
593
- },
594
- "Realtime socket joined workspace rooms."
595
- );
596
- } catch (error) {
597
- logger.warn(
598
- {
599
- listenerId: "runtime.realtime.domain-event-bridge",
600
- error: String(error?.message || error || "unknown error")
601
- },
602
- "Realtime socket audience bootstrap failed."
603
- );
604
- }
605
- });
606
- }
607
-
608
- class RealtimeServiceProvider {
609
- static id = "runtime.realtime";
610
-
611
- register(app) {
612
- if (!app || typeof app.singleton !== "function" || typeof app.tag !== "function") {
613
- throw new Error("RealtimeServiceProvider requires application singleton()/tag().");
614
- }
615
-
616
- app.singleton("runtime.realtime", () => REALTIME_RUNTIME_SERVER_API);
617
- app.singleton("runtime.realtime.io", (scope) => {
618
- const fastify = scope.make("jskit.fastify");
619
- return createSocketIoServer({
620
- fastify
621
- });
622
- });
623
-
624
- registerDomainEventListener(app, "runtime.realtime.domain-event-bridge", (scope) => {
625
- const io = scope.make("runtime.realtime.io");
626
- const realtimeDispatchIndex = buildRealtimeDispatchIndex(resolveServiceRegistrations(scope));
627
- const logger = createProviderLogger(scope, {
628
- debugEnabled: resolveRealtimeServerDebugEnabled(scope)
629
- });
630
-
631
- return {
632
- listenerId: "runtime.realtime.domain-event-bridge",
633
- async handle(event = {}) {
634
- const serviceToken = normalizeText(event?.meta?.service?.token);
635
- const methodName = normalizeText(event?.meta?.service?.method);
636
- const emittedRealtimeEvent = normalizeText(event?.meta?.realtime?.event);
637
- if (!serviceToken || !methodName) {
638
- logger.warn(
639
- {
640
- listenerId: "runtime.realtime.domain-event-bridge",
641
- reason: "missing-service-meta",
642
- meta: event?.meta || null
643
- },
644
- "Realtime bridge skipped domain event."
645
- );
646
- return;
647
- }
648
-
649
- const dispatchersForMethod = realtimeDispatchIndex.get(`${serviceToken}:${methodName}`) || [];
650
- const dispatchers =
651
- emittedRealtimeEvent.length > 0
652
- ? dispatchersForMethod.filter((dispatcher) => normalizeText(dispatcher?.event) === emittedRealtimeEvent)
653
- : dispatchersForMethod;
654
- logger.debug(
655
- {
656
- listenerId: "runtime.realtime.domain-event-bridge",
657
- serviceToken,
658
- methodName,
659
- emittedRealtimeEvent: emittedRealtimeEvent || null,
660
- dispatcherCount: dispatchers.length,
661
- methodDispatcherCount: dispatchersForMethod.length,
662
- eventType: event?.type || null,
663
- scope: event?.scope || null,
664
- entityId: event?.entityId || null
665
- },
666
- "Realtime bridge received service event."
667
- );
668
-
669
- if (dispatchers.length < 1) {
670
- logger.warn(
671
- {
672
- listenerId: "runtime.realtime.domain-event-bridge",
673
- serviceToken,
674
- methodName,
675
- emittedRealtimeEvent: emittedRealtimeEvent || null
676
- },
677
- "Realtime bridge found no matching dispatcher for service event."
678
- );
679
- return;
680
- }
681
-
682
- for (const dispatcher of dispatchers) {
683
- const payloadPatch =
684
- event?.meta?.realtime && typeof event.meta.realtime === "object"
685
- ? event.meta.realtime.payload
686
- : null;
687
- const payload = mergeRealtimePayload(event, payloadPatch);
688
- const targets = await resolveAudienceTargets(dispatcher, event, {
689
- scope,
690
- logger
691
- });
692
-
693
- if (targets.broadcastAllClients === true) {
694
- io.emit(dispatcher.event, payload);
695
- }
696
- for (const room of targets.rooms) {
697
- io.to(room).emit(dispatcher.event, payload);
698
- }
699
-
700
- logger.debug(
701
- {
702
- listenerId: "runtime.realtime.domain-event-bridge",
703
- socketEvent: dispatcher.event,
704
- serviceToken,
705
- methodName,
706
- rooms: targets.rooms,
707
- broadcastAllClients: targets.broadcastAllClients,
708
- connectedClients:
709
- io && io.engine && Number.isInteger(Number(io.engine.clientsCount))
710
- ? Number(io.engine.clientsCount)
711
- : null,
712
- payloadScope: payload?.scope || null,
713
- payloadEntityId: payload?.entityId || null
714
- },
715
- "Realtime bridge emitted socket event."
716
- );
717
- }
718
- }
719
- };
720
- });
721
- }
722
-
723
- async boot(app) {
724
- if (!app || typeof app.make !== "function") {
725
- throw new Error("RealtimeServiceProvider requires application make().");
726
- }
727
-
728
- this.socketIoServer = app.make("runtime.realtime.io");
729
- const debugEnabled = resolveRealtimeServerDebugEnabled(app);
730
- const logger = createProviderLogger(app, {
731
- debugEnabled
732
- });
733
- logger.debug(
734
- {
735
- providerId: RealtimeServiceProvider.id,
736
- debugEnabled
737
- },
738
- "Realtime server debug mode enabled."
739
- );
740
- registerRealtimeSocketAudienceBootstrap(app, this.socketIoServer, logger);
741
-
742
- const env = typeof app.has === "function" && app.has("jskit.env") ? app.make("jskit.env") : {};
743
- const redisUrl = resolveRealtimeRedisUrl(env);
744
- const redisNamespace = resolveRealtimeRedisNamespace(env);
745
- this.redisConnection = await configureSocketIoRedisAdapter(this.socketIoServer, {
746
- redisUrl,
747
- redisNamespace
748
- });
749
- }
750
-
751
- async shutdown() {
752
- if (!this.socketIoServer) {
753
- return;
754
- }
755
- await closeSocketIoServer(this.socketIoServer);
756
- await closeSocketIoRedisConnections(this.redisConnection || {});
757
- this.redisConnection = null;
758
- this.socketIoServer = null;
759
- }
760
- }
761
-
762
- export {
763
- RealtimeServiceProvider
764
- };