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.
- package/lib/api/AnearApi.js +1 -1
- package/lib/index.js +2 -1
- package/lib/messaging/AnearMessaging.js +83 -157
- package/lib/messaging/__mocks__/AnearMessaging.js +4 -0
- package/lib/models/AnearEvent.js +70 -20
- package/lib/models/AnearParticipant.js +84 -5
- package/lib/utils/AnearXstate.js +6 -1
- package/lib/utils/AnearXstateDefaults.js +6 -6
- package/lib/utils/Participants.js +62 -53
- package/package.json +1 -1
- package/tests/AnearEvent.test.js +84 -58
- package/tests/AnearParticipant.test.js +46 -4
- package/tests/Participants.test.js +71 -53
- package/tests/fixtures/ParticipantsFixture.js +100 -105
- package/tests/utils/AnearParticipantJSONBuilder.js +66 -0
|
@@ -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
|
-
|
|
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
|
package/lib/utils/AnearXstate.js
CHANGED
|
@@ -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 = '
|
|
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
|
-
|
|
39
|
-
target: '
|
|
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
|
-
|
|
65
|
+
participant_exit: {
|
|
66
66
|
invoke: {
|
|
67
|
-
id: '
|
|
68
|
-
src: '
|
|
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
|
-
|
|
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(
|
|
21
|
-
|
|
22
|
-
this.
|
|
23
|
-
this.
|
|
24
|
-
this.
|
|
25
|
-
|
|
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
|
-
|
|
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(
|
|
43
|
-
return this._participants[
|
|
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
|
|
78
|
+
return this.ids.length
|
|
73
79
|
}
|
|
74
80
|
|
|
75
|
-
numActive() {
|
|
76
|
-
return this.active
|
|
81
|
+
get numActive() {
|
|
82
|
+
return this.active.length
|
|
77
83
|
}
|
|
78
84
|
|
|
79
|
-
numIdle() {
|
|
80
|
-
return this.idle
|
|
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
|
-
|
|
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(
|
|
121
|
-
|
|
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.
|
|
128
|
+
this.host = anearParticipant
|
|
126
129
|
} else {
|
|
127
|
-
this.
|
|
130
|
+
this._participants[anearParticipant.id] = anearParticipant
|
|
131
|
+
this.markActive(anearParticipant, withTimestamp)
|
|
128
132
|
}
|
|
129
|
-
return
|
|
133
|
+
return anearParticipant
|
|
130
134
|
}
|
|
131
135
|
|
|
132
|
-
markActive(anearParticipant) {
|
|
133
|
-
|
|
134
|
-
|
|
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(
|
|
144
|
-
if (
|
|
145
|
-
|
|
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])
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
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
package/tests/AnearEvent.test.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
"use strict"
|
|
2
2
|
const { assign } = require('xstate')
|
|
3
|
-
const
|
|
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
|
|
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
|
-
|
|
34
|
-
actions: '
|
|
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
|
-
|
|
44
|
-
actions: '
|
|
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
|
-
|
|
63
|
-
anearEvent.
|
|
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
|
|
88
|
-
return
|
|
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
|
-
|
|
114
|
-
|
|
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
|
|
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.
|
|
184
|
+
await t.participantExit(p1)
|
|
185
185
|
await t.update()
|
|
186
186
|
|
|
187
|
-
expect(
|
|
188
|
-
expect(
|
|
189
|
-
expect(t.participants.numActive
|
|
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
|
|
244
|
-
expect(t.participants.host).
|
|
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
|
|
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.
|
|
256
|
-
await t.
|
|
255
|
+
await t.participantExit(p1)
|
|
256
|
+
await t.participantExit(p2)
|
|
257
257
|
await t.update()
|
|
258
258
|
await t.remove()
|
|
259
259
|
|
|
260
|
-
expect(
|
|
261
|
-
expect(
|
|
262
|
-
expect(
|
|
263
|
-
expect(t.participants.numActive
|
|
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
|
|
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
|
|
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
|
|
311
|
+
expect(t.participants.numActive).toBe(1)
|
|
292
312
|
expect(t.participants.get(p2).name).toBe('bbondfl93')
|
|
293
313
|
|
|
294
|
-
await t.
|
|
295
|
-
await t.
|
|
314
|
+
await t.participantExit(host)
|
|
315
|
+
await t.participantExit(p2)
|
|
296
316
|
await t.update()
|
|
297
317
|
await t.remove()
|
|
298
318
|
|
|
299
|
-
expect(
|
|
300
|
-
expect(
|
|
301
|
-
expect(
|
|
302
|
-
expect(t.participants.numActive
|
|
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
|
-
|
|
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
|
-
|
|
331
|
-
|
|
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.
|
|
358
|
-
await t.
|
|
382
|
+
await t.participantExit(p1)
|
|
383
|
+
await t.participantExit(p2)
|
|
384
|
+
await t.closeOutParticipants()
|
|
359
385
|
await t.remove()
|
|
360
386
|
})
|