anear-js-api 0.4.40 → 0.5.2

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.
@@ -24,15 +24,26 @@ const logger = require('../utils/Logger')
24
24
  //
25
25
 
26
26
  const { assign, createMachine, interpret } = require('xstate')
27
+ const C = require('../utils/Constants')
28
+
29
+ const getPlayingParticipantIds = (context) => {
30
+ return Object.keys(context.participants).filter(id => !context.participants[id].isHost);
31
+ };
32
+
33
+ const getAllParticipantIds = (context) => {
34
+ return Object.keys(context.participants);
35
+ };
27
36
 
28
- const AnearApi = require('../api/AnearApi')
29
- const AnearParticipantMachine = require('../state_machines/AnearParticipantMachine')
30
- const AnearParticipant = require('../models/AnearParticipant')
31
37
  const RealtimeMessaging = require('../utils/RealtimeMessaging')
32
38
  const AppMachineTransition = require('../utils/AppMachineTransition')
33
39
  const DisplayEventProcessor = require('../utils/DisplayEventProcessor')
34
40
  const PugHelpers = require('../utils/PugHelpers')
35
- const C = require('../utils/Constants')
41
+
42
+ const AnearApi = require('../api/AnearApi')
43
+ const AnearParticipantMachine = require('../state_machines/AnearParticipantMachine')
44
+ const AnearParticipant = require('../models/AnearParticipant')
45
+
46
+ const CurrentDateTimestamp = _ => new Date().getTime()
36
47
 
37
48
  const AnearEventMachineContext = (
38
49
  anearEvent,
@@ -54,7 +65,8 @@ const AnearEventMachineContext = (
54
65
  participantsDisplayChannel: null, // display all participants
55
66
  spectatorsDisplayChannel: null, // display all spectators
56
67
  participants: {},
57
- participantMachines: {}
68
+ participantMachines: {},
69
+ participantsActionTimeout: null
58
70
  })
59
71
 
60
72
  const DeferredStates = [
@@ -126,7 +138,7 @@ const ActiveEventStatesConfig = {
126
138
  target: '#waiting.fetching'
127
139
  },
128
140
  PARTICIPANT_MACHINE_READY: {
129
- actions: 'logAPMReady',
141
+ actions: ['logAPMReady', 'sendEnterToAppMachine'],
130
142
  target: '#eventCreated'
131
143
  }
132
144
  }
@@ -164,13 +176,38 @@ const ActiveEventStatesConfig = {
164
176
  id: 'eventCreated',
165
177
  initial: 'waitingAnnounce',
166
178
  entry: [
167
- 'sendParticipantEnterToAppEventMachine',
168
179
  'enableSpectatorPresenceEvents'
169
180
  ],
170
181
  on: {
182
+ ACTION: {
183
+ actions: ['processParticipantAction']
184
+ },
171
185
  RENDER_DISPLAY: {
172
186
  target: '#createdRendering'
173
- }
187
+ },
188
+ PARTICIPANT_LEAVE: [
189
+ {
190
+ cond: 'isHostLeaving',
191
+ actions: ['sendHostExitToAppMachine', 'sendExitToParticipantMachine']
192
+ },
193
+ {
194
+ cond: 'isPermanentLeave',
195
+ actions: ['sendExitToAppMachine', 'sendExitToParticipantMachine']
196
+ },
197
+ {
198
+ actions: ['processDisconnectEvents']
199
+ }
200
+ ],
201
+ PARTICIPANT_ENTER: [
202
+ {
203
+ cond: 'participantExists',
204
+ actions: 'processReconnectEvents'
205
+ },
206
+ {
207
+ // This shouldn't happen in eventCreated, but good to handle.
208
+ target: '#newParticipantJoining'
209
+ }
210
+ ]
174
211
  },
175
212
  states: {
176
213
  waitingAnnounce: {
@@ -207,8 +244,7 @@ const ActiveEventStatesConfig = {
207
244
  notifyingRenderComplete: {
208
245
  after: {
209
246
  timeoutRendered: {
210
- target: '#waitingAnnounce',
211
- actions: ['notifyAppMachineRendered']
247
+ target: '#waitingAnnounce'
212
248
  }
213
249
  }
214
250
  }
@@ -222,16 +258,23 @@ const ActiveEventStatesConfig = {
222
258
  // existing participant
223
259
  initial: 'transitioning',
224
260
  on: {
261
+ ACTION: {
262
+ actions: ['processParticipantAction']
263
+ },
225
264
  RENDER_DISPLAY: {
226
265
  target: '#announceRendering'
227
266
  },
228
267
  PARTICIPANT_LEAVE: [
268
+ {
269
+ cond: 'isHostLeaving',
270
+ actions: ['sendHostExitToAppMachine', 'sendExitToParticipantMachine']
271
+ },
229
272
  {
230
273
  cond: 'isPermanentLeave',
231
- actions: ['sendParticipantExitToAppEventMachine', 'sendExitToParticipantMachine']
274
+ actions: ['sendExitToAppMachine', 'sendExitToParticipantMachine']
232
275
  },
233
276
  {
234
- actions: ['sendParticipantDisconnectEvents']
277
+ actions: ['processDisconnectEvents']
235
278
  }
236
279
  ],
237
280
  PARTICIPANT_ENTER: [
@@ -239,7 +282,7 @@ const ActiveEventStatesConfig = {
239
282
  // a participant could re-entering the event after above PARTICIPANT_LEAVE (browser refresh).
240
283
  // so look for existing participants and send reconnect events to interested machines
241
284
  cond: 'participantExists',
242
- actions: 'sendParticipantReconnectEvents'
285
+ actions: 'processReconnectEvents'
243
286
  },
244
287
  {
245
288
  // spectator clicked JOIN
@@ -326,7 +369,7 @@ const ActiveEventStatesConfig = {
326
369
  deferred: DeferredStatesPlus('START'),
327
370
  on: {
328
371
  PARTICIPANT_MACHINE_READY: {
329
- actions: ['sendParticipantEnterToAppEventMachine'],
372
+ actions: ['sendEnterToAppMachine'],
330
373
  target: '#waitingToStart',
331
374
  internal: true
332
375
  }
@@ -335,28 +378,38 @@ const ActiveEventStatesConfig = {
335
378
  }
336
379
  },
337
380
  live: {
381
+ id: 'live',
338
382
  initial: 'transitioning',
339
383
  on: {
340
384
  RENDER_DISPLAY: {
341
385
  target: '#liveRendering'
342
386
  },
343
- SPECTATOR_ENTER: {
344
- actions: 'sendSpectatorEnterToAppEventMachine'
345
- },
346
387
  PARTICIPANT_LEAVE: [
388
+ {
389
+ cond: 'isHostLeaving',
390
+ actions: ['sendHostExitToAppMachine', 'sendExitToParticipantMachine']
391
+ },
347
392
  {
348
393
  cond: 'isPermanentLeave',
349
- actions: ['sendParticipantExitToAppEventMachine', 'sendExitToParticipantMachine']
394
+ actions: ['sendExitToAppMachine', 'sendExitToParticipantMachine']
350
395
  },
351
396
  {
352
- actions: ['sendParticipantDisconnectEvents']
397
+ actions: ['processDisconnectEvents']
353
398
  }
354
399
  ],
355
- PARTICIPANT_TIMEOUT: {
356
- actions: ['processParticipantTimeout']
357
- },
358
- PAUSE: '#activeEvent.paused', // Currently no use-case for these
359
- CLOSE: '#activeEvent.closeEvent' // AppM explicitly closed the event
400
+ PARTICIPANT_ENTER: [
401
+ {
402
+ cond: 'participantExists',
403
+ actions: 'processReconnectEvents'
404
+ },
405
+ {
406
+ // spectator clicked JOIN
407
+ target: '.participantEntering'
408
+ }
409
+ ],
410
+ PAUSE: {
411
+ target: 'paused'
412
+ }
360
413
  },
361
414
  states: {
362
415
  transitioning: {
@@ -373,28 +426,9 @@ const ActiveEventStatesConfig = {
373
426
  },
374
427
  waitingForActions: {
375
428
  id: 'waitingForActions',
429
+ entry: () => logger.debug('[AEM] live state...waiting for actions'),
376
430
  on: {
377
- PARTICIPANT_ENTER: [
378
- {
379
- // a participant could re-entering the event after above PARTICIPANT_LEAVE (browser refresh).
380
- // so look for existing participants and send reconnect events to interested machines
381
- cond: 'participantExists',
382
- actions: 'sendParticipantReconnectEvents'
383
- },
384
- {
385
- // in the future, open house events allow spectators to join and leave freely
386
- // during live.waitingForActions
387
- cond: 'isOpenHouseEvent',
388
- target: 'participantEntering'
389
- },
390
- {
391
- // shouldn't receive these so log it and return to current state
392
- actions: ['logInvalidParticipantEnter'],
393
- target: '#activeEvent.failure'
394
- }
395
- ],
396
431
  ACTION: {
397
- // A participant has clicked an anear-data-action
398
432
  actions: ['processParticipantAction']
399
433
  }
400
434
  }
@@ -404,10 +438,17 @@ const ActiveEventStatesConfig = {
404
438
  deferred: DeferredStates,
405
439
  invoke: {
406
440
  src: 'renderDisplay',
407
- onDone: {
408
- target: 'notifyingRenderComplete',
409
- internal: true
410
- },
441
+ onDone: [
442
+ {
443
+ cond: 'isParticipantsTimeoutActive',
444
+ target: 'notifyingRenderCompleteWithTimeout',
445
+ actions: 'setupParticipantsTimeout'
446
+ },
447
+ {
448
+ target: 'notifyingRenderComplete',
449
+ internal: true
450
+ }
451
+ ],
411
452
  onError: {
412
453
  target: '#activeEvent.failure'
413
454
  }
@@ -416,11 +457,57 @@ const ActiveEventStatesConfig = {
416
457
  notifyingRenderComplete: {
417
458
  after: {
418
459
  timeoutRendered: {
419
- target: '#waitingForActions',
460
+ target: 'waitingForActions',
461
+ actions: ['notifyAppMachineRendered']
462
+ }
463
+ }
464
+ },
465
+ notifyingRenderCompleteWithTimeout: {
466
+ after: {
467
+ timeoutRendered: {
468
+ target: 'waitAllParticipantsResponse',
420
469
  actions: ['notifyAppMachineRendered']
421
470
  }
422
471
  }
423
472
  },
473
+ waitAllParticipantsResponse: {
474
+ always: {
475
+ cond: 'allParticipantsResponded',
476
+ target: 'waitingForActions',
477
+ actions: ['clearParticipantsTimeout']
478
+ },
479
+ after: {
480
+ participantsActionTimeout: {
481
+ target: 'handleParticipantsTimeout'
482
+ }
483
+ },
484
+ on: {
485
+ ACTION: {
486
+ actions: ['processAndForwardAction', 'processParticipantResponse'],
487
+ internal: true
488
+ },
489
+ RENDER_DISPLAY: {
490
+ target: '#liveRendering'
491
+ },
492
+ PARTICIPANT_LEAVE: [
493
+ {
494
+ cond: 'isHostLeaving',
495
+ actions: ['removeLeavingParticipantFromTimeout', 'sendHostExitToAppMachine', 'sendExitToParticipantMachine']
496
+ },
497
+ {
498
+ cond: 'isPermanentLeave',
499
+ actions: ['removeLeavingParticipantFromTimeout', 'sendExitToAppMachine', 'sendExitToParticipantMachine']
500
+ },
501
+ {
502
+ actions: ['processDisconnectEvents']
503
+ }
504
+ ]
505
+ }
506
+ },
507
+ handleParticipantsTimeout: {
508
+ entry: ['sendActionsTimeoutToAppM', 'clearParticipantsTimeout'],
509
+ always: 'waitingForActions'
510
+ },
424
511
  participantEntering: {
425
512
  // a PARTICIPANT_ENTER received from a new user JOIN click. Unless already exists,
426
513
  // create an AnearParticipantMachine instance.
@@ -444,8 +531,8 @@ const ActiveEventStatesConfig = {
444
531
  deferred: DeferredStates,
445
532
  on: {
446
533
  PARTICIPANT_MACHINE_READY: {
447
- actions: ['sendParticipantEnterToAppEventMachine'],
448
- target: '#waitingForActions',
534
+ actions: ['sendEnterToAppMachine'],
535
+ target: 'waitingForActions',
449
536
  internal: true
450
537
  }
451
538
  }
@@ -484,10 +571,10 @@ const ActiveEventStatesConfig = {
484
571
  always: 'waitForParticipantsToExit'
485
572
  },
486
573
  waitForParticipantsToExit: {
487
- entry: context => logger.debug(`[AEM] Entering waitForParticipantsToExit with ${Object.keys(context.participants).length} participants`),
574
+ entry: context => logger.debug(`[AEM] Entering waitForParticipantsToExit with ${getAllParticipantIds(context).length} participants`),
488
575
  always: [
489
576
  {
490
- cond: context => Object.keys(context.participants).length === 0,
577
+ cond: context => getAllParticipantIds(context).length === 0,
491
578
  target: 'finalizing'
492
579
  }
493
580
  ],
@@ -530,10 +617,10 @@ const ActiveEventStatesConfig = {
530
617
  always: 'waitForParticipantsToExit'
531
618
  },
532
619
  waitForParticipantsToExit: {
533
- entry: context => logger.debug(`[AEM] Entering waitForParticipantsToExit with ${Object.keys(context.participants).length} participants`),
620
+ entry: context => logger.debug(`[AEM] Entering waitForParticipantsToExit with ${getAllParticipantIds(context).length} participants`),
534
621
  always: [
535
622
  {
536
- cond: context => Object.keys(context.participants).length === 0,
623
+ cond: context => getAllParticipantIds(context).length === 0,
537
624
  target: 'finalizing'
538
625
  }
539
626
  ],
@@ -625,7 +712,7 @@ const CreateEventChannelsAndAppMachineConfig = {
625
712
  invoke: {
626
713
  src: 'attachToEventChannel',
627
714
  onDone: {
628
- target: 'setupEventChannel',
715
+ target: '.',
629
716
  internal: true
630
717
  },
631
718
  onError: {
@@ -823,15 +910,68 @@ const AnearEventMachineFunctions = ({
823
910
  userJSON => context.appEventMachine.send('SPECTATOR_ENTER', userJSON)
824
911
  )
825
912
  },
826
- sendParticipantEnterToAppEventMachine: (context, event) => {
827
- const { anearParticipant } = event.data
913
+ sendEnterToAppMachine: (context, event) => {
914
+ const anearParticipant = event.data?.anearParticipant ?? event.anearParticipant;
915
+
916
+ if (!anearParticipant) {
917
+ logger.error('[AEM] sendEnterToAppMachine was called without an anearParticipant in the event', event);
918
+ return;
919
+ }
828
920
  const participantInfo = context.participants[anearParticipant.id]
829
921
 
830
- logger.debug(`[AEM] Participant machine ${participantInfo.id} is READY`)
922
+ if (!participantInfo) {
923
+ logger.error(`[AEM] participantInfo not found for ${anearParticipant.id} in sendEnterToAppMachine`);
924
+ return;
925
+ }
926
+
927
+ const isHost = anearParticipant.isHost;
928
+ const eventName = isHost ? 'HOST_ENTER' : 'PARTICIPANT_ENTER';
929
+ const eventPayload = { participant: participantInfo };
930
+
931
+ logger.debug(`[AEM] Sending ${eventName} for ${anearParticipant.id}`);
932
+ context.appEventMachine.send(eventName, eventPayload);
933
+ },
934
+ processDisconnectEvents: (context, event) => {
935
+ const participantId = event.data.id;
936
+ const participant = context.participants[participantId];
937
+ const isHost = participant && participant.isHost;
938
+ const eventName = isHost ? 'HOST_DISCONNECT' : 'PARTICIPANT_DISCONNECT';
939
+ const participantMachine = context.participantMachines[participantId];
831
940
 
832
- const startEvent = { participant: participantInfo }
941
+ logger.debug(`[AEM] processing disconnect for ${participantId}. Is host? ${isHost}`);
942
+ if (participantMachine) {
943
+ participantMachine.send('PARTICIPANT_DISCONNECT');
944
+ }
945
+ context.appEventMachine.send(eventName, { participantId });
946
+ },
947
+ processReconnectEvents: (context, event) => {
948
+ const participantId = event.data.id;
949
+ const participant = context.participants[participantId];
950
+ const isHost = participant && participant.isHost;
951
+ const eventName = isHost ? 'HOST_RECONNECT' : 'PARTICIPANT_RECONNECT';
952
+ const participantMachine = context.participantMachines[participantId];
833
953
 
834
- context.appEventMachine.send('PARTICIPANT_ENTER', startEvent)
954
+ logger.debug(`[AEM] processing reconnect for ${participantId}. Is host? ${isHost}`);
955
+ if (participantMachine) {
956
+ participantMachine.send('PARTICIPANT_RECONNECT');
957
+ }
958
+ context.appEventMachine.send(eventName, { participantId });
959
+ },
960
+ sendExitToAppMachine: (context, event) => {
961
+ // coming from an action channel message, event.data.id
962
+ const participantId = event.data.id;
963
+ const participantInfo = context.participants[participantId];
964
+ if (participantInfo) {
965
+ logger.debug(`[AEM] sending PARTICIPANT_EXIT to AppM for participant ${participantId}`);
966
+ context.appEventMachine.send('PARTICIPANT_EXIT', { participantId });
967
+ } else {
968
+ logger.warn(`[AEM] Participant info not found for id ${participantId} during sendExitToAppMachine`);
969
+ }
970
+ },
971
+ sendHostExitToAppMachine: (context, event) => {
972
+ const participantId = event.data.id;
973
+ logger.debug(`[AEM] sending HOST_EXIT to AppM for host ${participantId}`);
974
+ context.appEventMachine.send('HOST_EXIT', { participantId });
835
975
  },
836
976
  sendExitToParticipantMachine: (context, event) => {
837
977
  // coming from an action channel message, event.data.id
@@ -843,46 +983,11 @@ const AnearEventMachineFunctions = ({
843
983
  logger.warn(`[AEM] Participant machine not found for id ${event.data.id} during sendExitToParticipantMachine`)
844
984
  }
845
985
  },
846
- sendParticipantExitToAppEventMachine: (context, event) => {
847
- context.appEventMachine.send('PARTICIPANT_EXIT',
848
- { participantId: event.data.id }
849
- )
850
- },
851
986
  sendParticipantExitEvents: context => {
852
987
  Object.values(context.participantMachines).forEach(pm => pm.send('PARTICIPANT_EXIT'))
853
988
  },
854
989
 
855
990
 
856
- sendParticipantDisconnectEvents: (context, event) => {
857
- const participantMachine = context.participantMachines[event.data.id]
858
- if (participantMachine) {
859
- logger.debug("[AEM] sending PARTICIPANT_DISCONNECT to APM and AppM")
860
- // triggers a timer state in APM to timeout a missing Participant
861
- participantMachine.send('PARTICIPANT_DISCONNECT', event.data)
862
- // gives the App the opportunity to process the potential early exit
863
- // of a key player, possibly pausing or altering game behavior until
864
- // the participant either times out (exits game), or comes back after
865
- // the momentary outage
866
- context.appEventMachine.send('PARTICIPANT_DISCONNECT',
867
- { participantId: event.data.id }
868
- )
869
- }
870
- },
871
- sendParticipantReconnectEvents: (context, event) => {
872
- const participantId = event.data.id
873
- const participantMachine = context.participantMachines[participantId]
874
- if (participantMachine) {
875
- // suspends the disconnect timeout and restores participant presence state in
876
- // the event
877
- participantMachine.send('PARTICIPANT_RECONNECT')
878
- }
879
-
880
- const participantInfo = context.participants[participantId]
881
- // send this to the app so they can receive and trigger a meta display for private participants
882
- if (participantInfo) {
883
- context.appEventMachine.send('PARTICIPANT_RECONNECT', { participant: participantInfo })
884
- }
885
- },
886
991
  updateParticipantPresence: (context, event) => {
887
992
  // lookup the participantMachine and update its context
888
993
  const participantMachine = context.participantMachines[event.data.id]
@@ -902,6 +1007,9 @@ const AnearEventMachineFunctions = ({
902
1007
  const participantId = event.data.participantId
903
1008
  const eventMessagePayload = JSON.parse(event.data.payload) // { eventName: {eventObject} }
904
1009
  const [appEventName, payload] = Object.entries(eventMessagePayload)[0]
1010
+
1011
+ logger.debug(`[AEM] got Event ${appEventName} from payload from participant ${participantId}`)
1012
+
905
1013
  const actionEventPayload = {
906
1014
  participantId,
907
1015
  payload
@@ -913,10 +1021,90 @@ const AnearEventMachineFunctions = ({
913
1021
 
914
1022
  context.appEventMachine.send(appEventName, actionEventPayload)
915
1023
  },
1024
+ processAndForwardAction: (context, event) => {
1025
+ const participantId = event.data.participantId;
1026
+ const { nonResponders } = context.participantsActionTimeout;
1027
+
1028
+ // Check if this is the last responder before the context is updated.
1029
+ // This assumes the current participant IS a non-responder.
1030
+ const isFinalAction = nonResponders.size === 1 && nonResponders.has(participantId);
1031
+
1032
+ logger.info(`[AEM] Participants FINAL ACTION is ${isFinalAction}`)
1033
+
1034
+ // Forward to AppM with the finalAction flag
1035
+ const eventMessagePayload = JSON.parse(event.data.payload);
1036
+ const [appEventName, payload] = Object.entries(eventMessagePayload)[0];
1037
+ const appM_Payload = {
1038
+ participantId,
1039
+ payload,
1040
+ finalAction: isFinalAction
1041
+ };
1042
+ context.appEventMachine.send(appEventName, appM_Payload);
1043
+
1044
+ // Forward to APM (without the flag)
1045
+ const participantMachine = context.participantMachines[participantId];
1046
+ if (participantMachine) {
1047
+ const apm_Payload = { participantId, payload };
1048
+ participantMachine.send('ACTION', apm_Payload);
1049
+ }
1050
+ },
916
1051
  processParticipantTimeout: (context, event) => context.appEventMachine.send(
917
1052
  'PARTICIPANT_TIMEOUT',
918
1053
  { participantId: event.participantId }
919
1054
  ),
1055
+ setupParticipantsTimeout: assign((context, event) => {
1056
+ const timeoutMsecs = event.data.participantsTimeout.msecs
1057
+ const allParticipantIds = getPlayingParticipantIds(context)
1058
+ logger.debug(`[AEM] Starting participants action timeout for ${timeoutMsecs}ms. Responders: ${allParticipantIds.join(', ')}`)
1059
+
1060
+ return {
1061
+ participantsActionTimeout: {
1062
+ msecs: timeoutMsecs,
1063
+ nonResponders: new Set(allParticipantIds)
1064
+ }
1065
+ }
1066
+ }),
1067
+ processParticipantResponse: assign((context, event) => {
1068
+ const participantId = event.data.participantId
1069
+ const { nonResponders, ...rest } = context.participantsActionTimeout
1070
+ const newNonResponders = new Set(nonResponders)
1071
+ newNonResponders.delete(participantId)
1072
+
1073
+ return {
1074
+ participantsActionTimeout: {
1075
+ ...rest,
1076
+ nonResponders: newNonResponders
1077
+ }
1078
+ }
1079
+ }),
1080
+ removeLeavingParticipantFromTimeout: assign((context, event) => {
1081
+ const participantId = event.data.id
1082
+ const { nonResponders, ...rest } = context.participantsActionTimeout
1083
+ const newNonResponders = new Set(nonResponders)
1084
+ newNonResponders.delete(participantId)
1085
+
1086
+ return {
1087
+ participantsActionTimeout: {
1088
+ ...rest,
1089
+ nonResponders: newNonResponders
1090
+ }
1091
+ }
1092
+ }),
1093
+ sendActionsTimeoutToAppM: (context, _event) => {
1094
+ const { nonResponders, msecs } = context.participantsActionTimeout
1095
+ const nonResponderIds = [...nonResponders]
1096
+ logger.info(`[AEM] Participants action timed out. Non-responders: ${nonResponderIds.join(', ')}`)
1097
+
1098
+ if (context.appEventMachine) {
1099
+ context.appEventMachine.send('ACTIONS_TIMEOUT', {
1100
+ timeout: msecs,
1101
+ nonResponderIds
1102
+ })
1103
+ }
1104
+ },
1105
+ clearParticipantsTimeout: assign({
1106
+ participantsActionTimeout: null
1107
+ }),
920
1108
  cleanupExitingParticipant: assign((context, event) => {
921
1109
  const { participantId } = event
922
1110
  const participant = context.participants[participantId]
@@ -951,13 +1139,11 @@ const AnearEventMachineFunctions = ({
951
1139
  logInvalidParticipantEnter: (c, e) => logger.info("[AEM] Error: Unexepected PARTICIPANT_ENTER with id: ", e.data.id),
952
1140
  },
953
1141
  services: {
954
- renderDisplay: (context, event) => {
1142
+ renderDisplay: async (context, event) => {
955
1143
  const displayEventProcessor = new DisplayEventProcessor(context)
956
1144
 
957
- return displayEventProcessor.processAndPublish(event.displayEvents)
1145
+ return await displayEventProcessor.processAndPublish(event.displayEvents)
958
1146
  },
959
-
960
-
961
1147
  getAttachedCreatorOrHost: async (context, event) => {
962
1148
  logger.debug("[AEM] getAttachedCreatorOrHost() invoked")
963
1149
 
@@ -1051,16 +1237,30 @@ const AnearEventMachineFunctions = ({
1051
1237
  return false
1052
1238
  }
1053
1239
  },
1240
+ isHostLeaving: (context, event) => {
1241
+ const participantId = event.data.id;
1242
+ const participant = context.participants[participantId];
1243
+ return participant && participant.isHost;
1244
+ },
1054
1245
  participantExists: (context, event) => !!context.participants[event.data.id],
1055
1246
  eventCreatorIsHost: (context, event) => context.anearEvent.hosted,
1056
- isOpenHouseEvent: (context, event) => context.anearEvent.openHouse || false // TODO: need to have App open_house trait exposed in anear api
1247
+ isOpenHouseEvent: (context, event) => context.anearEvent.openHouse || false,
1248
+ isParticipantsTimeoutActive: (context, event) => {
1249
+ return event.data && event.data.participantsTimeout && event.data.participantsTimeout.msecs > 0
1250
+ },
1251
+ allParticipantsResponded: (context, event) => {
1252
+ return context.participantsActionTimeout && context.participantsActionTimeout.nonResponders.size === 0
1253
+ }
1057
1254
  },
1058
1255
  delays: {
1059
1256
  // in the future, these delays should be goverened by the type of App and
1060
1257
  // if there has been activity.
1061
1258
  timeoutEventAnnounce: context => C.TIMEOUT_MSECS.ANNOUNCE,
1062
1259
  timeoutEventStart: context => C.TIMEOUT_MSECS.START,
1063
- timeoutRendered: context => C.TIMEOUT_MSECS.RENDERED_EVENT_DELAY
1260
+ timeoutRendered: context => C.TIMEOUT_MSECS.RENDERED_EVENT_DELAY,
1261
+ participantsActionTimeout: (context, event) => {
1262
+ return context.participantsActionTimeout.msecs
1263
+ }
1064
1264
  }
1065
1265
  })
1066
1266