anear-js-api 0.2.1 → 0.3.1
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 +8 -1
- package/lib/api/ApiService.js +1 -1
- package/lib/index.js +2 -3
- package/lib/messaging/AnearMessaging.js +144 -105
- package/lib/models/AnearEvent.js +125 -69
- package/lib/models/JsonApiResource.js +5 -38
- package/lib/utils/AnearXstate.js +71 -0
- package/lib/utils/AnearXstateDefaults.js +108 -0
- package/lib/utils/Logger.js +11 -7
- package/lib/utils/Participants.js +10 -2
- package/lib/utils/Persist.js +1 -31
- package/package.json +8 -7
- package/tests/AnearEvent.test.js +250 -129
- package/tests/AnearParticipant.test.js +5 -5
- package/tests/Participants.test.js +18 -5
- package/tests/Persist.test.js +0 -42
package/lib/models/AnearEvent.js
CHANGED
|
@@ -1,33 +1,58 @@
|
|
|
1
1
|
"use strict"
|
|
2
|
-
|
|
2
|
+
|
|
3
|
+
const AnearXstate = require('../utils/AnearXstate')
|
|
4
|
+
const { DefaultConfigFunc, DefaultOptionsFunc } = require('../utils/AnearXstateDefaults')
|
|
5
|
+
|
|
3
6
|
const JsonApiResource = require('./JsonApiResource')
|
|
4
|
-
const logger = require('../utils/Logger')
|
|
5
7
|
const Participants = require('../utils/Participants')
|
|
8
|
+
const logger = require('../utils/Logger')
|
|
6
9
|
|
|
7
|
-
const Refresh = 'refresh' // refresh participant event
|
|
8
|
-
const Spectator = 'spectators' // refresh the spectators display. new spectator viewing
|
|
9
10
|
const PrivateDisplayMessageType = 'private_display'
|
|
10
11
|
|
|
11
12
|
class AnearEvent extends JsonApiResource {
|
|
12
13
|
|
|
13
|
-
constructor(json, messaging)
|
|
14
|
+
constructor(json, messaging) {
|
|
14
15
|
super(json)
|
|
15
16
|
|
|
16
|
-
this.emitter = new EventEmitter()
|
|
17
17
|
this.messaging = messaging
|
|
18
18
|
this.zone = this.findIncluded(this.relationships.zone)
|
|
19
19
|
this.app = this.findIncluded(this.zone.relationships.app)
|
|
20
|
-
|
|
21
20
|
this.participants = new Participants(json.participants)
|
|
21
|
+
this.anearStateMachine = this.initStateMachine(json.previousState)
|
|
22
22
|
}
|
|
23
23
|
|
|
24
24
|
toJSON() {
|
|
25
25
|
return {
|
|
26
26
|
...super.toJSON(),
|
|
27
|
+
context: this.stateMachineContext,
|
|
27
28
|
participants: this.participants.toJSON(),
|
|
29
|
+
previousState: this.anearStateMachine.currentState
|
|
28
30
|
}
|
|
29
31
|
}
|
|
30
32
|
|
|
33
|
+
startStateMachine() {
|
|
34
|
+
this.anearStateMachine.startService()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
stateMachineConfig() {
|
|
38
|
+
// override in subclass with custom Xstate config
|
|
39
|
+
return DefaultConfigFunc(this)
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
stateMachineOptions() {
|
|
43
|
+
// override in subclass with custom Xstate options
|
|
44
|
+
return DefaultOptionsFunc(this)
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
initStateMachine(previousState) {
|
|
48
|
+
return new AnearXstate(
|
|
49
|
+
this.stateMachineConfig(),
|
|
50
|
+
this.stateMachineOptions(),
|
|
51
|
+
previousState,
|
|
52
|
+
this.context
|
|
53
|
+
)
|
|
54
|
+
}
|
|
55
|
+
|
|
31
56
|
get userId() {
|
|
32
57
|
return this.relationships.user.data.id
|
|
33
58
|
}
|
|
@@ -36,12 +61,28 @@ class AnearEvent extends JsonApiResource {
|
|
|
36
61
|
return this.relationships.zone.data.id
|
|
37
62
|
}
|
|
38
63
|
|
|
39
|
-
|
|
64
|
+
async getClonedEvent() {
|
|
65
|
+
// if the current event was a clone of previous event, try to fetch if from
|
|
66
|
+
// Peristence and return
|
|
40
67
|
const clonedEventData = this.relationships["cloned-event"].data
|
|
41
|
-
|
|
68
|
+
if (!clonedEventData) return null
|
|
69
|
+
|
|
70
|
+
const clonedEvent = await this.constructor.getFromStorage(clonedEventData.id)
|
|
71
|
+
return clonedEvent
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
async clonedEventContext() {
|
|
75
|
+
const clonedEvent = await this.getClonedEvent()
|
|
76
|
+
|
|
77
|
+
return clonedEvent?.context
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
get stateMachineContext() {
|
|
81
|
+
// active XState context
|
|
82
|
+
return this.anearStateMachine.context
|
|
42
83
|
}
|
|
43
84
|
|
|
44
|
-
get
|
|
85
|
+
get eventState() {
|
|
45
86
|
return this.attributes.state
|
|
46
87
|
}
|
|
47
88
|
|
|
@@ -50,17 +91,20 @@ class AnearEvent extends JsonApiResource {
|
|
|
50
91
|
}
|
|
51
92
|
|
|
52
93
|
get participantTimeout() {
|
|
94
|
+
// TODO: This probably should be set for each publishEventPrivateMessage
|
|
95
|
+
// and then referenceable as an anear-data attribute in the html. That way
|
|
96
|
+
// the App has explicit control over each interaction. Each user game prompt
|
|
97
|
+
// can have its own appropriate timeout duration based on the app/game
|
|
98
|
+
// xStateContext/context.
|
|
99
|
+
//
|
|
100
|
+
// In the React Anear Browser, there is no local setTimer that calls back and
|
|
101
|
+
// transitions the participant. That will now be driven by the app and private
|
|
102
|
+
// display messages. For example, "Hey, wake up!! Rejoin? (countdown 3...2....1)"
|
|
103
|
+
// This allows on-premise game participants having a momentary distraction to rejoin
|
|
104
|
+
// the game after a first timeout.
|
|
53
105
|
return this.app.attributes["participant-timeout"]
|
|
54
106
|
}
|
|
55
107
|
|
|
56
|
-
on(eventName, listener) {
|
|
57
|
-
this.emitter.on(eventName, listener)
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
emit(eventName, ...params) {
|
|
61
|
-
this.emitter.emit(eventName, params)
|
|
62
|
-
}
|
|
63
|
-
|
|
64
108
|
hasFlag(flagName) {
|
|
65
109
|
return this.attributes.flags.includes(flagName)
|
|
66
110
|
}
|
|
@@ -76,34 +120,31 @@ class AnearEvent extends JsonApiResource {
|
|
|
76
120
|
return !this.hasFlag("no_spectators")
|
|
77
121
|
}
|
|
78
122
|
|
|
79
|
-
|
|
80
|
-
// this Callbacks are the key overrides to define the behavior of your Anear App
|
|
81
|
-
//
|
|
82
|
-
async createdEventCallback(anearParticipantCreator) {
|
|
123
|
+
async createdEventCallback(participantCreator) {
|
|
83
124
|
// You may implement createdEventCallback() in your AnearEvent sub-class
|
|
84
125
|
}
|
|
85
126
|
|
|
86
|
-
async participantEnterEventCallback(
|
|
87
|
-
throw new Error('You must implement participantEnterEventCallback() in your AnearEvent sub-class');
|
|
127
|
+
async participantEnterEventCallback(participant) {
|
|
128
|
+
throw new Error('You must implement an async participantEnterEventCallback() in your AnearEvent sub-class');
|
|
88
129
|
}
|
|
89
130
|
|
|
90
|
-
async participantRefreshEventCallback(
|
|
91
|
-
// You may implement participantRefreshEventCallback() in your AnearEvent sub-class
|
|
131
|
+
async participantRefreshEventCallback(participant) {
|
|
132
|
+
// You may implement an async participantRefreshEventCallback() in your AnearEvent sub-class
|
|
92
133
|
}
|
|
93
134
|
|
|
94
135
|
async spectatorRefreshEventCallback() {
|
|
95
|
-
// You may implement spectatorRefreshEventCallback() in your AnearEvent sub-class
|
|
136
|
+
// You may implement an async spectatorRefreshEventCallback() in your AnearEvent sub-class
|
|
96
137
|
}
|
|
97
138
|
|
|
98
|
-
async participantCloseEventCallback(
|
|
99
|
-
throw new Error('You must implement participantCloseEventCallback() in your AnearEvent sub-class');
|
|
139
|
+
async participantCloseEventCallback(participant) {
|
|
140
|
+
throw new Error('You must implement an async participantCloseEventCallback() in your AnearEvent sub-class');
|
|
100
141
|
}
|
|
101
142
|
|
|
102
|
-
async participantActionEventCallback(
|
|
103
|
-
throw new Error('You must implement participantActionEventCallback() in your AnearEvent sub-class');
|
|
143
|
+
async participantActionEventCallback(participant, actionEventName, message) {
|
|
144
|
+
throw new Error('You must implement an async participantActionEventCallback() in your AnearEvent sub-class');
|
|
104
145
|
}
|
|
105
146
|
|
|
106
|
-
async participantTimedOutEventCallback(
|
|
147
|
+
async participantTimedOutEventCallback(participant) {
|
|
107
148
|
// You may implement participantTimedOutEventCallback() in your AnearEvent sub-class'
|
|
108
149
|
}
|
|
109
150
|
|
|
@@ -111,11 +152,11 @@ class AnearEvent extends JsonApiResource {
|
|
|
111
152
|
// You may implement eventBroadcastEventCallback() in your AnearEvent sub-class'
|
|
112
153
|
}
|
|
113
154
|
|
|
114
|
-
isParticipantEventCreator(
|
|
115
|
-
return
|
|
155
|
+
isParticipantEventCreator(RParticipant) {
|
|
156
|
+
return participant.userId === this.userId
|
|
116
157
|
}
|
|
117
|
-
async refreshParticipant(
|
|
118
|
-
await this.participantRefreshEventCallback(
|
|
158
|
+
async refreshParticipant(participant) {
|
|
159
|
+
await this.participantRefreshEventCallback(participant)
|
|
119
160
|
}
|
|
120
161
|
|
|
121
162
|
async refreshSpectator() {
|
|
@@ -137,10 +178,10 @@ class AnearEvent extends JsonApiResource {
|
|
|
137
178
|
await this.messaging.publishEventSpectatorsMessage(this.id, this.css, message)
|
|
138
179
|
}
|
|
139
180
|
|
|
140
|
-
async publishEventPrivateMessage(
|
|
181
|
+
async publishEventPrivateMessage(participant, message, timeoutMilliseconds=0, timeoutCallback=null) {
|
|
141
182
|
await this.messaging.publishEventPrivateMessage(
|
|
142
183
|
this.id,
|
|
143
|
-
|
|
184
|
+
participant,
|
|
144
185
|
PrivateDisplayMessageType,
|
|
145
186
|
this.css,
|
|
146
187
|
message,
|
|
@@ -156,41 +197,42 @@ class AnearEvent extends JsonApiResource {
|
|
|
156
197
|
)
|
|
157
198
|
}
|
|
158
199
|
|
|
159
|
-
async participantEnter(
|
|
160
|
-
// Called each time a participant ENTERs
|
|
161
|
-
// an event for the first time, rejoining, or after a browser
|
|
162
|
-
// If the
|
|
163
|
-
// Invoke Enter/Refresh callbacks
|
|
200
|
+
async participantEnter(participant) {
|
|
201
|
+
// Called each time a participant ENTERs (attaches to) the Event's Action Channel.
|
|
202
|
+
// This could be when joining an event for the first time, rejoining, or after a browser
|
|
203
|
+
// refresh/reconnect. If the participant exists in this.participants, we call Refresh,
|
|
204
|
+
// else Enter Invoke Enter/Refresh callbacks
|
|
205
|
+
if (this.participants.exists(participant)) {
|
|
206
|
+
logger.info(`AnearEvent: participant ${participant.id} exists. Refreshing...`)
|
|
207
|
+
|
|
208
|
+
this.participants.add(this, participant) // update the participants entry
|
|
164
209
|
|
|
165
|
-
|
|
166
|
-
logger.info(`participant ${anearParticipant.id} exists. Refreshing...`)
|
|
210
|
+
this.anearStateMachine.sendRefreshEvent({participant: participant})
|
|
167
211
|
|
|
168
|
-
|
|
169
|
-
await this.participantRefreshEventCallback(anearParticipant)
|
|
170
|
-
await anearParticipant.update()
|
|
212
|
+
await participant.update()
|
|
171
213
|
} else {
|
|
172
|
-
this.participants.add(this,
|
|
173
|
-
|
|
174
|
-
|
|
214
|
+
this.participants.add(this, participant) // add the participants entry
|
|
215
|
+
|
|
216
|
+
this.anearStateMachine.sendJoinEvent({participant: participant})
|
|
217
|
+
|
|
218
|
+
await participant.persist()
|
|
175
219
|
}
|
|
176
220
|
}
|
|
177
221
|
|
|
178
|
-
async participantClose(
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
await this.participantCloseEventCallback(anearParticipant)
|
|
185
|
-
await anearParticipant.remove()
|
|
222
|
+
async participantClose(participant) {
|
|
223
|
+
this.participants.purge(participant)
|
|
224
|
+
|
|
225
|
+
this.anearStateMachine.sendCloseEvent({ participant })
|
|
226
|
+
|
|
227
|
+
await participant.remove()
|
|
186
228
|
}
|
|
187
229
|
|
|
188
|
-
|
|
189
|
-
|
|
230
|
+
participantAction(participant, actionEventName, actionPayload) {
|
|
231
|
+
this.anearStateMachine.sendActionEvent(actionEventName, {participant, payload: actionPayload})
|
|
190
232
|
}
|
|
191
233
|
|
|
192
|
-
|
|
193
|
-
|
|
234
|
+
participantTimedOut(participant) {
|
|
235
|
+
this.anearStateMachine.sendTimeoutEvent({ participant })
|
|
194
236
|
}
|
|
195
237
|
|
|
196
238
|
async eventBroadcast(message) {
|
|
@@ -198,28 +240,32 @@ class AnearEvent extends JsonApiResource {
|
|
|
198
240
|
}
|
|
199
241
|
|
|
200
242
|
async transitionEvent(eventName='next') {
|
|
243
|
+
//
|
|
244
|
+
// Allows the app/game to transition the remote AnearEvent which can un/hide and event
|
|
245
|
+
// and change its discoverability by mobile web users. Apps can also determine when
|
|
246
|
+
// and how many mobile-app users can join and event.
|
|
201
247
|
//
|
|
202
248
|
// 1. send the transition event to the Anear API
|
|
203
249
|
// 2. get back the event newState from Anear API response
|
|
204
250
|
// 3. publish the new Event State to all subscribers (e.g. participants)
|
|
205
251
|
//
|
|
206
|
-
logger.debug(`transitionEvent(${eventName})`)
|
|
252
|
+
logger.debug(`AnearEvent: transitionEvent(${eventName})`)
|
|
207
253
|
|
|
208
254
|
try {
|
|
209
255
|
const responseAttributes = await this.messaging.api.transitionEvent(this.id, eventName)
|
|
210
256
|
const newState = responseAttributes.state
|
|
211
257
|
await this.messaging.publishEventTransitionMessage(this.id, newState)
|
|
212
258
|
} catch(err) {
|
|
213
|
-
logger.error(`transitionEvent error: ${err}`)
|
|
259
|
+
logger.error(`AnearEvent: transitionEvent error: ${err}`)
|
|
214
260
|
}
|
|
215
261
|
}
|
|
216
262
|
|
|
217
263
|
isPlayable() {
|
|
218
|
-
return !['closing', 'closed', 'canceled'].includes(this.
|
|
264
|
+
return !['closing', 'closed', 'canceled'].includes(this.eventState)
|
|
219
265
|
}
|
|
220
266
|
|
|
221
267
|
async transitionToAnnounce() {
|
|
222
|
-
if (this.
|
|
268
|
+
if (this.eventState === 'created') await this.transitionEvent('announce')
|
|
223
269
|
}
|
|
224
270
|
|
|
225
271
|
async transitionNextNext() {
|
|
@@ -261,8 +307,18 @@ class AnearEvent extends JsonApiResource {
|
|
|
261
307
|
|
|
262
308
|
async closeMessaging () {
|
|
263
309
|
await this.messaging.detachAll(this.id)
|
|
264
|
-
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
logMessage(...args) {
|
|
313
|
+
logger.info(...args)
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
logError(context, event) {
|
|
317
|
+
logger.error("XState: ERROR: ", event.data)
|
|
265
318
|
}
|
|
266
319
|
}
|
|
267
320
|
|
|
268
|
-
module.exports =
|
|
321
|
+
module.exports = {
|
|
322
|
+
AnearEvent,
|
|
323
|
+
logger
|
|
324
|
+
}
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
"use strict"
|
|
2
2
|
|
|
3
|
-
const logger = require('../utils/Logger')
|
|
4
3
|
const Persist = require('../utils/Persist')
|
|
5
4
|
|
|
6
5
|
const Storage = new Persist()
|
|
@@ -10,27 +9,20 @@ class JsonApiResource {
|
|
|
10
9
|
constructor(json) {
|
|
11
10
|
this.data = json.data
|
|
12
11
|
this.included = json.included || {}
|
|
13
|
-
this.
|
|
12
|
+
this.context = json.context || this.initContext()
|
|
14
13
|
}
|
|
15
14
|
|
|
16
15
|
toJSON() {
|
|
17
16
|
return {
|
|
18
17
|
data: this.data,
|
|
19
18
|
included: this.included,
|
|
20
|
-
|
|
19
|
+
context: this.context
|
|
21
20
|
}
|
|
22
21
|
}
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
set appData(data) {
|
|
29
|
-
this._appData = data
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
get appData() {
|
|
33
|
-
return this._appData
|
|
23
|
+
initContext() {
|
|
24
|
+
// context can be used as subclass context, or it can become the XState context
|
|
25
|
+
return null // override in subclass if desired
|
|
34
26
|
}
|
|
35
27
|
|
|
36
28
|
async onLoad() {
|
|
@@ -61,19 +53,6 @@ class JsonApiResource {
|
|
|
61
53
|
})
|
|
62
54
|
}
|
|
63
55
|
|
|
64
|
-
static async getWithLockFromStorage(modelId, callback, ...args) {
|
|
65
|
-
return await this.getJsonWithLockFor(
|
|
66
|
-
modelId,
|
|
67
|
-
async json => {
|
|
68
|
-
if (!json) return null
|
|
69
|
-
const model = new this(json, ...args)
|
|
70
|
-
await model.onLoad()
|
|
71
|
-
await callback(model)
|
|
72
|
-
return model
|
|
73
|
-
}
|
|
74
|
-
)
|
|
75
|
-
}
|
|
76
|
-
|
|
77
56
|
static async getFromStorage(modelId, ...args) {
|
|
78
57
|
const json = await this.getJsonFor(modelId)
|
|
79
58
|
if (!json) return null
|
|
@@ -83,12 +62,10 @@ class JsonApiResource {
|
|
|
83
62
|
}
|
|
84
63
|
|
|
85
64
|
async persist() {
|
|
86
|
-
logger.debug(`${this.id} persisted`)
|
|
87
65
|
return await Storage.create(this)
|
|
88
66
|
}
|
|
89
67
|
|
|
90
68
|
async update() {
|
|
91
|
-
logger.debug(`${this.id} updated`)
|
|
92
69
|
return await Storage.update(this)
|
|
93
70
|
}
|
|
94
71
|
|
|
@@ -97,7 +74,6 @@ class JsonApiResource {
|
|
|
97
74
|
}
|
|
98
75
|
|
|
99
76
|
async remove() {
|
|
100
|
-
logger.debug(`${this.id} persistence removed`)
|
|
101
77
|
return await Storage.remove(this)
|
|
102
78
|
}
|
|
103
79
|
|
|
@@ -117,15 +93,6 @@ class JsonApiResource {
|
|
|
117
93
|
const key = this.persistKey(id)
|
|
118
94
|
return await Storage.fetch(key)
|
|
119
95
|
}
|
|
120
|
-
|
|
121
|
-
async lock(callback) {
|
|
122
|
-
await Storage.lockCall(this, callback)
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
static async getJsonWithLockFor(id, callback) {
|
|
126
|
-
const key = this.persistKey(id)
|
|
127
|
-
return await Storage.lockedFetch(key, callback)
|
|
128
|
-
}
|
|
129
96
|
}
|
|
130
97
|
|
|
131
98
|
module.exports = JsonApiResource
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
const { createMachine, interpret, State } = require('xstate')
|
|
3
|
+
|
|
4
|
+
const logger = require('../utils/Logger')
|
|
5
|
+
|
|
6
|
+
const JoinEvent = 'JOIN'
|
|
7
|
+
const RefreshEvent = 'REFRESH'
|
|
8
|
+
const CloseEvent = 'CLOSE'
|
|
9
|
+
const TimeoutEvent = 'TIMEOUT'
|
|
10
|
+
|
|
11
|
+
class AnearXstate {
|
|
12
|
+
constructor(machineConfig, machineOptions, previousState, anearEventContext) {
|
|
13
|
+
this.machine = createMachine(machineConfig, machineOptions).withContext(anearEventContext)
|
|
14
|
+
|
|
15
|
+
this._currentState = previousState ? State.create(previousState) : this.machine.initialState
|
|
16
|
+
this._currentContext = anearEventContext
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
startService() {
|
|
20
|
+
logger.debug("XState: spawning interpreter. initial state: ", this.currentState.value)
|
|
21
|
+
|
|
22
|
+
this.service = interpret(this.machine).
|
|
23
|
+
onTransition(newState => {
|
|
24
|
+
logger.debug("XState: NEW state: ", newState.value)
|
|
25
|
+
|
|
26
|
+
this._currentState = newState
|
|
27
|
+
this._currentContext = newState.context
|
|
28
|
+
}).
|
|
29
|
+
start(this.currentState)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
get context() {
|
|
33
|
+
// to reference appContext when using it as the xState context, use this method
|
|
34
|
+
return this._currentContext
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
get currentState() {
|
|
38
|
+
return this._currentState
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
sendJoinEvent(params) {
|
|
42
|
+
this.send(JoinEvent, params)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
sendRefreshEvent(params) {
|
|
46
|
+
this.send(RefreshEvent, params)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
sendCloseEvent(params) {
|
|
50
|
+
this.send(CloseEvent, params)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
sendTimeoutEvent(params) {
|
|
54
|
+
this.send(TimeoutEvent, params)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
sendActionEvent(eventName, params) {
|
|
58
|
+
this.send(eventName, params)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
send(eventName, params) {
|
|
62
|
+
const eventToSend = {
|
|
63
|
+
type: eventName,
|
|
64
|
+
...params
|
|
65
|
+
}
|
|
66
|
+
logger.debug(`XState: SENDING event ${eventName}`)
|
|
67
|
+
this.service.send(eventToSend)
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
module.exports = AnearXstate
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
// Default configuration and options funcs provide
|
|
4
|
+
// a simple default state machine for an anear Event. This is
|
|
5
|
+
// so we don't mandate that app developers use xState to drive
|
|
6
|
+
// their app's state transitions. Devs simply provide callbacks
|
|
7
|
+
// override implementations in their AnearEvent subclass.
|
|
8
|
+
// The xState context is simply the anearEvent.context
|
|
9
|
+
//
|
|
10
|
+
// If a developer wants an xState machine to drive the applications
|
|
11
|
+
// state transitions, the developer should override stateMachineConfig()
|
|
12
|
+
// and stateMachineOptions() in their AnearEvent subclass.
|
|
13
|
+
//
|
|
14
|
+
const DefaultConfigFunc = (anearEvent) => {
|
|
15
|
+
|
|
16
|
+
const PromiseResolveReject = {
|
|
17
|
+
onDone: {
|
|
18
|
+
target: 'eventActive'
|
|
19
|
+
},
|
|
20
|
+
onError: {
|
|
21
|
+
actions: 'logError',
|
|
22
|
+
target: 'eventActive'
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return {
|
|
27
|
+
id: "defaultXstateConfig",
|
|
28
|
+
initial: 'eventActive',
|
|
29
|
+
states: {
|
|
30
|
+
eventActive: {
|
|
31
|
+
on: {
|
|
32
|
+
JOIN: {
|
|
33
|
+
target: 'join'
|
|
34
|
+
},
|
|
35
|
+
REFRESH: {
|
|
36
|
+
target: 'refresh'
|
|
37
|
+
},
|
|
38
|
+
CLOSE: {
|
|
39
|
+
target: 'close'
|
|
40
|
+
},
|
|
41
|
+
TIMEOUT: {
|
|
42
|
+
target: 'timeout'
|
|
43
|
+
},
|
|
44
|
+
'*': {
|
|
45
|
+
// default wildcard state is presumed to be a custom ACTION name
|
|
46
|
+
// embedded in a anear-action-click property in the app's HTML
|
|
47
|
+
target: 'action'
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
join: {
|
|
52
|
+
invoke: {
|
|
53
|
+
id: 'join',
|
|
54
|
+
src: 'joinEventHandler',
|
|
55
|
+
...PromiseResolveReject
|
|
56
|
+
}
|
|
57
|
+
},
|
|
58
|
+
refresh: {
|
|
59
|
+
invoke: {
|
|
60
|
+
id: 'refresh',
|
|
61
|
+
src: 'refreshEventHandler',
|
|
62
|
+
...PromiseResolveReject
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
close: {
|
|
66
|
+
invoke: {
|
|
67
|
+
id: 'close',
|
|
68
|
+
src: 'closeEventHandler',
|
|
69
|
+
...PromiseResolveReject
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
timeout: {
|
|
73
|
+
invoke: {
|
|
74
|
+
id: 'timeout',
|
|
75
|
+
src: 'timeoutEventHandler',
|
|
76
|
+
...PromiseResolveReject
|
|
77
|
+
}
|
|
78
|
+
},
|
|
79
|
+
action: {
|
|
80
|
+
invoke: {
|
|
81
|
+
id: 'action',
|
|
82
|
+
src: 'actionEventHandler',
|
|
83
|
+
...PromiseResolveReject
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const DefaultOptionsFunc = anearEvent => {
|
|
91
|
+
return {
|
|
92
|
+
actions: {
|
|
93
|
+
logError: (context, event) => logger.error(`error message: ${event.data}`)
|
|
94
|
+
},
|
|
95
|
+
services: {
|
|
96
|
+
joinEventHandler: (context, event) => anearEvent.participantEnterEventCallback(event.participant),
|
|
97
|
+
refreshEventHandler: (context, event) => anearEvent.participantRefreshEventCallback(event.participant),
|
|
98
|
+
closeEventHandler: (context, event) => anearEvent.participantCloseEventCallback(event.participant),
|
|
99
|
+
timeoutEventHandler: (context, event) => anearEvent.participantTimedOutEventCallback(event.participant),
|
|
100
|
+
actionEventHandler: (context, event) => anearEvent.participantActionEventCallback(event.participant, event.type, event.payload)
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
DefaultConfigFunc,
|
|
107
|
+
DefaultOptionsFunc
|
|
108
|
+
}
|
package/lib/utils/Logger.js
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
|
-
const
|
|
1
|
+
const SimpleNodeLogger = require('simple-node-logger')
|
|
2
2
|
const defaultLevel = 'info'
|
|
3
|
+
const defaultTimestamp = 'YYYY-MM-DD HH:mm:ss.SSS'
|
|
4
|
+
const defaultLogFilePath = "logfile.out"
|
|
5
|
+
|
|
3
6
|
const logger = () => {
|
|
4
|
-
const
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
return date.toJSON() + ": " + message
|
|
7
|
+
const opts = {
|
|
8
|
+
logFilePath: process.env.ANEARAPP_LOGGER_FILE || defaultLogFilePath,
|
|
9
|
+
timestampFormat: defaultTimestamp
|
|
8
10
|
}
|
|
9
|
-
|
|
11
|
+
|
|
12
|
+
const lgr = SimpleNodeLogger.createSimpleFileLogger(opts)
|
|
13
|
+
lgr.setLevel(process.env.ANEARAPP_LOGGER_LEVEL||defaultLevel)
|
|
14
|
+
return lgr
|
|
10
15
|
}
|
|
11
16
|
|
|
12
17
|
module.exports = logger()
|
|
13
|
-
|
|
@@ -37,6 +37,11 @@ class Participants {
|
|
|
37
37
|
}
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
indexedById() {
|
|
41
|
+
// returns an object that has AnearParticipant.id as key
|
|
42
|
+
return this._participants
|
|
43
|
+
}
|
|
44
|
+
|
|
40
45
|
getById(anearParticipantId) {
|
|
41
46
|
return this._participants[anearParticipantId]
|
|
42
47
|
}
|
|
@@ -61,13 +66,13 @@ class Participants {
|
|
|
61
66
|
active(cached = true) {
|
|
62
67
|
if (!cached) this._actives = null
|
|
63
68
|
this._actives = this._actives || Object.values(this._participants).filter(c => c.state === ActiveState)
|
|
64
|
-
return this._actives
|
|
69
|
+
return this._actives
|
|
65
70
|
}
|
|
66
71
|
|
|
67
72
|
idle(cached = true) {
|
|
68
73
|
if (!cached) this._idles = null
|
|
69
74
|
this._idles = this._idles || Object.values(this._participants).filter(c => c.state === IdleState)
|
|
70
|
-
return this._idles
|
|
75
|
+
return this._idles
|
|
71
76
|
}
|
|
72
77
|
|
|
73
78
|
get count() {
|
|
@@ -83,14 +88,17 @@ class Participants {
|
|
|
83
88
|
}
|
|
84
89
|
|
|
85
90
|
isIdle(c, currentTimestamp) {
|
|
91
|
+
if (!this.idleMsecs) return false
|
|
86
92
|
return (currentTimestamp - c.timestamp) >= this.idleMsecs
|
|
87
93
|
}
|
|
88
94
|
|
|
89
95
|
isActive(c, currentTimestamp) {
|
|
96
|
+
if (!this.idleMsecs) return true
|
|
90
97
|
return (currentTimestamp - c.timestamp) < this.idleMsecs
|
|
91
98
|
}
|
|
92
99
|
|
|
93
100
|
isPurge(c, currentTimestamp) {
|
|
101
|
+
if (!this.purgeMsecs) return false
|
|
94
102
|
return (currentTimestamp - c.timestamp) >= this.purgeMsecs
|
|
95
103
|
}
|
|
96
104
|
|