anear-js-api 0.4.1 → 0.4.3
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/lib/api/AnearApi.js +1 -1
- package/lib/index.js +2 -1
- package/lib/messaging/AnearMessaging.js +83 -157
- package/lib/messaging/__mocks__/AnearMessaging.js +4 -0
- package/lib/models/AnearEvent.js +70 -20
- package/lib/models/AnearParticipant.js +84 -5
- package/lib/utils/AnearXstate.js +6 -1
- package/lib/utils/AnearXstateDefaults.js +6 -6
- package/lib/utils/Participants.js +62 -53
- package/package.json +1 -1
- package/tests/AnearEvent.test.js +84 -58
- package/tests/AnearParticipant.test.js +46 -4
- package/tests/Participants.test.js +71 -53
- package/tests/fixtures/ParticipantsFixture.js +100 -105
- package/tests/utils/AnearParticipantJSONBuilder.js +66 -0
package/lib/api/AnearApi.js
CHANGED
|
@@ -46,7 +46,7 @@ class AnearApi extends ApiService {
|
|
|
46
46
|
const json = await this.post("transitions", {event_name: eventName}, relationships)
|
|
47
47
|
const attrs = json.data.attributes
|
|
48
48
|
logger.info(`API: newState is ${attrs.state}`)
|
|
49
|
-
return
|
|
49
|
+
return attrs
|
|
50
50
|
} catch(err) {
|
|
51
51
|
logger.error(err)
|
|
52
52
|
}
|
package/lib/index.js
CHANGED
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
const JsonApiResource = require('./models/JsonApiResource')
|
|
4
4
|
const JsonApiArrayResource = require('./models/JsonApiArrayResource')
|
|
5
|
-
const
|
|
5
|
+
const AnearEvent = require('./models/AnearEvent')
|
|
6
|
+
const logger = require('./utils/Logger')
|
|
6
7
|
const AnearParticipant = require('./models/AnearParticipant')
|
|
7
8
|
const AnearMessaging = require('./messaging/AnearMessaging')
|
|
8
9
|
const AnearApiService = require('./api/ApiService')
|
|
@@ -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,76 +81,12 @@ 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
|
-
interruptParticipantTimer(participantId) {
|
|
132
|
-
const timer = this.participantTimers[participantId]
|
|
133
|
-
|
|
134
|
-
if (timer && timer.isRunning) timer.interrupt()
|
|
135
|
-
}
|
|
136
|
-
|
|
137
|
-
resetParticipantTimer(participantId) {
|
|
138
|
-
// called after participant takes Action before timer expires
|
|
139
|
-
const timer = this.participantTimers[participantId]
|
|
140
|
-
if (timer) timer.reset()
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
async timerExpired(anearEvent, participant, timeoutMsecs) {
|
|
144
|
-
logger.debug(`participant (${anearEvent.id}, ${participant.id}) TIMED OUT after ${timeoutMsecs} msecs`)
|
|
145
|
-
|
|
146
|
-
delete this.participantTimers[participant.id]
|
|
147
|
-
|
|
148
|
-
await anearEvent.participantTimedOut(participant)
|
|
149
|
-
await anearEvent.update()
|
|
150
|
-
}
|
|
151
|
-
|
|
152
84
|
async getAnearEventFromStorage(eventId) {
|
|
153
85
|
return await this.AnearEventClass.getFromStorage(eventId, this)
|
|
154
86
|
}
|
|
155
87
|
|
|
156
88
|
async getAnearParticipantFromStorage(participantId) {
|
|
157
|
-
return await this.AnearParticipantClass.getFromStorage(participantId)
|
|
89
|
+
return await this.AnearParticipantClass.getFromStorage(participantId, this)
|
|
158
90
|
}
|
|
159
91
|
|
|
160
92
|
async initEventRealtimeMessaging(anearEvent) {
|
|
@@ -195,41 +127,35 @@ class AnearMessaging {
|
|
|
195
127
|
// if we are getting this event create message from history after a quick restart,
|
|
196
128
|
// we just return if the event already exists
|
|
197
129
|
//
|
|
198
|
-
await
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
130
|
+
const eventExists = await anearEvent.exists()
|
|
131
|
+
|
|
132
|
+
logger.info(`Event ${anearEvent.id} ${eventExists ? "already exists" : "does not exist"} in Storage`)
|
|
133
|
+
|
|
134
|
+
let loadedEvent = anearEvent
|
|
135
|
+
|
|
136
|
+
if (!eventExists) {
|
|
137
|
+
await anearEvent.runExclusive(`createEventCallback ${anearEvent.id}`, async () => {
|
|
138
|
+
await anearEvent.createdEventCallback()
|
|
139
|
+
await anearEvent.persist()
|
|
140
|
+
// start the state machine before initialiing Realtime Messaging
|
|
141
|
+
// as REFRESH events come in and the state machine should be ready
|
|
142
|
+
// to handle those XState events
|
|
143
|
+
anearEvent.startStateMachine()
|
|
144
|
+
await this.initEventRealtimeMessaging(anearEvent)
|
|
145
|
+
})
|
|
146
|
+
} else {
|
|
147
|
+
loadedEvent = await this.getAnearEventFromStorage(anearEvent.id)
|
|
148
|
+
await this.initEventRealtimeMessaging(loadedEvent)
|
|
149
|
+
loadedEvent.startStateMachine()
|
|
150
|
+
}
|
|
207
151
|
|
|
208
|
-
|
|
152
|
+
logger.info(`New ${loadedEvent.constructor.name} Event: `, loadedEvent.toJSON())
|
|
209
153
|
|
|
210
|
-
|
|
154
|
+
this.anearEvents[loadedEvent.id] = loadedEvent
|
|
211
155
|
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
await anearEvent.createdEventCallback()
|
|
215
|
-
await anearEvent.persist()
|
|
216
|
-
// start the state machine before initialiing Realtime Messaging
|
|
217
|
-
// as REFRESH events come in and the state machine should be ready
|
|
218
|
-
// to handle those XState events
|
|
219
|
-
anearEvent.startStateMachine()
|
|
220
|
-
await this.initEventRealtimeMessaging(anearEvent)
|
|
221
|
-
})
|
|
222
|
-
} else {
|
|
223
|
-
loadedEvent = await this.getAnearEventFromStorage(anearEvent.id)
|
|
224
|
-
await this.initEventRealtimeMessaging(loadedEvent)
|
|
225
|
-
loadedEvent.startStateMachine()
|
|
156
|
+
} catch(err) {
|
|
157
|
+
logger.error(err)
|
|
226
158
|
}
|
|
227
|
-
|
|
228
|
-
logger.info(`New ${loadedEvent.constructor.name} Event: `, loadedEvent.toJSON())
|
|
229
|
-
|
|
230
|
-
this.anearEvents[loadedEvent.id] = loadedEvent
|
|
231
|
-
|
|
232
|
-
return loadedEvent
|
|
233
159
|
}
|
|
234
160
|
|
|
235
161
|
async reloadAnyEventsInProgress(appId) {
|
|
@@ -242,11 +168,16 @@ class AnearMessaging {
|
|
|
242
168
|
for (const eventData of events) {
|
|
243
169
|
const eventJson = await this.api.getEvent(eventData.id)
|
|
244
170
|
const anearEvent = new this.AnearEventClass(eventJson, this)
|
|
245
|
-
|
|
246
|
-
|
|
171
|
+
await this.initEventRealtimeMessaging(anearEvent)
|
|
172
|
+
|
|
173
|
+
const attachedParticipants = this.getPresentParticipants(anearEvent)
|
|
174
|
+
// TBD: might want to change the attach and presence logic on
|
|
175
|
+
// the actions channel. The Ably docs show subscribing to the
|
|
176
|
+
// presence events on the actions channel, and instead of using History,
|
|
177
|
+
// it does a get() to fetch all of the current members. This behavior
|
|
178
|
+
// is useful for both event start, and event restart within this function
|
|
179
|
+
// anearEvent.startStateMachine()
|
|
247
180
|
//
|
|
248
|
-
// const loadedEvent = await this.loadOrPersistEventAndInitialize(anearEvent)
|
|
249
|
-
// await this.refreshActiveParticipants(loadedEvent) DOES NOT WORK YET
|
|
250
181
|
}
|
|
251
182
|
}
|
|
252
183
|
} catch (err) {
|
|
@@ -254,20 +185,6 @@ class AnearMessaging {
|
|
|
254
185
|
}
|
|
255
186
|
}
|
|
256
187
|
|
|
257
|
-
async refreshActiveParticipants(anearEvent) {
|
|
258
|
-
const allParticipants = anearEvent.participants.active
|
|
259
|
-
|
|
260
|
-
return Promise.all(
|
|
261
|
-
allParticipants.map(
|
|
262
|
-
participant => this.processParticipantEnter(
|
|
263
|
-
anearEvent,
|
|
264
|
-
participant.id,
|
|
265
|
-
participant.geoLocation
|
|
266
|
-
)
|
|
267
|
-
)
|
|
268
|
-
)
|
|
269
|
-
}
|
|
270
|
-
|
|
271
188
|
async setupCreateEventChannel() {
|
|
272
189
|
logger.info(`attaching to channel ${AnearCreateEventChannelName}`)
|
|
273
190
|
|
|
@@ -307,6 +224,21 @@ class AnearMessaging {
|
|
|
307
224
|
)
|
|
308
225
|
}
|
|
309
226
|
|
|
227
|
+
async getSpectatorCount(anearEvent) {
|
|
228
|
+
if (!anearEvent.allowsSpectators()) return 0
|
|
229
|
+
|
|
230
|
+
const channel = this.eventChannels[anearEvent.id].spectators
|
|
231
|
+
const members = await channel.presence.get()
|
|
232
|
+
return members.length
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
async getPresentParticipants(anearEvent) {
|
|
236
|
+
// returns the participant presence data for each member who is present on
|
|
237
|
+
// the event's actions channel
|
|
238
|
+
const channel = this.eventChannels[anearEvent.id].actions
|
|
239
|
+
return await channel.presence.get()
|
|
240
|
+
}
|
|
241
|
+
|
|
310
242
|
async setupActionsChannel(anearEvent) {
|
|
311
243
|
const actionsChannel = this.getChannel(anearEvent.actionsChannelName())
|
|
312
244
|
|
|
@@ -372,7 +304,9 @@ class AnearMessaging {
|
|
|
372
304
|
await this.closeParticipant(
|
|
373
305
|
anearEvent,
|
|
374
306
|
participantId,
|
|
375
|
-
(anearEvent, participant) =>
|
|
307
|
+
async (anearEvent, participant) => {
|
|
308
|
+
await anearEvent.participantExit(participant)
|
|
309
|
+
}
|
|
376
310
|
)
|
|
377
311
|
}
|
|
378
312
|
|
|
@@ -391,7 +325,7 @@ class AnearMessaging {
|
|
|
391
325
|
await this.processParticipantEnter(anearEvent, participantId, geoLocation)
|
|
392
326
|
}
|
|
393
327
|
|
|
394
|
-
async processParticipantEnter(anearEvent, participantId, geoLocation) {
|
|
328
|
+
async processParticipantEnter(anearEvent, participantId, geoLocation = null) {
|
|
395
329
|
|
|
396
330
|
logger.debug(`processing Participant Enter for event: ${anearEvent.id}, participant: ${participantId}`)
|
|
397
331
|
//
|
|
@@ -401,17 +335,17 @@ class AnearMessaging {
|
|
|
401
335
|
//
|
|
402
336
|
try {
|
|
403
337
|
const participantJson = await this.api.getEventParticipantJson(participantId)
|
|
404
|
-
const participant = new this.AnearParticipantClass(participantJson)
|
|
338
|
+
const participant = new this.AnearParticipantClass(participantJson, anearEvent)
|
|
405
339
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const persistedAnearParticipant = await this.AnearParticipantClass.getFromStorage(participantId)
|
|
340
|
+
const persistedAnearParticipant = await this.AnearParticipantClass.getFromStorage(participantId, anearEvent)
|
|
409
341
|
|
|
410
342
|
if (persistedAnearParticipant) {
|
|
411
343
|
participant.context = persistedAnearParticipant.context
|
|
412
344
|
}
|
|
413
345
|
|
|
414
|
-
await
|
|
346
|
+
await anearEvent.runExclusive(`participantEnterCallback ${participant.id}`, async () => {
|
|
347
|
+
participant.geoLocation = geoLocation
|
|
348
|
+
|
|
415
349
|
await this.setupPrivatePublishingChannel(participant)
|
|
416
350
|
await anearEvent.participantEnter(participant)
|
|
417
351
|
await anearEvent.update()
|
|
@@ -438,27 +372,32 @@ class AnearMessaging {
|
|
|
438
372
|
}
|
|
439
373
|
|
|
440
374
|
async participantLeaveMessagingCallback(anearEvent, message) {
|
|
441
|
-
// this can be just a temporary leave
|
|
375
|
+
// this can be just a temporary leave from a participant refreshing their browser.
|
|
376
|
+
// currently, no action taken
|
|
442
377
|
const userId = message.clientId
|
|
443
378
|
const participantId = message.data.id
|
|
444
379
|
|
|
445
380
|
logger.debug(`**** LEAVE PARTICIPANT **** participantLeaveMessagingCallback(participant: ${participantId})`)
|
|
446
|
-
|
|
447
|
-
this.interruptParticipantTimer(participantId)
|
|
448
381
|
}
|
|
449
382
|
|
|
450
|
-
async closeParticipant(anearEvent, participantId, callback) {
|
|
383
|
+
async closeParticipant(anearEvent, participantId, callback = null) {
|
|
384
|
+
// closes out a single Participant. This is invoked when a single
|
|
385
|
+
// participant leaves an event, and the event may possibly continue,
|
|
386
|
+
// or possibly exit. Or this may be called by the event when exiting
|
|
387
|
+
// and cleaning out any remaining participants.
|
|
451
388
|
logger.debug(`closeParticipant(${participantId})`)
|
|
452
389
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const participant = await this.getAnearParticipantFromStorage(participantId)
|
|
390
|
+
const participant = anearEvent.participants.getById(participantId)
|
|
456
391
|
|
|
457
392
|
if (participant) {
|
|
393
|
+
participant.destroyTimer()
|
|
394
|
+
|
|
458
395
|
await this.detachParticipantPrivateChannel(anearEvent.id, participant)
|
|
459
396
|
|
|
460
|
-
await
|
|
461
|
-
|
|
397
|
+
await anearEvent.runExclusive(`closeParticipant ${participant.id}`, async () => {
|
|
398
|
+
if (callback) {
|
|
399
|
+
await callback(anearEvent, participant)
|
|
400
|
+
}
|
|
462
401
|
await anearEvent.update()
|
|
463
402
|
})
|
|
464
403
|
}
|
|
@@ -487,16 +426,16 @@ class AnearMessaging {
|
|
|
487
426
|
const participantId = message.data.participantId
|
|
488
427
|
const payload = message.data.payload
|
|
489
428
|
|
|
490
|
-
|
|
429
|
+
const participant = anearEvent.participants.getById(participantId)
|
|
430
|
+
|
|
431
|
+
participant.resetTimer() // participant responded in time, reset any running timer
|
|
491
432
|
|
|
492
433
|
logger.debug(`participantActionMessagingCallback(${anearEvent.id}, ${participantId})`)
|
|
493
434
|
|
|
494
435
|
const actionJSON = JSON.parse(payload)
|
|
495
436
|
const [actionEventName, actionPayload] = Object.entries(actionJSON)[0]
|
|
496
437
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
await this.runExclusive("participantActionCallback", async () => {
|
|
438
|
+
await anearEvent.runExclusive(`participantActionCallback ${participant.id}`, async () => {
|
|
500
439
|
await anearEvent.participantAction(participant, actionEventName, actionPayload)
|
|
501
440
|
await anearEvent.update()
|
|
502
441
|
await participant.update()
|
|
@@ -526,18 +465,6 @@ class AnearMessaging {
|
|
|
526
465
|
}
|
|
527
466
|
}
|
|
528
467
|
|
|
529
|
-
async runExclusive(name, callback) {
|
|
530
|
-
logger.debug(`waiting for ${name} mutex`)
|
|
531
|
-
|
|
532
|
-
await this.mutex.runExclusive(
|
|
533
|
-
async () => {
|
|
534
|
-
logger.debug(`mutex ${name} locked!`)
|
|
535
|
-
await callback()
|
|
536
|
-
}
|
|
537
|
-
)
|
|
538
|
-
logger.debug(`mutex ${name} released!`)
|
|
539
|
-
}
|
|
540
|
-
|
|
541
468
|
subscribeEventMessages(channel, messageType, callback) {
|
|
542
469
|
channel.subscribe(messageType, callback)
|
|
543
470
|
logger.debug(`subscribed to ${messageType} messages on ${channel.name}`)
|
|
@@ -579,14 +506,14 @@ class AnearMessaging {
|
|
|
579
506
|
)
|
|
580
507
|
}
|
|
581
508
|
|
|
582
|
-
setMultipleParticipantTimers(
|
|
509
|
+
setMultipleParticipantTimers(participants, timeoutMsecs) {
|
|
583
510
|
if (timeoutMsecs === 0) return [() => {}, 0]
|
|
584
511
|
|
|
585
512
|
const participantTimers = []
|
|
586
513
|
|
|
587
514
|
participants.forEach(
|
|
588
515
|
participant => {
|
|
589
|
-
const [startTimer, _timeRemaining] =
|
|
516
|
+
const [startTimer, _timeRemaining] = participant.ensureTimer(timeoutMsecs)
|
|
590
517
|
participantTimers.push(startTimer)
|
|
591
518
|
}
|
|
592
519
|
)
|
|
@@ -598,7 +525,7 @@ class AnearMessaging {
|
|
|
598
525
|
const eventId = anearEvent.id
|
|
599
526
|
const channel = this.eventChannels[eventId].participants
|
|
600
527
|
|
|
601
|
-
const [startTimers, timeRemaining] = this.setMultipleParticipantTimers(
|
|
528
|
+
const [startTimers, timeRemaining] = this.setMultipleParticipantTimers(participants, timeoutMsecs)
|
|
602
529
|
|
|
603
530
|
await this.publishMessage(
|
|
604
531
|
channel,
|
|
@@ -623,7 +550,7 @@ class AnearMessaging {
|
|
|
623
550
|
const channel = this.eventChannels[anearEvent.id].privates[userId]
|
|
624
551
|
if (!channel) throw new Error(`private channel not found. invalid user id ${userId}`)
|
|
625
552
|
|
|
626
|
-
const [startTimer, timeRemaining] =
|
|
553
|
+
const [startTimer, timeRemaining] = participant.ensureTimer(timeoutMsecs)
|
|
627
554
|
|
|
628
555
|
await this.publishMessage(
|
|
629
556
|
channel,
|
|
@@ -659,10 +586,9 @@ class AnearMessaging {
|
|
|
659
586
|
)
|
|
660
587
|
}
|
|
661
588
|
|
|
662
|
-
async publishEventTransitionMessage(anearEvent,
|
|
589
|
+
async publishEventTransitionMessage(anearEvent, newState) {
|
|
663
590
|
const channel = this.eventChannels[anearEvent.id].events
|
|
664
|
-
const
|
|
665
|
-
const payload = {content: {state: newState, event: eventJson}}
|
|
591
|
+
const payload = {content: {state: newState}}
|
|
666
592
|
|
|
667
593
|
logger.debug(`publishEventTransitionMessage: event ${anearEvent.id} transitioning to ${newState}`)
|
|
668
594
|
|
package/lib/models/AnearEvent.js
CHANGED
|
@@ -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
|
|
|
@@ -13,19 +15,19 @@ class AnearEvent extends JsonApiResource {
|
|
|
13
15
|
|
|
14
16
|
constructor(json, messaging) {
|
|
15
17
|
super(json)
|
|
16
|
-
|
|
17
|
-
this.messaging = messaging
|
|
18
18
|
this.zone = this.findIncluded(this.relationships.zone)
|
|
19
19
|
this.app = this.findIncluded(this.zone.relationships.app)
|
|
20
|
-
this.
|
|
20
|
+
this.messaging = messaging
|
|
21
21
|
this.anearStateMachine = this.initStateMachine(json.previousState)
|
|
22
|
+
this.participants = new Participants(this, json.participants)
|
|
23
|
+
this.mutex = new Mutex()
|
|
22
24
|
}
|
|
23
25
|
|
|
24
26
|
toJSON() {
|
|
25
27
|
return {
|
|
26
28
|
...super.toJSON(),
|
|
27
|
-
context: this.stateMachineContext,
|
|
28
29
|
participants: this.participants.toJSON(),
|
|
30
|
+
context: this.stateMachineContext,
|
|
29
31
|
previousState: this.anearStateMachine.currentState
|
|
30
32
|
}
|
|
31
33
|
}
|
|
@@ -120,6 +122,10 @@ class AnearEvent extends JsonApiResource {
|
|
|
120
122
|
return !this.hasFlag("no_spectators")
|
|
121
123
|
}
|
|
122
124
|
|
|
125
|
+
async spectatorCount() {
|
|
126
|
+
return await this.messaging.getSpectatorCount(this)
|
|
127
|
+
}
|
|
128
|
+
|
|
123
129
|
async createdEventCallback(participantCreator) {
|
|
124
130
|
// You may implement createdEventCallback() in your AnearEvent sub-class
|
|
125
131
|
}
|
|
@@ -136,8 +142,8 @@ class AnearEvent extends JsonApiResource {
|
|
|
136
142
|
// You may implement an async spectatorRefreshEventCallback() in your AnearEvent sub-class
|
|
137
143
|
}
|
|
138
144
|
|
|
139
|
-
async
|
|
140
|
-
throw new Error('You must implement an async
|
|
145
|
+
async participantExitEventCallback(participant) {
|
|
146
|
+
throw new Error('You must implement an async participantExitEventCallback() in your AnearEvent sub-class');
|
|
141
147
|
}
|
|
142
148
|
|
|
143
149
|
async participantActionEventCallback(participant, actionEventName, message) {
|
|
@@ -173,6 +179,18 @@ class AnearEvent extends JsonApiResource {
|
|
|
173
179
|
)
|
|
174
180
|
}
|
|
175
181
|
|
|
182
|
+
async runExclusive(name, callback) {
|
|
183
|
+
logger.debug(`waiting for ${name} mutex`)
|
|
184
|
+
|
|
185
|
+
await this.mutex.runExclusive(
|
|
186
|
+
async () => {
|
|
187
|
+
logger.debug(`mutex ${name} locked!`)
|
|
188
|
+
await callback()
|
|
189
|
+
}
|
|
190
|
+
)
|
|
191
|
+
logger.debug(`mutex ${name} released!`)
|
|
192
|
+
}
|
|
193
|
+
|
|
176
194
|
async publishEventSpectatorsMessage(message) {
|
|
177
195
|
await this.messaging.publishEventSpectatorsMessage(this, this.css, message)
|
|
178
196
|
}
|
|
@@ -203,13 +221,13 @@ class AnearEvent extends JsonApiResource {
|
|
|
203
221
|
if (this.participants.exists(participant)) {
|
|
204
222
|
logger.info(`AnearEvent: participant ${participant.id} exists. Refreshing...`)
|
|
205
223
|
|
|
206
|
-
this.participants.add(
|
|
224
|
+
this.participants.add(participant) // update the participants entry
|
|
207
225
|
|
|
208
226
|
this.anearStateMachine.sendRefreshEvent({ participant })
|
|
209
227
|
|
|
210
228
|
await participant.update()
|
|
211
229
|
} else {
|
|
212
|
-
this.participants.add(
|
|
230
|
+
this.participants.add(participant) // add the participants entry
|
|
213
231
|
|
|
214
232
|
this.anearStateMachine.sendJoinEvent({ participant })
|
|
215
233
|
|
|
@@ -217,11 +235,15 @@ class AnearEvent extends JsonApiResource {
|
|
|
217
235
|
}
|
|
218
236
|
}
|
|
219
237
|
|
|
220
|
-
async
|
|
221
|
-
this
|
|
222
|
-
|
|
223
|
-
this.anearStateMachine.
|
|
238
|
+
async participantExit(participant) {
|
|
239
|
+
// this informs the state machine that the participant has exited the event
|
|
240
|
+
// and removes that participant completely
|
|
241
|
+
this.anearStateMachine.sendParticipantExitEvent({ participant })
|
|
242
|
+
await this.participantPurge(participant)
|
|
243
|
+
}
|
|
224
244
|
|
|
245
|
+
async participantPurge(participant) {
|
|
246
|
+
this.participants.purge(participant)
|
|
225
247
|
await participant.remove()
|
|
226
248
|
}
|
|
227
249
|
|
|
@@ -250,11 +272,10 @@ class AnearEvent extends JsonApiResource {
|
|
|
250
272
|
logger.debug(`AnearEvent: transitionEvent(${eventName})`)
|
|
251
273
|
|
|
252
274
|
try {
|
|
253
|
-
const
|
|
254
|
-
const newState =
|
|
255
|
-
|
|
256
|
-
await this.messaging.publishEventTransitionMessage(this, eventJson)
|
|
275
|
+
const responseAttributes = await this.messaging.api.transitionEvent(this.id, eventName)
|
|
276
|
+
const newState = responseAttributes.state
|
|
257
277
|
this.attributes.state = newState
|
|
278
|
+
await this.messaging.publishEventTransitionMessage(this, newState)
|
|
258
279
|
} catch(err) {
|
|
259
280
|
logger.error(`AnearEvent: transitionEvent error: ${err}`)
|
|
260
281
|
}
|
|
@@ -285,6 +306,33 @@ class AnearEvent extends JsonApiResource {
|
|
|
285
306
|
await this.transitionEvent('cancel')
|
|
286
307
|
}
|
|
287
308
|
|
|
309
|
+
async closeOutParticipants() {
|
|
310
|
+
// upon exiting the event, this will clean up any participants remaining
|
|
311
|
+
return Promise.all(
|
|
312
|
+
this.participants.all.map(
|
|
313
|
+
async p => {
|
|
314
|
+
await this.messaging.closeParticipant(
|
|
315
|
+
this,
|
|
316
|
+
p.id,
|
|
317
|
+
async (anearEvent, participant) => {
|
|
318
|
+
await anearEvent.participantPurge(participant)
|
|
319
|
+
}
|
|
320
|
+
)
|
|
321
|
+
}
|
|
322
|
+
)
|
|
323
|
+
)
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
async purgeParticipants() {
|
|
327
|
+
// remove participants from Participants class and from Storage
|
|
328
|
+
const all = this.participants.all
|
|
329
|
+
if (this.participants.host) all.push(this.participants.host)
|
|
330
|
+
|
|
331
|
+
await Promise.all(
|
|
332
|
+
all.map(p => this.participantPurge(p))
|
|
333
|
+
)
|
|
334
|
+
}
|
|
335
|
+
|
|
288
336
|
eventChannelName () {
|
|
289
337
|
return this.getChannelName('event')
|
|
290
338
|
}
|
|
@@ -305,6 +353,11 @@ class AnearEvent extends JsonApiResource {
|
|
|
305
353
|
return this.attributes[`${key}-channel-name`]
|
|
306
354
|
}
|
|
307
355
|
|
|
356
|
+
async closeEvent() {
|
|
357
|
+
await this.closeOutParticipants()
|
|
358
|
+
await this.closeMessaging()
|
|
359
|
+
}
|
|
360
|
+
|
|
308
361
|
async closeMessaging () {
|
|
309
362
|
await this.messaging.detachAll(this.id)
|
|
310
363
|
}
|
|
@@ -318,7 +371,4 @@ class AnearEvent extends JsonApiResource {
|
|
|
318
371
|
}
|
|
319
372
|
}
|
|
320
373
|
|
|
321
|
-
module.exports =
|
|
322
|
-
AnearEvent,
|
|
323
|
-
logger
|
|
324
|
-
}
|
|
374
|
+
module.exports = AnearEvent
|