anear-js-api 0.4.1 → 0.4.3

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.
@@ -1,15 +1,45 @@
1
1
  "use strict"
2
+
2
3
  const JsonApiResource = require('./JsonApiResource')
4
+ const ParticipantTimer = require('../utils/ParticipantTimer')
5
+ const logger = require('../utils/Logger')
6
+
3
7
  const HostUserType = "host"
4
8
 
5
9
  class AnearParticipant extends JsonApiResource {
10
+ constructor(json, anearEvent) {
11
+ super(json)
12
+ this.anearEvent = anearEvent
13
+ this.timer = null
14
+ this._state = json.state
15
+ this._timestamp = json.timestamp
16
+ this._geoLocation = null
17
+ }
18
+
6
19
  toJSON() {
7
20
  return {
8
21
  ...super.toJSON(),
9
- geoLocation: this._geoLocation || null,
22
+ state: this.state,
23
+ timestamp: this.timestamp
10
24
  }
11
25
  }
12
26
 
27
+ get state() {
28
+ return this._state
29
+ }
30
+
31
+ set state(s) {
32
+ this._state = s
33
+ }
34
+
35
+ get timestamp() {
36
+ return this._timestamp
37
+ }
38
+
39
+ set timestamp(t) {
40
+ this._timestamp = t
41
+ }
42
+
13
43
  set geoLocation(loc) {
14
44
  this._geoLocation = loc
15
45
  }
@@ -18,10 +48,6 @@ class AnearParticipant extends JsonApiResource {
18
48
  return this._geoLocation
19
49
  }
20
50
 
21
- get identity() {
22
- return (({ id, userId, name, avatarUrl, geoLocation}) => ({ id, userId, name, avatarUrl, geoLocation}))(this)
23
- }
24
-
25
51
  get userId() {
26
52
  return this.relationships.user.data.id
27
53
  }
@@ -57,6 +83,59 @@ class AnearParticipant extends JsonApiResource {
57
83
  get privateChannelName() {
58
84
  return this.attributes['private-channel-name']
59
85
  }
86
+
87
+ ensureTimer(timeoutMsecs) {
88
+ // this is called when a new timer is being started for a privateMessage
89
+ // sent to a participant, or public message to all participants
90
+ // If the timer already exists and is paused, it is resumed with the timeRemaining
91
+ // [a starter function, timeRemaining] is returned
92
+ let timeRemaining = timeoutMsecs
93
+ let timerStarter = () => {}
94
+
95
+ if (timeoutMsecs > 0) {
96
+
97
+ this.timer ||= new ParticipantTimer(
98
+ this.id,
99
+ async () => await this.timerExpired(timeoutMsecs)
100
+ )
101
+
102
+ if (this.timer.isRunning) {
103
+ timeRemaining = this.timer.interrupt()
104
+ } else {
105
+ timerStarter = () => this.timer.start(timeoutMsecs)
106
+ }
107
+ }
108
+ logger.debug(`ensureTimer(timeRemaining: ${timeRemaining})`)
109
+
110
+ return [timerStarter, timeRemaining]
111
+ }
112
+
113
+ destroyTimer() {
114
+ // participant will not be receiving any more display messages
115
+ // so we close out and delete the timer
116
+ if (this.timer) {
117
+ this.timer.reset()
118
+ this.timer = null
119
+ }
120
+ }
121
+
122
+ async timerExpired(timeoutMsecs) {
123
+ logger.debug(`participant (${this.anearEvent.id}, ${this.id}) TIMED OUT after ${timeoutMsecs} msecs`)
124
+
125
+ this.timer = null
126
+
127
+ await this.anearEvent.participantTimedOut(this)
128
+ await this.anearEvent.update()
129
+ }
130
+
131
+ interruptTimer() {
132
+ if (this.timer && this.timer.isRunning) this.timer.interrupt()
133
+ }
134
+
135
+ resetTimer() {
136
+ // called after participant takes Action before timer expires
137
+ if (this.timer) this.timer.reset()
138
+ }
60
139
  }
61
140
 
62
141
  module.exports = AnearParticipant
@@ -4,8 +4,9 @@ const { createMachine, interpret, State } = require('xstate')
4
4
  const logger = require('../utils/Logger')
5
5
 
6
6
  const JoinEvent = 'JOIN'
7
+ const ParticipantExitEvent = 'PARTICIPANT_EXIT'
7
8
  const RefreshEvent = 'REFRESH'
8
- const CloseEvent = 'CLOSE'
9
+ const CloseEvent = 'EVENT_CLOSE'
9
10
  const TimeoutEvent = 'TIMEOUT'
10
11
 
11
12
  class AnearXstate {
@@ -50,6 +51,10 @@ class AnearXstate {
50
51
  this.send(CloseEvent, params)
51
52
  }
52
53
 
54
+ sendParticipantExitEvent(params) {
55
+ this.send(ParticipantExitEvent, params)
56
+ }
57
+
53
58
  sendTimeoutEvent(params) {
54
59
  this.send(TimeoutEvent, params)
55
60
  }
@@ -35,8 +35,8 @@ const DefaultConfigFunc = (anearEvent) => {
35
35
  REFRESH: {
36
36
  target: 'refresh'
37
37
  },
38
- CLOSE: {
39
- target: 'close'
38
+ PARTICIPANT_EXIT: {
39
+ target: 'participant_exit'
40
40
  },
41
41
  TIMEOUT: {
42
42
  target: 'timeout'
@@ -62,10 +62,10 @@ const DefaultConfigFunc = (anearEvent) => {
62
62
  ...PromiseResolveReject
63
63
  }
64
64
  },
65
- close: {
65
+ participant_exit: {
66
66
  invoke: {
67
- id: 'close',
68
- src: 'closeEventHandler',
67
+ id: 'participant_exit',
68
+ src: 'participantExitEventHandler',
69
69
  ...PromiseResolveReject
70
70
  }
71
71
  },
@@ -95,7 +95,7 @@ const DefaultOptionsFunc = anearEvent => {
95
95
  services: {
96
96
  joinEventHandler: (context, event) => anearEvent.participantEnterEventCallback(event.participant),
97
97
  refreshEventHandler: (context, event) => anearEvent.participantRefreshEventCallback(event.participant, event.remainingTimeout),
98
- closeEventHandler: (context, event) => anearEvent.participantCloseEventCallback(event.participant),
98
+ participantExitEventHandler: (context, event) => anearEvent.participantExitEventCallback(event.participant),
99
99
  timeoutEventHandler: (context, event) => anearEvent.participantTimedOutEventCallback(event.participant),
100
100
  actionEventHandler: (context, event) => anearEvent.participantActionEventCallback(event.participant, event.type, event.payload)
101
101
  }
@@ -6,41 +6,43 @@ const IdleState = "idle"
6
6
  const MINUTES = (60 * 1000)
7
7
  const HOURS = (60 * MINUTES)
8
8
  const DefaultIdleMsecs = (30 * MINUTES)
9
- const DefaultPurgeMsecs = (2 * HOURS)
10
-
11
- const DefaultSettings = {
12
- participants: {},
13
- host: {},
14
- idleMsecs: DefaultIdleMsecs,
15
- purgeMsecs: DefaultPurgeMsecs
16
- }
9
+ const DefaultPurgeMsecs = (2 * HOURS) // after Idle
17
10
 
18
11
  class Participants {
19
12
 
20
- constructor(json) {
21
- const init = json || JSON.parse(JSON.stringify(DefaultSettings))
22
- this._participants = init.participants
23
- this._host = init.host
24
- this.idleMsecs = init.idleMsecs // how long Active participants
25
- this.purgeMsecs = init.purgeMsecs
13
+ constructor(anearEvent, {idleMsecs = DefaultIdleMsecs, purgeMsecs = DefaultPurgeMsecs, ids = []} = {}) {
14
+ this.anearEvent = anearEvent
15
+ this.idleMsecs = idleMsecs
16
+ this.purgeMsecs = purgeMsecs
17
+ this._participants = {}
18
+ for (const id of ids) {
19
+ // app restart logic ...
20
+ // seeds from ids with empty objects awaiting full
21
+ // anearParticipant rehydration from redis
22
+ this._participants[id] = {}
23
+ }
24
+ this._host = null
26
25
  }
27
26
 
28
27
  toJSON() {
29
28
  return {
30
- participants: this._participants,
31
- host: this._host,
29
+ ids: this.all.map(p => p.id),
32
30
  idleMsecs: this.idleMsecs,
33
31
  purgeMsecs: this.purgeMsecs
34
32
  }
35
33
  }
36
34
 
35
+ get ids() {
36
+ return Object.keys(this._participants)
37
+ }
38
+
37
39
  indexedById() {
38
40
  // returns an object that has AnearParticipant.id as key
39
41
  return this._participants
40
42
  }
41
43
 
42
- getById(anearParticipantId) {
43
- return this._participants[anearParticipantId]
44
+ getById(participantId) {
45
+ return this._participants[participantId]
44
46
  }
45
47
 
46
48
  exists({id}) {
@@ -55,29 +57,33 @@ class Participants {
55
57
  return this._host
56
58
  }
57
59
 
60
+ set host(h) {
61
+ this._host = h
62
+ }
63
+
58
64
  get all() {
59
65
  return Object.values(this._participants).
60
66
  sort((ca, cb) => ca.timestamp - cb.timestamp)
61
67
  }
62
68
 
63
- active() {
69
+ get active() {
64
70
  return Object.values(this._participants).filter(c => c.state === ActiveState)
65
71
  }
66
72
 
67
- idle() {
73
+ get idle() {
68
74
  return Object.values(this._participants).filter(c => c.state === IdleState)
69
75
  }
70
76
 
71
77
  get count() {
72
- return Object.keys(this._participants).length
78
+ return this.ids.length
73
79
  }
74
80
 
75
- numActive() {
76
- return this.active().length
81
+ get numActive() {
82
+ return this.active.length
77
83
  }
78
84
 
79
- numIdle() {
80
- return this.idle().length
85
+ get numIdle() {
86
+ return this.idle.length
81
87
  }
82
88
 
83
89
  isIdle(c, currentTimestamp) {
@@ -108,8 +114,7 @@ class Participants {
108
114
  }
109
115
  }
110
116
 
111
- const keys = Object.keys(this._participants)
112
- keys.forEach(
117
+ this.ids.forEach(
113
118
  k => {
114
119
  const c = this._participants[k]
115
120
  sweeper(c)
@@ -117,34 +122,33 @@ class Participants {
117
122
  )
118
123
  }
119
124
 
120
- add(anearEvent, anearParticipant) {
121
- const rec = this.participantRec(anearParticipant)
122
-
123
- if (anearParticipant.isHost() && anearEvent.hosted) {
125
+ add(anearParticipant, withTimestamp = this.currentTimestamp) {
126
+ if (anearParticipant.isHost() && this.anearEvent.hosted) {
124
127
  // the host is not an eligible participant and isn't active nor idle
125
- this._host = rec
128
+ this.host = anearParticipant
126
129
  } else {
127
- this.markActive(anearParticipant)
130
+ this._participants[anearParticipant.id] = anearParticipant
131
+ this.markActive(anearParticipant, withTimestamp)
128
132
  }
129
- return rec
133
+ return anearParticipant
130
134
  }
131
135
 
132
- markActive(anearParticipant) {
133
- // anearParticipant has shown activity in the event (e.g. Action)
134
- const participant = this.get(anearParticipant)
135
- if (participant) {
136
- participant.timestamp = this.currentTimestamp
137
- participant.state = ActiveState
138
- } else {
139
- this._participants[anearParticipant.id] = this.participantRec(anearParticipant)
140
- }
136
+ markActive(anearParticipant, withTimestamp = this.currentTimestamp) {
137
+ anearParticipant.timestamp = withTimestamp
138
+ anearParticipant.state = ActiveState
141
139
  }
142
140
 
143
- purge({id}) {
144
- if (id === this.host.id) {
145
- this._host = JSON.parse(JSON.stringify(DefaultSettings)).host
141
+ purge(participant) {
142
+ if (!participant) return
143
+
144
+ const {id} = participant
145
+
146
+ if (this.host && (id === this.host.id)) {
147
+ this.host = null
146
148
  } else {
147
- if (this._participants[id]) delete this._participants[id]
149
+ if (this._participants[id]) {
150
+ delete this._participants[id]
151
+ }
148
152
  }
149
153
  }
150
154
 
@@ -152,12 +156,17 @@ class Participants {
152
156
  return new Date().getTime()
153
157
  }
154
158
 
155
- participantRec(anearParticipant, state = ActiveState) {
156
- return {
157
- ...anearParticipant.identity,
158
- state,
159
- timestamp: this.currentTimestamp
160
- }
159
+ load(anearParticipants) {
160
+ // used for tests only
161
+ anearParticipants.forEach(
162
+ p => {
163
+ if (p.isHost() && this.anearEvent.hosted) {
164
+ this.host = p
165
+ } else {
166
+ this._participants[p.id] = p
167
+ }
168
+ }
169
+ )
161
170
  }
162
171
  }
163
172
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "anear-js-api",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "Javascript Developer API for Anear Apps",
5
5
  "main": "lib/index.js",
6
6
  "scripts": {
@@ -1,12 +1,12 @@
1
1
  "use strict"
2
2
  const { assign } = require('xstate')
3
- const { AnearEvent } = require('../lib/models/AnearEvent')
3
+ const AnearEvent = require('../lib/models/AnearEvent')
4
4
  const AnearParticipant = require('../lib/models/AnearParticipant')
5
5
  const MockMessaging = require('../lib/messaging/__mocks__/AnearMessaging')
6
6
 
7
7
  const mockParticipantEnterHandler = jest.fn()
8
8
  const mockParticipantRefreshHandler = jest.fn()
9
- const mockParticipantCloseHandler = jest.fn()
9
+ const mockParticipantExitHandler = jest.fn()
10
10
  const mockParticipantActionHandler = jest.fn()
11
11
 
12
12
  const TicTacToeMachineConfig = anearEvent => ({
@@ -30,8 +30,8 @@ const TicTacToeMachineConfig = anearEvent => ({
30
30
  REFRESH: {
31
31
  actions: 'refreshHandler'
32
32
  },
33
- CLOSE: {
34
- actions: 'closeHandler'
33
+ PARTICIPANT_EXIT: {
34
+ actions: 'participantExitHandler'
35
35
  }
36
36
  }
37
37
  },
@@ -40,8 +40,8 @@ const TicTacToeMachineConfig = anearEvent => ({
40
40
  BULLSEYE: {
41
41
  actions: 'actionHandler'
42
42
  },
43
- CLOSE: {
44
- actions: 'closeHandler'
43
+ PARTICIPANT_EXIT: {
44
+ actions: 'participantExitHandler'
45
45
  },
46
46
  REFRESH: {
47
47
  actions: 'refreshHandler'
@@ -59,8 +59,8 @@ const TicTacToeMachineOptions = anearEvent => ({
59
59
  refreshHandler: (context, event) => {
60
60
  anearEvent.myParticipantRefreshHandler(event.participant)
61
61
  },
62
- closeHandler: (context, event) => {
63
- anearEvent.myParticipantCloseHandler(event.participant)
62
+ participantExitHandler: (context, event) => {
63
+ anearEvent.myParticipantExitHandler(event.participant)
64
64
  },
65
65
  actionHandler: assign({score: (context, event) => context.score + event.payload.points}),
66
66
  }
@@ -84,8 +84,8 @@ class TestEvent extends AnearEvent {
84
84
  async myParticipantEnterHandler(...args) {
85
85
  return mockParticipantEnterHandler(...args)
86
86
  }
87
- async myParticipantCloseHandler(...args) {
88
- return mockParticipantCloseHandler(...args)
87
+ async myParticipantExitHandler(...args) {
88
+ return mockParticipantExitHandler(...args)
89
89
  }
90
90
  async myParticipantRefreshHandler(...args) {
91
91
  return mockParticipantRefreshHandler(...args)
@@ -110,8 +110,8 @@ class TestEventWithDefaultXState extends AnearEvent {
110
110
  return Promise.resolve()
111
111
  }
112
112
 
113
- participantCloseEventCallback(participant) {
114
- mockParticipantCloseHandler(participant)
113
+ participantExitEventCallback(participant) {
114
+ mockParticipantExitHandler(participant)
115
115
  return Promise.resolve()
116
116
  }
117
117
 
@@ -142,14 +142,14 @@ afterAll(async () => await TestEvent.close())
142
142
  afterEach(() => {jest.clearAllMocks()})
143
143
 
144
144
  const newTestEvent = (hosted = false) => {
145
- const t = new TestEvent(chatEvent, MessagingStub)
145
+ const t = new TestEvent(chatEvent, TestPlayer, MessagingStub)
146
146
  t.attributes.hosted = hosted
147
147
  t.startStateMachine()
148
148
  return t
149
149
  }
150
150
 
151
151
  const newTestEventWithDefaultXState = testEvent => {
152
- const t = new TestEventWithDefaultXState(testEvent, MessagingStub)
152
+ const t = new TestEventWithDefaultXState(testEvent, TestPlayer, MessagingStub)
153
153
  t.startStateMachine()
154
154
  return t
155
155
  }
@@ -162,7 +162,7 @@ test('participant enter with Default Xstate Config', async () => {
162
162
  expect(t.relationships.user.data.type).toBe("users")
163
163
  expect(t.anearStateMachine.currentState.value).toBe("eventActive")
164
164
  expect(t.stateMachineContext.playerScores[0]).toBe(83)
165
- const p1 = new TestPlayer(chatParticipant1)
165
+ const p1 = new TestPlayer(chatParticipant1, t)
166
166
 
167
167
  await t.participantEnter(p1)
168
168
  await t.persist()
@@ -171,7 +171,7 @@ test('participant enter with Default Xstate Config', async () => {
171
171
  expect(p1.userType).toBe("participant")
172
172
  expect(mockParticipantEnterHandler).toHaveBeenCalledTimes(1)
173
173
  expect(mockParticipantEnterHandler).toHaveBeenCalledWith(p1)
174
- expect(t.participants.numActive(false)).toBe(1)
174
+ expect(t.participants.numActive).toBe(1)
175
175
 
176
176
  await p1.remove()
177
177
  })
@@ -179,21 +179,21 @@ test('participant enter with Default Xstate Config', async () => {
179
179
  test('participant close with Default Xstate Config', async () => {
180
180
  const t = newTestEventWithDefaultXState(chatEvent)
181
181
 
182
- const p1 = new TestPlayer(chatParticipant1)
182
+ const p1 = new TestPlayer(chatParticipant1, t)
183
183
 
184
- await t.participantClose(p1)
184
+ await t.participantExit(p1)
185
185
  await t.update()
186
186
 
187
- expect(mockParticipantCloseHandler).toHaveBeenCalledWith(p1)
188
- expect(mockParticipantCloseHandler).toHaveBeenCalledTimes(1)
189
- expect(t.participants.numActive(false)).toBe(0)
187
+ expect(mockParticipantExitHandler).toHaveBeenCalledWith(p1)
188
+ expect(mockParticipantExitHandler).toHaveBeenCalledTimes(1)
189
+ expect(t.participants.numActive).toBe(0)
190
190
 
191
191
  await t.remove()
192
192
  })
193
193
 
194
194
  test('participant refresh with Default Xstate Config', async () => {
195
195
  const t = newTestEventWithDefaultXState(chatEvent)
196
- const p1 = new TestPlayer(chatParticipant1)
196
+ const p1 = new TestPlayer(chatParticipant1, t)
197
197
 
198
198
  await t.refreshParticipant(p1)
199
199
  await t.update()
@@ -206,7 +206,7 @@ test('participant refresh with Default Xstate Config', async () => {
206
206
 
207
207
  test('participant action with Default Xstate Config', async () => {
208
208
  const t = newTestEventWithDefaultXState(chatEvent)
209
- const p1 = new TestPlayer(chatParticipant1)
209
+ const p1 = new TestPlayer(chatParticipant1, t)
210
210
  const eventName = "TEST_ACTION"
211
211
  const payload = {x: 1, y: 99}
212
212
 
@@ -230,8 +230,8 @@ test('can be persisted and removed repeatedly in storage', async () => {
230
230
 
231
231
  test('can add participants, not hosted', async () => {
232
232
  let t = newTestEvent(false)
233
- const p1 = new TestPlayer(chatParticipant1)
234
- const p2 = new TestPlayer(chatParticipant2)
233
+ const p1 = new TestPlayer(chatParticipant1, t)
234
+ const p2 = new TestPlayer(chatParticipant2, t)
235
235
  const id = t.id
236
236
 
237
237
  await t.participantEnter(p1)
@@ -240,27 +240,47 @@ test('can add participants, not hosted', async () => {
240
240
  expect(p1.userType).toBe("participant")
241
241
  expect(mockParticipantEnterHandler).toHaveBeenCalledTimes(1)
242
242
  expect(mockParticipantEnterHandler).toHaveBeenCalledWith(p1)
243
- expect(t.participants.numActive(false)).toBe(1)
244
- expect(t.participants.host).toStrictEqual({})
243
+ expect(t.participants.numActive).toBe(1)
244
+ expect(t.participants.host).toBe(null)
245
245
 
246
246
  await t.participantEnter(p2)
247
247
  await t.update()
248
248
 
249
249
  expect(mockParticipantEnterHandler).toHaveBeenCalledTimes(2)
250
250
  expect(mockParticipantEnterHandler).toHaveBeenCalledWith(p2)
251
- expect(t.participants.numActive(false)).toBe(2)
251
+ expect(t.participants.numActive).toBe(2)
252
252
  expect(t.participants.get(p2).name).toBe("bbondfl93")
253
253
  expect(p2.userType).toBe("participant")
254
254
 
255
- await t.participantClose(p1)
256
- await t.participantClose(p2)
255
+ await t.participantExit(p1)
256
+ await t.participantExit(p2)
257
257
  await t.update()
258
258
  await t.remove()
259
259
 
260
- expect(mockParticipantCloseHandler).toHaveBeenCalledWith(p1)
261
- expect(mockParticipantCloseHandler).toHaveBeenCalledWith(p2)
262
- expect(mockParticipantCloseHandler).toHaveBeenCalledTimes(2)
263
- expect(t.participants.numActive(false)).toBe(0)
260
+ expect(mockParticipantExitHandler).toHaveBeenCalledWith(p1)
261
+ expect(mockParticipantExitHandler).toHaveBeenCalledWith(p2)
262
+ expect(mockParticipantExitHandler).toHaveBeenCalledTimes(2)
263
+ expect(t.participants.numActive).toBe(0)
264
+ })
265
+
266
+ test('purge all participants', async () => {
267
+ let t = newTestEvent(true)
268
+ const host = new TestPlayer(chatHost, t)
269
+ const p1 = new TestPlayer(chatParticipant1, t)
270
+ const p2 = new TestPlayer(chatParticipant2, t)
271
+ await t.participantEnter(host)
272
+ await t.participantEnter(p1)
273
+ await t.participantEnter(p2)
274
+ await t.update()
275
+
276
+ expect(t.participants.host).toBe(host)
277
+ expect(t.participants.ids).toStrictEqual([p1.id, p2.id])
278
+
279
+ await t.purgeParticipants()
280
+
281
+ expect(t.participants.all).toHaveLength(0)
282
+
283
+ await t.remove()
264
284
  })
265
285
 
266
286
 
@@ -268,10 +288,10 @@ test('can add participant, hosted', async () => {
268
288
  let t = newTestEvent(true)
269
289
 
270
290
  expect(t.hosted).toBe(true)
271
- expect(t.participants.numActive(false)).toBe(0)
291
+ expect(t.participants.numActive).toBe(0)
272
292
 
273
- const host = new TestPlayer(chatHost)
274
- const p2 = new TestPlayer(chatParticipant2)
293
+ const host = new TestPlayer(chatHost, t)
294
+ const p2 = new TestPlayer(chatParticipant2, t)
275
295
  const id = t.id
276
296
 
277
297
  await t.participantEnter(host)
@@ -281,61 +301,66 @@ test('can add participant, hosted', async () => {
281
301
  expect(mockParticipantEnterHandler).toHaveBeenCalledTimes(1)
282
302
  expect(mockParticipantEnterHandler).toHaveBeenCalledWith(host)
283
303
  expect(t.participants.host.name).toBe('foxhole_host')
284
- expect(t.participants.numActive(false)).toBe(0) // event creator when hosted isn't active participant
304
+ expect(t.participants.numActive).toBe(0) // event creator when hosted isn't active participant
285
305
 
286
306
  await t.participantEnter(p2)
287
307
  await t.update()
288
308
 
289
309
  expect(mockParticipantEnterHandler).toHaveBeenCalledTimes(2)
290
310
  expect(mockParticipantEnterHandler).toHaveBeenCalledWith(p2)
291
- expect(t.participants.numActive(false)).toBe(1)
311
+ expect(t.participants.numActive).toBe(1)
292
312
  expect(t.participants.get(p2).name).toBe('bbondfl93')
293
313
 
294
- await t.participantClose(host)
295
- await t.participantClose(p2)
314
+ await t.participantExit(host)
315
+ await t.participantExit(p2)
296
316
  await t.update()
297
317
  await t.remove()
298
318
 
299
- expect(mockParticipantCloseHandler).toHaveBeenCalledWith(host)
300
- expect(mockParticipantCloseHandler).toHaveBeenCalledWith(p2)
301
- expect(mockParticipantCloseHandler).toHaveBeenCalledTimes(2)
302
- expect(t.participants.numActive(false)).toBe(0)
319
+ expect(mockParticipantExitHandler).toHaveBeenCalledWith(host)
320
+ expect(mockParticipantExitHandler).toHaveBeenCalledWith(p2)
321
+ expect(mockParticipantExitHandler).toHaveBeenCalledTimes(2)
322
+ expect(t.participants.numActive).toBe(0)
303
323
  })
304
324
 
305
325
  test('can be retrieved back from storage with participants, not hosted', async () => {
306
326
  const testEvent = newTestEvent(false)
307
- const p1 = new TestPlayer(chatParticipant1)
308
- const p2 = new TestPlayer(chatParticipant2)
327
+ const p1 = new TestPlayer(chatParticipant1, testEvent)
328
+ const p2 = new TestPlayer(chatParticipant2, testEvent)
309
329
 
310
330
  await testEvent.participantEnter(p1)
311
331
  await testEvent.participantEnter(p2)
312
332
  await testEvent.persist()
313
333
 
314
334
  const rehydratedTestEvent = await TestEvent.getFromStorage(testEvent.id, MessagingStub)
315
- const rehydratedPlayer1 = await TestPlayer.getFromStorage(p1.id)
316
- const rehydratedPlayer2 = await TestPlayer.getFromStorage(p2.id)
317
335
 
318
336
  rehydratedTestEvent.startStateMachine()
319
337
 
320
- expect(rehydratedTestEvent.participants.numActive(false)).toBe(2)
338
+ await rehydratedTestEvent.participantEnter(p1)
339
+ await rehydratedTestEvent.participantEnter(p2)
340
+
341
+ expect(rehydratedTestEvent.participants.numActive).toBe(2)
321
342
  expect(rehydratedTestEvent.id).toBe(testEvent.id)
322
343
  expect(rehydratedTestEvent.relationships['user'].data.type).toBe("users")
323
344
  expect(rehydratedTestEvent.relationships['zone'].data.type).toBe("zones")
324
345
  expect(rehydratedTestEvent.participantTimeout).toBe(32000)
325
346
  expect(rehydratedTestEvent.stateMachineContext.score).toBe(90)
326
347
  expect(rehydratedTestEvent.included[0].relationships.app.data.id).toBe("5b9d9838-17de-4a80-8a64-744c222ba722")
327
- expect(rehydratedPlayer1.context.name).toBe('machvee')
328
- expect(rehydratedPlayer2.context.name).toBe('bbondfl93')
329
348
 
330
- await rehydratedTestEvent.participantClose(rehydratedPlayer1)
331
- await rehydratedTestEvent.participantClose(rehydratedPlayer2)
349
+ const rp1 = rehydratedTestEvent.participants.getById(p1.id)
350
+ const rp2 = rehydratedTestEvent.participants.getById(p2.id)
351
+
352
+ expect(rehydratedTestEvent.participants.getById(rp1.id).context.name).toBe('machvee')
353
+ expect(rehydratedTestEvent.participants.getById(rp2.id).context.name).toBe('bbondfl93')
354
+
355
+ await rehydratedTestEvent.participantExit(rp1)
356
+ await rehydratedTestEvent.participantExit(rp2)
332
357
  await rehydratedTestEvent.remove()
333
358
  })
334
359
 
335
360
  test('can update state machine context via Action events', async () => {
336
361
  const t = newTestEvent(false)
337
- const p1 = new TestPlayer(chatParticipant1)
338
- const p2 = new TestPlayer(chatParticipant2)
362
+ const p1 = new TestPlayer(chatParticipant1, t)
363
+ const p2 = new TestPlayer(chatParticipant2, t)
339
364
 
340
365
  await t.participantEnter(p1)
341
366
  await t.participantEnter(p2)
@@ -354,7 +379,8 @@ test('can update state machine context via Action events', async () => {
354
379
 
355
380
  expect(t.anearStateMachine.context.score).toBe(92)
356
381
 
357
- await t.participantClose(p1)
358
- await t.participantClose(p2)
382
+ await t.participantExit(p1)
383
+ await t.participantExit(p2)
384
+ await t.closeOutParticipants()
359
385
  await t.remove()
360
386
  })