anear-js-api 0.2.2 → 0.3.0

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,6 +24,13 @@ class AnearApi extends ApiService {
24
24
  return json
25
25
  }
26
26
 
27
+ async getApp(appId) {
28
+ logger.debug(`API: GET app ${appId}`)
29
+
30
+ const json = await this.get("apps", {id: appId})
31
+ return json
32
+ }
33
+
27
34
  async getZoneEvents(zoneId) {
28
35
  logger.debug(`API: GET zone_events ${zoneId}`)
29
36
 
@@ -38,7 +45,7 @@ class AnearApi extends ApiService {
38
45
  try {
39
46
  const json = await this.post("transitions", {event_name: eventName}, relationships)
40
47
  const attrs = json.data.attributes
41
- logger.info(`newState is ${attrs.state}`)
48
+ logger.info(`API: newState is ${attrs.state}`)
42
49
  return attrs
43
50
  } catch(err) {
44
51
  logger.error(err)
@@ -1,7 +1,7 @@
1
1
  "use strict"
2
2
  const ErrorResponse = require('./ErrorResponse')
3
3
  const qs = require('qs')
4
- const fetch = require('node-fetch')
4
+ const fetch = require('cross-fetch')
5
5
 
6
6
  const DeveloperApiURL = "https://api.anearapp.com/developer/v1"
7
7
 
package/lib/index.js CHANGED
@@ -2,11 +2,10 @@
2
2
 
3
3
  const JsonApiResource = require('./models/JsonApiResource')
4
4
  const JsonApiArrayResource = require('./models/JsonApiArrayResource')
5
- const AnearEvent = require('./models/AnearEvent')
5
+ const {AnearEvent, logger} = require('./models/AnearEvent')
6
6
  const AnearParticipant = require('./models/AnearParticipant')
7
7
  const AnearMessaging = require('./messaging/AnearMessaging')
8
8
  const AnearApiService = require('./api/ApiService')
9
- const Logger = require('./utils/Logger')
10
9
  const Fixtures = require('../tests/fixtures')
11
10
  const MockMessaging = require('./messaging/__mocks__/AnearMessaging')
12
11
 
@@ -14,10 +13,10 @@ module.exports = {
14
13
  JsonApiResource,
15
14
  JsonApiArrayResource,
16
15
  AnearEvent,
16
+ logger,
17
17
  AnearParticipant,
18
18
  AnearMessaging,
19
19
  AnearApiService,
20
- Logger,
21
20
  Fixtures,
22
21
  MockMessaging,
23
22
  }
@@ -2,6 +2,7 @@
2
2
  const Ably = require('ably/promises')
3
3
  const AnearApi = require('../api/AnearApi')
4
4
  const logger = require('../utils/Logger')
5
+ const { Mutex } = require('async-mutex')
5
6
 
6
7
  const AppId = process.env.ANEARAPP_APP_ID
7
8
 
@@ -12,6 +13,9 @@ const CreateEventMessageType = 'create_event'
12
13
  const ExitEventMessageType = 'exit_event'
13
14
  const EventTransitionMessageType = 'event_transition'
14
15
 
16
+ const PRESENCE_ENTER = 'enter'
17
+ const PRESENCE_LEAVE = 'leave'
18
+
15
19
  // any channel messages sent with 5 secs (5s) of initial attach will be delivered
16
20
  // to the subscribers
17
21
  const ChannelParams = {params: {rewind: "5s"}}
@@ -26,6 +30,7 @@ class AnearMessaging {
26
30
  this.api = new AnearApi()
27
31
  this.AnearEventClass = AnearEventClass
28
32
  this.AnearParticipantClass = AnearParticipantClass
33
+ this.mutex = new Mutex()
29
34
 
30
35
 
31
36
  const baseUrl = this.api.api_base_url
@@ -62,6 +67,7 @@ class AnearMessaging {
62
67
  this.realtime.connection.on(
63
68
  "connected",
64
69
  async () => {
70
+ await this.getAppInfo(AppId)
65
71
  logger.info("Ably connected!")
66
72
  await this.reloadAnyEventsInProgress(AppId)
67
73
  await this.setupCreateEventChannel()
@@ -69,6 +75,13 @@ class AnearMessaging {
69
75
  )
70
76
  }
71
77
 
78
+ async getAppInfo(appId) {
79
+ const anearApp = await this.api.getApp(appId)
80
+ logger.info("================")
81
+ logger.info(`STARTING APP ${anearApp.data.attributes['short-name']}`)
82
+ logger.info("================")
83
+ }
84
+
72
85
  getChannel(channelName, channelParams = ChannelParams) {
73
86
  return this.realtime.channels.get(channelName, channelParams)
74
87
  }
@@ -81,31 +94,31 @@ class AnearMessaging {
81
94
  }
82
95
  }
83
96
 
84
- setParticipantTimer(eventId, anearParticipant, timeoutMilliseconds) {
85
- const participantId = anearParticipant.id
97
+ setParticipantTimer(eventId, participant, timeoutMilliseconds) {
98
+ const participantId = participant.id
86
99
 
87
100
  this.clearParticipantTimer(participantId)
88
101
 
89
102
  if (timeoutMilliseconds === 0) return
90
103
 
91
- logger.debug(`setting ${timeoutMilliseconds} msec timer for event ${eventId}, participant ${anearParticipant.id}`)
104
+ logger.debug(`setting ${timeoutMilliseconds} msec timer for event ${eventId}, participant ${participant.id}`)
92
105
 
93
106
  this.participantTimers[participantId] = setTimeout(
94
- async () => await this.timerExpired(eventId, anearParticipant, timeoutMilliseconds),
107
+ async () => await this.timerExpired(eventId, participant, timeoutMilliseconds),
95
108
  timeoutMilliseconds
96
109
  )
97
110
  }
98
111
 
99
- async timerExpired(eventId, anearParticipant, timeoutMilliseconds) {
100
- const participantId = anearParticipant.id
112
+ async timerExpired(eventId, participant, timeoutMilliseconds) {
113
+ const participantId = participant.id
101
114
 
102
115
  logger.debug(`participant (${eventId}, ${participantId}) TIMED OUT after ${timeoutMilliseconds} msecs`)
103
116
 
104
117
  await this.getAnearEventWithLockFromStorage(
105
118
  eventId,
106
119
  async anearEvent => {
107
- const anearParticipant = await this.getAnearParticipantFromStorage(participantId)
108
- await anearEvent.participantTimedOut(anearParticipant)
120
+ const participant = await this.getAnearParticipantFromStorage(participantId)
121
+ await anearEvent.participantTimedOut(participant)
109
122
  await anearEvent.update()
110
123
  }
111
124
  )
@@ -115,25 +128,10 @@ class AnearMessaging {
115
128
  return await this.AnearEventClass.getFromStorage(eventId, this)
116
129
  }
117
130
 
118
- async getAnearEventWithLockFromStorage(eventId, callback) {
119
- return await this.AnearEventClass.getWithLockFromStorage(
120
- eventId,
121
- callback,
122
- this
123
- )
124
- }
125
-
126
131
  async getAnearParticipantFromStorage(participantId) {
127
132
  return await this.AnearParticipantClass.getFromStorage(participantId)
128
133
  }
129
134
 
130
- async getAnearParticipantWithLockFromStorage(participantId, callback) {
131
- return await this.AnearParticipantClass.getWithLockFromStorage(
132
- participantId,
133
- callback
134
- )
135
- }
136
-
137
135
  async initEventRealtimeMessaging(anearEvent) {
138
136
 
139
137
  if (this.eventChannels.hasOwnProperty(anearEvent.id)) {
@@ -162,33 +160,38 @@ class AnearMessaging {
162
160
  // create the AnearEvent subclass, persist it in storage, and
163
161
  // initialize its realtime messaging
164
162
  //
165
- logger.info(`Ably message received....createEventMessagingCallback(${message.name})`)
163
+ logger.info(`createEventMessagingCallback(${message.name})`)
166
164
 
167
165
  try {
168
166
  const eventJson = JSON.parse(message.data)
169
167
  const anearEvent = new this.AnearEventClass(eventJson, this)
168
+
170
169
  //
171
170
  // if we are getting this event create message from history after a quick restart,
172
171
  // we just return if the event already exists
173
172
  //
174
- await this.loadOrPersistEventAndInitMessaging(anearEvent)
173
+ await this.loadOrPersistEventAndInitialize(anearEvent)
175
174
  } catch(err) {
176
175
  logger.error(err)
177
176
  }
178
177
  }
179
178
 
180
- async loadOrPersistEventAndInitMessaging(anearEvent) {
179
+ async loadOrPersistEventAndInitialize(anearEvent) {
181
180
  const eventExists = await anearEvent.exists()
182
181
 
183
182
  logger.info(`Event ${anearEvent.id} ${eventExists ? "already exists" : "does not exist"} in Storage`)
184
183
 
185
184
  if (!eventExists) {
186
- await anearEvent.createdEventCallback()
187
- await anearEvent.persist()
185
+ await this.runExclusive("createEventCallback", async () => {
186
+ await anearEvent.createdEventCallback()
187
+ await anearEvent.persist()
188
+ })
188
189
  }
189
190
 
190
191
  logger.info(`New ${anearEvent.constructor.name} Event: `, anearEvent.toJSON())
191
192
 
193
+ anearEvent.startStateMachine()
194
+
192
195
  await this.initEventRealtimeMessaging(anearEvent)
193
196
  }
194
197
 
@@ -202,7 +205,7 @@ class AnearMessaging {
202
205
  for (const eventData of events) {
203
206
  const eventJson = await this.api.getEvent(eventData.id)
204
207
  const anearEvent = new this.AnearEventClass(eventJson, this)
205
- await this.loadOrPersistEventAndInitMessaging(anearEvent)
208
+ await this.loadOrPersistEventAndInitialize(anearEvent)
206
209
  }
207
210
  }
208
211
  } catch (err) {
@@ -238,14 +241,14 @@ class AnearMessaging {
238
241
 
239
242
  this.subscribePresenceEvent(
240
243
  spectatorsChannel,
241
- 'enter',
242
- async message => await this.spectatorEnterMessagingCallback(anearEvent.id, message)
244
+ PRESENCE_ENTER,
245
+ async message => await this.spectatorEnterMessagingCallback(anearEvent, message)
243
246
  )
244
247
 
245
248
  this.subscribePresenceEvent(
246
249
  spectatorsChannel,
247
- 'leave',
248
- async message => await this.spectatorLeaveMessagingCallback(anearEvent.id, message)
250
+ PRESENCE_LEAVE,
251
+ async message => await this.spectatorLeaveMessagingCallback(anearEvent, message)
249
252
  )
250
253
  }
251
254
 
@@ -258,23 +261,23 @@ class AnearMessaging {
258
261
 
259
262
  await this.subscribePresenceEventWithHistory(
260
263
  actionsChannel,
261
- 'enter',
262
- async message => await this.participantEnterMessagingCallback(anearEvent.id, message)
264
+ PRESENCE_ENTER,
265
+ async message => await this.participantEnterMessagingCallback(anearEvent, message)
263
266
  )
264
267
  this.subscribeEventMessages(
265
268
  actionsChannel,
266
269
  ActionMessageType,
267
- async message => await this.participantActionMessagingCallback(anearEvent.id, message)
270
+ async message => await this.participantActionMessagingCallback(anearEvent, message)
268
271
  )
269
272
  this.subscribePresenceEvent(
270
273
  actionsChannel,
271
- 'leave',
272
- async message => await this.participantLeaveMessagingCallback(anearEvent.id, message)
274
+ PRESENCE_LEAVE,
275
+ async message => await this.participantLeaveMessagingCallback(anearEvent, message)
273
276
  )
274
277
  this.subscribeEventMessages(
275
278
  actionsChannel,
276
279
  ExitEventMessageType,
277
- async message => await this.participantExplicitExitMessagingCallback(anearEvent.id, message)
280
+ async message => await this.participantExplicitExitMessagingCallback(anearEvent, message)
278
281
  )
279
282
  }
280
283
 
@@ -298,20 +301,22 @@ class AnearMessaging {
298
301
  return participantsChannel
299
302
  }
300
303
 
301
- async participantExplicitExitMessagingCallback(eventId, message) {
304
+ async participantExplicitExitMessagingCallback(anearEvent, message) {
302
305
  //
303
306
  // client user deliberately cancels out of event
304
307
  //
305
308
  const participantId = message.data.participantId
306
309
 
307
- logger.debug(`ExitEventMessage received from ${participantId} for event ${eventId}`)
310
+ logger.debug(`ExitEventMessage received from ${participantId} for event ${anearEvent.id}`)
308
311
 
309
- await this.closeParticipant(eventId, participantId,
310
- (anearEvent, anearParticipant) => anearEvent.participantClose(anearParticipant)
312
+ await this.closeParticipant(
313
+ anearEvent,
314
+ participantId,
315
+ (anearEvent, participant) => anearEvent.participantClose(participant)
311
316
  )
312
317
  }
313
318
 
314
- async participantEnterMessagingCallback(eventId, presenceMessage) {
319
+ async participantEnterMessagingCallback(anearEvent, presenceMessage) {
315
320
  // presenceMessage.clientId is the participant's user_id
316
321
  // presenceMessage.data = {
317
322
  // id: participantId,
@@ -321,7 +326,7 @@ class AnearMessaging {
321
326
  const participantId = presenceMessage.data.id
322
327
  const geoLocation = presenceMessage.data.geoLocation
323
328
 
324
- logger.debug(`**** ENTER PARTICIPANT **** event: ${eventId}, participant: ${participantId}`)
329
+ logger.debug(`**** ENTER PARTICIPANT **** event: ${anearEvent.id}, participant: ${participantId}`)
325
330
 
326
331
  //
327
332
  // get the participant data from the API (this will also validate the participant).
@@ -331,79 +336,70 @@ class AnearMessaging {
331
336
  try {
332
337
  logger.debug(`API fetch participant info for ${participantId}`)
333
338
 
334
- const anearParticipantJson = await this.api.getEventParticipantJson(participantId)
335
- const anearParticipant = new this.AnearParticipantClass(anearParticipantJson)
339
+ const participantJson = await this.api.getEventParticipantJson(participantId)
340
+ const participant = new this.AnearParticipantClass(participantJson)
336
341
 
337
- anearParticipant.geoLocation = geoLocation
342
+ participant.geoLocation = geoLocation
338
343
 
339
- await this.setupPrivatePublishingChannel(anearParticipant)
344
+ await this.setupPrivatePublishingChannel(participant)
340
345
 
341
346
  const persistedAnearParticipant = await this.AnearParticipantClass.getFromStorage(participantId)
342
347
 
343
- const eventLockCallback = async (anearEvent) => {
344
- await anearEvent.participantEnter(anearParticipant)
345
- await anearEvent.update()
346
- }
347
-
348
348
  if (persistedAnearParticipant) {
349
- anearParticipant.appData = persistedAnearParticipant.appData
350
- await anearParticipant.update()
349
+ participant.context = persistedAnearParticipant.context
351
350
  }
352
351
 
353
- await this.getAnearEventWithLockFromStorage(anearParticipant.eventId, eventLockCallback)
354
-
352
+ await this.runExclusive("participantEnterCallback", async () => {
353
+ await anearEvent.participantEnter(participant)
354
+ await anearEvent.update()
355
+ })
355
356
  } catch(error) {
356
357
  // participant not found or is not currently marked active at the API service
357
358
  // don't allow participation. FIX: we need to publish to the private channel
358
359
  // with an error message type.
359
- logger.error(`participantEnterMessagingCallback(${eventId}, ${participantId}) error: `, error)
360
+ logger.error(`participantEnterMessagingCallback(${anearEvent.id}, ${participantId}) error: `, error)
360
361
  }
361
362
  }
362
363
 
363
- async spectatorEnterMessagingCallback(eventId, message) {
364
+ async spectatorEnterMessagingCallback(anearEvent, message) {
364
365
  const userId = message.clientId
365
- const anearEvent = await this.getAnearEventFromStorage(eventId)
366
366
 
367
- logger.debug(`**** ENTER SPECTATOR **** event: ${eventId}, user: ${userId}`)
367
+ logger.debug(`**** ENTER SPECTATOR **** event: ${anearEvent.id}, user: ${userId}`)
368
368
 
369
369
  await anearEvent.refreshSpectator()
370
370
  }
371
371
 
372
- async spectatorLeaveMessagingCallback(eventId, message) {
372
+ async spectatorLeaveMessagingCallback(anearEvent, message) {
373
373
  const userId = message.clientId
374
- logger.debug(`**** LEAVE SPECTATOR **** event: ${eventId}, user: ${userId}`)
374
+ logger.debug(`**** LEAVE SPECTATOR **** event: ${anearEvent.id}, user: ${userId}`)
375
375
  }
376
376
 
377
- async participantLeaveMessagingCallback(eventId, message) {
377
+ async participantLeaveMessagingCallback(anearEvent, message) {
378
378
  // this can be just a temporary leave (refresh browser for example), so we don't do anything
379
379
  // for now
380
380
  const userId = message.clientId
381
381
  logger.debug(`**** LEAVE PARTICIPANT **** participantLeaveMessagingCallback(user: ${userId})`)
382
382
  }
383
383
 
384
- async closeParticipant(eventId, participantId, callback) {
384
+ async closeParticipant(anearEvent, participantId, callback) {
385
385
  logger.debug(`closeParticipant(${participantId})`)
386
386
 
387
387
  this.clearParticipantTimer(participantId)
388
388
 
389
- await this.getAnearEventWithLockFromStorage(
390
- eventId,
391
- async (anearEvent) => {
392
- const anearParticipant = await this.getAnearParticipantFromStorage(participantId)
393
-
389
+ const participant = await this.getAnearParticipantFromStorage(participantId)
394
390
 
395
- if (anearParticipant) {
396
- await this.detachParticipantPrivateChannel(eventId, anearParticipant)
391
+ if (participant) {
392
+ await this.detachParticipantPrivateChannel(anearEvent.id, participant)
397
393
 
398
- await callback(anearEvent, anearParticipant)
399
- await anearEvent.update()
400
- }
401
- }
402
- )
394
+ await this.runExclusive("closeParticipant", async () => {
395
+ await callback(anearEvent, participant)
396
+ await anearEvent.update()
397
+ })
398
+ }
403
399
  }
404
400
 
405
- async detachParticipantPrivateChannel(eventId, anearParticipant) {
406
- const userId = anearParticipant.userId
401
+ async detachParticipantPrivateChannel(eventId, participant) {
402
+ const userId = participant.userId
407
403
  const channel = this.eventChannels[eventId].privates[userId]
408
404
 
409
405
  if (channel) {
@@ -411,26 +407,33 @@ class AnearMessaging {
411
407
  delete this.eventChannels[eventId].privates[userId]
412
408
  }
413
409
  }
414
-
415
- async participantActionMessagingCallback(eventId, message) {
410
+ async participantActionMessagingCallback(anearEvent, message) {
411
+ // e.g. message.data
412
+ // {
413
+ // participantId: "93387343489",
414
+ // payload: "{"reviewResponse":{"questionId": "ab88373ccf", "decision":"approved"}}"
415
+ // }
416
+ //
417
+ // actionEventName => "reviewResponse"
418
+ // actionPayload => {questionId: "ab88373ccf", decision:"approved"}
419
+ //
416
420
  const payload = message.data.payload
417
421
  const participantId = message.data.participantId
418
422
 
419
- logger.debug(`participantActionMessagingCallback(${eventId}, ${participantId})`)
423
+ logger.debug(`participantActionMessagingCallback(${anearEvent.id}, ${participantId})`)
420
424
 
421
425
  this.clearParticipantTimer(participantId)
422
426
 
423
427
  const actionJSON = JSON.parse(payload)
424
428
  const [actionEventName, actionPayload] = Object.entries(actionJSON)[0]
425
429
 
426
- await this.getAnearEventWithLockFromStorage(
427
- eventId,
428
- async anearEvent => {
429
- const anearParticipant = await this.getAnearParticipantFromStorage(participantId)
430
- await anearEvent.participantAction(anearParticipant, actionEventName, actionPayload)
431
- await anearEvent.update()
432
- }
433
- )
430
+ const participant = await this.getAnearParticipantFromStorage(participantId)
431
+
432
+ await this.runExclusive("participantActionCallback", async () => {
433
+ await anearEvent.participantAction(participant, actionEventName, actionPayload)
434
+ await anearEvent.update()
435
+ await participant.update()
436
+ })
434
437
  }
435
438
 
436
439
  async eventBroadcastMessagingCallback(eventId, message) {
@@ -441,12 +444,12 @@ class AnearMessaging {
441
444
  await anearEvent.update()
442
445
  }
443
446
 
444
- async setupPrivatePublishingChannel(anearParticipant) {
445
- const privateChannel = this.getChannel(anearParticipant.privateChannelName, {})
446
- this.eventChannels[anearParticipant.eventId].privates[anearParticipant.userId] = privateChannel
447
+ async setupPrivatePublishingChannel(participant) {
448
+ const privateChannel = this.getChannel(participant.privateChannelName, {})
449
+ this.eventChannels[participant.eventId].privates[participant.userId] = privateChannel
447
450
  await this.attachChannel(privateChannel)
448
451
 
449
- logger.debug(`setupPrivatePublishingChannel(${anearParticipant.privateChannelName}) state ${privateChannel.state}`)
452
+ logger.debug(`setupPrivatePublishingChannel(${participant.privateChannelName}) state ${privateChannel.state}`)
450
453
  }
451
454
 
452
455
  async attachChannel(channel) {
@@ -457,6 +460,18 @@ class AnearMessaging {
457
460
  }
458
461
  }
459
462
 
463
+ async runExclusive(name, callback) {
464
+ logger.debug(`waiting for ${name} mutex`)
465
+
466
+ await this.mutex.runExclusive(
467
+ async () => {
468
+ logger.debug(`mutex ${name} locked!`)
469
+ await callback()
470
+ }
471
+ )
472
+ logger.debug(`mutex ${name} released!`)
473
+ }
474
+
460
475
  subscribeEventMessages(channel, messageType, callback) {
461
476
  channel.subscribe(messageType, callback)
462
477
  logger.debug(`subscribed to ${messageType} messages on ${channel.name}`)
@@ -505,7 +520,7 @@ class AnearMessaging {
505
520
  if (timeoutMilliseconds === 0) return
506
521
 
507
522
  participants.forEach(
508
- anearParticipant => this.setParticipantTimer(eventId, anearParticipant, timeoutMilliseconds)
523
+ participant => this.setParticipantTimer(eventId, participant, timeoutMilliseconds)
509
524
  )
510
525
  }
511
526
 
@@ -521,18 +536,18 @@ class AnearMessaging {
521
536
 
522
537
  async publishEventPrivateMessage(
523
538
  eventId,
524
- anearParticipant,
539
+ participant,
525
540
  messageType,
526
541
  css,
527
542
  message,
528
543
  timeoutMilliseconds=0,
529
544
  timeoutCallback=null) {
530
545
 
531
- const userId = anearParticipant.userId
546
+ const userId = participant.userId
532
547
  const channel = this.eventChannels[eventId].privates[userId]
533
548
  if (!channel) throw new Error(`private channel not found. invalid user id ${userId}`)
534
549
 
535
- const setTimerFunction = () => this.setParticipantTimer(eventId, anearParticipant, timeoutMilliseconds)
550
+ const setTimerFunction = () => this.setParticipantTimer(eventId, participant, timeoutMilliseconds)
536
551
 
537
552
  await this.publishChannelMessageWithTimeout(
538
553
  channel,