anear-js-api 2.1.1 → 2.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/EACH_PARTICIPANT_LIFECYCLE.md +3 -1
- package/README.md +12 -19
- package/lib/AnearService.js +3 -3
- package/lib/models/AnearEvent.js +2 -4
- package/lib/state_machines/AnearCoreServiceMachine.js +3 -5
- package/lib/state_machines/AnearEventMachine.js +210 -366
- package/lib/utils/AssetFileCollector.js +0 -2
- package/lib/utils/DisplayEventProcessor.js +21 -40
- package/package.json +1 -1
- package/tests/DisplayEventProcessor.test.js +39 -26
- package/lib/state_machines/AnearParticipantMachine.js +0 -765
|
@@ -42,14 +42,12 @@ class AssetFileCollector {
|
|
|
42
42
|
await this.walkDirectory(fullPath, filesData);
|
|
43
43
|
} else if (entry.isFile()) {
|
|
44
44
|
const relativePath = path.relative(this.baseDir, fullPath).replace(/\\/g, '/'); // Ensure forward slashes
|
|
45
|
-
const stat = await fs.stat(fullPath)
|
|
46
45
|
const contentHash = await this.computeFileHash(fullPath);
|
|
47
46
|
const contentType = this.getContentType(fullPath);
|
|
48
47
|
filesData.push({
|
|
49
48
|
path: relativePath,
|
|
50
49
|
content_hash: contentHash,
|
|
51
50
|
content_type: contentType,
|
|
52
|
-
mtime_ms: stat.mtimeMs,
|
|
53
51
|
});
|
|
54
52
|
}
|
|
55
53
|
}
|
|
@@ -50,7 +50,7 @@ class DisplayEventProcessor {
|
|
|
50
50
|
this.participantsActionTimeout = anearEventMachineContext.participantsActionTimeout
|
|
51
51
|
this.pugTemplates = anearEventMachineContext.pugTemplates
|
|
52
52
|
this.pugHelpers = anearEventMachineContext.pugHelpers
|
|
53
|
-
this.
|
|
53
|
+
this.participantPrivateChannels = anearEventMachineContext.participantPrivateChannels || {}
|
|
54
54
|
this.participantsDisplayChannel = anearEventMachineContext.participantsDisplayChannel
|
|
55
55
|
this.spectatorsDisplayChannel = anearEventMachineContext.spectatorsDisplayChannel
|
|
56
56
|
this.participants = anearEventMachineContext.participants
|
|
@@ -329,32 +329,23 @@ class DisplayEventProcessor {
|
|
|
329
329
|
}
|
|
330
330
|
|
|
331
331
|
const hostId = hostEntry[0];
|
|
332
|
-
const
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
throw new Error(`[DisplayEventProcessor] Host participant machine not found for hostId: ${hostId}`)
|
|
332
|
+
const hostChannel = this.participantPrivateChannels[hostId]
|
|
333
|
+
if (!hostChannel) {
|
|
334
|
+
throw new Error(`[DisplayEventProcessor] Host private channel not found for hostId: ${hostId}`)
|
|
336
335
|
}
|
|
337
|
-
this._sendPrivateDisplay(
|
|
338
|
-
return Promise.resolve()
|
|
336
|
+
return this._sendPrivateDisplay(hostChannel, hostId, template, templateRenderContext, timeoutFn, anchor, type, targetsArray, content, contentType)
|
|
339
337
|
}
|
|
340
338
|
|
|
341
339
|
_processPrivateParticipantDisplays(template, templateRenderContext, timeoutFn, anchor = null, type = null, targetsArray = null, content = null, contentType = null) {
|
|
342
|
-
|
|
343
|
-
participantStruct => {
|
|
344
|
-
if (participantStruct.info.isHost) return
|
|
345
|
-
if (participantStruct.info.active === false) return;
|
|
346
|
-
|
|
340
|
+
return Promise.all(
|
|
341
|
+
Object.values(this.participantsIndex.all).map(participantStruct => {
|
|
342
|
+
if (participantStruct.info.isHost || participantStruct.info.active === false) return Promise.resolve()
|
|
347
343
|
const participantId = participantStruct.info.id
|
|
348
|
-
const
|
|
349
|
-
if (!
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
}
|
|
353
|
-
this._sendPrivateDisplay(participantMachine, participantId, template, templateRenderContext, timeoutFn, anchor, type, targetsArray, content, contentType)
|
|
354
|
-
}
|
|
344
|
+
const participantChannel = this.participantPrivateChannels[participantId]
|
|
345
|
+
if (!participantChannel) return Promise.resolve()
|
|
346
|
+
return this._sendPrivateDisplay(participantChannel, participantId, template, templateRenderContext, timeoutFn, anchor, type, targetsArray, content, contentType)
|
|
347
|
+
})
|
|
355
348
|
)
|
|
356
|
-
|
|
357
|
-
return Promise.resolve()
|
|
358
349
|
}
|
|
359
350
|
|
|
360
351
|
/**
|
|
@@ -380,9 +371,9 @@ class DisplayEventProcessor {
|
|
|
380
371
|
return { promise: Promise.resolve(), displaySent: false }
|
|
381
372
|
}
|
|
382
373
|
|
|
383
|
-
const
|
|
384
|
-
if (!
|
|
385
|
-
logger.debug(`[DisplayEventProcessor] Participant
|
|
374
|
+
const participantChannel = this.participantPrivateChannels[participantId]
|
|
375
|
+
if (!participantChannel) {
|
|
376
|
+
logger.debug(`[DisplayEventProcessor] Participant private channel not found for ${participantId} (exited); treating as delivered`)
|
|
386
377
|
return { promise: Promise.resolve(), displaySent: false }
|
|
387
378
|
}
|
|
388
379
|
|
|
@@ -390,8 +381,8 @@ class DisplayEventProcessor {
|
|
|
390
381
|
() => displayTimeout :
|
|
391
382
|
timeoutFn
|
|
392
383
|
|
|
393
|
-
this._sendPrivateDisplay(
|
|
394
|
-
return { promise
|
|
384
|
+
const promise = this._sendPrivateDisplay(participantChannel, participantId, template, templateRenderContext, effectiveTimeoutFn, anchor, type, targetsArray, content, contentType)
|
|
385
|
+
return { promise, displaySent: true }
|
|
395
386
|
}
|
|
396
387
|
|
|
397
388
|
_processSelectiveParticipantDisplay(template, templateRenderContext, timeoutFn, participantId, displayTimeout = null, anchor = null, type = null, targetsArray = null, content = null, contentType = null) {
|
|
@@ -399,7 +390,7 @@ class DisplayEventProcessor {
|
|
|
399
390
|
return promise
|
|
400
391
|
}
|
|
401
392
|
|
|
402
|
-
_sendPrivateDisplay(
|
|
393
|
+
_sendPrivateDisplay(participantChannel, participantId, template, templateRenderContext, timeoutFn, anchor = null, type = null, targetsArray = null, content = null, contentType = null) {
|
|
403
394
|
let timeout = null
|
|
404
395
|
|
|
405
396
|
const participantStruct = this.participantsIndex.get(participantId)
|
|
@@ -435,18 +426,7 @@ class DisplayEventProcessor {
|
|
|
435
426
|
const remainingMsecs = Math.max(0, pat.msecs - (now - start))
|
|
436
427
|
visualTimeout = { msecs: pat.msecs, remainingMsecs }
|
|
437
428
|
} else {
|
|
438
|
-
|
|
439
|
-
const ctx = state?.context
|
|
440
|
-
if (ctx && ctx.actionTimeoutStart && ctx.actionTimeoutMsecs != null && ctx.actionTimeoutMsecs > 0) {
|
|
441
|
-
const now = Date.now()
|
|
442
|
-
const elapsed = now - ctx.actionTimeoutStart
|
|
443
|
-
const remaining = ctx.actionTimeoutMsecs - elapsed
|
|
444
|
-
const remainingMsecs = remaining > 0 ? remaining : 0
|
|
445
|
-
visualTimeout = {
|
|
446
|
-
msecs: ctx.actionTimeoutMsecs,
|
|
447
|
-
remainingMsecs
|
|
448
|
-
}
|
|
449
|
-
} else if (timeout !== null) {
|
|
429
|
+
if (timeout !== null) {
|
|
450
430
|
visualTimeout = { msecs: timeout, remainingMsecs: timeout }
|
|
451
431
|
}
|
|
452
432
|
}
|
|
@@ -493,7 +473,8 @@ class DisplayEventProcessor {
|
|
|
493
473
|
renderMessage.timeout = timeout
|
|
494
474
|
}
|
|
495
475
|
|
|
496
|
-
|
|
476
|
+
EventStats.recordPublish(this.eventStats, participantChannel, 'PRIVATE_DISPLAY', renderMessage)
|
|
477
|
+
return RealtimeMessaging.publish(participantChannel, 'PRIVATE_DISPLAY', renderMessage)
|
|
497
478
|
}
|
|
498
479
|
|
|
499
480
|
_normalizeAnchorUpdateType(type) {
|
package/package.json
CHANGED
|
@@ -1,13 +1,17 @@
|
|
|
1
1
|
const DisplayEventProcessor = require('../lib/utils/DisplayEventProcessor')
|
|
2
|
+
const RealtimeMessaging = require('../lib/utils/RealtimeMessaging')
|
|
3
|
+
|
|
4
|
+
jest.mock('../lib/utils/RealtimeMessaging', () => ({
|
|
5
|
+
publish: jest.fn(() => Promise.resolve())
|
|
6
|
+
}))
|
|
2
7
|
|
|
3
8
|
const makeProcessor = (overrides = {}) => {
|
|
4
|
-
const participantMachine = { send: jest.fn(), state: { context: {} } }
|
|
5
9
|
const baseContext = {
|
|
6
10
|
anearEvent: {},
|
|
7
11
|
participantsActionTimeout: null,
|
|
8
12
|
pugTemplates: {},
|
|
9
13
|
pugHelpers: {},
|
|
10
|
-
|
|
14
|
+
participantPrivateChannels: { p1: 'private-channel-p1' },
|
|
11
15
|
participantsDisplayChannel: 'participants-channel',
|
|
12
16
|
spectatorsDisplayChannel: null,
|
|
13
17
|
participants: {
|
|
@@ -18,14 +22,17 @@ const makeProcessor = (overrides = {}) => {
|
|
|
18
22
|
}
|
|
19
23
|
|
|
20
24
|
return {
|
|
21
|
-
processor: new DisplayEventProcessor(baseContext)
|
|
22
|
-
participantMachine
|
|
25
|
+
processor: new DisplayEventProcessor(baseContext)
|
|
23
26
|
}
|
|
24
27
|
}
|
|
25
28
|
|
|
26
29
|
describe('DisplayEventProcessor anchor updates', () => {
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
jest.clearAllMocks()
|
|
32
|
+
})
|
|
33
|
+
|
|
27
34
|
test('sends direct text content to an anchor for eachParticipant', () => {
|
|
28
|
-
const { processor
|
|
35
|
+
const { processor } = makeProcessor()
|
|
29
36
|
|
|
30
37
|
processor._processSingle({
|
|
31
38
|
appRenderContext: { app: { score: 98 }, meta: { viewer: 'eachParticipant', timeoutFn: null } },
|
|
@@ -38,20 +45,23 @@ describe('DisplayEventProcessor anchor updates', () => {
|
|
|
38
45
|
props: {}
|
|
39
46
|
})
|
|
40
47
|
|
|
41
|
-
expect(
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
+
expect(RealtimeMessaging.publish).toHaveBeenCalledWith(
|
|
49
|
+
'private-channel-p1',
|
|
50
|
+
'PRIVATE_DISPLAY',
|
|
51
|
+
{
|
|
52
|
+
targets: {
|
|
53
|
+
seat_p1_score: {
|
|
54
|
+
content: '98',
|
|
55
|
+
contentType: 'text',
|
|
56
|
+
type: 'replace'
|
|
57
|
+
}
|
|
48
58
|
}
|
|
49
59
|
}
|
|
50
|
-
|
|
60
|
+
)
|
|
51
61
|
})
|
|
52
62
|
|
|
53
63
|
test('supports multi-target participant updates mixing text and html', () => {
|
|
54
|
-
const { processor
|
|
64
|
+
const { processor } = makeProcessor({
|
|
55
65
|
pugTemplates: {
|
|
56
66
|
'seat_label.pug': () => '<span>Seat A</span>'
|
|
57
67
|
}
|
|
@@ -68,20 +78,23 @@ describe('DisplayEventProcessor anchor updates', () => {
|
|
|
68
78
|
props: {}
|
|
69
79
|
})
|
|
70
80
|
|
|
71
|
-
expect(
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
81
|
+
expect(RealtimeMessaging.publish).toHaveBeenCalledWith(
|
|
82
|
+
'private-channel-p1',
|
|
83
|
+
'PRIVATE_DISPLAY',
|
|
84
|
+
{
|
|
85
|
+
targets: {
|
|
86
|
+
seat_p1_score: {
|
|
87
|
+
content: '12',
|
|
88
|
+
contentType: 'text',
|
|
89
|
+
type: 'replace'
|
|
90
|
+
},
|
|
91
|
+
seat_p1_label: {
|
|
92
|
+
content: '<span>Seat A</span>',
|
|
93
|
+
type: 'replace'
|
|
94
|
+
}
|
|
82
95
|
}
|
|
83
96
|
}
|
|
84
|
-
|
|
97
|
+
)
|
|
85
98
|
})
|
|
86
99
|
})
|
|
87
100
|
|