anear-js-api 0.3.32 → 0.3.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.
@@ -1,9 +1,7 @@
1
1
  "use strict"
2
2
  const Ably = require('ably/promises')
3
3
  const AnearApi = require('../api/AnearApi')
4
- const ParticipantTimer = require('../utils/ParticipantTimer')
5
4
  const logger = require('../utils/Logger')
6
- const { Mutex } = require('async-mutex')
7
5
 
8
6
  const AppId = process.env.ANEARAPP_APP_ID
9
7
 
@@ -29,7 +27,6 @@ class AnearMessaging {
29
27
  this.api = new AnearApi()
30
28
  this.AnearEventClass = AnearEventClass
31
29
  this.AnearParticipantClass = AnearParticipantClass
32
- this.mutex = new Mutex()
33
30
  this.anearEvents = {}
34
31
 
35
32
  const baseUrl = this.api.api_base_url
@@ -53,7 +50,6 @@ class AnearMessaging {
53
50
  }
54
51
 
55
52
  this.eventChannels = {}
56
- this.participantTimers = {}
57
53
 
58
54
  this.initRealtime(clientOptions)
59
55
  }
@@ -85,75 +81,6 @@ class AnearMessaging {
85
81
  return this.realtime.channels.get(channelName, channelParams)
86
82
  }
87
83
 
88
- ensureParticipantTimer(anearEvent, participant, timeoutMsecs) {
89
- // this is called when a new timer is being started for a privateMessage
90
- // sent to a participant, or public message to all participants
91
- // If the timer already exists and is paused, it is resumed with the timeRemaining
92
- // [a starter function, timeRemaining] is returned
93
- let timeRemaining = timeoutMsecs
94
- let timerStarter = () => {}
95
-
96
- if (timeoutMsecs > 0) {
97
- const timer = this.participantTimers[participant.id] || this.createTimer(anearEvent, participant, timeoutMsecs)
98
-
99
- if (timer.isRunning) {
100
- timeRemaining = timer.interrupt()
101
- } else {
102
- timerStarter = () => timer.start(timeoutMsecs)
103
- }
104
- }
105
- logger.debug(`ensureParticipantTimer(timeRemaining: ${timeRemaining})`)
106
-
107
- return [timerStarter, timeRemaining]
108
- }
109
-
110
- createTimer(anearEvent, participant, timeoutMsecs) {
111
- const timer = new ParticipantTimer(
112
- participant.id,
113
- async () => await this.timerExpired(anearEvent, participant, timeoutMsecs)
114
- )
115
- this.participantTimers[participant.id] = timer
116
-
117
- return timer
118
- }
119
-
120
- destroyParticipantTimer(participantId) {
121
- // participant will not be receiving any more display messages
122
- // so we close out and delete the ParticipantTimer
123
- const timer = this.participantTimers[participantId]
124
-
125
- if (timer) {
126
- timer.reset()
127
- delete this.participantTimers[participantId]
128
- }
129
- }
130
-
131
- resetAllParticipantTimers() {
132
- // turns off all participant timers
133
- Object.values(this.participantTimers).forEach(timer => timer.reset())
134
- }
135
-
136
- interruptParticipantTimer(participantId) {
137
- const timer = this.participantTimers[participantId]
138
-
139
- if (timer && timer.isRunning) timer.interrupt()
140
- }
141
-
142
- resetParticipantTimer(participantId) {
143
- // called after participant takes Action before timer expires
144
- const timer = this.participantTimers[participantId]
145
- if (timer) timer.reset()
146
- }
147
-
148
- async timerExpired(anearEvent, participant, timeoutMsecs) {
149
- logger.debug(`participant (${anearEvent.id}, ${participant.id}) TIMED OUT after ${timeoutMsecs} msecs`)
150
-
151
- delete this.participantTimers[participant.id]
152
-
153
- await anearEvent.participantTimedOut(participant)
154
- await anearEvent.update()
155
- }
156
-
157
84
  async getAnearEventFromStorage(eventId) {
158
85
  return await this.AnearEventClass.getFromStorage(eventId, this.AnearParticipantClass, this)
159
86
  }
@@ -215,7 +142,7 @@ class AnearMessaging {
215
142
  let loadedEvent = anearEvent
216
143
 
217
144
  if (!eventExists) {
218
- await this.runExclusive("createEventCallback", async () => {
145
+ await anearEvent.runExclusive("createEventCallback", async () => {
219
146
  await anearEvent.createdEventCallback()
220
147
  await anearEvent.persist()
221
148
  // start the state machine before initialiing Realtime Messaging
@@ -249,6 +176,8 @@ class AnearMessaging {
249
176
  const anearEvent = new this.AnearEventClass(eventJson, this.AnearParticipantClass, this)
250
177
  // This needs work!!
251
178
  // loadedEvent = await this.getAnearEventFromStorage(anearEvent.id)
179
+ // NOTE: there should be existing presence state to read from action channel
180
+ // to drive this...not a reload from storage
252
181
  // await this.refreshActiveParticipants(loadedEvent)
253
182
  // await this.initEventRealtimeMessaging(loadedEvent)
254
183
  // loadedEvent.startStateMachine()
@@ -262,7 +191,7 @@ class AnearMessaging {
262
191
 
263
192
  async refreshActiveParticipants(anearEvent) {
264
193
  await this.participants.reloadFromStorage(
265
- p => this.processParticipantEnter(anearEvent, p.id, p.geoLocation)
194
+ p => this.processParticipantEnter(anearEvent, p.id)
266
195
  )
267
196
  }
268
197
 
@@ -399,7 +328,7 @@ class AnearMessaging {
399
328
  await this.processParticipantEnter(anearEvent, participantId, geoLocation)
400
329
  }
401
330
 
402
- async processParticipantEnter(anearEvent, participantId, geoLocation) {
331
+ async processParticipantEnter(anearEvent, participantId, geoLocation = null) {
403
332
 
404
333
  logger.debug(`processing Participant Enter for event: ${anearEvent.id}, participant: ${participantId}`)
405
334
  //
@@ -411,15 +340,15 @@ class AnearMessaging {
411
340
  const participantJson = await this.api.getEventParticipantJson(participantId)
412
341
  const participant = new this.AnearParticipantClass(participantJson, anearEvent)
413
342
 
414
- participant.geoLocation = geoLocation
415
-
416
343
  const persistedAnearParticipant = await this.getAnearParticipantFromStorage(participantId, anearEvent)
417
344
 
418
345
  if (persistedAnearParticipant) {
419
346
  participant.context = persistedAnearParticipant.context
420
347
  }
421
348
 
422
- await this.runExclusive("participantEnterCallback", async () => {
349
+ await anearEvent.runExclusive("participantEnterCallback", async () => {
350
+ participant.geoLocation = geoLocation
351
+
423
352
  await this.setupPrivatePublishingChannel(participant)
424
353
  await anearEvent.participantEnter(participant)
425
354
  await anearEvent.update()
@@ -446,13 +375,15 @@ class AnearMessaging {
446
375
  }
447
376
 
448
377
  async participantLeaveMessagingCallback(anearEvent, message) {
449
- // this can be just a temporary leave (a participant refreshing their browser for example), so pause any participant timers
378
+ // this can be just a temporary leave (a participant refreshing their browser for example),
379
+ // so pause any participant timers
450
380
  const userId = message.clientId
451
381
  const participantId = message.data.id
382
+ const participant = anearEvent.participants.getById(participantId)
452
383
 
453
- logger.debug(`**** LEAVE PARTICIPANT **** participantLeaveMessagingCallback(participant: ${participantId})`)
384
+ logger.debug(`**** LEAVE PARTICIPANT **** participantLeaveMessagingCallback(participant: ${participant.id})`)
454
385
 
455
- this.interruptParticipantTimer(participantId)
386
+ participant.interruptTimer()
456
387
  }
457
388
 
458
389
  async closeParticipant(anearEvent, participantId, callback = null) {
@@ -462,14 +393,14 @@ class AnearMessaging {
462
393
  // and cleaning out any remaining participants.
463
394
  logger.debug(`closeParticipant(${participantId})`)
464
395
 
465
- this.destroyParticipantTimer(participantId)
466
-
467
- const participant = await this.getAnearParticipantFromStorage(participantId, anearEvent)
396
+ const participant = anearEvent.participants.getById(participantId)
468
397
 
469
398
  if (participant) {
399
+ participant.destroyTimer()
400
+
470
401
  await this.detachParticipantPrivateChannel(anearEvent.id, participant)
471
402
 
472
- await this.runExclusive("closeParticipant", async () => {
403
+ await anearEvent.runExclusive("closeParticipant", async () => {
473
404
  if (callback) {
474
405
  await callback(anearEvent, participant)
475
406
  }
@@ -501,16 +432,16 @@ class AnearMessaging {
501
432
  const participantId = message.data.participantId
502
433
  const payload = message.data.payload
503
434
 
504
- this.resetParticipantTimer(participantId) // participant responded in time, reset any running timer
435
+ const participant = anearEvent.participants.getById(participantId)
436
+
437
+ participant.resetTimer() // participant responded in time, reset any running timer
505
438
 
506
439
  logger.debug(`participantActionMessagingCallback(${anearEvent.id}, ${participantId})`)
507
440
 
508
441
  const actionJSON = JSON.parse(payload)
509
442
  const [actionEventName, actionPayload] = Object.entries(actionJSON)[0]
510
443
 
511
- const participant = await this.getAnearParticipantFromStorage(participantId, anearEvent)
512
-
513
- await this.runExclusive("participantActionCallback", async () => {
444
+ await anearEvent.runExclusive("participantActionCallback", async () => {
514
445
  await anearEvent.participantAction(participant, actionEventName, actionPayload)
515
446
  await anearEvent.update()
516
447
  await participant.update()
@@ -540,18 +471,6 @@ class AnearMessaging {
540
471
  }
541
472
  }
542
473
 
543
- async runExclusive(name, callback) {
544
- logger.debug(`waiting for ${name} mutex`)
545
-
546
- await this.mutex.runExclusive(
547
- async () => {
548
- logger.debug(`mutex ${name} locked!`)
549
- await callback()
550
- }
551
- )
552
- logger.debug(`mutex ${name} released!`)
553
- }
554
-
555
474
  subscribeEventMessages(channel, messageType, callback) {
556
475
  channel.subscribe(messageType, callback)
557
476
  logger.debug(`subscribed to ${messageType} messages on ${channel.name}`)
@@ -593,14 +512,14 @@ class AnearMessaging {
593
512
  )
594
513
  }
595
514
 
596
- setMultipleParticipantTimers(anearEvent, participants, timeoutMsecs) {
515
+ setMultipleParticipantTimers(participants, timeoutMsecs) {
597
516
  if (timeoutMsecs === 0) return [() => {}, 0]
598
517
 
599
518
  const participantTimers = []
600
519
 
601
520
  participants.forEach(
602
521
  participant => {
603
- const [startTimer, _timeRemaining] = this.ensureParticipantTimer(anearEvent, participant, timeoutMsecs)
522
+ const [startTimer, _timeRemaining] = participant.ensureTimer(timeoutMsecs)
604
523
  participantTimers.push(startTimer)
605
524
  }
606
525
  )
@@ -612,7 +531,7 @@ class AnearMessaging {
612
531
  const eventId = anearEvent.id
613
532
  const channel = this.eventChannels[eventId].participants
614
533
 
615
- const [startTimers, timeRemaining] = this.setMultipleParticipantTimers(eventId, participants, timeoutMsecs)
534
+ const [startTimers, timeRemaining] = this.setMultipleParticipantTimers(participants, timeoutMsecs)
616
535
 
617
536
  await this.publishMessage(
618
537
  channel,
@@ -637,7 +556,7 @@ class AnearMessaging {
637
556
  const channel = this.eventChannels[anearEvent.id].privates[userId]
638
557
  if (!channel) throw new Error(`private channel not found. invalid user id ${userId}`)
639
558
 
640
- const [startTimer, timeRemaining] = this.ensureParticipantTimer(anearEvent, participant, timeoutMsecs)
559
+ const [startTimer, timeRemaining] = participant.ensureTimer(timeoutMsecs)
641
560
 
642
561
  await this.publishMessage(
643
562
  channel,
@@ -1,5 +1,7 @@
1
1
  "use strict"
2
2
 
3
+ const { Mutex } = require('async-mutex')
4
+
3
5
  const AnearXstate = require('../utils/AnearXstate')
4
6
  const { DefaultConfigFunc, DefaultOptionsFunc } = require('../utils/AnearXstateDefaults')
5
7
 
@@ -19,6 +21,7 @@ class AnearEvent extends JsonApiResource {
19
21
  this.messaging = messaging
20
22
  this.anearStateMachine = this.initStateMachine(json.previousState)
21
23
  this.participants = new Participants(this, json.participants)
24
+ this.mutex = new Mutex()
22
25
  }
23
26
 
24
27
  toJSON() {
@@ -177,6 +180,18 @@ class AnearEvent extends JsonApiResource {
177
180
  )
178
181
  }
179
182
 
183
+ async runExclusive(name, callback) {
184
+ logger.debug(`waiting for ${name} mutex`)
185
+
186
+ await this.mutex.runExclusive(
187
+ async () => {
188
+ logger.debug(`mutex ${name} locked!`)
189
+ await callback()
190
+ }
191
+ )
192
+ logger.debug(`mutex ${name} released!`)
193
+ }
194
+
180
195
  async publishEventSpectatorsMessage(message) {
181
196
  await this.messaging.publishEventSpectatorsMessage(this, this.css, message)
182
197
  }
@@ -1,19 +1,23 @@
1
1
  "use strict"
2
2
  const JsonApiResource = require('./JsonApiResource')
3
+ const ParticipantTimer = require('../utils/ParticipantTimer')
4
+ const logger = require('../utils/Logger')
5
+
3
6
  const HostUserType = "host"
4
7
 
5
8
  class AnearParticipant extends JsonApiResource {
6
9
  constructor(json, anearEvent) {
7
10
  super(json)
8
11
  this.anearEvent = anearEvent
12
+ this.timer = null
9
13
  this._state = json.state
10
14
  this._timestamp = json.timestamp
15
+ this._geoLocation = null
11
16
  }
12
17
 
13
18
  toJSON() {
14
19
  return {
15
20
  ...super.toJSON(),
16
- geoLocation: this._geoLocation || null,
17
21
  state: this.state,
18
22
  timestamp: this.timestamp
19
23
  }
@@ -78,6 +82,59 @@ class AnearParticipant extends JsonApiResource {
78
82
  get privateChannelName() {
79
83
  return this.attributes['private-channel-name']
80
84
  }
85
+
86
+ ensureTimer(timeoutMsecs) {
87
+ // this is called when a new timer is being started for a privateMessage
88
+ // sent to a participant, or public message to all participants
89
+ // If the timer already exists and is paused, it is resumed with the timeRemaining
90
+ // [a starter function, timeRemaining] is returned
91
+ let timeRemaining = timeoutMsecs
92
+ let timerStarter = () => {}
93
+
94
+ if (timeoutMsecs > 0) {
95
+
96
+ this.timer ||= new ParticipantTimer(
97
+ this.id,
98
+ async () => await this.timerExpired(timeoutMsecs)
99
+ )
100
+
101
+ if (this.timer.isRunning) {
102
+ timeRemaining = this.timer.interrupt()
103
+ } else {
104
+ timerStarter = () => this.timer.start(timeoutMsecs)
105
+ }
106
+ }
107
+ logger.debug(`ensureTimer(timeRemaining: ${timeRemaining})`)
108
+
109
+ return [timerStarter, timeRemaining]
110
+ }
111
+
112
+ destroyTimer() {
113
+ // participant will not be receiving any more display messages
114
+ // so we close out and delete the timer
115
+ if (this.timer) {
116
+ this.timer.reset()
117
+ this.timer = null
118
+ }
119
+ }
120
+
121
+ async timerExpired(timeoutMsecs) {
122
+ logger.debug(`participant (${this.anearEvent.id}, ${this.id}) TIMED OUT after ${timeoutMsecs} msecs`)
123
+
124
+ this.timer = null
125
+
126
+ await this.anearEvent.participantTimedOut(this)
127
+ await this.anearEvent.update()
128
+ }
129
+
130
+ interruptTimer() {
131
+ if (this.timer && this.timer.isRunning) this.timer.interrupt()
132
+ }
133
+
134
+ resetTimer() {
135
+ // called after participant takes Action before timer expires
136
+ if (this.timer) this.timer.reset()
137
+ }
81
138
  }
82
139
 
83
140
  module.exports = AnearParticipant
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anear-js-api",
3
- "version": "0.3.32",
3
+ "version": "0.3.34",
4
4
  "description": "Javascript Developer API for Anear Apps",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -43,3 +43,40 @@ test('participant can be repeatedly rehydrated and updated', async () => {
43
43
  console.error(error)
44
44
  }
45
45
  })
46
+
47
+ test('participant does not persist geoLocation', async () => {
48
+ try {
49
+ const someGeoLocation = {
50
+ coords: {
51
+ latitude: 25.7862067,
52
+ longitude: -80.1415718
53
+ }
54
+ }
55
+ const participant = new TestParticipant(player1, MockEvent)
56
+
57
+ participant.geoLocation = someGeoLocation
58
+
59
+ await participant.persist()
60
+
61
+ expect(participant.geoLocation.coords.latitude).toBe(someGeoLocation.coords.latitude)
62
+
63
+ let p = await TestParticipant.getFromStorage(player1.data.id, MockEvent)
64
+
65
+ expect(p.geoLocation).toBe(null)
66
+
67
+ expect(p.context.responses).toStrictEqual(['A', 'C', 'D', 'A'])
68
+ expect(p.anearEvent).toBe(MockEvent)
69
+ p.context.responses.push('B')
70
+
71
+ await p.update()
72
+
73
+ p = await TestParticipant.getFromStorage(player1.data.id, MockEvent)
74
+ expect(p.context.responses[4]).toBe('B')
75
+ expect(p.anearEvent).toBe(MockEvent)
76
+
77
+ await p.remove()
78
+
79
+ } catch(error) {
80
+ console.error(error)
81
+ }
82
+ })