anear-js-api 0.4.2 → 0.4.4
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 +4 -8
- package/lib/messaging/AnearMessaging.js +111 -135
- package/lib/models/AnearEvent.js +11 -14
- package/lib/utils/AnearXstate.js +6 -1
- package/package.json +1 -1
package/lib/api/AnearApi.js
CHANGED
|
@@ -42,14 +42,10 @@ class AnearApi extends ApiService {
|
|
|
42
42
|
logger.debug(`API: POST transition event ${eventName}`)
|
|
43
43
|
|
|
44
44
|
const relationships = {event: eventId}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
return attrs
|
|
50
|
-
} catch(err) {
|
|
51
|
-
logger.error(err)
|
|
52
|
-
}
|
|
45
|
+
const json = await this.post("transitions", {event_name: eventName}, relationships)
|
|
46
|
+
const attrs = json.data.attributes
|
|
47
|
+
logger.debug(`API: newState is ${attrs.state}`)
|
|
48
|
+
return attrs
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
async getEventParticipantJson(participantId, geoLocation) {
|
|
@@ -28,6 +28,7 @@ class AnearMessaging {
|
|
|
28
28
|
this.AnearEventClass = AnearEventClass
|
|
29
29
|
this.AnearParticipantClass = AnearParticipantClass
|
|
30
30
|
this.anearEvents = {}
|
|
31
|
+
this.eventChannels = {}
|
|
31
32
|
|
|
32
33
|
const baseUrl = this.api.api_base_url
|
|
33
34
|
const authUrl = `${baseUrl}/messaging_auth`
|
|
@@ -49,8 +50,6 @@ class AnearMessaging {
|
|
|
49
50
|
}
|
|
50
51
|
}
|
|
51
52
|
|
|
52
|
-
this.eventChannels = {}
|
|
53
|
-
|
|
54
53
|
this.initRealtime(clientOptions)
|
|
55
54
|
}
|
|
56
55
|
|
|
@@ -59,30 +58,35 @@ class AnearMessaging {
|
|
|
59
58
|
|
|
60
59
|
this.realtime = new Ably.Realtime(clientOptions)
|
|
61
60
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
)
|
|
61
|
+
const connectedCallback = async () => {
|
|
62
|
+
await this.getAppInfo(AppId)
|
|
63
|
+
logger.debug("Ably connected!")
|
|
64
|
+
await this.setupCreateEventChannel()
|
|
65
|
+
await this.reloadAnyEventsInProgress(AppId)
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
this.realtime.connection.on("connected", connectedCallback)
|
|
71
69
|
}
|
|
72
70
|
|
|
73
71
|
async getAppInfo(appId) {
|
|
72
|
+
// fetch the App info from the Anear API
|
|
74
73
|
const anearApp = await this.api.getApp(appId)
|
|
75
|
-
|
|
76
|
-
logger.
|
|
77
|
-
logger.
|
|
74
|
+
|
|
75
|
+
logger.debug("================")
|
|
76
|
+
logger.debug(`STARTING APP ${anearApp.data.attributes['short-name']}`)
|
|
77
|
+
logger.debug("================")
|
|
78
78
|
}
|
|
79
79
|
|
|
80
80
|
getChannel(channelName, channelParams = ChannelParams) {
|
|
81
81
|
return this.realtime.channels.get(channelName, channelParams)
|
|
82
82
|
}
|
|
83
83
|
|
|
84
|
-
|
|
85
|
-
return
|
|
84
|
+
getAnearEventFromStorage(eventId) {
|
|
85
|
+
return this.AnearEventClass.getFromStorage(eventId, this)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
getAnearParticipantFromStorage(participantId) {
|
|
89
|
+
return this.AnearParticipantClass.getFromStorage(participantId, this)
|
|
86
90
|
}
|
|
87
91
|
|
|
88
92
|
async initEventRealtimeMessaging(anearEvent) {
|
|
@@ -94,14 +98,10 @@ class AnearMessaging {
|
|
|
94
98
|
|
|
95
99
|
this.eventChannels[anearEvent.id] = {privates: {}}
|
|
96
100
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
await this.setupActionsChannel(anearEvent)
|
|
102
|
-
} catch(error) {
|
|
103
|
-
logger.error(`initEventRealtimeMessaging(${anearEvent.id}): `, error)
|
|
104
|
-
}
|
|
101
|
+
await this.setupSpectatorsChannel(anearEvent)
|
|
102
|
+
this.setupEventsChannel(anearEvent)
|
|
103
|
+
await this.setupParticipantsChannel(anearEvent)
|
|
104
|
+
await this.setupActionsChannel(anearEvent)
|
|
105
105
|
|
|
106
106
|
logger.debug(`initEventRealtimeMessaging(${anearEvent.id}) is complete`)
|
|
107
107
|
|
|
@@ -113,76 +113,75 @@ class AnearMessaging {
|
|
|
113
113
|
// create the AnearEvent subclass, persist it in storage, and
|
|
114
114
|
// initialize its realtime messaging
|
|
115
115
|
//
|
|
116
|
-
logger.
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
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
|
-
}
|
|
116
|
+
logger.debug(`createEventMessagingCallback(${message.name})`)
|
|
117
|
+
|
|
118
|
+
const eventJson = JSON.parse(message.data)
|
|
119
|
+
const anearEvent = new this.AnearEventClass(eventJson, this)
|
|
120
|
+
|
|
121
|
+
//
|
|
122
|
+
// if we are getting this event create message from history after a quick restart,
|
|
123
|
+
// we just return if the event already exists
|
|
124
|
+
//
|
|
125
|
+
const eventExists = await anearEvent.exists()
|
|
147
126
|
|
|
148
|
-
|
|
127
|
+
logger.debug(
|
|
128
|
+
`Event ${anearEvent.id} ${eventExists ? "already exists" : "does not exist"} in Storage`
|
|
129
|
+
)
|
|
149
130
|
|
|
150
|
-
|
|
131
|
+
let loadedEvent = anearEvent
|
|
132
|
+
|
|
133
|
+
if (!eventExists) {
|
|
134
|
+
const messagingInitializedCallback = async () => {
|
|
135
|
+
await anearEvent.createdEventCallback()
|
|
136
|
+
await anearEvent.persist()
|
|
137
|
+
// start the state machine before initializing Realtime Messaging
|
|
138
|
+
// as REFRESH events come in and the state machine should be ready
|
|
139
|
+
// to handle those XState events
|
|
140
|
+
anearEvent.startStateMachine()
|
|
141
|
+
await this.initEventRealtimeMessaging(anearEvent)
|
|
142
|
+
}
|
|
151
143
|
|
|
152
|
-
|
|
153
|
-
|
|
144
|
+
await anearEvent.runExclusive(
|
|
145
|
+
`createEventCallback ${anearEvent.id}`,
|
|
146
|
+
messagingInitializedCallback
|
|
147
|
+
)
|
|
148
|
+
} else {
|
|
149
|
+
loadedEvent = await this.getAnearEventFromStorage(anearEvent.id)
|
|
150
|
+
await this.initEventRealtimeMessaging(loadedEvent)
|
|
151
|
+
loadedEvent.startStateMachine()
|
|
154
152
|
}
|
|
153
|
+
|
|
154
|
+
logger.debug(`New ${loadedEvent.constructor.name} Event: `, loadedEvent.toJSON())
|
|
155
|
+
|
|
156
|
+
this.anearEvents[loadedEvent.id] = loadedEvent
|
|
155
157
|
}
|
|
156
158
|
|
|
157
159
|
async reloadAnyEventsInProgress(appId) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
}
|
|
160
|
+
const anearApp = await this.api.getAppZones(appId)
|
|
161
|
+
|
|
162
|
+
for (const zone of anearApp.data.relationships.zones.data) {
|
|
163
|
+
const zoneEvents = await this.api.getZoneEvents(zone.id)
|
|
164
|
+
const events = zoneEvents.data.relationships.events.data
|
|
165
|
+
|
|
166
|
+
for (const eventData of events) {
|
|
167
|
+
const eventJson = await this.api.getEvent(eventData.id)
|
|
168
|
+
const anearEvent = new this.AnearEventClass(eventJson, this)
|
|
169
|
+
await this.initEventRealtimeMessaging(anearEvent)
|
|
170
|
+
|
|
171
|
+
const attachedParticipants = await this.getPresentParticipants(anearEvent)
|
|
172
|
+
// TBD: might want to change the attach and presence logic on
|
|
173
|
+
// the actions channel. The Ably docs show subscribing to the
|
|
174
|
+
// presence events on the actions channel, and instead of using History,
|
|
175
|
+
// it does a get() to fetch all of the current members. This behavior
|
|
176
|
+
// is useful for both event start, and event restart within this function
|
|
177
|
+
// anearEvent.startStateMachine()
|
|
178
|
+
//
|
|
178
179
|
}
|
|
179
|
-
} catch (err) {
|
|
180
|
-
logger.error(err)
|
|
181
180
|
}
|
|
182
181
|
}
|
|
183
182
|
|
|
184
183
|
async setupCreateEventChannel() {
|
|
185
|
-
logger.
|
|
184
|
+
logger.debug(`attaching to channel ${AnearCreateEventChannelName}`)
|
|
186
185
|
|
|
187
186
|
const createEventsChannel = this.getChannel(AnearCreateEventChannelName)
|
|
188
187
|
|
|
@@ -228,11 +227,11 @@ class AnearMessaging {
|
|
|
228
227
|
return members.length
|
|
229
228
|
}
|
|
230
229
|
|
|
231
|
-
|
|
230
|
+
getPresentParticipants(anearEvent) {
|
|
232
231
|
// returns the participant presence data for each member who is present on
|
|
233
232
|
// the event's actions channel
|
|
234
233
|
const channel = this.eventChannels[anearEvent.id].actions
|
|
235
|
-
return
|
|
234
|
+
return channel.presence.get() // returns Promise
|
|
236
235
|
}
|
|
237
236
|
|
|
238
237
|
async setupActionsChannel(anearEvent) {
|
|
@@ -329,29 +328,22 @@ class AnearMessaging {
|
|
|
329
328
|
// check if the participant is already in storage, and if so, instantiate, else
|
|
330
329
|
// instantiate from API response
|
|
331
330
|
//
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
const participant = new this.AnearParticipantClass(participantJson, anearEvent)
|
|
331
|
+
const participantJson = await this.api.getEventParticipantJson(participantId)
|
|
332
|
+
const participant = new this.AnearParticipantClass(participantJson, anearEvent)
|
|
335
333
|
|
|
336
|
-
|
|
334
|
+
const persistedAnearParticipant = await this.AnearParticipantClass.getFromStorage(participantId, anearEvent)
|
|
337
335
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
336
|
+
if (persistedAnearParticipant) {
|
|
337
|
+
participant.context = persistedAnearParticipant.context
|
|
338
|
+
}
|
|
341
339
|
|
|
342
|
-
|
|
343
|
-
|
|
340
|
+
await anearEvent.runExclusive(`participantEnterCallback ${participant.id}`, async () => {
|
|
341
|
+
participant.geoLocation = geoLocation
|
|
344
342
|
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
} catch(error) {
|
|
350
|
-
// participant not found or is not currently marked active at the API service
|
|
351
|
-
// don't allow participation. FIX: we need to publish to the private channel
|
|
352
|
-
// with an error message type.
|
|
353
|
-
logger.error(`processParticipanEnter(${anearEvent.id}, ${participantId}) error: `, error)
|
|
354
|
-
}
|
|
343
|
+
await this.setupPrivatePublishingChannel(participant)
|
|
344
|
+
await anearEvent.participantEnter(participant)
|
|
345
|
+
await anearEvent.update()
|
|
346
|
+
})
|
|
355
347
|
}
|
|
356
348
|
|
|
357
349
|
async spectatorEnterMessagingCallback(anearEvent, message) {
|
|
@@ -454,11 +446,7 @@ class AnearMessaging {
|
|
|
454
446
|
}
|
|
455
447
|
|
|
456
448
|
async attachChannel(channel) {
|
|
457
|
-
|
|
458
|
-
return await channel.attach()
|
|
459
|
-
} catch(err) {
|
|
460
|
-
this.logErrorInfo(err)
|
|
461
|
-
}
|
|
449
|
+
return await channel.attach()
|
|
462
450
|
}
|
|
463
451
|
|
|
464
452
|
subscribeEventMessages(channel, messageType, callback) {
|
|
@@ -471,22 +459,18 @@ class AnearMessaging {
|
|
|
471
459
|
}
|
|
472
460
|
|
|
473
461
|
async subscribePresenceEventWithHistory(channel, action, callback) {
|
|
474
|
-
logger.
|
|
462
|
+
logger.debug(`subscribePresenceEvents(${action}) for channel ${channel.name}`)
|
|
475
463
|
|
|
476
464
|
this.subscribePresenceEvent(channel, action, callback)
|
|
477
465
|
|
|
478
|
-
|
|
479
|
-
const history = await channel.presence.history({limit: 25})
|
|
466
|
+
const history = await channel.presence.history({limit: 25})
|
|
480
467
|
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
} catch(err) {
|
|
488
|
-
logger.error('Unable to get presence history; err = ' + err.message)
|
|
489
|
-
}
|
|
468
|
+
history.items.filter(message => message.action === action).forEach(
|
|
469
|
+
async message => {
|
|
470
|
+
logger.debug(`presence history ${action} event received`, message)
|
|
471
|
+
await callback(message)
|
|
472
|
+
}
|
|
473
|
+
)
|
|
490
474
|
}
|
|
491
475
|
|
|
492
476
|
async publishEventSpectatorsMessage(anearEvent, css, message, messageType = PublicDisplayMessageType) {
|
|
@@ -592,14 +576,10 @@ class AnearMessaging {
|
|
|
592
576
|
}
|
|
593
577
|
|
|
594
578
|
async publishChannelMessage(channel, messageType, payload) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
)
|
|
600
|
-
} catch(err) {
|
|
601
|
-
this.logErrorInfo(err)
|
|
602
|
-
}
|
|
579
|
+
await channel.publish(
|
|
580
|
+
messageType,
|
|
581
|
+
payload
|
|
582
|
+
)
|
|
603
583
|
}
|
|
604
584
|
|
|
605
585
|
async detachAll(eventId) {
|
|
@@ -621,17 +601,13 @@ class AnearMessaging {
|
|
|
621
601
|
async detachChannel (channel) {
|
|
622
602
|
if (!channel || channel.state !== 'attached') return
|
|
623
603
|
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
logger.info(`channel ${channel.name} detached`)
|
|
627
|
-
} catch(err) {
|
|
628
|
-
this.logErrorInfo(err)
|
|
629
|
-
}
|
|
604
|
+
await channel.detach(this.logErrorInfo)
|
|
605
|
+
logger.debug(`channel ${channel.name} detached`)
|
|
630
606
|
}
|
|
631
607
|
|
|
632
608
|
logErrorInfo(errInfo) {
|
|
633
609
|
if (errInfo) {
|
|
634
|
-
logger.error(
|
|
610
|
+
logger.error("Ably ERROR:", errInfo)
|
|
635
611
|
}
|
|
636
612
|
}
|
|
637
613
|
}
|
package/lib/models/AnearEvent.js
CHANGED
|
@@ -11,6 +11,8 @@ const logger = require('../utils/Logger')
|
|
|
11
11
|
|
|
12
12
|
const PrivateDisplayMessageType = 'private_display'
|
|
13
13
|
|
|
14
|
+
const PlayableStates = ['closing', 'closed', 'canceled']
|
|
15
|
+
|
|
14
16
|
class AnearEvent extends JsonApiResource {
|
|
15
17
|
|
|
16
18
|
constructor(json, messaging) {
|
|
@@ -64,7 +66,7 @@ class AnearEvent extends JsonApiResource {
|
|
|
64
66
|
}
|
|
65
67
|
|
|
66
68
|
async getClonedEvent() {
|
|
67
|
-
// if the current event was a clone of previous event,
|
|
69
|
+
// if the current event was a clone of previous event, fetch if from
|
|
68
70
|
// Peristence and return
|
|
69
71
|
const clonedEventData = this.relationships["cloned-event"].data
|
|
70
72
|
if (!clonedEventData) return null
|
|
@@ -221,13 +223,13 @@ class AnearEvent extends JsonApiResource {
|
|
|
221
223
|
if (this.participants.exists(participant)) {
|
|
222
224
|
logger.info(`AnearEvent: participant ${participant.id} exists. Refreshing...`)
|
|
223
225
|
|
|
224
|
-
this.participants.add(participant) // update the participants
|
|
226
|
+
this.participants.add(participant) // update the participants record
|
|
225
227
|
|
|
226
228
|
this.anearStateMachine.sendRefreshEvent({ participant })
|
|
227
229
|
|
|
228
230
|
await participant.update()
|
|
229
231
|
} else {
|
|
230
|
-
this.participants.add(participant) // add the participants
|
|
232
|
+
this.participants.add(participant) // add the participants record
|
|
231
233
|
|
|
232
234
|
this.anearStateMachine.sendJoinEvent({ participant })
|
|
233
235
|
|
|
@@ -271,18 +273,14 @@ class AnearEvent extends JsonApiResource {
|
|
|
271
273
|
//
|
|
272
274
|
logger.debug(`AnearEvent: transitionEvent(${eventName})`)
|
|
273
275
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
await this.messaging.publishEventTransitionMessage(this, newState)
|
|
279
|
-
} catch(err) {
|
|
280
|
-
logger.error(`AnearEvent: transitionEvent error: ${err}`)
|
|
281
|
-
}
|
|
276
|
+
const responseAttributes = await this.messaging.api.transitionEvent(this.id, eventName)
|
|
277
|
+
const newState = responseAttributes.state
|
|
278
|
+
this.attributes.state = newState
|
|
279
|
+
await this.messaging.publishEventTransitionMessage(this, newState)
|
|
282
280
|
}
|
|
283
281
|
|
|
284
282
|
isPlayable() {
|
|
285
|
-
return !
|
|
283
|
+
return !PlayableStates.includes(this.eventState)
|
|
286
284
|
}
|
|
287
285
|
|
|
288
286
|
async transitionToAnnounce() {
|
|
@@ -311,10 +309,9 @@ class AnearEvent extends JsonApiResource {
|
|
|
311
309
|
return Promise.all(
|
|
312
310
|
this.participants.all.map(
|
|
313
311
|
async p => {
|
|
314
|
-
const participant = await this.messaging.getAnearParticipantFromStorage(p.id)
|
|
315
312
|
await this.messaging.closeParticipant(
|
|
316
313
|
this,
|
|
317
|
-
|
|
314
|
+
p.id,
|
|
318
315
|
async (anearEvent, participant) => {
|
|
319
316
|
await anearEvent.participantPurge(participant)
|
|
320
317
|
}
|
package/lib/utils/AnearXstate.js
CHANGED
|
@@ -11,7 +11,12 @@ const TimeoutEvent = 'TIMEOUT'
|
|
|
11
11
|
|
|
12
12
|
class AnearXstate {
|
|
13
13
|
constructor(machineConfig, machineOptions, previousState, anearEventContext) {
|
|
14
|
-
|
|
14
|
+
const config = {predictableActionArguments: true, ...machineConfig}
|
|
15
|
+
|
|
16
|
+
this.machine = createMachine(
|
|
17
|
+
config,
|
|
18
|
+
machineOptions
|
|
19
|
+
).withContext(anearEventContext)
|
|
15
20
|
|
|
16
21
|
this._currentState = previousState ? State.create(previousState) : this.machine.initialState
|
|
17
22
|
this._currentContext = anearEventContext
|