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.
@@ -1,33 +1,58 @@
1
1
  "use strict"
2
- const EventEmitter = require('events').EventEmitter
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
- get clonedEventId() {
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
- return clonedEventData ? clonedEventData.id : null
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 state() {
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(anearParticipant) {
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(anearParticipant) {
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(anearParticipant) {
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(anearParticipant, actionEventName, message) {
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(anearParticipant) {
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(anearParticipant) {
115
- return anearParticipant.userId === this.userId
155
+ isParticipantEventCreator(RParticipant) {
156
+ return participant.userId === this.userId
116
157
  }
117
- async refreshParticipant(anearParticipant) {
118
- await this.participantRefreshEventCallback(anearParticipant)
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(anearParticipant, message, timeoutMilliseconds=0, timeoutCallback=null) {
181
+ async publishEventPrivateMessage(participant, message, timeoutMilliseconds=0, timeoutCallback=null) {
141
182
  await this.messaging.publishEventPrivateMessage(
142
183
  this.id,
143
- anearParticipant,
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(anearParticipant) {
160
- // Called each time a participant ENTERs the ActionChannel. This could be when joining
161
- // an event for the first time, rejoining, or after a browser refresh/reconnect
162
- // If the anearParticipant exists in this.participants, we call Refresh, else Enter
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
- if (this.participants.exists(anearParticipant)) {
166
- logger.info(`participant ${anearParticipant.id} exists. Refreshing...`)
210
+ this.anearStateMachine.sendRefreshEvent({participant: participant})
167
211
 
168
- this.participants.add(this, anearParticipant) // update the participants entry
169
- await this.participantRefreshEventCallback(anearParticipant)
170
- await anearParticipant.update()
212
+ await participant.update()
171
213
  } else {
172
- this.participants.add(this, anearParticipant) // add the participants entry
173
- await this.participantEnterEventCallback(anearParticipant)
174
- await anearParticipant.persist()
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(anearParticipant) {
179
- //
180
- // this is invoked when a participant explicitly exits the event because
181
- // he no longer wants to participate
182
- //
183
- this.participants.purge(anearParticipant)
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
- async participantAction(anearParticipant, actionEventName, actionPayload) {
189
- await this.participantActionEventCallback(anearParticipant, actionEventName, actionPayload)
230
+ participantAction(participant, actionEventName, actionPayload) {
231
+ this.anearStateMachine.sendActionEvent(actionEventName, {participant, payload: actionPayload})
190
232
  }
191
233
 
192
- async participantTimedOut(anearParticipant) {
193
- await this.participantTimedOutEventCallback(anearParticipant)
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.state)
264
+ return !['closing', 'closed', 'canceled'].includes(this.eventState)
219
265
  }
220
266
 
221
267
  async transitionToAnnounce() {
222
- if (this.state === 'created') await this.transitionEvent('announce')
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
- this.emitter.removeAllListeners()
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 = AnearEvent
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._appData = json.appData || this.initAppData()
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
- appData: this._appData,
19
+ context: this.context
21
20
  }
22
21
  }
23
22
 
24
- initAppData() {
25
- return {} // override in subclass if desired
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
+ }
@@ -1,13 +1,17 @@
1
- const node_logger = require('node-logger')
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 _logger = node_logger.createLogger(process.env.ANEARAPP_LOGGER_FILE)
5
- _logger.setLevel(process.env.ANEARAPP_LOGGER_LEVEL||defaultLevel)
6
- _logger.format = (level, date, message) => {
7
- return date.toJSON() + ": " + message
7
+ const opts = {
8
+ logFilePath: process.env.ANEARAPP_LOGGER_FILE || defaultLogFilePath,
9
+ timestampFormat: defaultTimestamp
8
10
  }
9
- return _logger
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