anear-js-api 0.4.30 → 0.4.33
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.
|
@@ -30,6 +30,7 @@ const AnearParticipantMachine = require('../state_machines/AnearParticipantMachi
|
|
|
30
30
|
const AnearParticipant = require('../models/AnearParticipant')
|
|
31
31
|
const RealtimeMessaging = require('../utils/RealtimeMessaging')
|
|
32
32
|
const MetaProcessing = require('../utils/MetaProcessing')
|
|
33
|
+
const DisplayEventProcessor = require('../utils/DisplayEventProcessor')
|
|
33
34
|
const PugHelpers = require('../utils/PugHelpers')
|
|
34
35
|
const C = require('../utils/Constants')
|
|
35
36
|
|
|
@@ -180,7 +181,7 @@ const ActiveEventStatesConfig = {
|
|
|
180
181
|
],
|
|
181
182
|
on: {
|
|
182
183
|
RENDER_DISPLAY: {
|
|
183
|
-
target: '
|
|
184
|
+
target: '#createdRendering'
|
|
184
185
|
}
|
|
185
186
|
},
|
|
186
187
|
states: {
|
|
@@ -195,7 +196,8 @@ const ActiveEventStatesConfig = {
|
|
|
195
196
|
}
|
|
196
197
|
}
|
|
197
198
|
},
|
|
198
|
-
|
|
199
|
+
createdRendering: {
|
|
200
|
+
id: 'createdRendering',
|
|
199
201
|
deferred: DeferredStatesPlus('ANNOUNCE', 'START'),
|
|
200
202
|
invoke: {
|
|
201
203
|
src: 'renderDisplay',
|
|
@@ -219,7 +221,7 @@ const ActiveEventStatesConfig = {
|
|
|
219
221
|
initial: 'transitioning',
|
|
220
222
|
on: {
|
|
221
223
|
RENDER_DISPLAY: {
|
|
222
|
-
target: '
|
|
224
|
+
target: '#announceRendering'
|
|
223
225
|
},
|
|
224
226
|
PARTICIPANT_LEAVE: {
|
|
225
227
|
// creator browser refresh or MIA. Send disconnect events
|
|
@@ -230,19 +232,15 @@ const ActiveEventStatesConfig = {
|
|
|
230
232
|
// a participant could re-entering the event after above PARTICIPANT_LEAVE (browser refresh).
|
|
231
233
|
// so look for existing participants and send reconnect events to interested machines
|
|
232
234
|
cond: 'participantExists',
|
|
233
|
-
actions: 'sendParticipantReconnectEvents'
|
|
234
|
-
target: '#waitingToStart',
|
|
235
|
-
internal: true
|
|
235
|
+
actions: 'sendParticipantReconnectEvents'
|
|
236
236
|
},
|
|
237
237
|
{
|
|
238
238
|
// spectator clicked JOIN
|
|
239
|
-
target: '#
|
|
239
|
+
target: '#spectatorJoining'
|
|
240
240
|
}
|
|
241
241
|
],
|
|
242
242
|
SPECTATOR_ENTER: {
|
|
243
|
-
actions: 'sendSpectatorEnterToAppEventMachine'
|
|
244
|
-
target: '#waitingToStart',
|
|
245
|
-
internal: true
|
|
243
|
+
actions: 'sendSpectatorEnterToAppEventMachine'
|
|
246
244
|
}
|
|
247
245
|
},
|
|
248
246
|
states: {
|
|
@@ -271,7 +269,8 @@ const ActiveEventStatesConfig = {
|
|
|
271
269
|
}
|
|
272
270
|
}
|
|
273
271
|
},
|
|
274
|
-
|
|
272
|
+
announceRendering: {
|
|
273
|
+
id: 'announceRendering',
|
|
275
274
|
deferred: DeferredStatesPlus('START'),
|
|
276
275
|
invoke: {
|
|
277
276
|
src: 'renderDisplay',
|
|
@@ -284,30 +283,56 @@ const ActiveEventStatesConfig = {
|
|
|
284
283
|
}
|
|
285
284
|
}
|
|
286
285
|
},
|
|
287
|
-
|
|
286
|
+
spectatorJoining: {
|
|
288
287
|
// a PARTICIPANT_ENTER received from a new user JOIN click. create an AnearParticipantMachine instance.
|
|
289
288
|
// This machine tracks presence, geo-location (when approved by mobile participant),
|
|
290
289
|
// manages active/idle state for long-running events, and manages any ACTION timeouts.
|
|
291
|
-
id: '
|
|
290
|
+
id: 'spectatorJoining',
|
|
292
291
|
deferred: DeferredStatesPlus('START'),
|
|
293
292
|
invoke: {
|
|
294
293
|
src: 'fetchParticipantData',
|
|
295
294
|
onDone: {
|
|
296
|
-
actions: [
|
|
297
|
-
|
|
298
|
-
'sendParticipantEnterToAppEventMachine'
|
|
299
|
-
],
|
|
300
|
-
target: '#waitingToStart',
|
|
295
|
+
actions: ['startNewParticipantMachine'],
|
|
296
|
+
target: '#waitParticipantReady'
|
|
301
297
|
},
|
|
302
298
|
onError: {
|
|
303
299
|
target: '#activeEvent.failure'
|
|
304
300
|
}
|
|
305
301
|
}
|
|
302
|
+
},
|
|
303
|
+
waitParticipantReady: {
|
|
304
|
+
id: 'waitParticipantReady',
|
|
305
|
+
deferred: DeferredStatesPlus('START'),
|
|
306
|
+
on: {
|
|
307
|
+
PARTICIPANT_MACHINE_READY: {
|
|
308
|
+
actions: ['sendParticipantEnterToAppEventMachine'],
|
|
309
|
+
target: '#waitingToStart',
|
|
310
|
+
internal: true
|
|
311
|
+
}
|
|
312
|
+
}
|
|
306
313
|
}
|
|
307
314
|
}
|
|
308
315
|
},
|
|
309
316
|
live: {
|
|
310
317
|
initial: 'transitioning',
|
|
318
|
+
on: {
|
|
319
|
+
RENDER_DISPLAY: {
|
|
320
|
+
target: '#liveRendering'
|
|
321
|
+
},
|
|
322
|
+
SPECTATOR_ENTER: {
|
|
323
|
+
actions: 'sendSpectatorEnterToAppEventMachine'
|
|
324
|
+
},
|
|
325
|
+
PARTICIPANT_LEAVE: {
|
|
326
|
+
// mid-event browser refresh or MIA. Send disconnect events
|
|
327
|
+
actions: ['sendParticipantDisconnectEvents']
|
|
328
|
+
},
|
|
329
|
+
PARTICIPANT_TIMEOUT: {
|
|
330
|
+
// TBD: A participant(s) who was required to take a click action has
|
|
331
|
+
// failed to do so
|
|
332
|
+
},
|
|
333
|
+
PAUSE: '#activeEvent.paused', // Currently no use-case for these
|
|
334
|
+
CLOSE: '#activeEvent.closed'
|
|
335
|
+
},
|
|
311
336
|
states: {
|
|
312
337
|
transitioning: {
|
|
313
338
|
deferred: DeferredStates,
|
|
@@ -324,8 +349,72 @@ const ActiveEventStatesConfig = {
|
|
|
324
349
|
waitingForActions: {
|
|
325
350
|
id: 'waitingForActions',
|
|
326
351
|
on: {
|
|
327
|
-
|
|
328
|
-
|
|
352
|
+
PARTICIPANT_ENTER: [
|
|
353
|
+
{
|
|
354
|
+
// a participant could re-entering the event after above PARTICIPANT_LEAVE (browser refresh).
|
|
355
|
+
// so look for existing participants and send reconnect events to interested machines
|
|
356
|
+
cond: 'participantExists',
|
|
357
|
+
actions: 'sendParticipantReconnectEvents'
|
|
358
|
+
},
|
|
359
|
+
{
|
|
360
|
+
// in the future, open house events allow spectators to join and leave freely
|
|
361
|
+
// during live.waitingForActions
|
|
362
|
+
cond: 'isOpenHouseEvent',
|
|
363
|
+
target: 'participantEntering'
|
|
364
|
+
},
|
|
365
|
+
{
|
|
366
|
+
// shouldn't receive these so log it and return to current state
|
|
367
|
+
actions: [(c, e) => logger.info("Error: Unexepected PARTICIPANT_ENTER")],
|
|
368
|
+
target: '#activeEvent.failure'
|
|
369
|
+
}
|
|
370
|
+
],
|
|
371
|
+
ACTION: {
|
|
372
|
+
// A participant has clicked an anear-data-action
|
|
373
|
+
actions: ['processParticipantAction']
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
},
|
|
377
|
+
liveRendering: {
|
|
378
|
+
id: 'liveRendering',
|
|
379
|
+
deferred: DeferredStates,
|
|
380
|
+
invoke: {
|
|
381
|
+
src: 'renderDisplay',
|
|
382
|
+
onDone: {
|
|
383
|
+
target: '#waitingForActions',
|
|
384
|
+
internal: true
|
|
385
|
+
},
|
|
386
|
+
onError: {
|
|
387
|
+
target: '#activeEvent.failure'
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
},
|
|
391
|
+
participantEntering: {
|
|
392
|
+
// a PARTICIPANT_ENTER received from a new user JOIN click. Unless already exists,
|
|
393
|
+
// create an AnearParticipantMachine instance.
|
|
394
|
+
// This machine tracks presence, geo-location (when approved by mobile participant),
|
|
395
|
+
// manages active/idle state for long-running events, and manages any ACTION timeouts.
|
|
396
|
+
id: 'participantEntering',
|
|
397
|
+
deferred: DeferredStates,
|
|
398
|
+
invoke: {
|
|
399
|
+
src: 'fetchParticipantData',
|
|
400
|
+
onDone: {
|
|
401
|
+
actions: ['startNewParticipantMachine'],
|
|
402
|
+
target: '#waitParticipantJoined',
|
|
403
|
+
},
|
|
404
|
+
onError: {
|
|
405
|
+
target: '#activeEvent.failure'
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
},
|
|
409
|
+
waitParticipantJoined: {
|
|
410
|
+
id: 'waitParticipantJoined',
|
|
411
|
+
deferred: DeferredStates,
|
|
412
|
+
on: {
|
|
413
|
+
PARTICIPANT_MACHINE_READY: {
|
|
414
|
+
actions: ['sendParticipantEnterToAppEventMachine'],
|
|
415
|
+
target: '#waitingForActions',
|
|
416
|
+
internal: true
|
|
417
|
+
}
|
|
329
418
|
}
|
|
330
419
|
}
|
|
331
420
|
}
|
|
@@ -684,6 +773,9 @@ const AnearEventMachineFunctions = ({
|
|
|
684
773
|
sendParticipantEnterToAppEventMachine: (context, event) => {
|
|
685
774
|
const { anearParticipant } = event.data
|
|
686
775
|
const participantInfo = context.participants[anearParticipant.id]
|
|
776
|
+
|
|
777
|
+
logger.debug(`Participant machine ${participantInfo.id} is READY`)
|
|
778
|
+
|
|
687
779
|
const startEvent = { participant: participantInfo }
|
|
688
780
|
|
|
689
781
|
context.appEventMachine.send('PARTICIPANT_ENTER', startEvent)
|
|
@@ -790,88 +882,10 @@ const AnearEventMachineFunctions = ({
|
|
|
790
882
|
}
|
|
791
883
|
},
|
|
792
884
|
services: {
|
|
793
|
-
renderDisplay:
|
|
794
|
-
const
|
|
795
|
-
const { displayType, viewPath, timeout, appExecutionContext } = displayEvent
|
|
796
|
-
|
|
797
|
-
logger.debug("processDisplayEvent - displayType: ", displayType, ", viewPath: ", viewPath)
|
|
798
|
-
|
|
799
|
-
const renderedDisplayContent = (template, additionalExecutionContext, timeout) => {
|
|
800
|
-
const renderedMessage = template(
|
|
801
|
-
{
|
|
802
|
-
displayType,
|
|
803
|
-
...appExecutionContext,
|
|
804
|
-
...additionalExecutionContext,
|
|
805
|
-
participants: context.participants,
|
|
806
|
-
...context.pugHelpers
|
|
807
|
-
}
|
|
808
|
-
)
|
|
809
|
-
const displayContent = { content: renderedMessage }
|
|
810
|
-
if (timeout) {
|
|
811
|
-
displayContent['timeout'] = timeout
|
|
812
|
-
}
|
|
813
|
-
return displayContent
|
|
814
|
-
}
|
|
815
|
-
|
|
816
|
-
const privateParticipantSend = participantMachine => {
|
|
817
|
-
const participantState = participantMachine.state
|
|
818
|
-
const participantContext = participantState.context
|
|
819
|
-
const { appParticipantMachine, anearParticipant } = participantContext
|
|
820
|
-
|
|
821
|
-
// TODO: don't send to participants that aren't in an active state
|
|
822
|
-
|
|
823
|
-
const appParticipantContext =
|
|
824
|
-
appParticipantMachine ? participantContext.appParticipantMachine.state.context : {}
|
|
825
|
-
|
|
826
|
-
const privateContext = {
|
|
827
|
-
anearParticipant: anearParticipant.participantInfo,
|
|
828
|
-
participantContext: appParticipantContext
|
|
829
|
-
}
|
|
885
|
+
renderDisplay: (context, event) => {
|
|
886
|
+
const displayEventProcessor = new DisplayEventProcessor(context)
|
|
830
887
|
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
participantMachine.send('PRIVATE_DISPLAY', displayEventPayload)
|
|
834
|
-
}
|
|
835
|
-
|
|
836
|
-
const normalizedPath = viewPath.endsWith(C.PugSuffix) ? viewPath : `${viewPath}${C.PugSuffix}`
|
|
837
|
-
const template = context.pugTemplates[normalizedPath]
|
|
838
|
-
if (!template) {
|
|
839
|
-
throw new Error(`Template not found for path "${normalizedPath}".`)
|
|
840
|
-
}
|
|
841
|
-
let displayMessage
|
|
842
|
-
|
|
843
|
-
switch (displayType) {
|
|
844
|
-
case 'participants':
|
|
845
|
-
displayMessage = renderedDisplayContent(template, {}, timeout)
|
|
846
|
-
return RealtimeMessaging.publish(
|
|
847
|
-
context.participantsDisplayChannel,
|
|
848
|
-
'PARTICIPANTS_DISPLAY',
|
|
849
|
-
displayMessage.content
|
|
850
|
-
)
|
|
851
|
-
break
|
|
852
|
-
case 'spectators':
|
|
853
|
-
displayMessage = renderedDisplayContent(template, {})
|
|
854
|
-
|
|
855
|
-
return RealtimeMessaging.publish(
|
|
856
|
-
context.spectatorsDisplayChannel,
|
|
857
|
-
'SPECTATORS_DISPLAY',
|
|
858
|
-
displayMessage.content
|
|
859
|
-
)
|
|
860
|
-
break
|
|
861
|
-
case 'participant':
|
|
862
|
-
// For private displays, iterate over active active participant machines.
|
|
863
|
-
Object.values(context.participantMachines).forEach(privateParticipantSend)
|
|
864
|
-
return Promise.resolve()
|
|
865
|
-
break
|
|
866
|
-
default:
|
|
867
|
-
throw new Error(`Unknown display type: ${displayType}`)
|
|
868
|
-
}
|
|
869
|
-
}
|
|
870
|
-
|
|
871
|
-
const publishPromises = event.displayEvents.map(
|
|
872
|
-
displayEvent => processDisplayEvent(displayEvent)
|
|
873
|
-
)
|
|
874
|
-
return Promise.all(publishPromises)
|
|
888
|
+
return displayEventProcessor.processAndPublish(event.displayEvents)
|
|
875
889
|
},
|
|
876
890
|
getAttachedCreatorOrHost: async (context, event) => {
|
|
877
891
|
logger.debug("getAttachedCreatorOrHost() invoked")
|
|
@@ -937,7 +951,8 @@ const AnearEventMachineFunctions = ({
|
|
|
937
951
|
},
|
|
938
952
|
guards: {
|
|
939
953
|
participantExists: (context, event) => !!context.participants[event.data.id],
|
|
940
|
-
eventCreatorIsHost: (context, event) => context.anearEvent.hosted
|
|
954
|
+
eventCreatorIsHost: (context, event) => context.anearEvent.hosted,
|
|
955
|
+
isOpenHouseEvent: (context, event) => false // TODO: need to have App open_house trait exposed in anear api
|
|
941
956
|
}
|
|
942
957
|
})
|
|
943
958
|
|
|
@@ -22,7 +22,7 @@ const CurrentDateTimestamp = _ => new Date().getTime()
|
|
|
22
22
|
const MISSING_TIMEOUT = 10000 // msecs
|
|
23
23
|
|
|
24
24
|
const DeferredStates = [
|
|
25
|
-
'
|
|
25
|
+
'RENDER_DISPLAY',
|
|
26
26
|
'PARTICIPANT_EXIT',
|
|
27
27
|
'PARTICIPANT_DISCONNECT',
|
|
28
28
|
'PARTICIPANT_RECONNECT'
|
|
@@ -92,8 +92,8 @@ const AnearParticipantMachineConfig = participantId => ({
|
|
|
92
92
|
idle: {
|
|
93
93
|
id: 'idle',
|
|
94
94
|
on: {
|
|
95
|
-
|
|
96
|
-
target: '#
|
|
95
|
+
RENDER_DISPLAY: {
|
|
96
|
+
target: '#renderDisplay'
|
|
97
97
|
},
|
|
98
98
|
PARTICIPANT_DISCONNECT: {
|
|
99
99
|
target: 'missing'
|
|
@@ -116,11 +116,11 @@ const AnearParticipantMachineConfig = participantId => ({
|
|
|
116
116
|
}
|
|
117
117
|
}
|
|
118
118
|
},
|
|
119
|
-
|
|
120
|
-
id: '
|
|
119
|
+
renderDisplay: {
|
|
120
|
+
id: 'renderDisplay',
|
|
121
121
|
deferred: DeferredStates,
|
|
122
122
|
invoke: {
|
|
123
|
-
src: '
|
|
123
|
+
src: 'participantDisplay',
|
|
124
124
|
onDone: [
|
|
125
125
|
{
|
|
126
126
|
cond: 'hasMetaTimeout',
|
|
@@ -233,7 +233,7 @@ const AnearParticipantMachineFunctions = {
|
|
|
233
233
|
}
|
|
234
234
|
},
|
|
235
235
|
services: {
|
|
236
|
-
|
|
236
|
+
participantDisplay: async (context, event) => {
|
|
237
237
|
await RealtimeMessaging.publish(
|
|
238
238
|
context.privateChannel,
|
|
239
239
|
'PRIVATE_DISPLAY',
|
|
@@ -248,7 +248,7 @@ const AnearParticipantMachineFunctions = {
|
|
|
248
248
|
},
|
|
249
249
|
guards: {
|
|
250
250
|
hasAppParticipantMachine: context => context.appParticipantMachineFactory !== null,
|
|
251
|
-
hasMetaTimeout: (context, event) => event.data.timeout
|
|
251
|
+
hasMetaTimeout: (context, event) => event.data.timeout?.msecs > 0
|
|
252
252
|
},
|
|
253
253
|
delays: {
|
|
254
254
|
timeoutMsecsAfterNoResponse: context => context.noResponseTimeout
|
|
@@ -258,7 +258,7 @@ const AnearParticipantMachineFunctions = {
|
|
|
258
258
|
// The AnearParticipantMachine:
|
|
259
259
|
// 1. maintains the presence and geo-location for a Participant in an Event
|
|
260
260
|
// 2. instantiates the XState Machine return by the (optional) appParticipantMachineFactory
|
|
261
|
-
// 3. creates a private display ChannelMachine to which any
|
|
261
|
+
// 3. creates a private display ChannelMachine to which any participant displayType messages get published
|
|
262
262
|
// 4. handles activity state, response timeouts, idle state
|
|
263
263
|
// 5. receives ACTION events relayed by the AnearEventMachine
|
|
264
264
|
// 6. relays all relevant events to the participant XState Machine for Application-specific handling
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
/**
|
|
3
|
+
* --------------------------------------------------------------------------
|
|
4
|
+
* Pug templates receive the following context at render:
|
|
5
|
+
*
|
|
6
|
+
* {
|
|
7
|
+
* app: application XState context
|
|
8
|
+
* meta: {
|
|
9
|
+
* viewer: // 'participant', 'participants', 'spectators'
|
|
10
|
+
* state: // Stringified state name (e.g., 'live.registration.waitForOpponentToJoin')
|
|
11
|
+
* event: // The triggering event for this transition ('PARTICIPANT_ENTER')
|
|
12
|
+
* timeout: // null|| { msecs:, [participantId:]}
|
|
13
|
+
* }
|
|
14
|
+
*
|
|
15
|
+
* // When 'participant' displayType
|
|
16
|
+
* participants: // all: Map of all participants [info, context] for this event, get(id) => info, context
|
|
17
|
+
* participant: // info, context
|
|
18
|
+
*
|
|
19
|
+
* // PLUS all helpers from PugHelpers:
|
|
20
|
+
* cdnImg(filename) // Resolves CDN image path
|
|
21
|
+
* action(payload) // JSON-encodes click action payloads for data attributes
|
|
22
|
+
* }
|
|
23
|
+
*
|
|
24
|
+
* Example usage in Pug:
|
|
25
|
+
*
|
|
26
|
+
* img(src=cdnImg('logo.png'))
|
|
27
|
+
* .sq(data-anear-click=action({MOVE: {x, y}}))
|
|
28
|
+
* each participant in Object.values(participants)
|
|
29
|
+
* li= participant.name
|
|
30
|
+
*
|
|
31
|
+
* --------------------------------------------------------------------------
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
const logger = require('./Logger')
|
|
35
|
+
const RealtimeMessaging = require('./RealtimeMessaging')
|
|
36
|
+
const C = require('./Constants')
|
|
37
|
+
|
|
38
|
+
class DisplayEventProcessor {
|
|
39
|
+
constructor(anearEventMachineContext) {
|
|
40
|
+
this.pugTemplates = anearEventMachineContext.pugTemplates
|
|
41
|
+
this.pugHelpers = anearEventMachineContext.pugHelpers
|
|
42
|
+
this.participantMachines = anearEventMachineContext.participantMachines
|
|
43
|
+
this.participantsDisplayChannel = anearEventMachineContext.participantsDisplayChannel
|
|
44
|
+
this.spectatorsDisplayChannel = anearEventMachineContext.spectatorsDisplayChannel
|
|
45
|
+
this.participantsIndex = this._buildParticipantsIndex(anearEventMachineContext.participants)
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
processAndPublish(displayEvents) {
|
|
49
|
+
return Promise.all(displayEvents.map(event => this._processSingle(event)))
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
_buildParticipantsIndex(participants) {
|
|
53
|
+
const participantStructs = Object.fromEntries(
|
|
54
|
+
Object.entries(participants).map(([id, info]) => [ id, { info, context: null } ])
|
|
55
|
+
)
|
|
56
|
+
const all = Object.values(participantStructs)
|
|
57
|
+
|
|
58
|
+
return { all, get: id => participantStructs[id] }
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
_processSingle(displayEvent) {
|
|
62
|
+
const { viewPath, appRenderContext } = displayEvent
|
|
63
|
+
const timeout = appRenderContext.meta.timeout
|
|
64
|
+
|
|
65
|
+
const normalizedPath = viewPath.endsWith(C.PugSuffix) ? viewPath : `${viewPath}${C.PugSuffix}`
|
|
66
|
+
const template = this.pugTemplates[normalizedPath]
|
|
67
|
+
|
|
68
|
+
if (!template) {
|
|
69
|
+
throw new Error(`Template not found: ${normalizedPath}`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const templateRenderContext = {
|
|
73
|
+
...appRenderContext,
|
|
74
|
+
participants: this.participantsIndex,
|
|
75
|
+
...this.pugHelpers
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
switch (appRenderContext.meta.viewer) {
|
|
79
|
+
case 'participants':
|
|
80
|
+
logger.debug(`[DisplayEventProcessor] Publishing PARTICIPANTS_DISPLAY`)
|
|
81
|
+
|
|
82
|
+
return RealtimeMessaging.publish(
|
|
83
|
+
this.participantsDisplayChannel,
|
|
84
|
+
'PARTICIPANTS_DISPLAY',
|
|
85
|
+
template(templateRenderContext)
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
case 'spectators':
|
|
89
|
+
logger.debug(`[DisplayEventProcessor] Publishing SPECTATORS_DISPLAY`)
|
|
90
|
+
|
|
91
|
+
return RealtimeMessaging.publish(
|
|
92
|
+
this.spectatorsDisplayChannel,
|
|
93
|
+
'SPECTATORS_DISPLAY',
|
|
94
|
+
template(templateRenderContext)
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
case 'participant':
|
|
98
|
+
logger.debug(`[DisplayEventProcessor] Processing RENDER_DISPLAY for each participant`)
|
|
99
|
+
return this._processPrivateParticipantDisplays(template, templateRenderContext, timeout)
|
|
100
|
+
|
|
101
|
+
default:
|
|
102
|
+
throw new Error(`Unknown meta.viewer: ${appRenderContext.meta.viewer}`)
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
_processPrivateParticipantDisplays(template, templateRenderContext, timeout) {
|
|
107
|
+
Object.values(this.participantsIndex.all).forEach(
|
|
108
|
+
participantStruct => {
|
|
109
|
+
const participantId = participantStruct.info.id
|
|
110
|
+
const participantMachine = this.participantMachines[participantId]
|
|
111
|
+
const privateRenderContext = {
|
|
112
|
+
...templateRenderContext,
|
|
113
|
+
participant: participantStruct
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const privateHtml = template(privateRenderContext)
|
|
117
|
+
|
|
118
|
+
const renderMessage = { content: privateHtml }
|
|
119
|
+
if (timeout) renderMessage.timeout = timeout
|
|
120
|
+
|
|
121
|
+
participantMachine.send('RENDER_DISPLAY', renderMessage)
|
|
122
|
+
}
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
return Promise.resolve()
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
module.exports = DisplayEventProcessor
|
|
@@ -2,146 +2,97 @@
|
|
|
2
2
|
|
|
3
3
|
const logger = require('./Logger')
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const metaConfig = (meta, timeoutType, appExecutionContext) => {
|
|
32
|
-
let timeout = {msecs: 0}
|
|
33
|
-
let view = null
|
|
34
|
-
|
|
35
|
-
switch (typeof meta) {
|
|
36
|
-
case 'string':
|
|
37
|
-
view = meta
|
|
38
|
-
break
|
|
39
|
-
case 'object':
|
|
40
|
-
view = meta.view
|
|
41
|
-
if (meta.timeout) {
|
|
42
|
-
timeout = buildTimeout(timeoutType, meta.timeout, appExecutionContext)
|
|
5
|
+
/**
|
|
6
|
+
* MetaProcessing:
|
|
7
|
+
* - Runs inside your appEventMachine onTransition.
|
|
8
|
+
* - Knows how to parse the XState meta shape (participants, participant, spectators).
|
|
9
|
+
* - Normalizes viewPath + timeout.
|
|
10
|
+
* - Emits displayEvents → sends to AnearEventMachine for rendering.
|
|
11
|
+
*/
|
|
12
|
+
const MetaProcessing = (anearEvent) => {
|
|
13
|
+
return (appEventMachineState) => {
|
|
14
|
+
const { meta: rawMeta, context: appContext, value, event } = appEventMachineState
|
|
15
|
+
const [meta] = Object.values(rawMeta)
|
|
16
|
+
|
|
17
|
+
if (!meta) return
|
|
18
|
+
|
|
19
|
+
const appStateName = _stringifiedState(value)
|
|
20
|
+
|
|
21
|
+
const appRenderContext = (displayType, timeout = null) => (
|
|
22
|
+
{
|
|
23
|
+
app: appContext,
|
|
24
|
+
meta: {
|
|
25
|
+
state: appStateName,
|
|
26
|
+
event,
|
|
27
|
+
timeout,
|
|
28
|
+
viewer: displayType
|
|
29
|
+
}
|
|
43
30
|
}
|
|
31
|
+
)
|
|
44
32
|
|
|
45
|
-
break
|
|
46
|
-
default:
|
|
47
|
-
throw new Error(`unknown ${timeoutType} meta format ${meta}`)
|
|
48
|
-
}
|
|
49
|
-
return [view, timeout]
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const participantsMetaConfig = (participantsMeta, appExecutionContext) => {
|
|
53
|
-
return metaConfig(participantsMeta, 'participants', appExecutionContext)
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
const participantMetaConfig = (participantMeta, appExecutionContext) => {
|
|
57
|
-
return metaConfig(participantMeta, 'participant', appExecutionContext)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const spectatorMetaConfig = config => {
|
|
61
|
-
if (typeof config === 'string')
|
|
62
|
-
return config
|
|
63
|
-
else if (typeof config === 'object')
|
|
64
|
-
return config.view
|
|
65
|
-
else
|
|
66
|
-
throw new Error(`unknown spectator meta format ${config}`)
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
const Stringified = (stateValue) => {
|
|
70
|
-
switch (typeof stateValue) {
|
|
71
|
-
case 'string':
|
|
72
|
-
return stateValue
|
|
73
|
-
case 'object':
|
|
74
|
-
return Object.entries(stateValue)
|
|
75
|
-
.map(([key, nested]) => `${key}.${Stringified(nested)}`)
|
|
76
|
-
.join('.')
|
|
77
|
-
default:
|
|
78
|
-
throw new Error(`unknown Xstate state value type ${typeof stateValue}`)
|
|
79
|
-
}
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
const MetaProcessing = anearEvent => {
|
|
83
|
-
return appEventMachineState => {
|
|
84
|
-
const { context: appContext, meta: rawMeta } = appEventMachineState
|
|
85
|
-
const appStateName = Stringified(appEventMachineState.value)
|
|
86
33
|
const displayEvents = []
|
|
87
34
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
35
|
+
if (meta.participants) {
|
|
36
|
+
const viewPath = _getViewPath(meta.participants)
|
|
37
|
+
const timeout = _buildTimeout(meta.participants.timeout, appContext)
|
|
38
|
+
const displayEvent = {
|
|
39
|
+
viewPath,
|
|
40
|
+
appRenderContext: appRenderContext('participants', timeout)
|
|
41
|
+
}
|
|
42
|
+
displayEvents.push(displayEvent)
|
|
93
43
|
}
|
|
94
44
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
45
|
+
if (meta.participant) {
|
|
46
|
+
const viewPath = _getViewPath(meta.participant)
|
|
47
|
+
const timeout = _buildTimeout(meta.participant.timeout, appContext)
|
|
48
|
+
const displayEvent = {
|
|
49
|
+
viewPath,
|
|
50
|
+
appRenderContext: appRenderContext('participant', timeout)
|
|
51
|
+
}
|
|
52
|
+
displayEvents.push(displayEvent)
|
|
102
53
|
}
|
|
103
54
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
}
|
|
112
|
-
)
|
|
55
|
+
if (meta.spectators) {
|
|
56
|
+
const viewPath = _getViewPath(meta.spectators)
|
|
57
|
+
const displayEvent = {
|
|
58
|
+
viewPath,
|
|
59
|
+
appRenderContext: appRenderContext('spectators')
|
|
60
|
+
}
|
|
61
|
+
displayEvents.push(displayEvent)
|
|
113
62
|
}
|
|
114
63
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
event: appEventMachineState.event
|
|
64
|
+
if (displayEvents.length > 0) {
|
|
65
|
+
logger.debug(`[MetaProcessing] sending RENDER_DISPLAY with ${displayEvents.length} displayEvents`)
|
|
66
|
+
anearEvent.send('RENDER_DISPLAY', { displayEvents })
|
|
119
67
|
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
120
70
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
71
|
+
const _getViewPath = (config) => {
|
|
72
|
+
if (!config) return null
|
|
73
|
+
if (typeof config === 'string') return config
|
|
74
|
+
if (typeof config === 'object') return config.view
|
|
75
|
+
throw new Error(`Unknown meta format: ${JSON.stringify(config)}`)
|
|
76
|
+
}
|
|
124
77
|
|
|
125
|
-
|
|
126
|
-
|
|
78
|
+
const _buildTimeout = (timeoutConfig, appContext) => {
|
|
79
|
+
if (!timeoutConfig) return null
|
|
127
80
|
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
81
|
+
if (typeof timeoutConfig === 'function') {
|
|
82
|
+
const [msecs, participantId] = timeoutConfig(appContext)
|
|
83
|
+
return { msecs, participantId }
|
|
84
|
+
}
|
|
132
85
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
}
|
|
86
|
+
if (typeof timeoutConfig === 'number') {
|
|
87
|
+
return { msecs: timeoutConfig }
|
|
88
|
+
}
|
|
137
89
|
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
appendDisplayEvent('spectators', viewPath)
|
|
141
|
-
}
|
|
90
|
+
throw new Error(`Unknown timeout config: ${typeof timeoutConfig}`)
|
|
91
|
+
}
|
|
142
92
|
|
|
143
|
-
|
|
144
|
-
|
|
93
|
+
const _stringifiedState = (stateValue) => {
|
|
94
|
+
if (typeof stateValue === 'string') return stateValue
|
|
95
|
+
return Object.entries(stateValue).map(([k, v]) => `${k}.${_stringifiedState(v)}`).join('.')
|
|
145
96
|
}
|
|
146
97
|
|
|
147
98
|
module.exports = MetaProcessing
|