anear-js-api 0.4.1 → 0.4.2
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 +82 -160
- package/lib/messaging/__mocks__/AnearMessaging.js +4 -0
- package/lib/models/AnearEvent.js +71 -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,78 +81,10 @@ 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
|
-
async getAnearParticipantFromStorage(participantId) {
|
|
157
|
-
return await this.AnearParticipantClass.getFromStorage(participantId)
|
|
158
|
-
}
|
|
159
|
-
|
|
160
88
|
async initEventRealtimeMessaging(anearEvent) {
|
|
161
89
|
|
|
162
90
|
if (this.eventChannels.hasOwnProperty(anearEvent.id)) {
|
|
@@ -195,41 +123,35 @@ class AnearMessaging {
|
|
|
195
123
|
// if we are getting this event create message from history after a quick restart,
|
|
196
124
|
// we just return if the event already exists
|
|
197
125
|
//
|
|
198
|
-
await
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
126
|
+
const eventExists = await anearEvent.exists()
|
|
127
|
+
|
|
128
|
+
logger.info(`Event ${anearEvent.id} ${eventExists ? "already exists" : "does not exist"} in Storage`)
|
|
129
|
+
|
|
130
|
+
let loadedEvent = anearEvent
|
|
131
|
+
|
|
132
|
+
if (!eventExists) {
|
|
133
|
+
await anearEvent.runExclusive(`createEventCallback ${anearEvent.id}`, async () => {
|
|
134
|
+
await anearEvent.createdEventCallback()
|
|
135
|
+
await anearEvent.persist()
|
|
136
|
+
// start the state machine before initialiing Realtime Messaging
|
|
137
|
+
// as REFRESH events come in and the state machine should be ready
|
|
138
|
+
// to handle those XState events
|
|
139
|
+
anearEvent.startStateMachine()
|
|
140
|
+
await this.initEventRealtimeMessaging(anearEvent)
|
|
141
|
+
})
|
|
142
|
+
} else {
|
|
143
|
+
loadedEvent = await this.getAnearEventFromStorage(anearEvent.id)
|
|
144
|
+
await this.initEventRealtimeMessaging(loadedEvent)
|
|
145
|
+
loadedEvent.startStateMachine()
|
|
146
|
+
}
|
|
207
147
|
|
|
208
|
-
|
|
148
|
+
logger.info(`New ${loadedEvent.constructor.name} Event: `, loadedEvent.toJSON())
|
|
209
149
|
|
|
210
|
-
|
|
150
|
+
this.anearEvents[loadedEvent.id] = loadedEvent
|
|
211
151
|
|
|
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()
|
|
152
|
+
} catch(err) {
|
|
153
|
+
logger.error(err)
|
|
226
154
|
}
|
|
227
|
-
|
|
228
|
-
logger.info(`New ${loadedEvent.constructor.name} Event: `, loadedEvent.toJSON())
|
|
229
|
-
|
|
230
|
-
this.anearEvents[loadedEvent.id] = loadedEvent
|
|
231
|
-
|
|
232
|
-
return loadedEvent
|
|
233
155
|
}
|
|
234
156
|
|
|
235
157
|
async reloadAnyEventsInProgress(appId) {
|
|
@@ -242,11 +164,16 @@ class AnearMessaging {
|
|
|
242
164
|
for (const eventData of events) {
|
|
243
165
|
const eventJson = await this.api.getEvent(eventData.id)
|
|
244
166
|
const anearEvent = new this.AnearEventClass(eventJson, this)
|
|
245
|
-
|
|
246
|
-
|
|
167
|
+
await this.initEventRealtimeMessaging(anearEvent)
|
|
168
|
+
|
|
169
|
+
const attachedParticipants = this.getPresentParticipants(anearEvent)
|
|
170
|
+
// TBD: might want to change the attach and presence logic on
|
|
171
|
+
// the actions channel. The Ably docs show subscribing to the
|
|
172
|
+
// presence events on the actions channel, and instead of using History,
|
|
173
|
+
// it does a get() to fetch all of the current members. This behavior
|
|
174
|
+
// is useful for both event start, and event restart within this function
|
|
175
|
+
// anearEvent.startStateMachine()
|
|
247
176
|
//
|
|
248
|
-
// const loadedEvent = await this.loadOrPersistEventAndInitialize(anearEvent)
|
|
249
|
-
// await this.refreshActiveParticipants(loadedEvent) DOES NOT WORK YET
|
|
250
177
|
}
|
|
251
178
|
}
|
|
252
179
|
} catch (err) {
|
|
@@ -254,20 +181,6 @@ class AnearMessaging {
|
|
|
254
181
|
}
|
|
255
182
|
}
|
|
256
183
|
|
|
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
184
|
async setupCreateEventChannel() {
|
|
272
185
|
logger.info(`attaching to channel ${AnearCreateEventChannelName}`)
|
|
273
186
|
|
|
@@ -307,6 +220,21 @@ class AnearMessaging {
|
|
|
307
220
|
)
|
|
308
221
|
}
|
|
309
222
|
|
|
223
|
+
async getSpectatorCount(anearEvent) {
|
|
224
|
+
if (!anearEvent.allowsSpectators()) return 0
|
|
225
|
+
|
|
226
|
+
const channel = this.eventChannels[anearEvent.id].spectators
|
|
227
|
+
const members = await channel.presence.get()
|
|
228
|
+
return members.length
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
async getPresentParticipants(anearEvent) {
|
|
232
|
+
// returns the participant presence data for each member who is present on
|
|
233
|
+
// the event's actions channel
|
|
234
|
+
const channel = this.eventChannels[anearEvent.id].actions
|
|
235
|
+
return await channel.presence.get()
|
|
236
|
+
}
|
|
237
|
+
|
|
310
238
|
async setupActionsChannel(anearEvent) {
|
|
311
239
|
const actionsChannel = this.getChannel(anearEvent.actionsChannelName())
|
|
312
240
|
|
|
@@ -372,7 +300,9 @@ class AnearMessaging {
|
|
|
372
300
|
await this.closeParticipant(
|
|
373
301
|
anearEvent,
|
|
374
302
|
participantId,
|
|
375
|
-
(anearEvent, participant) =>
|
|
303
|
+
async (anearEvent, participant) => {
|
|
304
|
+
await anearEvent.participantExit(participant)
|
|
305
|
+
}
|
|
376
306
|
)
|
|
377
307
|
}
|
|
378
308
|
|
|
@@ -391,7 +321,7 @@ class AnearMessaging {
|
|
|
391
321
|
await this.processParticipantEnter(anearEvent, participantId, geoLocation)
|
|
392
322
|
}
|
|
393
323
|
|
|
394
|
-
async processParticipantEnter(anearEvent, participantId, geoLocation) {
|
|
324
|
+
async processParticipantEnter(anearEvent, participantId, geoLocation = null) {
|
|
395
325
|
|
|
396
326
|
logger.debug(`processing Participant Enter for event: ${anearEvent.id}, participant: ${participantId}`)
|
|
397
327
|
//
|
|
@@ -401,17 +331,17 @@ class AnearMessaging {
|
|
|
401
331
|
//
|
|
402
332
|
try {
|
|
403
333
|
const participantJson = await this.api.getEventParticipantJson(participantId)
|
|
404
|
-
const participant = new this.AnearParticipantClass(participantJson)
|
|
334
|
+
const participant = new this.AnearParticipantClass(participantJson, anearEvent)
|
|
405
335
|
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
const persistedAnearParticipant = await this.AnearParticipantClass.getFromStorage(participantId)
|
|
336
|
+
const persistedAnearParticipant = await this.AnearParticipantClass.getFromStorage(participantId, anearEvent)
|
|
409
337
|
|
|
410
338
|
if (persistedAnearParticipant) {
|
|
411
339
|
participant.context = persistedAnearParticipant.context
|
|
412
340
|
}
|
|
413
341
|
|
|
414
|
-
await
|
|
342
|
+
await anearEvent.runExclusive(`participantEnterCallback ${participant.id}`, async () => {
|
|
343
|
+
participant.geoLocation = geoLocation
|
|
344
|
+
|
|
415
345
|
await this.setupPrivatePublishingChannel(participant)
|
|
416
346
|
await anearEvent.participantEnter(participant)
|
|
417
347
|
await anearEvent.update()
|
|
@@ -438,27 +368,32 @@ class AnearMessaging {
|
|
|
438
368
|
}
|
|
439
369
|
|
|
440
370
|
async participantLeaveMessagingCallback(anearEvent, message) {
|
|
441
|
-
// this can be just a temporary leave
|
|
371
|
+
// this can be just a temporary leave from a participant refreshing their browser.
|
|
372
|
+
// currently, no action taken
|
|
442
373
|
const userId = message.clientId
|
|
443
374
|
const participantId = message.data.id
|
|
444
375
|
|
|
445
376
|
logger.debug(`**** LEAVE PARTICIPANT **** participantLeaveMessagingCallback(participant: ${participantId})`)
|
|
446
|
-
|
|
447
|
-
this.interruptParticipantTimer(participantId)
|
|
448
377
|
}
|
|
449
378
|
|
|
450
|
-
async closeParticipant(anearEvent, participantId, callback) {
|
|
379
|
+
async closeParticipant(anearEvent, participantId, callback = null) {
|
|
380
|
+
// closes out a single Participant. This is invoked when a single
|
|
381
|
+
// participant leaves an event, and the event may possibly continue,
|
|
382
|
+
// or possibly exit. Or this may be called by the event when exiting
|
|
383
|
+
// and cleaning out any remaining participants.
|
|
451
384
|
logger.debug(`closeParticipant(${participantId})`)
|
|
452
385
|
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
const participant = await this.getAnearParticipantFromStorage(participantId)
|
|
386
|
+
const participant = anearEvent.participants.getById(participantId)
|
|
456
387
|
|
|
457
388
|
if (participant) {
|
|
389
|
+
participant.destroyTimer()
|
|
390
|
+
|
|
458
391
|
await this.detachParticipantPrivateChannel(anearEvent.id, participant)
|
|
459
392
|
|
|
460
|
-
await
|
|
461
|
-
|
|
393
|
+
await anearEvent.runExclusive(`closeParticipant ${participant.id}`, async () => {
|
|
394
|
+
if (callback) {
|
|
395
|
+
await callback(anearEvent, participant)
|
|
396
|
+
}
|
|
462
397
|
await anearEvent.update()
|
|
463
398
|
})
|
|
464
399
|
}
|
|
@@ -487,16 +422,16 @@ class AnearMessaging {
|
|
|
487
422
|
const participantId = message.data.participantId
|
|
488
423
|
const payload = message.data.payload
|
|
489
424
|
|
|
490
|
-
|
|
425
|
+
const participant = anearEvent.participants.getById(participantId)
|
|
426
|
+
|
|
427
|
+
participant.resetTimer() // participant responded in time, reset any running timer
|
|
491
428
|
|
|
492
429
|
logger.debug(`participantActionMessagingCallback(${anearEvent.id}, ${participantId})`)
|
|
493
430
|
|
|
494
431
|
const actionJSON = JSON.parse(payload)
|
|
495
432
|
const [actionEventName, actionPayload] = Object.entries(actionJSON)[0]
|
|
496
433
|
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
await this.runExclusive("participantActionCallback", async () => {
|
|
434
|
+
await anearEvent.runExclusive(`participantActionCallback ${participant.id}`, async () => {
|
|
500
435
|
await anearEvent.participantAction(participant, actionEventName, actionPayload)
|
|
501
436
|
await anearEvent.update()
|
|
502
437
|
await participant.update()
|
|
@@ -526,18 +461,6 @@ class AnearMessaging {
|
|
|
526
461
|
}
|
|
527
462
|
}
|
|
528
463
|
|
|
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
464
|
subscribeEventMessages(channel, messageType, callback) {
|
|
542
465
|
channel.subscribe(messageType, callback)
|
|
543
466
|
logger.debug(`subscribed to ${messageType} messages on ${channel.name}`)
|
|
@@ -579,14 +502,14 @@ class AnearMessaging {
|
|
|
579
502
|
)
|
|
580
503
|
}
|
|
581
504
|
|
|
582
|
-
setMultipleParticipantTimers(
|
|
505
|
+
setMultipleParticipantTimers(participants, timeoutMsecs) {
|
|
583
506
|
if (timeoutMsecs === 0) return [() => {}, 0]
|
|
584
507
|
|
|
585
508
|
const participantTimers = []
|
|
586
509
|
|
|
587
510
|
participants.forEach(
|
|
588
511
|
participant => {
|
|
589
|
-
const [startTimer, _timeRemaining] =
|
|
512
|
+
const [startTimer, _timeRemaining] = participant.ensureTimer(timeoutMsecs)
|
|
590
513
|
participantTimers.push(startTimer)
|
|
591
514
|
}
|
|
592
515
|
)
|
|
@@ -598,7 +521,7 @@ class AnearMessaging {
|
|
|
598
521
|
const eventId = anearEvent.id
|
|
599
522
|
const channel = this.eventChannels[eventId].participants
|
|
600
523
|
|
|
601
|
-
const [startTimers, timeRemaining] = this.setMultipleParticipantTimers(
|
|
524
|
+
const [startTimers, timeRemaining] = this.setMultipleParticipantTimers(participants, timeoutMsecs)
|
|
602
525
|
|
|
603
526
|
await this.publishMessage(
|
|
604
527
|
channel,
|
|
@@ -623,7 +546,7 @@ class AnearMessaging {
|
|
|
623
546
|
const channel = this.eventChannels[anearEvent.id].privates[userId]
|
|
624
547
|
if (!channel) throw new Error(`private channel not found. invalid user id ${userId}`)
|
|
625
548
|
|
|
626
|
-
const [startTimer, timeRemaining] =
|
|
549
|
+
const [startTimer, timeRemaining] = participant.ensureTimer(timeoutMsecs)
|
|
627
550
|
|
|
628
551
|
await this.publishMessage(
|
|
629
552
|
channel,
|
|
@@ -659,10 +582,9 @@ class AnearMessaging {
|
|
|
659
582
|
)
|
|
660
583
|
}
|
|
661
584
|
|
|
662
|
-
async publishEventTransitionMessage(anearEvent,
|
|
585
|
+
async publishEventTransitionMessage(anearEvent, newState) {
|
|
663
586
|
const channel = this.eventChannels[anearEvent.id].events
|
|
664
|
-
const
|
|
665
|
-
const payload = {content: {state: newState, event: eventJson}}
|
|
587
|
+
const payload = {content: {state: newState}}
|
|
666
588
|
|
|
667
589
|
logger.debug(`publishEventTransitionMessage: event ${anearEvent.id} transitioning to ${newState}`)
|
|
668
590
|
|
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,34 @@ 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
|
+
const participant = await this.messaging.getAnearParticipantFromStorage(p.id)
|
|
315
|
+
await this.messaging.closeParticipant(
|
|
316
|
+
this,
|
|
317
|
+
participant.id,
|
|
318
|
+
async (anearEvent, participant) => {
|
|
319
|
+
await anearEvent.participantPurge(participant)
|
|
320
|
+
}
|
|
321
|
+
)
|
|
322
|
+
}
|
|
323
|
+
)
|
|
324
|
+
)
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
async purgeParticipants() {
|
|
328
|
+
// remove participants from Participants class and from Storage
|
|
329
|
+
const all = this.participants.all
|
|
330
|
+
if (this.participants.host) all.push(this.participants.host)
|
|
331
|
+
|
|
332
|
+
await Promise.all(
|
|
333
|
+
all.map(p => this.participantPurge(p))
|
|
334
|
+
)
|
|
335
|
+
}
|
|
336
|
+
|
|
288
337
|
eventChannelName () {
|
|
289
338
|
return this.getChannelName('event')
|
|
290
339
|
}
|
|
@@ -305,6 +354,11 @@ class AnearEvent extends JsonApiResource {
|
|
|
305
354
|
return this.attributes[`${key}-channel-name`]
|
|
306
355
|
}
|
|
307
356
|
|
|
357
|
+
async closeEvent() {
|
|
358
|
+
await this.closeOutParticipants()
|
|
359
|
+
await this.closeMessaging()
|
|
360
|
+
}
|
|
361
|
+
|
|
308
362
|
async closeMessaging () {
|
|
309
363
|
await this.messaging.detachAll(this.id)
|
|
310
364
|
}
|
|
@@ -318,7 +372,4 @@ class AnearEvent extends JsonApiResource {
|
|
|
318
372
|
}
|
|
319
373
|
}
|
|
320
374
|
|
|
321
|
-
module.exports =
|
|
322
|
-
AnearEvent,
|
|
323
|
-
logger
|
|
324
|
-
}
|
|
375
|
+
module.exports = AnearEvent
|