anear-js-api 0.4.31 → 0.4.34

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/.env.test CHANGED
@@ -1,7 +1,7 @@
1
1
  # connects with the machtest developer account
2
- ANEARAPP_API_KEY=a197d1e4da2bfd4bb5d85ca82553f14f4fad2c5c650c25f49c8514eb80ef4fe1
2
+ ANEARAPP_API_KEY=abacdeaiefibiefieghtereijief
3
3
  ANEARAPP_API_VERSION=v1
4
- ANEARAPP_APP_ID=e64a78d8-48f5-44f7-8b89-af2d6977a8c2
4
+ ANEARAPP_APP_ID=e6da7818-4825d43f7-8979-e1ad6977a8c2
5
5
  ANEARAPP_LOGGER_FILE=logs/development.log
6
6
  ANEARAPP_LOGGER_LEVEL=debug
7
7
  ANEARAPP_ABLY_LOG_LEVEL=2
@@ -129,7 +129,9 @@ const AnearCoreServiceMachineConfig = appId => ({
129
129
  actions: ['startNewEventMachine']
130
130
  },
131
131
  EVENT_MACHINE_EXIT: {
132
- actions: ['cleanupEventMachine']
132
+ actions: [
133
+ 'cleanupEventMachine'
134
+ ]
133
135
  }
134
136
  }
135
137
  },
@@ -161,9 +163,6 @@ const AnearCoreServiceMachineFunctions = {
161
163
  return uploader.uploadCss()
162
164
  }
163
165
  },
164
- delays: {
165
- retry_with_backoff_delay: (context, event) => context.retryDelay
166
- },
167
166
  actions: {
168
167
  updateContextWithMachineRef: assign(
169
168
  {
@@ -228,14 +227,18 @@ const AnearCoreServiceMachineFunctions = {
228
227
  }
229
228
  }
230
229
  ),
231
- cleanupEventMachine: assign({
232
- anearEventMachines: (context, event) => {
233
- const { [event.eventId]: dropped, ...remaining } = context.anearEventMachines
234
- logger.debug(`AEM ${event.eventId} is ${dropped ? "done → cleaning up" : "NOT FOUND"}`)
235
- return remaining
230
+ cleanupEventMachine: assign((context, event) => {
231
+ const { [event.eventId]: dropped, ...remaining } = context.anearEventMachines
232
+ logger.debug(`ACSM ${event.eventId} is ${dropped ? "done cleaning up" : "NOT FOUND"}`)
233
+
234
+ return {
235
+ anearEventMachines: remaining
236
236
  }
237
237
  })
238
238
  },
239
+ delays: {
240
+ retry_with_backoff_delay: (context, event) => context.retryDelay
241
+ },
239
242
  guards: {
240
243
  noImageAssetFilesFound: (_, event) => event.data === null
241
244
  }
@@ -63,6 +63,8 @@ const AnearEventMachineContext = (
63
63
  }
64
64
  )
65
65
 
66
+ const MinuteMsecs = minutes => minutes * (60 * 1000)
67
+
66
68
  const DeferredStates = [
67
69
  // do not process these if they arrive during an unresolved
68
70
  // invoke Promise service
@@ -181,12 +183,18 @@ const ActiveEventStatesConfig = {
181
183
  ],
182
184
  on: {
183
185
  RENDER_DISPLAY: {
184
- target: '.rendering'
186
+ target: '#createdRendering'
185
187
  }
186
188
  },
187
189
  states: {
188
190
  waitingAnnounce: {
189
191
  id: 'waitingAnnounce',
192
+ after: {
193
+ timeoutEventAnnounce: {
194
+ actions: context => logger.info(`Event ${context.anearEvent.id} TIMED OUT waiting for ANNOUNCE`),
195
+ target: '#canceled'
196
+ }
197
+ },
190
198
  on: {
191
199
  ANNOUNCE: {
192
200
  target: '#activeEvent.announce'
@@ -196,7 +204,8 @@ const ActiveEventStatesConfig = {
196
204
  }
197
205
  }
198
206
  },
199
- rendering: {
207
+ createdRendering: {
208
+ id: 'createdRendering',
200
209
  deferred: DeferredStatesPlus('ANNOUNCE', 'START'),
201
210
  invoke: {
202
211
  src: 'renderDisplay',
@@ -220,7 +229,7 @@ const ActiveEventStatesConfig = {
220
229
  initial: 'transitioning',
221
230
  on: {
222
231
  RENDER_DISPLAY: {
223
- target: '.rendering'
232
+ target: '#announceRendering'
224
233
  },
225
234
  PARTICIPANT_LEAVE: {
226
235
  // creator browser refresh or MIA. Send disconnect events
@@ -231,19 +240,15 @@ const ActiveEventStatesConfig = {
231
240
  // a participant could re-entering the event after above PARTICIPANT_LEAVE (browser refresh).
232
241
  // so look for existing participants and send reconnect events to interested machines
233
242
  cond: 'participantExists',
234
- actions: 'sendParticipantReconnectEvents',
235
- target: '#waitingToStart',
236
- internal: true
243
+ actions: 'sendParticipantReconnectEvents'
237
244
  },
238
245
  {
239
246
  // spectator clicked JOIN
240
- target: '#newParticipant'
247
+ target: '#newParticipantJoining'
241
248
  }
242
249
  ],
243
250
  SPECTATOR_ENTER: {
244
- actions: 'sendSpectatorEnterToAppEventMachine',
245
- target: '#waitingToStart',
246
- internal: true
251
+ actions: 'sendSpectatorEnterToAppEventMachine'
247
252
  }
248
253
  },
249
254
  states: {
@@ -266,13 +271,20 @@ const ActiveEventStatesConfig = {
266
271
  waitingToStart: {
267
272
  id: 'waitingToStart',
268
273
  entry: (c, e) => logger.debug("announce state...waiting for event START"),
274
+ after: {
275
+ timeoutEventStart: {
276
+ actions: context => logger.info(`Event ${context.anearEvent.id} TIMED OUT waiting for START`),
277
+ target: '#canceled'
278
+ }
279
+ },
269
280
  on: {
270
281
  START: {
271
282
  target: '#activeEvent.live'
272
283
  }
273
284
  }
274
285
  },
275
- rendering: {
286
+ announceRendering: {
287
+ id: 'announceRendering',
276
288
  deferred: DeferredStatesPlus('START'),
277
289
  invoke: {
278
290
  src: 'renderDisplay',
@@ -285,30 +297,56 @@ const ActiveEventStatesConfig = {
285
297
  }
286
298
  }
287
299
  },
288
- newParticipant: {
300
+ newParticipantJoining: {
289
301
  // a PARTICIPANT_ENTER received from a new user JOIN click. create an AnearParticipantMachine instance.
290
302
  // This machine tracks presence, geo-location (when approved by mobile participant),
291
303
  // manages active/idle state for long-running events, and manages any ACTION timeouts.
292
- id: 'newParticipant',
304
+ id: 'newParticipantJoining',
293
305
  deferred: DeferredStatesPlus('START'),
294
306
  invoke: {
295
307
  src: 'fetchParticipantData',
296
308
  onDone: {
297
- actions: [
298
- 'startNewParticipantMachine',
299
- 'sendParticipantEnterToAppEventMachine'
300
- ],
301
- target: '#waitingToStart',
309
+ actions: ['startNewParticipantMachine'],
310
+ target: '#waitParticipantReady'
302
311
  },
303
312
  onError: {
304
313
  target: '#activeEvent.failure'
305
314
  }
306
315
  }
316
+ },
317
+ waitParticipantReady: {
318
+ id: 'waitParticipantReady',
319
+ deferred: DeferredStatesPlus('START'),
320
+ on: {
321
+ PARTICIPANT_MACHINE_READY: {
322
+ actions: ['sendParticipantEnterToAppEventMachine'],
323
+ target: '#waitingToStart',
324
+ internal: true
325
+ }
326
+ }
307
327
  }
308
328
  }
309
329
  },
310
330
  live: {
311
331
  initial: 'transitioning',
332
+ on: {
333
+ RENDER_DISPLAY: {
334
+ target: '#liveRendering'
335
+ },
336
+ SPECTATOR_ENTER: {
337
+ actions: 'sendSpectatorEnterToAppEventMachine'
338
+ },
339
+ PARTICIPANT_LEAVE: {
340
+ // mid-event browser refresh or MIA. Send disconnect events
341
+ actions: ['sendParticipantDisconnectEvents']
342
+ },
343
+ PARTICIPANT_TIMEOUT: {
344
+ // TBD: A participant(s) who was required to take a click action has
345
+ // failed to do so
346
+ },
347
+ PAUSE: '#activeEvent.paused', // Currently no use-case for these
348
+ CLOSE: '#activeEvent.closed'
349
+ },
312
350
  states: {
313
351
  transitioning: {
314
352
  deferred: DeferredStates,
@@ -325,8 +363,72 @@ const ActiveEventStatesConfig = {
325
363
  waitingForActions: {
326
364
  id: 'waitingForActions',
327
365
  on: {
328
- PAUSE: '#activeEvent.paused',
329
- CLOSE: '#activeEvent.closed'
366
+ PARTICIPANT_ENTER: [
367
+ {
368
+ // a participant could re-entering the event after above PARTICIPANT_LEAVE (browser refresh).
369
+ // so look for existing participants and send reconnect events to interested machines
370
+ cond: 'participantExists',
371
+ actions: 'sendParticipantReconnectEvents'
372
+ },
373
+ {
374
+ // in the future, open house events allow spectators to join and leave freely
375
+ // during live.waitingForActions
376
+ cond: 'isOpenHouseEvent',
377
+ target: 'participantEntering'
378
+ },
379
+ {
380
+ // shouldn't receive these so log it and return to current state
381
+ actions: [(c, e) => logger.info("Error: Unexepected PARTICIPANT_ENTER")],
382
+ target: '#activeEvent.failure'
383
+ }
384
+ ],
385
+ ACTION: {
386
+ // A participant has clicked an anear-data-action
387
+ actions: ['processParticipantAction']
388
+ }
389
+ }
390
+ },
391
+ liveRendering: {
392
+ id: 'liveRendering',
393
+ deferred: DeferredStates,
394
+ invoke: {
395
+ src: 'renderDisplay',
396
+ onDone: {
397
+ target: '#waitingForActions',
398
+ internal: true
399
+ },
400
+ onError: {
401
+ target: '#activeEvent.failure'
402
+ }
403
+ }
404
+ },
405
+ participantEntering: {
406
+ // a PARTICIPANT_ENTER received from a new user JOIN click. Unless already exists,
407
+ // create an AnearParticipantMachine instance.
408
+ // This machine tracks presence, geo-location (when approved by mobile participant),
409
+ // manages active/idle state for long-running events, and manages any ACTION timeouts.
410
+ id: 'participantEntering',
411
+ deferred: DeferredStates,
412
+ invoke: {
413
+ src: 'fetchParticipantData',
414
+ onDone: {
415
+ actions: ['startNewParticipantMachine'],
416
+ target: '#waitParticipantJoined',
417
+ },
418
+ onError: {
419
+ target: '#activeEvent.failure'
420
+ }
421
+ }
422
+ },
423
+ waitParticipantJoined: {
424
+ id: 'waitParticipantJoined',
425
+ deferred: DeferredStates,
426
+ on: {
427
+ PARTICIPANT_MACHINE_READY: {
428
+ actions: ['sendParticipantEnterToAppEventMachine'],
429
+ target: '#waitingForActions',
430
+ internal: true
431
+ }
330
432
  }
331
433
  }
332
434
  }
@@ -685,6 +787,9 @@ const AnearEventMachineFunctions = ({
685
787
  sendParticipantEnterToAppEventMachine: (context, event) => {
686
788
  const { anearParticipant } = event.data
687
789
  const participantInfo = context.participants[anearParticipant.id]
790
+
791
+ logger.debug(`Participant machine ${participantInfo.id} is READY`)
792
+
688
793
  const startEvent = { participant: participantInfo }
689
794
 
690
795
  context.appEventMachine.send('PARTICIPANT_ENTER', startEvent)
@@ -786,7 +891,7 @@ const AnearEventMachineFunctions = ({
786
891
  }
787
892
  }),
788
893
  notifyCoreServiceMachineExit: context => {
789
- logger.debug("sending EVENT_MACHINE_EXIT to ACSM")
894
+ logger.debug("sending EVENT_MACHINE_EXIT to coreServiceMachine")
790
895
  context.coreServiceMachine.send('EVENT_MACHINE_EXIT', {eventId: context.anearEvent.id})
791
896
  }
792
897
  },
@@ -860,7 +965,14 @@ const AnearEventMachineFunctions = ({
860
965
  },
861
966
  guards: {
862
967
  participantExists: (context, event) => !!context.participants[event.data.id],
863
- eventCreatorIsHost: (context, event) => context.anearEvent.hosted
968
+ eventCreatorIsHost: (context, event) => context.anearEvent.hosted,
969
+ isOpenHouseEvent: (context, event) => false // TODO: need to have App open_house trait exposed in anear api
970
+ },
971
+ delays: {
972
+ // in the future, these delays should be goverened by the type of App and
973
+ // if there has been activity.
974
+ timeoutEventAnnounce: context => MinuteMsecs(C.TIMEOUT_MINUTES.ANNOUNCE),
975
+ timeoutEventStart: context => MinuteMsecs(C.TIMEOUT_MINUTES.START)
864
976
  }
865
977
  })
866
978
 
@@ -6,5 +6,9 @@ module.exports = {
6
6
  SpectatorPresenceEvents: ['enter'],
7
7
  ImagesDirPath: 'assets/images',
8
8
  CssDirPath: 'assets/css',
9
- PugSuffix: '.pug'
9
+ PugSuffix: '.pug',
10
+ TIMEOUT_MINUTES: {
11
+ START: 60, // 1 hour
12
+ ANNOUNCE: 120 // 2 hours
13
+ }
10
14
  }
@@ -4,15 +4,17 @@
4
4
  * Pug templates receive the following context at render:
5
5
  *
6
6
  * {
7
- * displayType: // 'participants' | 'spectators' | 'participant'
8
- * context: // AppEventMachine's XState context for this transition
9
- * state: // Stringified state name (e.g., 'live.registration.waitForOpponentToJoin')
10
- * event: // The triggering event for this transition
11
- * participants: // Map of all participants for this event
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
+ * }
12
14
  *
13
15
  * // When 'participant' displayType
14
- * participant // current participant
15
- * participantContext // current participant private context if AppParticipantMachine
16
+ * participants: // all: Map of all participants [info, context] for this event, get(id) => info, context
17
+ * participant: // info, context
16
18
  *
17
19
  * // PLUS all helpers from PugHelpers:
18
20
  * cdnImg(filename) // Resolves CDN image path
@@ -37,18 +39,28 @@ class DisplayEventProcessor {
37
39
  constructor(anearEventMachineContext) {
38
40
  this.pugTemplates = anearEventMachineContext.pugTemplates
39
41
  this.pugHelpers = anearEventMachineContext.pugHelpers
40
- this.participants = anearEventMachineContext.participants
41
42
  this.participantMachines = anearEventMachineContext.participantMachines
42
43
  this.participantsDisplayChannel = anearEventMachineContext.participantsDisplayChannel
43
44
  this.spectatorsDisplayChannel = anearEventMachineContext.spectatorsDisplayChannel
45
+ this.participantsIndex = this._buildParticipantsIndex(anearEventMachineContext.participants)
44
46
  }
45
47
 
46
48
  processAndPublish(displayEvents) {
47
49
  return Promise.all(displayEvents.map(event => this._processSingle(event)))
48
50
  }
49
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
+
50
61
  _processSingle(displayEvent) {
51
- const { displayType, viewPath, timeout, appExecutionContext } = displayEvent
62
+ const { viewPath, appRenderContext } = displayEvent
63
+ const timeout = appRenderContext.meta.timeout
52
64
 
53
65
  const normalizedPath = viewPath.endsWith(C.PugSuffix) ? viewPath : `${viewPath}${C.PugSuffix}`
54
66
  const template = this.pugTemplates[normalizedPath]
@@ -57,61 +69,58 @@ class DisplayEventProcessor {
57
69
  throw new Error(`Template not found: ${normalizedPath}`)
58
70
  }
59
71
 
60
- const renderContext = {
61
- displayType,
62
- ...appExecutionContext,
63
- participants: this.participants,
72
+ const templateRenderContext = {
73
+ ...appRenderContext,
74
+ participants: this.participantsIndex,
64
75
  ...this.pugHelpers
65
76
  }
66
77
 
67
- const renderedContent = template(renderContext)
68
- const message = { content: renderedContent }
69
- if (timeout) message.timeout = timeout
70
-
71
- switch (displayType) {
78
+ switch (appRenderContext.meta.viewer) {
72
79
  case 'participants':
73
80
  logger.debug(`[DisplayEventProcessor] Publishing PARTICIPANTS_DISPLAY`)
81
+
74
82
  return RealtimeMessaging.publish(
75
83
  this.participantsDisplayChannel,
76
84
  'PARTICIPANTS_DISPLAY',
77
- message.content
85
+ template(templateRenderContext)
78
86
  )
79
87
 
80
88
  case 'spectators':
81
89
  logger.debug(`[DisplayEventProcessor] Publishing SPECTATORS_DISPLAY`)
90
+
82
91
  return RealtimeMessaging.publish(
83
92
  this.spectatorsDisplayChannel,
84
93
  'SPECTATORS_DISPLAY',
85
- message.content
94
+ template(templateRenderContext)
86
95
  )
87
96
 
88
97
  case 'participant':
89
98
  logger.debug(`[DisplayEventProcessor] Processing RENDER_DISPLAY for each participant`)
90
- return this._processPrivateParticipantDisplays(template, renderContext, timeout)
99
+ return this._processPrivateParticipantDisplays(template, templateRenderContext, timeout)
91
100
 
92
101
  default:
93
- throw new Error(`Unknown displayType: ${displayType}`)
102
+ throw new Error(`Unknown meta.viewer: ${appRenderContext.meta.viewer}`)
94
103
  }
95
104
  }
96
105
 
97
- _processPrivateParticipantDisplays(template, renderContextBase, timeout) {
98
- Object.values(this.participantMachines).forEach(participantMachine => {
99
- const participantState = participantMachine.state
100
- const { anearParticipant, appParticipantMachine } = participantState.context
101
-
102
- const privateRenderContext = {
103
- ...renderContextBase,
104
- participant: anearParticipant.participantInfo,
105
- participantContext: appParticipantMachine?.state?.context || {}
106
- }
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
+ }
107
115
 
108
- const privateHtml = template(privateRenderContext)
116
+ const privateHtml = template(privateRenderContext)
109
117
 
110
- const renderMessage = { content: privateHtml }
111
- if (timeout) renderMessage.timeout = timeout
118
+ const renderMessage = { content: privateHtml }
119
+ if (timeout) renderMessage.timeout = timeout
112
120
 
113
- participantMachine.send('RENDER_DISPLAY', renderMessage)
114
- })
121
+ participantMachine.send('RENDER_DISPLAY', renderMessage)
122
+ }
123
+ )
115
124
 
116
125
  return Promise.resolve()
117
126
  }
@@ -18,44 +18,47 @@ const MetaProcessing = (anearEvent) => {
18
18
 
19
19
  const appStateName = _stringifiedState(value)
20
20
 
21
- const appExecutionContext = {
22
- context: appContext,
23
- state: appStateName,
24
- event
25
- }
21
+ const appRenderContext = (displayType, timeout = null) => (
22
+ {
23
+ app: appContext,
24
+ meta: {
25
+ state: appStateName,
26
+ event,
27
+ timeout,
28
+ viewer: displayType
29
+ }
30
+ }
31
+ )
26
32
 
27
33
  const displayEvents = []
28
34
 
29
35
  if (meta.participants) {
30
36
  const viewPath = _getViewPath(meta.participants)
31
- const timeout = _buildTimeout(meta.participants.timeout, appExecutionContext)
32
- displayEvents.push({
33
- displayType: 'participants',
37
+ const timeout = _buildTimeout(meta.participants.timeout, appContext)
38
+ const displayEvent = {
34
39
  viewPath,
35
- timeout,
36
- appExecutionContext
37
- })
40
+ appRenderContext: appRenderContext('participants', timeout)
41
+ }
42
+ displayEvents.push(displayEvent)
38
43
  }
39
44
 
40
45
  if (meta.participant) {
41
46
  const viewPath = _getViewPath(meta.participant)
42
- const timeout = _buildTimeout(meta.participant.timeout, appExecutionContext)
43
- displayEvents.push({
44
- displayType: 'participant',
47
+ const timeout = _buildTimeout(meta.participant.timeout, appContext)
48
+ const displayEvent = {
45
49
  viewPath,
46
- timeout,
47
- appExecutionContext
48
- })
50
+ appRenderContext: appRenderContext('participant', timeout)
51
+ }
52
+ displayEvents.push(displayEvent)
49
53
  }
50
54
 
51
55
  if (meta.spectators) {
52
56
  const viewPath = _getViewPath(meta.spectators)
53
- displayEvents.push({
54
- displayType: 'spectators',
57
+ const displayEvent = {
55
58
  viewPath,
56
- timeout: null,
57
- appExecutionContext
58
- })
59
+ appRenderContext: appRenderContext('spectators')
60
+ }
61
+ displayEvents.push(displayEvent)
59
62
  }
60
63
 
61
64
  if (displayEvents.length > 0) {
@@ -72,11 +75,11 @@ const _getViewPath = (config) => {
72
75
  throw new Error(`Unknown meta format: ${JSON.stringify(config)}`)
73
76
  }
74
77
 
75
- const _buildTimeout = (timeoutConfig, appExecutionContext) => {
78
+ const _buildTimeout = (timeoutConfig, appContext) => {
76
79
  if (!timeoutConfig) return null
77
80
 
78
81
  if (typeof timeoutConfig === 'function') {
79
- const [msecs, participantId] = timeoutConfig(appExecutionContext)
82
+ const [msecs, participantId] = timeoutConfig(appContext)
80
83
  return { msecs, participantId }
81
84
  }
82
85
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anear-js-api",
3
- "version": "0.4.31",
3
+ "version": "0.4.34",
4
4
  "description": "Javascript Developer API for Anear Apps",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {