anear-js-api 1.3.2 → 1.3.3
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/lib/state_machines/AnearCoreServiceMachine.js +21 -5
- package/lib/state_machines/AnearEventMachine.js +67 -30
- package/lib/state_machines/AnearParticipantMachine.js +35 -12
- package/lib/utils/AppMachineTransition.js +2 -1
- package/lib/utils/CssUploader.js +3 -1
- package/lib/utils/DisplayEventProcessor.js +23 -17
- package/lib/utils/FontAssetsUploader.js +5 -2
- package/lib/utils/ImageAssetsUploader.js +3 -0
- package/lib/utils/RealtimeMessaging.js +49 -3
- package/package.json +1 -1
|
@@ -80,7 +80,10 @@ const AnearCoreServiceMachineConfig = appId => ({
|
|
|
80
80
|
context: ({ input }) => input,
|
|
81
81
|
states: {
|
|
82
82
|
fetchAppDataWithRetry: {
|
|
83
|
-
entry:
|
|
83
|
+
entry: ({ context }) => {
|
|
84
|
+
const appId = context.appId || process.env.ANEARAPP_APP_ID || 'unknown'
|
|
85
|
+
logger.info(`[ACSM] ===== Booting App (${appId}) on anear-js-api version ${anearJsApiVersion} =====`)
|
|
86
|
+
},
|
|
84
87
|
initial: 'fetchAppData',
|
|
85
88
|
states: {
|
|
86
89
|
fetchAppData: {
|
|
@@ -111,7 +114,7 @@ const AnearCoreServiceMachineConfig = appId => ({
|
|
|
111
114
|
entry: ['initRealtime'],
|
|
112
115
|
on: {
|
|
113
116
|
CONNECTED: {
|
|
114
|
-
actions: ['createEventsCreationChannel', 'subscribeCreateEventMessages']
|
|
117
|
+
actions: ['createEventsCreationChannel', 'subscribeCreateEventMessages', ({ context }) => logger.info(`[ACSM] Realtime messaging connected for app ${context.appId}`)]
|
|
115
118
|
},
|
|
116
119
|
ATTACHED: 'uploadNewImageAssets'
|
|
117
120
|
}
|
|
@@ -168,7 +171,11 @@ const AnearCoreServiceMachineConfig = appId => ({
|
|
|
168
171
|
// The Anear API backend will send CREATE_EVENT or LOAD_EVENT messages with the event JSON data
|
|
169
172
|
// to this createEventMessages Channel when it needs to create a new instance of an
|
|
170
173
|
// Event
|
|
171
|
-
entry: ({ context }) =>
|
|
174
|
+
entry: ({ context }) => {
|
|
175
|
+
const shortName = context.appData.data.attributes['short-name']
|
|
176
|
+
logger.info(`[ACSM] Ready to receive events for app ${shortName}`)
|
|
177
|
+
logger.debug(`Waiting on ${shortName} lifecycle command`)
|
|
178
|
+
},
|
|
172
179
|
on: {
|
|
173
180
|
CREATE_EVENT: {
|
|
174
181
|
actions: ['startNewEventMachine']
|
|
@@ -221,6 +228,8 @@ const AnearCoreServiceMachineFunctions = {
|
|
|
221
228
|
pugTemplates: (_args) => {
|
|
222
229
|
const pugLoader = new PugLoader(DefaultTemplatesRootDir)
|
|
223
230
|
const templates = pugLoader.compiledPugTemplates()
|
|
231
|
+
const templateCount = Object.keys(templates).length
|
|
232
|
+
logger.info(`[ACSM] Loaded ${templateCount} Pug template(s)`)
|
|
224
233
|
logger.debug(`loaded pug templates ${Object.keys(templates)}`)
|
|
225
234
|
return templates
|
|
226
235
|
}
|
|
@@ -240,7 +249,9 @@ const AnearCoreServiceMachineFunctions = {
|
|
|
240
249
|
{
|
|
241
250
|
appData: ({ event }) => {
|
|
242
251
|
const output = event.output
|
|
243
|
-
|
|
252
|
+
const attrs = output.data.attributes
|
|
253
|
+
logger.info(`[ACSM] Loading App from API app.short_name=${attrs["short-name"]} app.slug=${attrs.slug} app.id=${output.data.id}`)
|
|
254
|
+
logger.debug(`fetched ${attrs["short-name"]} app data`)
|
|
244
255
|
return output
|
|
245
256
|
}
|
|
246
257
|
}
|
|
@@ -264,6 +275,7 @@ const AnearCoreServiceMachineFunctions = {
|
|
|
264
275
|
self,
|
|
265
276
|
'LOAD_EVENT'
|
|
266
277
|
)
|
|
278
|
+
logger.info(`[ACSM] Subscribed to CREATE_EVENT and LOAD_EVENT on events channel`)
|
|
267
279
|
},
|
|
268
280
|
startNewEventMachine: assign(
|
|
269
281
|
{
|
|
@@ -294,7 +306,11 @@ const AnearCoreServiceMachineFunctions = {
|
|
|
294
306
|
),
|
|
295
307
|
cleanupEventMachine: assign(({ context, event }) => {
|
|
296
308
|
const { [event.eventId]: dropped, ...remaining } = context.anearEventMachines
|
|
297
|
-
|
|
309
|
+
if (dropped) {
|
|
310
|
+
logger.info(`[ACSM] Event ${event.eventId} closed, freeing event slot`)
|
|
311
|
+
} else {
|
|
312
|
+
logger.debug(`ACSM ${event.eventId} is NOT FOUND`)
|
|
313
|
+
}
|
|
298
314
|
|
|
299
315
|
return {
|
|
300
316
|
anearEventMachines: remaining
|
|
@@ -78,6 +78,12 @@ const getAllParticipantIds = (context) => {
|
|
|
78
78
|
return Object.keys(context.participants);
|
|
79
79
|
};
|
|
80
80
|
|
|
81
|
+
// Helper to get participant name from ID, fallback to ID if not found
|
|
82
|
+
const getParticipantName = (context, participantId) => {
|
|
83
|
+
const participant = context.participants[participantId];
|
|
84
|
+
return participant?.name || participantId;
|
|
85
|
+
};
|
|
86
|
+
|
|
81
87
|
// Participant status helper functions for active/idle state management
|
|
82
88
|
const getActiveParticipantIds = (context) => {
|
|
83
89
|
return Object.keys(context.participants).filter(id => {
|
|
@@ -187,6 +193,7 @@ const ActiveEventGlobalEvents = {
|
|
|
187
193
|
},
|
|
188
194
|
CANCEL: {
|
|
189
195
|
// appM does an abrupt shutdown of the event
|
|
196
|
+
actions: ({ context }) => logger.info(`[AEM] Event ${context.anearEvent.id} CANCEL received`),
|
|
190
197
|
target: '#canceled'
|
|
191
198
|
},
|
|
192
199
|
APPM_FINAL: {
|
|
@@ -518,7 +525,7 @@ const ActiveEventStatesConfig = {
|
|
|
518
525
|
},
|
|
519
526
|
waitingToStart: {
|
|
520
527
|
id: 'waitingToStart',
|
|
521
|
-
entry: () => logger.
|
|
528
|
+
entry: ({ context }) => logger.info(`[AEM] Event ${context.anearEvent.id} created → announce`),
|
|
522
529
|
after: {
|
|
523
530
|
timeoutEventStart: {
|
|
524
531
|
actions: ({ context }) => logger.info(`[AEM] Event ${context.anearEvent.id} TIMED OUT waiting for START`),
|
|
@@ -527,6 +534,7 @@ const ActiveEventStatesConfig = {
|
|
|
527
534
|
},
|
|
528
535
|
on: {
|
|
529
536
|
START: {
|
|
537
|
+
actions: ({ context }) => logger.info(`[AEM] Event ${context.anearEvent.id} announce → live`),
|
|
530
538
|
target: '#activeEvent.live'
|
|
531
539
|
}
|
|
532
540
|
}
|
|
@@ -653,7 +661,13 @@ const ActiveEventStatesConfig = {
|
|
|
653
661
|
},
|
|
654
662
|
waitingForActions: {
|
|
655
663
|
id: 'waitingForActions',
|
|
656
|
-
entry: () =>
|
|
664
|
+
entry: ({ context }) => {
|
|
665
|
+
// Only log transition on initial entry to live state, not on nested state changes
|
|
666
|
+
const currentState = context.appEventMachine?.getSnapshot?.()?.value
|
|
667
|
+
if (!currentState || typeof currentState === 'string' || Object.keys(currentState).length === 0) {
|
|
668
|
+
logger.debug('[AEM] live state...waiting for actions')
|
|
669
|
+
}
|
|
670
|
+
},
|
|
657
671
|
initial: 'idle',
|
|
658
672
|
states: {
|
|
659
673
|
idle: {},
|
|
@@ -887,6 +901,7 @@ const ActiveEventStatesConfig = {
|
|
|
887
901
|
},
|
|
888
902
|
closeEvent: {
|
|
889
903
|
id: 'closeEvent',
|
|
904
|
+
entry: ({ context }) => logger.info(`[AEM] Event ${context.anearEvent.id} live → closed`),
|
|
890
905
|
initial: 'notifyingParticipants',
|
|
891
906
|
on: {
|
|
892
907
|
APPM_FINAL: {
|
|
@@ -954,6 +969,7 @@ const ActiveEventStatesConfig = {
|
|
|
954
969
|
},
|
|
955
970
|
canceled: {
|
|
956
971
|
id: 'canceled',
|
|
972
|
+
entry: ({ context }) => logger.info(`[AEM] Event ${context.anearEvent.id} → canceled`),
|
|
957
973
|
initial: 'notifyingParticipants',
|
|
958
974
|
on: {
|
|
959
975
|
CLOSE: {
|
|
@@ -1231,7 +1247,7 @@ const CreateEventChannelsAndAppMachineConfig = {
|
|
|
1231
1247
|
// get(eventChannelName) and setup state-change callbacks
|
|
1232
1248
|
entry: [({ context }) => {
|
|
1233
1249
|
const eventType = context.rehydrate ? 'LOAD_EVENT' : 'CREATE_EVENT'
|
|
1234
|
-
logger.
|
|
1250
|
+
logger.info(`[AEM] ${eventType} Event ${context.anearEvent.id}`)
|
|
1235
1251
|
}, 'createEventChannel'],
|
|
1236
1252
|
invoke: {
|
|
1237
1253
|
src: 'attachToEventChannel',
|
|
@@ -1526,6 +1542,7 @@ const AnearEventMachineFunctions = ({
|
|
|
1526
1542
|
userJSON => {
|
|
1527
1543
|
if (!context.appEventMachine) return
|
|
1528
1544
|
|
|
1545
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} SPECTATOR_ENTER spectatorId=${userJSON.id}`)
|
|
1529
1546
|
const spectatorEvent = { type: 'SPECTATOR_ENTER', data: userJSON }
|
|
1530
1547
|
context.appEventMachine.send(spectatorEvent)
|
|
1531
1548
|
}
|
|
@@ -1548,7 +1565,7 @@ const AnearEventMachineFunctions = ({
|
|
|
1548
1565
|
const eventName = getPresenceEventName(anearParticipant, 'ENTER');
|
|
1549
1566
|
const eventPayload = { type: eventName, participant: participantInfo };
|
|
1550
1567
|
|
|
1551
|
-
logger.
|
|
1568
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} ${eventName} participantId=${anearParticipant.id} name=${participantInfo.name}`);
|
|
1552
1569
|
context.appEventMachine.send(eventPayload);
|
|
1553
1570
|
},
|
|
1554
1571
|
processDisconnectEvents: ({ context, event }) => {
|
|
@@ -1556,8 +1573,9 @@ const AnearEventMachineFunctions = ({
|
|
|
1556
1573
|
const participant = context.participants[participantId];
|
|
1557
1574
|
const eventName = getPresenceEventName(participant, 'DISCONNECT');
|
|
1558
1575
|
const participantMachine = context.participantMachines[participantId];
|
|
1576
|
+
const participantName = getParticipantName(context, participantId);
|
|
1559
1577
|
|
|
1560
|
-
logger.
|
|
1578
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} ${eventName} participantId=${participantId} name=${participantName}`);
|
|
1561
1579
|
if (participantMachine) {
|
|
1562
1580
|
participantMachine.send({ type: 'PARTICIPANT_DISCONNECT' });
|
|
1563
1581
|
}
|
|
@@ -1569,7 +1587,7 @@ const AnearEventMachineFunctions = ({
|
|
|
1569
1587
|
const eventName = getPresenceEventName(participant, 'RECONNECT');
|
|
1570
1588
|
const participantMachine = context.participantMachines[participantId];
|
|
1571
1589
|
|
|
1572
|
-
logger.
|
|
1590
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} ${eventName} participantId=${participantId}`);
|
|
1573
1591
|
if (participantMachine) {
|
|
1574
1592
|
participantMachine.send({ type: 'PARTICIPANT_RECONNECT' });
|
|
1575
1593
|
}
|
|
@@ -1683,7 +1701,8 @@ const AnearEventMachineFunctions = ({
|
|
|
1683
1701
|
const participant = context.participants[participantId];
|
|
1684
1702
|
if (participant) {
|
|
1685
1703
|
const eventName = getPresenceEventName(participant, 'EXIT');
|
|
1686
|
-
|
|
1704
|
+
const participantName = getParticipantName(context, participantId);
|
|
1705
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} ${eventName} participantId=${participantId} name=${participantName}`);
|
|
1687
1706
|
context.appEventMachine.send({ type: eventName, participantId });
|
|
1688
1707
|
} else {
|
|
1689
1708
|
logger.warn(`[AEM] Participant info not found for id ${participantId} during sendExitToAppMachine`);
|
|
@@ -1719,7 +1738,7 @@ const AnearEventMachineFunctions = ({
|
|
|
1719
1738
|
// participant-level timeouts. This is useful when a game event (e.g., LIAR call)
|
|
1720
1739
|
// should immediately cancel all individual participant timeouts.
|
|
1721
1740
|
// APMs that are not in waitParticipantResponse state will benignly ignore this event.
|
|
1722
|
-
logger.info(`[AEM]
|
|
1741
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} cancelling all participant timeouts (manual) for ${Object.keys(context.participantMachines).length} participants`)
|
|
1723
1742
|
Object.values(context.participantMachines).forEach(pm => pm.send({ type: 'CANCEL_TIMEOUT' }))
|
|
1724
1743
|
|
|
1725
1744
|
// Note: If we're in waitAllParticipantsResponse state, the nested state's handler
|
|
@@ -1733,7 +1752,8 @@ const AnearEventMachineFunctions = ({
|
|
|
1733
1752
|
}),
|
|
1734
1753
|
sendCancelTimeoutToAllAPMs: ({ context }) => {
|
|
1735
1754
|
const participantCount = Object.keys(context.participantMachines).length
|
|
1736
|
-
|
|
1755
|
+
const actionName = context.eachParticipantCancelActionType || 'unknown'
|
|
1756
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} sending CANCEL_TIMEOUT to all ${participantCount} APMs for ACTION ${actionName}`)
|
|
1737
1757
|
Object.values(context.participantMachines).forEach(pm => {
|
|
1738
1758
|
pm.send({ type: 'CANCEL_TIMEOUT' })
|
|
1739
1759
|
})
|
|
@@ -1743,7 +1763,8 @@ const AnearEventMachineFunctions = ({
|
|
|
1743
1763
|
const activeParticipantIds = getActiveParticipantIds(context)
|
|
1744
1764
|
const pendingConfirmations = new Set(activeParticipantIds)
|
|
1745
1765
|
|
|
1746
|
-
|
|
1766
|
+
const participantName = getParticipantName(context, event.data.participantId)
|
|
1767
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} starting cancellation sequence for ACTION ${event.data.appEventName} from participantId=${event.data.participantId} name=${participantName}, waiting for ${pendingConfirmations.size} confirmations`)
|
|
1747
1768
|
|
|
1748
1769
|
return {
|
|
1749
1770
|
pendingCancelConfirmations: pendingConfirmations,
|
|
@@ -1760,7 +1781,7 @@ const AnearEventMachineFunctions = ({
|
|
|
1760
1781
|
const activeParticipantIds = getActiveParticipantIds(context)
|
|
1761
1782
|
const pendingConfirmations = new Set(activeParticipantIds)
|
|
1762
1783
|
|
|
1763
|
-
logger.info(`[AEM]
|
|
1784
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} starting manual cancellation sequence, waiting for ${pendingConfirmations.size} confirmations`)
|
|
1764
1785
|
|
|
1765
1786
|
return {
|
|
1766
1787
|
pendingCancelConfirmations: pendingConfirmations,
|
|
@@ -1795,7 +1816,8 @@ const AnearEventMachineFunctions = ({
|
|
|
1795
1816
|
payload
|
|
1796
1817
|
}
|
|
1797
1818
|
|
|
1798
|
-
|
|
1819
|
+
const participantName = getParticipantName(context, participantId)
|
|
1820
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} all cancellation confirmations received, forwarding ACTION ${appEventName} from participantId=${participantId} name=${participantName}`)
|
|
1799
1821
|
|
|
1800
1822
|
// Forward to AppM
|
|
1801
1823
|
context.appEventMachine.send({ type: appEventName, ...actionEventPayload })
|
|
@@ -1879,13 +1901,14 @@ const AnearEventMachineFunctions = ({
|
|
|
1879
1901
|
const participantId = event.data.participantId
|
|
1880
1902
|
const eventMessagePayload = JSON.parse(event.data.payload) // { eventName: {eventObject} }
|
|
1881
1903
|
const [appEventName, payload] = Object.entries(eventMessagePayload)[0]
|
|
1904
|
+
const participantName = getParticipantName(context, participantId)
|
|
1882
1905
|
|
|
1883
|
-
logger.
|
|
1906
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} ACTION participantId=${participantId} name=${participantName} action=${appEventName}`)
|
|
1884
1907
|
logger.debug(`[AEM] eachParticipantCancelActionType is: ${context.eachParticipantCancelActionType}`)
|
|
1885
1908
|
|
|
1886
1909
|
// Check if this ACTION should trigger cancellation for eachParticipant timeout
|
|
1887
1910
|
if (context.eachParticipantCancelActionType === appEventName) {
|
|
1888
|
-
logger.info(`[AEM]
|
|
1911
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} intercepting cancel ACTION ${appEventName} from participantId=${participantId} name=${participantName} (eachParticipant timeout)`)
|
|
1889
1912
|
// Trigger cancellation sequence via self-send
|
|
1890
1913
|
self.send({
|
|
1891
1914
|
type: 'TRIGGER_CANCEL_SEQUENCE',
|
|
@@ -1915,6 +1938,13 @@ const AnearEventMachineFunctions = ({
|
|
|
1915
1938
|
const participantId = event.data.participantId;
|
|
1916
1939
|
const { nonResponders } = context.participantsActionTimeout;
|
|
1917
1940
|
|
|
1941
|
+
// Forward to AppM with the finalAction flag
|
|
1942
|
+
const eventMessagePayload = JSON.parse(event.data.payload);
|
|
1943
|
+
const [appEventName, payload] = Object.entries(eventMessagePayload)[0];
|
|
1944
|
+
const participantName = getParticipantName(context, participantId);
|
|
1945
|
+
|
|
1946
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} ACTION participantId=${participantId} name=${participantName} action=${appEventName}`)
|
|
1947
|
+
|
|
1918
1948
|
// Check if this is the last responder before the context is updated.
|
|
1919
1949
|
// This assumes the current participant IS a non-responder.
|
|
1920
1950
|
const isFinalAction = nonResponders.size === 1 && nonResponders.has(participantId);
|
|
@@ -1922,11 +1952,6 @@ const AnearEventMachineFunctions = ({
|
|
|
1922
1952
|
if (isFinalAction) {
|
|
1923
1953
|
logger.debug(`[AEM] Final ACTION received from ${participantId}, allParticipants timeout cancelled (all responded)`)
|
|
1924
1954
|
}
|
|
1925
|
-
logger.info(`[AEM] Participants FINAL ACTION is ${isFinalAction}`)
|
|
1926
|
-
|
|
1927
|
-
// Forward to AppM with the finalAction flag
|
|
1928
|
-
const eventMessagePayload = JSON.parse(event.data.payload);
|
|
1929
|
-
const [appEventName, payload] = Object.entries(eventMessagePayload)[0];
|
|
1930
1955
|
const appM_Payload = {
|
|
1931
1956
|
participantId,
|
|
1932
1957
|
payload,
|
|
@@ -1945,8 +1970,9 @@ const AnearEventMachineFunctions = ({
|
|
|
1945
1970
|
const participantId = event.data.participantId
|
|
1946
1971
|
const eventMessagePayload = JSON.parse(event.data.payload)
|
|
1947
1972
|
const [appEventName, payload] = Object.entries(eventMessagePayload)[0]
|
|
1973
|
+
const participantName = getParticipantName(context, participantId)
|
|
1948
1974
|
|
|
1949
|
-
logger.info(`[AEM]
|
|
1975
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} intercepting cancel ACTION ${appEventName} from participantId=${participantId} name=${participantName} (allParticipants timeout)`)
|
|
1950
1976
|
|
|
1951
1977
|
return {
|
|
1952
1978
|
pendingCancelAction: {
|
|
@@ -1968,15 +1994,17 @@ const AnearEventMachineFunctions = ({
|
|
|
1968
1994
|
}
|
|
1969
1995
|
}),
|
|
1970
1996
|
processParticipantTimeout: ({ context, event, self }) => {
|
|
1997
|
+
const participantId = event.participantId || event.data?.participantId
|
|
1971
1998
|
// Suppress PARTICIPANT_TIMEOUT events after cancelAllParticipantTimeouts() until new timers
|
|
1972
1999
|
// are set up via render. This handles race conditions where an APM's timer fires just
|
|
1973
2000
|
// before receiving CANCEL_TIMEOUT. The flag is cleared when renders complete (new timers set up).
|
|
1974
2001
|
if (context.suppressParticipantTimeouts) {
|
|
1975
|
-
logger.debug(`[AEM] Suppressing PARTICIPANT_TIMEOUT from ${
|
|
2002
|
+
logger.debug(`[AEM] Suppressing PARTICIPANT_TIMEOUT from ${participantId} (cancelled timers, waiting for new render)`)
|
|
1976
2003
|
return
|
|
1977
2004
|
}
|
|
1978
2005
|
|
|
1979
|
-
const
|
|
2006
|
+
const participantName = getParticipantName(context, participantId)
|
|
2007
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} PARTICIPANT_TIMEOUT participantId=${participantId} name=${participantName}`)
|
|
1980
2008
|
const timeoutCount = context.consecutiveTimeoutCount || 0
|
|
1981
2009
|
const maxConsecutiveTimeouts = C.DEAD_MAN_SWITCH.MAX_CONSECUTIVE_PARTICIPANT_TIMEOUTS
|
|
1982
2010
|
|
|
@@ -2080,7 +2108,7 @@ const AnearEventMachineFunctions = ({
|
|
|
2080
2108
|
const nonResponderIds = [...nonResponders]
|
|
2081
2109
|
const allActiveParticipantIds = getActiveParticipantIds(context)
|
|
2082
2110
|
|
|
2083
|
-
logger.info(`[AEM] allParticipants timeout expired after ${msecs}ms
|
|
2111
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} allParticipants timeout expired after ${msecs}ms, nonResponderIds=[${nonResponderIds.join(',')}]`)
|
|
2084
2112
|
|
|
2085
2113
|
// Track consecutive allParticipants timeouts where ALL participants timed out
|
|
2086
2114
|
const allTimedOut = nonResponders.size === allActiveParticipantIds.length && allActiveParticipantIds.length > 0
|
|
@@ -2109,7 +2137,7 @@ const AnearEventMachineFunctions = ({
|
|
|
2109
2137
|
|
|
2110
2138
|
if (context.appEventMachine) {
|
|
2111
2139
|
// Send ACTIONS_TIMEOUT when timeout expires
|
|
2112
|
-
logger.info(`[AEM]
|
|
2140
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} sending ACTIONS_TIMEOUT, nonResponderIds=[${nonResponderIds.join(',')}]`)
|
|
2113
2141
|
context.appEventMachine.send({ type: 'ACTIONS_TIMEOUT', timeout: msecs, nonResponderIds })
|
|
2114
2142
|
}
|
|
2115
2143
|
|
|
@@ -2118,7 +2146,7 @@ const AnearEventMachineFunctions = ({
|
|
|
2118
2146
|
sendActionsTimeoutToAppMCanceled: ({ context }) => {
|
|
2119
2147
|
const { nonResponders, msecs } = context.participantsActionTimeout
|
|
2120
2148
|
const nonResponderIds = [...nonResponders]
|
|
2121
|
-
logger.info(`[AEM] allParticipants timeout cancelled after ${msecs}ms
|
|
2149
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} allParticipants timeout cancelled after ${msecs}ms, nonResponderIds=[${nonResponderIds.join(',')}]`)
|
|
2122
2150
|
|
|
2123
2151
|
if (context.appEventMachine) {
|
|
2124
2152
|
// Send ACTIONS_TIMEOUT when timeout is canceled (backward compatibility)
|
|
@@ -2147,7 +2175,8 @@ const AnearEventMachineFunctions = ({
|
|
|
2147
2175
|
participantMachine.send({ type: 'ACTION', ...actionEventPayload })
|
|
2148
2176
|
}
|
|
2149
2177
|
|
|
2150
|
-
|
|
2178
|
+
const participantName = getParticipantName(context, participantId)
|
|
2179
|
+
logger.info(`[AEM] Event ${context.anearEvent.id} forwarded cancel ACTION ${appEventName} from participantId=${participantId} name=${participantName} after allParticipants cancellation`)
|
|
2151
2180
|
|
|
2152
2181
|
// Clear the pending action
|
|
2153
2182
|
context.pendingCancelAction = null
|
|
@@ -2212,8 +2241,8 @@ const AnearEventMachineFunctions = ({
|
|
|
2212
2241
|
},
|
|
2213
2242
|
logCreatorEnter: ({ event }) => logger.debug("[AEM] got creator PARTICIPANT_ENTER: ", event.data.id),
|
|
2214
2243
|
logAPMReady: ({ event }) => logger.debug("[AEM] PARTICIPANT_MACHINE_READY for: ", event.data.anearParticipant.id),
|
|
2215
|
-
logInvalidParticipantEnter: ({ event }) => logger.info(
|
|
2216
|
-
logDeferringAppmFinal: (
|
|
2244
|
+
logInvalidParticipantEnter: ({ context, event }) => logger.info(`[AEM] Event ${context.anearEvent.id} unexpected PARTICIPANT_ENTER participantId=${event.data.id}`),
|
|
2245
|
+
logDeferringAppmFinal: ({ context }) => logger.info(`[AEM] Event ${context.anearEvent.id} deferring APPM_FINAL (already transitioning to terminated)`),
|
|
2217
2246
|
logImplicitShutdownWarning: (_c, _e) => {
|
|
2218
2247
|
logger.error("********************************************************************************");
|
|
2219
2248
|
logger.error("[AEM] WARNING: AppMachine reached a 'final' state without explicitly calling anearEvent.closeEvent() or anearEvent.cancelEvent().");
|
|
@@ -2633,8 +2662,16 @@ const AnearEventMachine = (anearEvent, {
|
|
|
2633
2662
|
// v5 note: subscriptions do not emit the current snapshot immediately;
|
|
2634
2663
|
// use getSnapshot() if you want the current value right away.
|
|
2635
2664
|
service.subscribe(state => {
|
|
2636
|
-
|
|
2637
|
-
|
|
2665
|
+
// Log major state transitions at INFO level, keep detailed state dumps at DEBUG
|
|
2666
|
+
const stateValue = state.value
|
|
2667
|
+
const stateStr = typeof stateValue === 'string' ? stateValue : JSON.stringify(stateValue)
|
|
2668
|
+
// Only log if it's a major state change (not nested state transitions)
|
|
2669
|
+
if (typeof stateValue === 'string' || (typeof stateValue === 'object' && Object.keys(stateValue).length === 1)) {
|
|
2670
|
+
logger.debug(`[AEM] State: ${stateStr}`)
|
|
2671
|
+
} else {
|
|
2672
|
+
logger.debug('─'.repeat(40))
|
|
2673
|
+
logger.debug(`[AEM] NEXT STATE → ${stateStr}`)
|
|
2674
|
+
}
|
|
2638
2675
|
})
|
|
2639
2676
|
|
|
2640
2677
|
return service
|
|
@@ -110,6 +110,7 @@ const AnearParticipantMachineContext = (anearParticipant, anearEvent, appPartici
|
|
|
110
110
|
appParticipantMachine: null,
|
|
111
111
|
actionTimeoutMsecs: null,
|
|
112
112
|
actionTimeoutStart: null,
|
|
113
|
+
capturedTimeoutMsecs: null, // Captured timeout value for logging before clearing
|
|
113
114
|
lastSeen: CurrentDateTimestamp()
|
|
114
115
|
})
|
|
115
116
|
|
|
@@ -262,7 +263,7 @@ const AnearParticipantMachineConfig = participantId => ({
|
|
|
262
263
|
// XState v5 handles this correctly - the timer fires from the parent state
|
|
263
264
|
// and can transition even during invoke processing.
|
|
264
265
|
actionTimeout: {
|
|
265
|
-
actions: 'nullActionTimeout',
|
|
266
|
+
actions: ['captureTimeoutForLogging', 'nullActionTimeout'],
|
|
266
267
|
target: '#participantTimedOut'
|
|
267
268
|
}
|
|
268
269
|
},
|
|
@@ -303,13 +304,22 @@ const AnearParticipantMachineConfig = participantId => ({
|
|
|
303
304
|
}
|
|
304
305
|
},
|
|
305
306
|
on: {
|
|
306
|
-
RENDER_DISPLAY:
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
307
|
+
RENDER_DISPLAY: [
|
|
308
|
+
{
|
|
309
|
+
// BLOCKED: RENDER_DISPLAY that carries a NEW timeout while we're already
|
|
310
|
+
// waiting on an ACTION is not allowed (it would reset / conflict timers).
|
|
311
|
+
// The AppM must first cancel timers (via cancelAllParticipantTimeouts()
|
|
312
|
+
// or cancel: "ACTION" in meta) and wait for ACK before issuing new
|
|
313
|
+
// RENDER_DISPLAY with timeouts.
|
|
314
|
+
guard: 'incomingRenderHasTimeout',
|
|
315
|
+
actions: 'logBlockedRenderDuringWait'
|
|
316
|
+
},
|
|
317
|
+
{
|
|
318
|
+
// Allowed: visual-only refresh (no timeout). We publish the display while
|
|
319
|
+
// staying inside waitParticipantResponse so the parent after: timer continues.
|
|
320
|
+
target: '.rendering'
|
|
321
|
+
}
|
|
322
|
+
],
|
|
313
323
|
PARTICIPANT_EXIT: {
|
|
314
324
|
actions: 'logExit',
|
|
315
325
|
target: '#cleanupAndExit'
|
|
@@ -342,7 +352,7 @@ const AnearParticipantMachineConfig = participantId => ({
|
|
|
342
352
|
},
|
|
343
353
|
participantTimedOut: {
|
|
344
354
|
id: 'participantTimedOut',
|
|
345
|
-
entry: ['logParticipantTimedOut', 'sendTimeoutEvents'
|
|
355
|
+
entry: ['logParticipantTimedOut', 'sendTimeoutEvents'],
|
|
346
356
|
always: 'idle'
|
|
347
357
|
},
|
|
348
358
|
cleanupAndExit: {
|
|
@@ -454,8 +464,15 @@ const AnearParticipantMachineFunctions = {
|
|
|
454
464
|
return actor
|
|
455
465
|
}
|
|
456
466
|
}),
|
|
467
|
+
captureTimeoutForLogging: assign(({ context }) => {
|
|
468
|
+
// Capture timeout value before it's cleared by nullActionTimeout
|
|
469
|
+
return {
|
|
470
|
+
capturedTimeoutMsecs: context.actionTimeoutMsecs
|
|
471
|
+
}
|
|
472
|
+
}),
|
|
457
473
|
sendTimeoutEvents: ({ context }) => {
|
|
458
|
-
|
|
474
|
+
const timeoutMsecs = context.capturedTimeoutMsecs ?? context.actionTimeoutMsecs ?? 'unknown'
|
|
475
|
+
logger.info(`[APM] Timer expired participantId=${context.anearParticipant.id} timeout=${timeoutMsecs}ms, sending PARTICIPANT_TIMEOUT to AEM`)
|
|
459
476
|
// AEM currently expects `event.participantId` (not nested under `data`).
|
|
460
477
|
context.anearEvent.send({
|
|
461
478
|
type: 'PARTICIPANT_TIMEOUT',
|
|
@@ -488,7 +505,7 @@ const AnearParticipantMachineFunctions = {
|
|
|
488
505
|
if (existingMsecs != null || existingStart != null) {
|
|
489
506
|
logger.debug(`[APM] WARNING: Starting new timer for ${context.anearParticipant.id} but existing timer state found: msecs=${existingMsecs}, start=${existingStart}`)
|
|
490
507
|
}
|
|
491
|
-
logger.
|
|
508
|
+
logger.info(`[APM] Timer started participantId=${context.anearParticipant.id} timeout=${timeoutFromEvent}ms`)
|
|
492
509
|
return {
|
|
493
510
|
actionTimeoutMsecs: timeoutFromEvent,
|
|
494
511
|
actionTimeoutStart: now
|
|
@@ -518,7 +535,7 @@ const AnearParticipantMachineFunctions = {
|
|
|
518
535
|
}
|
|
519
536
|
|
|
520
537
|
// No existing timer - start a new one (shouldn't happen in waitParticipantResponse, but handle gracefully)
|
|
521
|
-
logger.
|
|
538
|
+
logger.info(`[APM] Timer started participantId=${context.anearParticipant.id} timeout=${timeoutFromEvent}ms`)
|
|
522
539
|
return {
|
|
523
540
|
actionTimeoutMsecs: timeoutFromEvent,
|
|
524
541
|
actionTimeoutStart: now
|
|
@@ -638,6 +655,12 @@ const AnearParticipantMachineFunctions = {
|
|
|
638
655
|
},
|
|
639
656
|
guards: {
|
|
640
657
|
hasAppParticipantMachine: ({ context }) => context.appParticipantMachineFactory !== null,
|
|
658
|
+
incomingRenderHasTimeout: ({ event }) => {
|
|
659
|
+
// RENDER_DISPLAY events are sent directly as { type, content, timeout? }
|
|
660
|
+
// We only block when a *new* timeout is being asserted during an active wait.
|
|
661
|
+
const timeout = event?.timeout ?? event?.data?.timeout
|
|
662
|
+
return timeout != null && timeout > 0
|
|
663
|
+
},
|
|
641
664
|
hasActionTimeout: ({ event }) => {
|
|
642
665
|
const timeout = event?.output?.timeout ?? event?.data?.timeout
|
|
643
666
|
return timeout != null && timeout > 0
|
|
@@ -55,7 +55,8 @@ const AppMachineTransition = (anearEvent) => {
|
|
|
55
55
|
const stateName = _stringifiedState(value)
|
|
56
56
|
const hasMeta = rawMeta && Object.keys(rawMeta).length > 0;
|
|
57
57
|
|
|
58
|
-
logger.
|
|
58
|
+
logger.info(`[AppMachineTransition] AppM state transition to '${stateName}' event=${event.type}`)
|
|
59
|
+
logger.debug(`[AppMachineTransition] Meta detected: ${hasMeta}`)
|
|
59
60
|
|
|
60
61
|
// Handle unpredictable meta structure
|
|
61
62
|
const metaObjects = rawMeta ? Object.values(rawMeta) : []
|
package/lib/utils/CssUploader.js
CHANGED
|
@@ -27,7 +27,7 @@ class CSSUploader {
|
|
|
27
27
|
*/
|
|
28
28
|
async uploadCss() {
|
|
29
29
|
if (!fs.existsSync(this.cssDirPath)) {
|
|
30
|
-
logger.info(`No css directory found at ${this.cssDirPath}. Skipping CSS upload.`)
|
|
30
|
+
logger.info(`[CssUploader] No css directory found at ${this.cssDirPath}. Skipping CSS upload.`)
|
|
31
31
|
return
|
|
32
32
|
}
|
|
33
33
|
|
|
@@ -38,8 +38,10 @@ class CSSUploader {
|
|
|
38
38
|
|
|
39
39
|
if (presignedUrl) {
|
|
40
40
|
await this.uploadToS3(presignedUrl, css, 'text/css')
|
|
41
|
+
logger.info(`[CssUploader] CSS uploaded successfully`)
|
|
41
42
|
logger.debug('Uploaded CSS file to S3.')
|
|
42
43
|
} else {
|
|
44
|
+
logger.info(`[CssUploader] CSS up to date`)
|
|
43
45
|
logger.debug('CSS file is up to date; no upload necessary.')
|
|
44
46
|
}
|
|
45
47
|
|
|
@@ -141,7 +141,8 @@ class DisplayEventProcessor {
|
|
|
141
141
|
|
|
142
142
|
switch (displayViewer) {
|
|
143
143
|
case 'allParticipants':
|
|
144
|
-
|
|
144
|
+
const templateName = normalizedPath.replace(C.PugSuffix, '')
|
|
145
|
+
logger.info(`[DisplayEventProcessor] RENDER_DISPLAY template=${templateName} viewer=allParticipants`)
|
|
145
146
|
|
|
146
147
|
// Use display timeout if available, otherwise fall back to timeoutFn
|
|
147
148
|
if (displayTimeout !== null && displayTimeout > 0) {
|
|
@@ -175,7 +176,8 @@ class DisplayEventProcessor {
|
|
|
175
176
|
break
|
|
176
177
|
|
|
177
178
|
case 'spectators':
|
|
178
|
-
|
|
179
|
+
const templateNameSpectators = normalizedPath.replace(C.PugSuffix, '')
|
|
180
|
+
logger.info(`[DisplayEventProcessor] RENDER_DISPLAY template=${templateNameSpectators} viewer=spectators`)
|
|
179
181
|
|
|
180
182
|
// If this event/app does not support spectators, the spectatorsDisplayChannel
|
|
181
183
|
// will be null. In that case, skip publishing instead of throwing.
|
|
@@ -217,13 +219,14 @@ class DisplayEventProcessor {
|
|
|
217
219
|
break
|
|
218
220
|
|
|
219
221
|
case 'eachParticipant':
|
|
222
|
+
const templateNameEach = normalizedPath.replace(C.PugSuffix, '')
|
|
220
223
|
if (participantId === 'ALL_PARTICIPANTS') {
|
|
221
224
|
// Legacy behavior - send to all participants (uses timeoutFn)
|
|
222
|
-
logger.
|
|
225
|
+
logger.info(`[DisplayEventProcessor] RENDER_DISPLAY template=${templateNameEach} viewer=eachParticipant participantId=ALL_PARTICIPANTS`)
|
|
223
226
|
publishPromise = this._processPrivateParticipantDisplays(template, templateRenderContext, timeoutFn)
|
|
224
227
|
} else if (participantId) {
|
|
225
228
|
// Selective participant rendering - send to specific participant only (uses displayTimeout)
|
|
226
|
-
logger.
|
|
229
|
+
logger.info(`[DisplayEventProcessor] RENDER_DISPLAY template=${templateNameEach} viewer=eachParticipant participantId=${participantId}`)
|
|
227
230
|
publishPromise = this._processSelectiveParticipantDisplay(template, templateRenderContext, null, participantId, displayTimeout)
|
|
228
231
|
} else {
|
|
229
232
|
// Fallback - should not happen with unified approach
|
|
@@ -233,7 +236,8 @@ class DisplayEventProcessor {
|
|
|
233
236
|
break
|
|
234
237
|
|
|
235
238
|
case 'host':
|
|
236
|
-
|
|
239
|
+
const templateNameHost = normalizedPath.replace(C.PugSuffix, '')
|
|
240
|
+
logger.info(`[DisplayEventProcessor] RENDER_DISPLAY template=${templateNameHost} viewer=host`)
|
|
237
241
|
// Host may optionally have a real timeout (if configured). Otherwise, host
|
|
238
242
|
// mirrors the allParticipants visual timer without starting a host timeout.
|
|
239
243
|
let hostOwnMsecs = null
|
|
@@ -402,21 +406,25 @@ class DisplayEventProcessor {
|
|
|
402
406
|
const start = pat.startedAt || Date.now()
|
|
403
407
|
const remainingMsecs = Math.max(0, pat.msecs - (now - start))
|
|
404
408
|
visualTimeout = { msecs: pat.msecs, remainingMsecs }
|
|
405
|
-
} else
|
|
406
|
-
// Fallback: no global timeout
|
|
407
|
-
// show a bar based on
|
|
409
|
+
} else {
|
|
410
|
+
// Fallback: no global timeout.
|
|
411
|
+
// If this participant currently has an active APM timer, show a bar based on that.
|
|
412
|
+
// This enables "visual-only" re-renders (timeout=null) during an active wait without
|
|
413
|
+
// attempting to restart/reset the timer.
|
|
408
414
|
const state = participantMachine?.state
|
|
409
415
|
const ctx = state?.context
|
|
410
|
-
|
|
411
|
-
if (ctx && ctx.actionTimeoutStart && ctx.actionTimeoutMsecs != null) {
|
|
416
|
+
if (ctx && ctx.actionTimeoutStart && ctx.actionTimeoutMsecs != null && ctx.actionTimeoutMsecs > 0) {
|
|
412
417
|
const now = Date.now()
|
|
413
418
|
const elapsed = now - ctx.actionTimeoutStart
|
|
414
419
|
const remaining = ctx.actionTimeoutMsecs - elapsed
|
|
415
|
-
remainingMsecs = remaining > 0 ? remaining : 0
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
+
const remainingMsecs = remaining > 0 ? remaining : 0
|
|
421
|
+
visualTimeout = {
|
|
422
|
+
msecs: ctx.actionTimeoutMsecs,
|
|
423
|
+
remainingMsecs
|
|
424
|
+
}
|
|
425
|
+
} else if (timeout !== null) {
|
|
426
|
+
// Backward compatibility: if we have an explicit timeout, mirror it.
|
|
427
|
+
visualTimeout = { msecs: timeout, remainingMsecs: timeout }
|
|
420
428
|
}
|
|
421
429
|
}
|
|
422
430
|
} catch (_e) {
|
|
@@ -438,8 +446,6 @@ class DisplayEventProcessor {
|
|
|
438
446
|
renderMessage.timeout = timeout
|
|
439
447
|
}
|
|
440
448
|
|
|
441
|
-
logger.debug(`Calculated a timeout of ${renderMessage.timeout} for ${participantId}`)
|
|
442
|
-
|
|
443
449
|
// v5: send events as objects
|
|
444
450
|
participantMachine.send({ type: 'RENDER_DISPLAY', ...renderMessage })
|
|
445
451
|
}
|
|
@@ -19,7 +19,7 @@ class FontAssetsUploader {
|
|
|
19
19
|
await fs.stat(this.fontsDirPath)
|
|
20
20
|
} catch (e) {
|
|
21
21
|
if (e.code === 'ENOENT') {
|
|
22
|
-
logger.info(`assets/fonts directory not found at ${this.fontsDirPath}. Skipping font upload.`)
|
|
22
|
+
logger.info(`[FontAssetsUploader] assets/fonts directory not found at ${this.fontsDirPath}. Skipping font upload.`)
|
|
23
23
|
return null
|
|
24
24
|
}
|
|
25
25
|
throw e
|
|
@@ -29,7 +29,7 @@ class FontAssetsUploader {
|
|
|
29
29
|
const filesToUpload = await collector.collectFiles()
|
|
30
30
|
|
|
31
31
|
if (filesToUpload.length === 0) {
|
|
32
|
-
logger.info("No font files to upload.")
|
|
32
|
+
logger.info("[FontAssetsUploader] No font files to upload.")
|
|
33
33
|
return null
|
|
34
34
|
}
|
|
35
35
|
|
|
@@ -42,6 +42,7 @@ class FontAssetsUploader {
|
|
|
42
42
|
}
|
|
43
43
|
|
|
44
44
|
if (presignedUrls && presignedUrls.length > 0) {
|
|
45
|
+
logger.info(`[FontAssetsUploader] Uploading ${presignedUrls.length} font asset(s)`)
|
|
45
46
|
await Promise.all(presignedUrls.map(async (uploadInfo) => {
|
|
46
47
|
const { path: relativePath, 'presigned-url': presignedUrl } = uploadInfo
|
|
47
48
|
const fileData = filesToUpload.find(file => file.path === relativePath)
|
|
@@ -55,7 +56,9 @@ class FontAssetsUploader {
|
|
|
55
56
|
throw new Error(`File data not found for path: ${relativePath}`)
|
|
56
57
|
}
|
|
57
58
|
}))
|
|
59
|
+
logger.info(`[FontAssetsUploader] Font assets uploaded successfully`)
|
|
58
60
|
} else {
|
|
61
|
+
logger.info(`[FontAssetsUploader] Font assets up to date`)
|
|
59
62
|
logger.debug("No new or updated font files to upload.")
|
|
60
63
|
}
|
|
61
64
|
|
|
@@ -46,6 +46,7 @@ class ImageAssetsUploader {
|
|
|
46
46
|
|
|
47
47
|
// Step 3: For each presigned URL, upload image file content to S3
|
|
48
48
|
if (presignedUrls && presignedUrls.length > 0) {
|
|
49
|
+
logger.info(`[ImageAssetsUploader] Uploading ${presignedUrls.length} image asset(s)`)
|
|
49
50
|
await Promise.all(presignedUrls.map(async (uploadInfo) => {
|
|
50
51
|
const { path: filePath, 'presigned-url': presignedUrl } = uploadInfo
|
|
51
52
|
|
|
@@ -64,7 +65,9 @@ class ImageAssetsUploader {
|
|
|
64
65
|
throw new Error(`File data not found for path: ${filePath}`)
|
|
65
66
|
}
|
|
66
67
|
}))
|
|
68
|
+
logger.info(`[ImageAssetsUploader] Image assets uploaded successfully`)
|
|
67
69
|
} else {
|
|
70
|
+
logger.info(`[ImageAssetsUploader] Image assets up to date`)
|
|
68
71
|
logger.debug('No files to upload; all files are up to date.')
|
|
69
72
|
}
|
|
70
73
|
|
|
@@ -7,6 +7,42 @@ const AnearApi = require('../api/AnearApi')
|
|
|
7
7
|
const DefaultHeartbeatIntervalMsecs = 15000
|
|
8
8
|
const NotableChannelEvents = ['attached', 'suspended', 'failed']
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Extracts a short channel name abbreviation from the full channel name
|
|
12
|
+
* @param {string} channelName - Full channel name (e.g., "anear:appId:e:eventId:actions")
|
|
13
|
+
* @returns {string} - Short abbreviation (e.g., "actions", "private", "participants", "spectators", "events")
|
|
14
|
+
*/
|
|
15
|
+
function getChannelShortName(channelName) {
|
|
16
|
+
if (!channelName) return 'unknown'
|
|
17
|
+
|
|
18
|
+
// Pattern: anear:appId:e:eventId:actions → actions
|
|
19
|
+
// Pattern: anear:appId:e:eventId:private:participantId → private
|
|
20
|
+
// Pattern: anear:appId:e:eventId:participants → participants
|
|
21
|
+
// Pattern: anear:appId:e:eventId:spectators → spectators
|
|
22
|
+
// Pattern: anear:appId:e → events
|
|
23
|
+
|
|
24
|
+
const parts = channelName.split(':')
|
|
25
|
+
|
|
26
|
+
// Check for event-scoped channels (anear:appId:e:eventId:...)
|
|
27
|
+
if (parts.length >= 4 && parts[2] === 'e') {
|
|
28
|
+
if (parts.length === 5) {
|
|
29
|
+
// anear:appId:e:eventId:channelType
|
|
30
|
+
return parts[4] // actions, participants, spectators
|
|
31
|
+
} else if (parts.length === 6 && parts[4] === 'private') {
|
|
32
|
+
// anear:appId:e:eventId:private:participantId
|
|
33
|
+
return 'private'
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Check for event creation channel (anear:appId:e)
|
|
38
|
+
if (parts.length === 3 && parts[2] === 'e') {
|
|
39
|
+
return 'events'
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Fallback: return last part or full name if pattern doesn't match
|
|
43
|
+
return parts[parts.length - 1] || channelName
|
|
44
|
+
}
|
|
45
|
+
|
|
10
46
|
class RealtimeMessaging {
|
|
11
47
|
constructor() {
|
|
12
48
|
this.ablyRealtime = null
|
|
@@ -160,7 +196,9 @@ class RealtimeMessaging {
|
|
|
160
196
|
// Actions reference event.data to get at participantId, etc
|
|
161
197
|
const { data } = message // ably presence event message from browser contains message.data.id
|
|
162
198
|
const type = data.type ? data.type : 'NONE'
|
|
163
|
-
|
|
199
|
+
const channelShortName = getChannelShortName(channel.name)
|
|
200
|
+
const channelLabel = channelShortName === 'actions' ? 'actions:presence' : channelShortName
|
|
201
|
+
logger.info(`[RTM] received presence ${eventName} participantId=${data.id} on channel ${channelLabel}`)
|
|
164
202
|
actor.send({ type: eventName, data })
|
|
165
203
|
}
|
|
166
204
|
)
|
|
@@ -183,13 +221,18 @@ class RealtimeMessaging {
|
|
|
183
221
|
}
|
|
184
222
|
|
|
185
223
|
async publish(channel, msgType, message) {
|
|
224
|
+
const participantId = message?.data?.participantId || message?.participantId || null
|
|
225
|
+
const participantIdStr = participantId ? ` participantId=${participantId}` : ''
|
|
226
|
+
const channelShortName = getChannelShortName(channel.name)
|
|
227
|
+
logger.info(`[RTM] publishing ${msgType}${participantIdStr} on channel ${channelShortName}`)
|
|
186
228
|
return channel.publish(msgType, message)
|
|
187
229
|
}
|
|
188
230
|
|
|
189
231
|
async setPresence(channel, data) {
|
|
190
232
|
try {
|
|
191
233
|
await channel.presence.enter(data)
|
|
192
|
-
|
|
234
|
+
const channelShortName = getChannelShortName(channel.name)
|
|
235
|
+
logger.info(`[RTM] presence.enter participantId=${data.id} on channel ${channelShortName}`)
|
|
193
236
|
} catch (e) {
|
|
194
237
|
logger.warn(`[RTM] presence.enter failed on ${channel.name}`, e)
|
|
195
238
|
}
|
|
@@ -223,7 +266,10 @@ class RealtimeMessaging {
|
|
|
223
266
|
if (eventName) args.push(eventName)
|
|
224
267
|
args.push(
|
|
225
268
|
message => {
|
|
226
|
-
|
|
269
|
+
const participantId = message.data?.participantId || null
|
|
270
|
+
const participantIdStr = participantId ? ` participantId=${participantId}` : ''
|
|
271
|
+
const channelShortName = getChannelShortName(channel.name)
|
|
272
|
+
logger.info(`[RTM] received message ${message.name}${participantIdStr} on channel ${channelShortName}`)
|
|
227
273
|
actor.send({ type: message.name, data: message.data })
|
|
228
274
|
}
|
|
229
275
|
)
|