anear-js-api 0.4.7 → 0.4.9

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.
@@ -137,7 +137,6 @@ class AnearMessaging {
137
137
 
138
138
  if (!eventExists) {
139
139
  const messagingInitializedCallback = async () => {
140
- await anearEvent.createdEventCallback()
141
140
  await anearEvent.persist()
142
141
  // start the state machine before initializing Realtime Messaging
143
142
  // as REFRESH events come in and the state machine should be ready
@@ -214,7 +213,7 @@ class AnearMessaging {
214
213
  this.subscribePresenceEvent(
215
214
  spectatorsChannel,
216
215
  PRESENCE_ENTER,
217
- async message => await this.spectatorEnterMessagingCallback(anearEvent, message)
216
+ async message => await this.spectatorViewMessagingCallback(anearEvent, message)
218
217
  )
219
218
 
220
219
  this.subscribePresenceEvent(
@@ -351,12 +350,11 @@ class AnearMessaging {
351
350
  })
352
351
  }
353
352
 
354
- async spectatorEnterMessagingCallback(anearEvent, message) {
353
+ spectatorViewMessagingCallback(anearEvent, message) {
355
354
  const userId = message.clientId
355
+ logger.debug(`**** ENTER SPECTATOR FOR VIEWING **** event: ${anearEvent.id}, user: ${userId}`)
356
356
 
357
- logger.debug(`**** ENTER SPECTATOR **** event: ${anearEvent.id}, user: ${userId}`)
358
-
359
- await anearEvent.refreshSpectator()
357
+ anearEvent.spectatorView(userId)
360
358
  }
361
359
 
362
360
  async spectatorLeaveMessagingCallback(anearEvent, message) {
@@ -450,8 +448,8 @@ class AnearMessaging {
450
448
  logger.debug(`setupPrivatePublishingChannel(${participant.privateChannelName}) state ${privateChannel.state}`)
451
449
  }
452
450
 
453
- async attachChannel(channel) {
454
- return await channel.attach()
451
+ attachChannel(channel) {
452
+ return channel.attach()
455
453
  }
456
454
 
457
455
  subscribeEventMessages(channel, messageType, callback) {
@@ -3,7 +3,6 @@
3
3
  const { Mutex } = require('async-mutex')
4
4
 
5
5
  const AnearXstate = require('../utils/AnearXstate')
6
- const { DefaultConfigFunc, DefaultOptionsFunc } = require('../utils/AnearXstateDefaults')
7
6
 
8
7
  const JsonApiResource = require('./JsonApiResource')
9
8
  const Participants = require('../utils/Participants')
@@ -40,12 +39,12 @@ class AnearEvent extends JsonApiResource {
40
39
 
41
40
  stateMachineConfig() {
42
41
  // override in subclass with custom Xstate config
43
- return DefaultConfigFunc(this)
42
+ return {}
44
43
  }
45
44
 
46
45
  stateMachineOptions() {
47
46
  // override in subclass with custom Xstate options
48
- return DefaultOptionsFunc(this)
47
+ return {}
49
48
  }
50
49
 
51
50
  initStateMachine(previousState) {
@@ -65,14 +64,13 @@ class AnearEvent extends JsonApiResource {
65
64
  return this.relationships.zone.data.id
66
65
  }
67
66
 
68
- async getClonedEvent() {
67
+ getClonedEvent() {
69
68
  // if the current event was a clone of previous event, fetch if from
70
69
  // Peristence and return
71
70
  const clonedEventData = this.relationships["cloned-event"].data
72
71
  if (!clonedEventData) return null
73
72
 
74
- const clonedEvent = await this.constructor.getFromStorage(clonedEventData.id)
75
- return clonedEvent
73
+ return this.constructor.getFromStorage(clonedEventData.id)
76
74
  }
77
75
 
78
76
  async clonedEventContext() {
@@ -124,55 +122,16 @@ class AnearEvent extends JsonApiResource {
124
122
  return !this.hasFlag("no_spectators")
125
123
  }
126
124
 
127
- async spectatorCount() {
128
- return await this.messaging.getSpectatorCount(this)
129
- }
130
-
131
- async createdEventCallback(participantCreator) {
132
- // You may implement createdEventCallback() in your AnearEvent sub-class
133
- }
134
-
135
- async participantEnterEventCallback(participant) {
136
- throw new Error('You must implement an async participantEnterEventCallback() in your AnearEvent sub-class');
137
- }
138
-
139
- async participantRefreshEventCallback(participant, remainingTimeout = null) {
140
- // You may implement an async participantRefreshEventCallback() in your AnearEvent sub-class
141
- }
142
-
143
- async spectatorRefreshEventCallback() {
144
- // You may implement an async spectatorRefreshEventCallback() in your AnearEvent sub-class
145
- }
146
-
147
- async participantExitEventCallback(participant) {
148
- throw new Error('You must implement an async participantExitEventCallback() in your AnearEvent sub-class');
149
- }
150
-
151
- async participantActionEventCallback(participant, actionEventName, message) {
152
- throw new Error('You must implement an async participantActionEventCallback() in your AnearEvent sub-class');
153
- }
154
-
155
- async participantTimedOutEventCallback(participant) {
156
- // You may implement participantTimedOutEventCallback() in your AnearEvent sub-class'
157
- }
158
-
159
- async eventBroadcastEventCallback(message) {
160
- // You may implement eventBroadcastEventCallback() in your AnearEvent sub-class'
125
+ spectatorCount() {
126
+ return this.messaging.getSpectatorCount(this)
161
127
  }
162
128
 
163
129
  isParticipantEventCreator(RParticipant) {
164
130
  return participant.userId === this.userId
165
131
  }
166
- async refreshParticipant(participant) {
167
- await this.participantRefreshEventCallback(participant)
168
- }
169
-
170
- async refreshSpectator() {
171
- await this.spectatorRefreshEventCallback()
172
- }
173
132
 
174
- async publishEventParticipantsMessage(message, timeoutMsecs=0) {
175
- await this.messaging.publishEventParticipantsMessage(
133
+ publishEventParticipantsMessage(message, timeoutMsecs=0) {
134
+ return this.messaging.publishEventParticipantsMessage(
176
135
  this,
177
136
  this.participants.active(),
178
137
  this.css,
@@ -193,12 +152,12 @@ class AnearEvent extends JsonApiResource {
193
152
  logger.debug(`mutex ${name} released!`)
194
153
  }
195
154
 
196
- async publishEventSpectatorsMessage(message) {
197
- await this.messaging.publishEventSpectatorsMessage(this, this.css, message)
155
+ publishEventSpectatorsMessage(message) {
156
+ return this.messaging.publishEventSpectatorsMessage(this, this.css, message)
198
157
  }
199
158
 
200
- async publishEventPrivateMessage(participant, message, timeoutMsecs=0) {
201
- await this.messaging.publishEventPrivateMessage(
159
+ publishEventPrivateMessage(participant, message, timeoutMsecs=0) {
160
+ return this.messaging.publishEventPrivateMessage(
202
161
  this,
203
162
  participant,
204
163
  PrivateDisplayMessageType,
@@ -208,8 +167,8 @@ class AnearEvent extends JsonApiResource {
208
167
  )
209
168
  }
210
169
 
211
- async publishEventTransitionMessage(newState) {
212
- await this.messaging.publishEventTransitionMessage(
170
+ publishEventTransitionMessage(newState) {
171
+ return this.messaging.publishEventTransitionMessage(
213
172
  this,
214
173
  newState
215
174
  )
@@ -247,16 +206,20 @@ class AnearEvent extends JsonApiResource {
247
206
  }
248
207
  }
249
208
 
250
- async participantExit(participant) {
209
+ participantExit(participant) {
251
210
  // this informs the state machine that the participant has exited the event
252
211
  // and removes that participant completely
253
212
  this.anearStateMachine.sendParticipantExitEvent({ participant })
254
- await this.participantPurge(participant)
213
+ return this.participantPurge(participant)
214
+ }
215
+
216
+ spectatorView(userId) {
217
+ this.anearStateMachine.sendSpectatorViewEvent({userId})
255
218
  }
256
219
 
257
- async participantPurge(participant) {
220
+ participantPurge(participant) {
258
221
  this.participants.purge(participant)
259
- await participant.remove()
222
+ return participant.remove()
260
223
  }
261
224
 
262
225
  participantAction(participant, actionEventName, actionPayload) {
@@ -267,10 +230,6 @@ class AnearEvent extends JsonApiResource {
267
230
  this.anearStateMachine.sendTimeoutEvent({ participant })
268
231
  }
269
232
 
270
- async eventBroadcast(message) {
271
- await this.eventBroadcastEventCallback(message)
272
- }
273
-
274
233
  async transitionEvent(eventName='next') {
275
234
  //
276
235
  // Allows the app/game to transition the remote AnearEvent which can un/hide and event
@@ -302,24 +261,24 @@ class AnearEvent extends JsonApiResource {
302
261
  await this.transitionEvent('next')
303
262
  }
304
263
 
305
- async transitionLive() {
306
- await this.transitionNextNext()
264
+ transitionLive() {
265
+ return this.transitionNextNext()
307
266
  }
308
267
 
309
- async transitionClosed() {
310
- await this.transitionNextNext()
268
+ transitionClosed() {
269
+ return this.transitionNextNext()
311
270
  }
312
271
 
313
- async transitionCanceled() {
314
- await this.transitionEvent('cancel')
272
+ transitionCanceled() {
273
+ return this.transitionEvent('cancel')
315
274
  }
316
275
 
317
- async closeOutParticipants() {
276
+ closeOutParticipants() {
318
277
  // upon exiting the event, this will clean up any participants remaining
319
278
  return Promise.all(
320
279
  this.participants.all.map(
321
- async p => {
322
- await this.messaging.closeParticipant(
280
+ p => {
281
+ return this.messaging.closeParticipant(
323
282
  this,
324
283
  p.id,
325
284
  async (anearEvent, participant) => {
@@ -331,12 +290,12 @@ class AnearEvent extends JsonApiResource {
331
290
  )
332
291
  }
333
292
 
334
- async purgeParticipants() {
335
- // remove participants from Participants class and from Storage
293
+ purgeParticipants() {
294
+ // remove participants and host from Participants class and from Storage
336
295
  const all = this.participants.all
337
296
  if (this.participants.host) all.push(this.participants.host)
338
297
 
339
- await Promise.all(
298
+ return Promise.all(
340
299
  all.map(p => this.participantPurge(p))
341
300
  )
342
301
  }
@@ -366,8 +325,8 @@ class AnearEvent extends JsonApiResource {
366
325
  await this.closeMessaging()
367
326
  }
368
327
 
369
- async closeMessaging () {
370
- await this.messaging.detachAll(this.id)
328
+ closeMessaging () {
329
+ return this.messaging.detachAll(this.id)
371
330
  }
372
331
 
373
332
  logDebugMessage(...args) {
@@ -3,7 +3,8 @@ const { createMachine, interpret, State } = require('xstate')
3
3
 
4
4
  const logger = require('../utils/Logger')
5
5
 
6
- const JoinEvent = 'JOIN'
6
+ const JoinEvent = 'PARTICIPANT_JOIN'
7
+ const SpectatorViewEvent = 'SPECTATOR_VIEW'
7
8
  const ParticipantExitEvent = 'PARTICIPANT_EXIT'
8
9
  const RefreshEvent = 'REFRESH'
9
10
  const CloseEvent = 'EVENT_CLOSE'
@@ -60,6 +61,10 @@ class AnearXstate {
60
61
  this.send(ParticipantExitEvent, params)
61
62
  }
62
63
 
64
+ sendSpectatorViewEvent(params) {
65
+ this.send(SpectatorViewEvent, params)
66
+ }
67
+
63
68
  sendTimeoutEvent(params) {
64
69
  this.send(TimeoutEvent, params)
65
70
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anear-js-api",
3
- "version": "0.4.7",
3
+ "version": "0.4.9",
4
4
  "description": "Javascript Developer API for Anear Apps",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -5,6 +5,7 @@ const AnearParticipant = require('../lib/models/AnearParticipant')
5
5
  const MockMessaging = require('../lib/messaging/__mocks__/AnearMessaging')
6
6
 
7
7
  const mockParticipantEnterHandler = jest.fn()
8
+ const mockSpectatorViewHandler = jest.fn()
8
9
  const mockParticipantRefreshHandler = jest.fn()
9
10
  const mockParticipantExitHandler = jest.fn()
10
11
  const mockParticipantActionHandler = jest.fn()
@@ -15,15 +16,19 @@ const TicTacToeMachineConfig = anearEvent => ({
15
16
  states: {
16
17
  waitingForHost: {
17
18
  on: {
18
- JOIN: {
19
+ PARTICIPANT_JOIN: {
19
20
  actions: 'enterHandler',
20
21
  target: 'waitingForOpponent'
22
+ },
23
+ SPECTATOR_VIEW: {
24
+ actions: 'viewHandler',
25
+ target: 'waitingForHost'
21
26
  }
22
27
  }
23
28
  },
24
29
  waitingForOpponent: {
25
30
  on: {
26
- JOIN: {
31
+ PARTICIPANT_JOIN: {
27
32
  actions: 'enterHandler',
28
33
  target: 'gameStart'
29
34
  },
@@ -37,6 +42,9 @@ const TicTacToeMachineConfig = anearEvent => ({
37
42
  },
38
43
  gameStart: {
39
44
  on: {
45
+ TEST_ACTION: {
46
+ actions: 'testActionHandler'
47
+ },
40
48
  BULLSEYE: {
41
49
  actions: 'actionHandler'
42
50
  },
@@ -59,10 +67,20 @@ const TicTacToeMachineOptions = anearEvent => ({
59
67
  refreshHandler: (context, event) => {
60
68
  anearEvent.myParticipantRefreshHandler(event.participant)
61
69
  },
70
+ viewHandler: (context, event) => {
71
+ anearEvent.mySpectatorViewHandler(event.userId)
72
+ },
62
73
  participantExitHandler: (context, event) => {
63
74
  anearEvent.myParticipantExitHandler(event.participant)
64
75
  },
65
76
  actionHandler: assign({score: (context, event) => context.score + event.payload.points}),
77
+ testActionHandler: (context, event) => {
78
+ anearEvent.myParticipantActionHandler(
79
+ event.participant.id,
80
+ event.type,
81
+ event.payload
82
+ )
83
+ }
66
84
  }
67
85
  })
68
86
 
@@ -81,6 +99,9 @@ class TestEvent extends AnearEvent {
81
99
  return TicTacToeMachineOptions(this)
82
100
  }
83
101
 
102
+ async mySpectatorViewHandler(...args) {
103
+ return mockSpectatorViewHandler(...args)
104
+ }
84
105
  async myParticipantEnterHandler(...args) {
85
106
  return mockParticipantEnterHandler(...args)
86
107
  }
@@ -90,37 +111,12 @@ class TestEvent extends AnearEvent {
90
111
  async myParticipantRefreshHandler(...args) {
91
112
  return mockParticipantRefreshHandler(...args)
92
113
  }
93
- }
94
-
95
-
96
- class TestEventWithDefaultXState extends AnearEvent {
97
- initContext() {
98
- return {
99
- playerScores: [83, 22]
100
- }
101
- }
102
-
103
- participantEnterEventCallback(participant) {
104
- mockParticipantEnterHandler(participant)
105
- return Promise.resolve()
106
- }
107
-
108
- participantRefreshEventCallback(participant) {
109
- mockParticipantRefreshHandler(participant)
110
- return Promise.resolve()
111
- }
112
-
113
- participantExitEventCallback(participant) {
114
- mockParticipantExitHandler(participant)
115
- return Promise.resolve()
116
- }
117
-
118
- participantActionEventCallback(participant, actionEventName, payload) {
119
- mockParticipantActionHandler(participant, actionEventName, payload)
120
- return Promise.resolve()
114
+ async myParticipantActionHandler(...args) {
115
+ return mockParticipantActionHandler(...args)
121
116
  }
122
117
  }
123
118
 
119
+
124
120
  class TestPlayer extends AnearParticipant {
125
121
  initContext() {
126
122
  return {
@@ -148,23 +144,18 @@ const newTestEvent = (hosted = false) => {
148
144
  return t
149
145
  }
150
146
 
151
- const newTestEventWithDefaultXState = testEvent => {
152
- const t = new TestEventWithDefaultXState(testEvent, TestPlayer, MessagingStub)
153
- t.startStateMachine()
154
- return t
155
- }
156
-
157
- test('participant enter with Default Xstate Config', async () => {
158
- const t = newTestEventWithDefaultXState(chatEvent)
147
+ test('participant enter', async () => {
148
+ const t = newTestEvent()
159
149
 
160
150
  const id = t.id
161
151
  expect(t.id).toBe(chatEvent.data.id)
162
152
  expect(t.relationships.user.data.type).toBe("users")
163
- expect(t.anearStateMachine.currentState.value).toBe("eventActive")
164
- expect(t.stateMachineContext.playerScores[0]).toBe(83)
153
+ expect(t.anearStateMachine.currentState.value).toBe("waitingForHost")
154
+ expect(t.stateMachineContext.score).toBe(90)
165
155
  const p1 = new TestPlayer(chatParticipant1, t)
166
156
 
167
157
  await t.participantEnter(p1)
158
+ expect(t.anearStateMachine.currentState.value).toBe("waitingForOpponent")
168
159
  await t.persist()
169
160
  await t.remove()
170
161
 
@@ -176,10 +167,25 @@ test('participant enter with Default Xstate Config', async () => {
176
167
  await p1.remove()
177
168
  })
178
169
 
179
- test('participant close with Default Xstate Config', async () => {
180
- const t = newTestEventWithDefaultXState(chatEvent)
170
+ test('spectator viewer', async () => {
171
+ const t = newTestEvent()
172
+ const userId = 999837834
173
+
174
+ const id = t.id
175
+ expect(t.anearStateMachine.currentState.value).toBe("waitingForHost")
176
+ await t.spectatorView(userId)
177
+ expect(t.anearStateMachine.currentState.value).toBe("waitingForHost")
178
+
179
+ expect(mockSpectatorViewHandler).toHaveBeenCalledTimes(1)
180
+ expect(mockSpectatorViewHandler).toHaveBeenCalledWith(userId)
181
+ })
182
+
183
+
184
+ test('participant close', async () => {
185
+ const t = newTestEvent()
181
186
 
182
187
  const p1 = new TestPlayer(chatParticipant1, t)
188
+ await t.participantEnter(p1)
183
189
 
184
190
  await t.participantExit(p1)
185
191
  await t.update()
@@ -191,29 +197,20 @@ test('participant close with Default Xstate Config', async () => {
191
197
  await t.remove()
192
198
  })
193
199
 
194
- test('participant refresh with Default Xstate Config', async () => {
195
- const t = newTestEventWithDefaultXState(chatEvent)
200
+ test('participant action', async () => {
201
+ const t = newTestEvent()
196
202
  const p1 = new TestPlayer(chatParticipant1, t)
203
+ const p2 = new TestPlayer(chatParticipant2, t)
204
+ await t.participantEnter(p1)
205
+ await t.participantEnter(p2)
197
206
 
198
- await t.refreshParticipant(p1)
199
- await t.update()
200
-
201
- expect(mockParticipantRefreshHandler).toHaveBeenCalledWith(p1)
202
- expect(mockParticipantRefreshHandler).toHaveBeenCalledTimes(1)
203
- await p1.remove()
204
- await t.remove()
205
- })
206
-
207
- test('participant action with Default Xstate Config', async () => {
208
- const t = newTestEventWithDefaultXState(chatEvent)
209
- const p1 = new TestPlayer(chatParticipant1, t)
210
207
  const eventName = "TEST_ACTION"
211
208
  const payload = {x: 1, y: 99}
212
209
 
213
210
  await t.participantAction(p1, eventName, payload)
214
211
  await t.update()
215
212
 
216
- expect(mockParticipantActionHandler).toHaveBeenCalledWith(p1, eventName, payload)
213
+ expect(mockParticipantActionHandler).toHaveBeenCalledWith(p1.id, eventName, payload)
217
214
  expect(mockParticipantActionHandler).toHaveBeenCalledTimes(1)
218
215
  await p1.remove()
219
216
  await t.remove()
@@ -279,6 +276,7 @@ test('purge all participants', async () => {
279
276
  await t.purgeParticipants()
280
277
 
281
278
  expect(t.participants.all).toHaveLength(0)
279
+ expect(t.participants.host).toBe(null)
282
280
 
283
281
  await t.remove()
284
282
  })
@@ -1,108 +0,0 @@
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
- PARTICIPANT_EXIT: {
39
- target: 'participant_exit'
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
- participant_exit: {
66
- invoke: {
67
- id: 'participant_exit',
68
- src: 'participantExitEventHandler',
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, event.remainingTimeout),
98
- participantExitEventHandler: (context, event) => anearEvent.participantExitEventCallback(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
- }