anear-js-api 1.6.2 → 1.6.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.
|
@@ -204,7 +204,8 @@ const AnearEventMachineContext = (
|
|
|
204
204
|
pendingCancelAction: null, // ACTION to forward after cancellation completes: { participantId, appEventName, payload }
|
|
205
205
|
consecutiveTimeoutCount: 0, // Global counter tracking consecutive timeouts across all participants for dead-man switch
|
|
206
206
|
consecutiveAllParticipantsTimeoutCount: 0, // Counter tracking consecutive allParticipants timeouts where ALL participants timed out
|
|
207
|
-
eventStats: null // Set on createAppEventMachine onDone (initial or rehydrated); createInitialEventStats() shape
|
|
207
|
+
eventStats: null, // Set on createAppEventMachine onDone (initial or rehydrated); createInitialEventStats() shape
|
|
208
|
+
lastRenderResult: null // { anyDisplaySent } from renderDisplay onDone; used to send noDisplaysSent on RENDERED and avoid re-render loops when all participants exited
|
|
208
209
|
})
|
|
209
210
|
|
|
210
211
|
const ActiveEventGlobalEvents = {
|
|
@@ -425,7 +426,7 @@ const ActiveEventStatesConfig = {
|
|
|
425
426
|
input: ({ context, event }) => ({ context, event }),
|
|
426
427
|
onDone: {
|
|
427
428
|
target: 'notifyingPausedRenderComplete',
|
|
428
|
-
actions: ['clearParticipantTimeoutSuppression']
|
|
429
|
+
actions: ['setLastRenderResult', 'clearParticipantTimeoutSuppression']
|
|
429
430
|
// v5 note: internal transitions are the default (v4 had `internal: true`)
|
|
430
431
|
},
|
|
431
432
|
onError: {
|
|
@@ -464,7 +465,7 @@ const ActiveEventStatesConfig = {
|
|
|
464
465
|
input: ({ context, event }) => ({ context, event }),
|
|
465
466
|
onDone: {
|
|
466
467
|
target: 'notifyingRenderComplete',
|
|
467
|
-
actions: ['clearParticipantTimeoutSuppression']
|
|
468
|
+
actions: ['setLastRenderResult', 'clearParticipantTimeoutSuppression']
|
|
468
469
|
// v5 note: internal transitions are the default (v4 had `internal: true`)
|
|
469
470
|
},
|
|
470
471
|
onError: {
|
|
@@ -580,7 +581,7 @@ const ActiveEventStatesConfig = {
|
|
|
580
581
|
input: ({ context, event }) => ({ context, event }),
|
|
581
582
|
onDone: {
|
|
582
583
|
target: 'notifyingRenderComplete',
|
|
583
|
-
actions: ['clearParticipantTimeoutSuppression']
|
|
584
|
+
actions: ['setLastRenderResult', 'clearParticipantTimeoutSuppression']
|
|
584
585
|
// v5 note: internal transitions are the default (v4 had `internal: true`)
|
|
585
586
|
},
|
|
586
587
|
onError: {
|
|
@@ -763,11 +764,11 @@ const ActiveEventStatesConfig = {
|
|
|
763
764
|
{
|
|
764
765
|
guard: 'isParticipantsTimeoutActive',
|
|
765
766
|
target: 'notifyingRenderCompleteWithTimeout',
|
|
766
|
-
actions: ['setupParticipantsTimeout', 'setupCancelActionType', 'clearParticipantTimeoutSuppression']
|
|
767
|
+
actions: ['setLastRenderResult', 'setupParticipantsTimeout', 'setupCancelActionType', 'clearParticipantTimeoutSuppression']
|
|
767
768
|
},
|
|
768
769
|
{
|
|
769
770
|
target: 'notifyingRenderComplete',
|
|
770
|
-
actions: ['setupCancelActionType', 'clearParticipantTimeoutSuppression']
|
|
771
|
+
actions: ['setLastRenderResult', 'setupCancelActionType', 'clearParticipantTimeoutSuppression']
|
|
771
772
|
// v5 note: internal transitions are the default (v4 had `internal: true`)
|
|
772
773
|
}
|
|
773
774
|
],
|
|
@@ -816,13 +817,13 @@ const ActiveEventStatesConfig = {
|
|
|
816
817
|
{
|
|
817
818
|
guard: 'isParticipantsTimeoutActive',
|
|
818
819
|
target: 'notifyingRenderComplete',
|
|
819
|
-
actions: ['setupCancelActionType', 'clearParticipantTimeoutSuppression']
|
|
820
|
+
actions: ['setLastRenderResult', 'setupCancelActionType', 'clearParticipantTimeoutSuppression']
|
|
820
821
|
// Note: We do NOT call setupParticipantsTimeout here because we're already in waitAllParticipantsResponse
|
|
821
822
|
// and the timer is still running from the parent state (preserved by nested state pattern).
|
|
822
823
|
},
|
|
823
824
|
{
|
|
824
825
|
target: '#waitingForActions',
|
|
825
|
-
actions: ['setupCancelActionType', 'clearParticipantTimeoutSuppression', 'notifyAppMachineRendered']
|
|
826
|
+
actions: ['setLastRenderResult', 'setupCancelActionType', 'clearParticipantTimeoutSuppression', 'notifyAppMachineRendered']
|
|
826
827
|
}
|
|
827
828
|
],
|
|
828
829
|
onError: {
|
|
@@ -1474,6 +1475,10 @@ const AnearEventMachineFunctions = ({
|
|
|
1474
1475
|
return { ...s, liveDurationMs: (s.liveDurationMs || 0) + add, liveStartedAt: null }
|
|
1475
1476
|
}
|
|
1476
1477
|
}),
|
|
1478
|
+
setLastRenderResult: assign(({ event }) => {
|
|
1479
|
+
const output = event?.output ?? event?.data
|
|
1480
|
+
return { lastRenderResult: output && typeof output === 'object' ? output : null }
|
|
1481
|
+
}),
|
|
1477
1482
|
notifyAppMachineRendered: ({ context }) => {
|
|
1478
1483
|
if (context.appEventMachine) {
|
|
1479
1484
|
logger.debug('[AEM] Sending RENDERED to AppM')
|
|
@@ -121,14 +121,12 @@ const AppMachineTransition = (anearEvent) => {
|
|
|
121
121
|
}
|
|
122
122
|
|
|
123
123
|
if (event.type === 'RENDERED') {
|
|
124
|
-
//
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
const hasEachParticipant = (rawMeta && typeof rawMeta === 'object' && rawMeta.eachParticipant != null) ||
|
|
128
|
-
metaObjects.some(m => m && typeof m === 'object' && m.eachParticipant != null)
|
|
124
|
+
// RENDERED means "display processing done; AppM transitions out of the display state."
|
|
125
|
+
// Do not re-send RENDER_DISPLAY on RENDERED. New participants get their initial view via
|
|
126
|
+
// PARTICIPANT_ENTER flow, not by re-running eachParticipant here.
|
|
129
127
|
const sameSignature = baseSignature === lastBaseSignature
|
|
130
|
-
if (sameSignature
|
|
131
|
-
logger.debug(`[AppMachineTransition] RENDERED
|
|
128
|
+
if (sameSignature) {
|
|
129
|
+
logger.debug(`[AppMachineTransition] RENDERED: no change, skipping RENDER_DISPLAY stateName='${stateName}'`)
|
|
132
130
|
return
|
|
133
131
|
}
|
|
134
132
|
lastBaseSignature = baseSignature
|
|
@@ -81,16 +81,18 @@ class DisplayEventProcessor {
|
|
|
81
81
|
// ignore precompute errors; fall back to runtime values
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
const
|
|
85
|
-
|
|
84
|
+
const results = displayEvents.map(event => this._processSingle(event))
|
|
85
|
+
const publishPromises = results.map(r => r.publishPromise)
|
|
86
|
+
const anyDisplaySent = results.some(r => r.displaySent === true)
|
|
87
|
+
|
|
88
|
+
results.forEach(({ timeout }) => {
|
|
86
89
|
if (timeout) {
|
|
87
90
|
participantsTimeout = timeout
|
|
88
91
|
}
|
|
89
|
-
return publishPromise
|
|
90
92
|
})
|
|
91
93
|
|
|
92
94
|
return Promise.all(publishPromises).then(() => {
|
|
93
|
-
return { participantsTimeout, cancelActionType }
|
|
95
|
+
return { participantsTimeout, cancelActionType, anyDisplaySent }
|
|
94
96
|
})
|
|
95
97
|
}
|
|
96
98
|
|
|
@@ -141,6 +143,7 @@ class DisplayEventProcessor {
|
|
|
141
143
|
// display event, but if not, it falls back to the viewer from the meta block.
|
|
142
144
|
const displayViewer = viewer || appRenderContext.meta.viewer
|
|
143
145
|
|
|
146
|
+
let displaySent = false
|
|
144
147
|
switch (displayViewer) {
|
|
145
148
|
case 'allParticipants':
|
|
146
149
|
const templateName = normalizedPath.replace(C.PugSuffix, '')
|
|
@@ -176,6 +179,7 @@ class DisplayEventProcessor {
|
|
|
176
179
|
'PARTICIPANTS_DISPLAY',
|
|
177
180
|
formattedDisplayMessage()
|
|
178
181
|
)
|
|
182
|
+
displaySent = true
|
|
179
183
|
break
|
|
180
184
|
|
|
181
185
|
case 'spectators':
|
|
@@ -187,6 +191,7 @@ class DisplayEventProcessor {
|
|
|
187
191
|
if (!this.spectatorsDisplayChannel) {
|
|
188
192
|
logger.debug('[DisplayEventProcessor] spectatorsDisplayChannel is null; skipping SPECTATORS_DISPLAY publish')
|
|
189
193
|
publishPromise = Promise.resolve()
|
|
194
|
+
displaySent = false
|
|
190
195
|
break
|
|
191
196
|
}
|
|
192
197
|
|
|
@@ -220,24 +225,29 @@ class DisplayEventProcessor {
|
|
|
220
225
|
'SPECTATORS_DISPLAY',
|
|
221
226
|
formattedDisplayMessage()
|
|
222
227
|
)
|
|
228
|
+
displaySent = true
|
|
223
229
|
break
|
|
224
230
|
|
|
225
|
-
case 'eachParticipant':
|
|
231
|
+
case 'eachParticipant': {
|
|
226
232
|
const templateNameEach = normalizedPath.replace(C.PugSuffix, '')
|
|
227
233
|
if (participantId === 'ALL_PARTICIPANTS') {
|
|
228
234
|
// Legacy behavior - send to all participants (uses timeoutFn)
|
|
229
235
|
logger.info(`[DisplayEventProcessor] RENDER_DISPLAY template=${templateNameEach} viewer=eachParticipant participantId=ALL_PARTICIPANTS`)
|
|
230
236
|
publishPromise = this._processPrivateParticipantDisplays(template, templateRenderContext, timeoutFn)
|
|
237
|
+
displaySent = true // We attempt to send to all in index; at least one may receive
|
|
231
238
|
} else if (participantId) {
|
|
232
239
|
// Selective participant rendering - send to specific participant only (uses displayTimeout)
|
|
233
240
|
logger.info(`[DisplayEventProcessor] RENDER_DISPLAY template=${templateNameEach} viewer=eachParticipant participantId=${participantId}`)
|
|
234
|
-
|
|
241
|
+
const result = this._processSelectiveParticipantDisplayWithSent(template, templateRenderContext, null, participantId, displayTimeout)
|
|
242
|
+
publishPromise = result.promise
|
|
243
|
+
displaySent = result.displaySent
|
|
235
244
|
} else {
|
|
236
245
|
// Fallback - should not happen with unified approach
|
|
237
246
|
logger.warn(`[DisplayEventProcessor] Unexpected participant display event without participantId`)
|
|
238
247
|
publishPromise = Promise.resolve()
|
|
239
248
|
}
|
|
240
249
|
break
|
|
250
|
+
}
|
|
241
251
|
|
|
242
252
|
case 'host':
|
|
243
253
|
const templateNameHost = normalizedPath.replace(C.PugSuffix, '')
|
|
@@ -277,12 +287,13 @@ class DisplayEventProcessor {
|
|
|
277
287
|
templateRenderContext,
|
|
278
288
|
hostOwnMsecs !== null ? (() => hostOwnMsecs) : null
|
|
279
289
|
)
|
|
290
|
+
displaySent = true
|
|
280
291
|
break
|
|
281
292
|
|
|
282
293
|
default:
|
|
283
294
|
throw new Error(`Unknown display viewer: ${displayViewer}`)
|
|
284
295
|
}
|
|
285
|
-
return { publishPromise, timeout }
|
|
296
|
+
return { publishPromise, timeout, displaySent }
|
|
286
297
|
}
|
|
287
298
|
|
|
288
299
|
_processHostDisplay(template, templateRenderContext, timeoutFn) {
|
|
@@ -329,30 +340,35 @@ class DisplayEventProcessor {
|
|
|
329
340
|
return Promise.resolve()
|
|
330
341
|
}
|
|
331
342
|
|
|
332
|
-
|
|
343
|
+
/**
|
|
344
|
+
* Returns { promise, displaySent } for selective participant display.
|
|
345
|
+
* When participant is not found (exited), we treat as delivered (displaySent: false) so the
|
|
346
|
+
* render cycle can complete and we avoid infinite RENDER_DISPLAY/RENDERED loops.
|
|
347
|
+
*/
|
|
348
|
+
_processSelectiveParticipantDisplayWithSent(template, templateRenderContext, timeoutFn, participantId, displayTimeout = null) {
|
|
333
349
|
const participantStruct = this.participantsIndex.get(participantId)
|
|
334
350
|
|
|
335
351
|
if (!participantStruct) {
|
|
336
|
-
logger.
|
|
337
|
-
return Promise.resolve()
|
|
352
|
+
logger.debug(`[DisplayEventProcessor] Participant ${participantId} not found for selective display (exited); treating as delivered`)
|
|
353
|
+
return { promise: Promise.resolve(), displaySent: false }
|
|
338
354
|
}
|
|
339
355
|
|
|
340
356
|
// Exclude the host from receiving displays targeted at participants
|
|
341
357
|
if (participantStruct.info.isHost) {
|
|
342
358
|
logger.warn(`[DisplayEventProcessor] Cannot send participant display to host ${participantId}`)
|
|
343
|
-
return Promise.resolve()
|
|
359
|
+
return { promise: Promise.resolve(), displaySent: false }
|
|
344
360
|
}
|
|
345
361
|
|
|
346
362
|
// Skip inactive participants (idle in open house events)
|
|
347
363
|
if (participantStruct.info.active === false) {
|
|
348
364
|
logger.debug(`[DisplayEventProcessor] Skipping display for inactive participant ${participantId}`)
|
|
349
|
-
return Promise.resolve()
|
|
365
|
+
return { promise: Promise.resolve(), displaySent: false }
|
|
350
366
|
}
|
|
351
367
|
|
|
352
368
|
const participantMachine = this.participantMachines[participantId]
|
|
353
369
|
if (!participantMachine) {
|
|
354
|
-
logger.
|
|
355
|
-
return Promise.resolve()
|
|
370
|
+
logger.debug(`[DisplayEventProcessor] Participant machine not found for ${participantId} (exited); treating as delivered`)
|
|
371
|
+
return { promise: Promise.resolve(), displaySent: false }
|
|
356
372
|
}
|
|
357
373
|
|
|
358
374
|
// Use displayTimeout if available, otherwise fall back to timeoutFn
|
|
@@ -361,7 +377,12 @@ class DisplayEventProcessor {
|
|
|
361
377
|
timeoutFn
|
|
362
378
|
|
|
363
379
|
this._sendPrivateDisplay(participantMachine, participantId, template, templateRenderContext, effectiveTimeoutFn)
|
|
364
|
-
return Promise.resolve()
|
|
380
|
+
return { promise: Promise.resolve(), displaySent: true }
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
_processSelectiveParticipantDisplay(template, templateRenderContext, timeoutFn, participantId, displayTimeout = null) {
|
|
384
|
+
const { promise } = this._processSelectiveParticipantDisplayWithSent(template, templateRenderContext, timeoutFn, participantId, displayTimeout)
|
|
385
|
+
return promise
|
|
365
386
|
}
|
|
366
387
|
|
|
367
388
|
_sendPrivateDisplay(participantMachine, participantId, template, templateRenderContext, timeoutFn) {
|