anear-js-api 0.4.3 → 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 +109 -137
- package/lib/models/AnearEvent.js +10 -12
- 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,34 +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
86
|
}
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
return
|
|
88
|
+
getAnearParticipantFromStorage(participantId) {
|
|
89
|
+
return this.AnearParticipantClass.getFromStorage(participantId, this)
|
|
90
90
|
}
|
|
91
91
|
|
|
92
92
|
async initEventRealtimeMessaging(anearEvent) {
|
|
@@ -98,14 +98,10 @@ class AnearMessaging {
|
|
|
98
98
|
|
|
99
99
|
this.eventChannels[anearEvent.id] = {privates: {}}
|
|
100
100
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
await this.setupActionsChannel(anearEvent)
|
|
106
|
-
} catch(error) {
|
|
107
|
-
logger.error(`initEventRealtimeMessaging(${anearEvent.id}): `, error)
|
|
108
|
-
}
|
|
101
|
+
await this.setupSpectatorsChannel(anearEvent)
|
|
102
|
+
this.setupEventsChannel(anearEvent)
|
|
103
|
+
await this.setupParticipantsChannel(anearEvent)
|
|
104
|
+
await this.setupActionsChannel(anearEvent)
|
|
109
105
|
|
|
110
106
|
logger.debug(`initEventRealtimeMessaging(${anearEvent.id}) is complete`)
|
|
111
107
|
|
|
@@ -117,76 +113,75 @@ class AnearMessaging {
|
|
|
117
113
|
// create the AnearEvent subclass, persist it in storage, and
|
|
118
114
|
// initialize its realtime messaging
|
|
119
115
|
//
|
|
120
|
-
logger.
|
|
121
|
-
|
|
122
|
-
try {
|
|
123
|
-
const eventJson = JSON.parse(message.data)
|
|
124
|
-
const anearEvent = new this.AnearEventClass(eventJson, this)
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
// if we are getting this event create message from history after a quick restart,
|
|
128
|
-
// we just return if the event already exists
|
|
129
|
-
//
|
|
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
|
-
}
|
|
116
|
+
logger.debug(`createEventMessagingCallback(${message.name})`)
|
|
151
117
|
|
|
152
|
-
|
|
118
|
+
const eventJson = JSON.parse(message.data)
|
|
119
|
+
const anearEvent = new this.AnearEventClass(eventJson, this)
|
|
153
120
|
|
|
154
|
-
|
|
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()
|
|
126
|
+
|
|
127
|
+
logger.debug(
|
|
128
|
+
`Event ${anearEvent.id} ${eventExists ? "already exists" : "does not exist"} in Storage`
|
|
129
|
+
)
|
|
130
|
+
|
|
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
|
+
}
|
|
155
143
|
|
|
156
|
-
|
|
157
|
-
|
|
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()
|
|
158
152
|
}
|
|
153
|
+
|
|
154
|
+
logger.debug(`New ${loadedEvent.constructor.name} Event: `, loadedEvent.toJSON())
|
|
155
|
+
|
|
156
|
+
this.anearEvents[loadedEvent.id] = loadedEvent
|
|
159
157
|
}
|
|
160
158
|
|
|
161
159
|
async reloadAnyEventsInProgress(appId) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
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
|
+
//
|
|
182
179
|
}
|
|
183
|
-
} catch (err) {
|
|
184
|
-
logger.error(err)
|
|
185
180
|
}
|
|
186
181
|
}
|
|
187
182
|
|
|
188
183
|
async setupCreateEventChannel() {
|
|
189
|
-
logger.
|
|
184
|
+
logger.debug(`attaching to channel ${AnearCreateEventChannelName}`)
|
|
190
185
|
|
|
191
186
|
const createEventsChannel = this.getChannel(AnearCreateEventChannelName)
|
|
192
187
|
|
|
@@ -232,11 +227,11 @@ class AnearMessaging {
|
|
|
232
227
|
return members.length
|
|
233
228
|
}
|
|
234
229
|
|
|
235
|
-
|
|
230
|
+
getPresentParticipants(anearEvent) {
|
|
236
231
|
// returns the participant presence data for each member who is present on
|
|
237
232
|
// the event's actions channel
|
|
238
233
|
const channel = this.eventChannels[anearEvent.id].actions
|
|
239
|
-
return
|
|
234
|
+
return channel.presence.get() // returns Promise
|
|
240
235
|
}
|
|
241
236
|
|
|
242
237
|
async setupActionsChannel(anearEvent) {
|
|
@@ -333,29 +328,22 @@ class AnearMessaging {
|
|
|
333
328
|
// check if the participant is already in storage, and if so, instantiate, else
|
|
334
329
|
// instantiate from API response
|
|
335
330
|
//
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
const participant = new this.AnearParticipantClass(participantJson, anearEvent)
|
|
331
|
+
const participantJson = await this.api.getEventParticipantJson(participantId)
|
|
332
|
+
const participant = new this.AnearParticipantClass(participantJson, anearEvent)
|
|
339
333
|
|
|
340
|
-
|
|
334
|
+
const persistedAnearParticipant = await this.AnearParticipantClass.getFromStorage(participantId, anearEvent)
|
|
341
335
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
336
|
+
if (persistedAnearParticipant) {
|
|
337
|
+
participant.context = persistedAnearParticipant.context
|
|
338
|
+
}
|
|
345
339
|
|
|
346
|
-
|
|
347
|
-
|
|
340
|
+
await anearEvent.runExclusive(`participantEnterCallback ${participant.id}`, async () => {
|
|
341
|
+
participant.geoLocation = geoLocation
|
|
348
342
|
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
} catch(error) {
|
|
354
|
-
// participant not found or is not currently marked active at the API service
|
|
355
|
-
// don't allow participation. FIX: we need to publish to the private channel
|
|
356
|
-
// with an error message type.
|
|
357
|
-
logger.error(`processParticipanEnter(${anearEvent.id}, ${participantId}) error: `, error)
|
|
358
|
-
}
|
|
343
|
+
await this.setupPrivatePublishingChannel(participant)
|
|
344
|
+
await anearEvent.participantEnter(participant)
|
|
345
|
+
await anearEvent.update()
|
|
346
|
+
})
|
|
359
347
|
}
|
|
360
348
|
|
|
361
349
|
async spectatorEnterMessagingCallback(anearEvent, message) {
|
|
@@ -458,11 +446,7 @@ class AnearMessaging {
|
|
|
458
446
|
}
|
|
459
447
|
|
|
460
448
|
async attachChannel(channel) {
|
|
461
|
-
|
|
462
|
-
return await channel.attach()
|
|
463
|
-
} catch(err) {
|
|
464
|
-
this.logErrorInfo(err)
|
|
465
|
-
}
|
|
449
|
+
return await channel.attach()
|
|
466
450
|
}
|
|
467
451
|
|
|
468
452
|
subscribeEventMessages(channel, messageType, callback) {
|
|
@@ -475,22 +459,18 @@ class AnearMessaging {
|
|
|
475
459
|
}
|
|
476
460
|
|
|
477
461
|
async subscribePresenceEventWithHistory(channel, action, callback) {
|
|
478
|
-
logger.
|
|
462
|
+
logger.debug(`subscribePresenceEvents(${action}) for channel ${channel.name}`)
|
|
479
463
|
|
|
480
464
|
this.subscribePresenceEvent(channel, action, callback)
|
|
481
465
|
|
|
482
|
-
|
|
483
|
-
const history = await channel.presence.history({limit: 25})
|
|
466
|
+
const history = await channel.presence.history({limit: 25})
|
|
484
467
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
} catch(err) {
|
|
492
|
-
logger.error('Unable to get presence history; err = ' + err.message)
|
|
493
|
-
}
|
|
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
|
+
)
|
|
494
474
|
}
|
|
495
475
|
|
|
496
476
|
async publishEventSpectatorsMessage(anearEvent, css, message, messageType = PublicDisplayMessageType) {
|
|
@@ -596,14 +576,10 @@ class AnearMessaging {
|
|
|
596
576
|
}
|
|
597
577
|
|
|
598
578
|
async publishChannelMessage(channel, messageType, payload) {
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
)
|
|
604
|
-
} catch(err) {
|
|
605
|
-
this.logErrorInfo(err)
|
|
606
|
-
}
|
|
579
|
+
await channel.publish(
|
|
580
|
+
messageType,
|
|
581
|
+
payload
|
|
582
|
+
)
|
|
607
583
|
}
|
|
608
584
|
|
|
609
585
|
async detachAll(eventId) {
|
|
@@ -625,17 +601,13 @@ class AnearMessaging {
|
|
|
625
601
|
async detachChannel (channel) {
|
|
626
602
|
if (!channel || channel.state !== 'attached') return
|
|
627
603
|
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
logger.info(`channel ${channel.name} detached`)
|
|
631
|
-
} catch(err) {
|
|
632
|
-
this.logErrorInfo(err)
|
|
633
|
-
}
|
|
604
|
+
await channel.detach(this.logErrorInfo)
|
|
605
|
+
logger.debug(`channel ${channel.name} detached`)
|
|
634
606
|
}
|
|
635
607
|
|
|
636
608
|
logErrorInfo(errInfo) {
|
|
637
609
|
if (errInfo) {
|
|
638
|
-
logger.error(
|
|
610
|
+
logger.error("Ably ERROR:", errInfo)
|
|
639
611
|
}
|
|
640
612
|
}
|
|
641
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() {
|
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
|