anear-js-api 1.1.5 → 1.1.6
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.
|
@@ -1312,18 +1312,6 @@ const AnearEventMachineFunctions = ({
|
|
|
1312
1312
|
|
|
1313
1313
|
const spectatorEvent = { type: 'SPECTATOR_ENTER', data: userJSON }
|
|
1314
1314
|
context.appEventMachine.send(spectatorEvent)
|
|
1315
|
-
|
|
1316
|
-
// v5: AppM snapshots often don't include the triggering event, so the
|
|
1317
|
-
// AppMachineTransition subscriber can't reliably "refresh render" on
|
|
1318
|
-
// presence events. Explicitly invoke the transition hook here so
|
|
1319
|
-
// spectators always get the current view.
|
|
1320
|
-
if (typeof context.appMachineTransition === 'function') {
|
|
1321
|
-
try {
|
|
1322
|
-
context.appMachineTransition(context.appEventMachine.getSnapshot(), spectatorEvent)
|
|
1323
|
-
} catch (e) {
|
|
1324
|
-
logger.warn('[AEM] AppMachineTransition failed during SPECTATOR_ENTER refresh', e)
|
|
1325
|
-
}
|
|
1326
|
-
}
|
|
1327
1315
|
}
|
|
1328
1316
|
)
|
|
1329
1317
|
},
|
|
@@ -1372,15 +1360,6 @@ const AnearEventMachineFunctions = ({
|
|
|
1372
1360
|
if (context.appEventMachine) {
|
|
1373
1361
|
const reconnectEvent = { type: eventName, participantId }
|
|
1374
1362
|
context.appEventMachine.send(reconnectEvent)
|
|
1375
|
-
|
|
1376
|
-
// Explicitly refresh meta-driven displays on reconnect (browser refresh)
|
|
1377
|
-
if (typeof context.appMachineTransition === 'function') {
|
|
1378
|
-
try {
|
|
1379
|
-
context.appMachineTransition(context.appEventMachine.getSnapshot(), reconnectEvent)
|
|
1380
|
-
} catch (e) {
|
|
1381
|
-
logger.warn('[AEM] AppMachineTransition failed during RECONNECT refresh', e)
|
|
1382
|
-
}
|
|
1383
|
-
}
|
|
1384
1363
|
}
|
|
1385
1364
|
},
|
|
1386
1365
|
updateParticipantGeoLocation: assign({
|
|
@@ -1425,15 +1404,6 @@ const AnearEventMachineFunctions = ({
|
|
|
1425
1404
|
}
|
|
1426
1405
|
if (context.appEventMachine) {
|
|
1427
1406
|
context.appEventMachine.send(appMPayload);
|
|
1428
|
-
|
|
1429
|
-
// Explicitly refresh meta-driven displays on UPDATE (e.g. geo changes)
|
|
1430
|
-
if (typeof context.appMachineTransition === 'function') {
|
|
1431
|
-
try {
|
|
1432
|
-
context.appMachineTransition(context.appEventMachine.getSnapshot(), appMPayload)
|
|
1433
|
-
} catch (e) {
|
|
1434
|
-
logger.warn('[AEM] AppMachineTransition failed during UPDATE refresh', e)
|
|
1435
|
-
}
|
|
1436
|
-
}
|
|
1437
1407
|
}
|
|
1438
1408
|
},
|
|
1439
1409
|
sendExitToAppMachine: ({ context, event }) => {
|
|
@@ -1699,19 +1669,118 @@ const AnearEventMachineFunctions = ({
|
|
|
1699
1669
|
}
|
|
1700
1670
|
}
|
|
1701
1671
|
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
//
|
|
1672
|
+
// Observe AppM transitions with full event payloads.
|
|
1673
|
+
// v5 note: `subscribe()` only provides snapshots, which often do not include
|
|
1674
|
+
// the triggering event. We use `inspect` so AppMachineTransition always gets
|
|
1675
|
+
// (snapshot, event) for meta.eachParticipant(ctx, event) and timeout payloads.
|
|
1706
1676
|
const appMachineTransition = AppMachineTransition(context.anearEvent)
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1677
|
+
|
|
1678
|
+
const REFRESH_EVENT_TYPES = AppMachineTransition.REFRESH_EVENT_TYPES
|
|
1679
|
+
|
|
1680
|
+
const defer =
|
|
1681
|
+
(typeof queueMicrotask === 'function')
|
|
1682
|
+
? queueMicrotask
|
|
1683
|
+
: (fn) => Promise.resolve().then(fn)
|
|
1684
|
+
|
|
1685
|
+
let appActor
|
|
1686
|
+
let lastRefreshEvent = null
|
|
1687
|
+
let lastRefreshHandledBySnapshot = false
|
|
1688
|
+
let sawAnySnapshot = false
|
|
1689
|
+
|
|
1690
|
+
const inspect = (inspectionEvent) => {
|
|
1691
|
+
if (!inspectionEvent || !appActor) return
|
|
1692
|
+
|
|
1693
|
+
// Only observe the root AppM actor; ignore child actors.
|
|
1694
|
+
if (inspectionEvent.actorRef !== appActor) return
|
|
1695
|
+
|
|
1696
|
+
if (inspectionEvent.type === '@xstate.snapshot') {
|
|
1697
|
+
const { snapshot, event } = inspectionEvent
|
|
1698
|
+
sawAnySnapshot = true
|
|
1699
|
+
try {
|
|
1700
|
+
logger.debug('[AEM][inspect] @xstate.snapshot', {
|
|
1701
|
+
eventType: event?.type,
|
|
1702
|
+
participantId: event?.participantId,
|
|
1703
|
+
nonResponderIds: event?.nonResponderIds,
|
|
1704
|
+
value: snapshot?.value,
|
|
1705
|
+
status: snapshot?.status
|
|
1706
|
+
})
|
|
1707
|
+
} catch (_e) {
|
|
1708
|
+
// logging should never interrupt state processing
|
|
1709
|
+
}
|
|
1710
|
+
try {
|
|
1711
|
+
appMachineTransition(snapshot, event)
|
|
1712
|
+
} catch (e) {
|
|
1713
|
+
logger.warn('[AEM] AppMachineTransition failed during inspect snapshot', e)
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
// If we deferred a "refresh render" for this exact event, mark it as handled
|
|
1717
|
+
// so the fallback won't run and double-render.
|
|
1718
|
+
if (event && lastRefreshEvent && event === lastRefreshEvent) {
|
|
1719
|
+
lastRefreshHandledBySnapshot = true
|
|
1720
|
+
logger.debug('[AEM][inspect] refresh event handled by snapshot (suppress fallback)', {
|
|
1721
|
+
eventType: event?.type,
|
|
1722
|
+
participantId: event?.participantId
|
|
1723
|
+
})
|
|
1724
|
+
}
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
if (inspectionEvent.type === '@xstate.event') {
|
|
1728
|
+
const ev = inspectionEvent.event
|
|
1729
|
+
if (!ev || !ev.type) return
|
|
1730
|
+
|
|
1731
|
+
// Some presence refresh events are internal/no-op transitions, which may not
|
|
1732
|
+
// produce a snapshot update. Defer a refresh render and cancel it if a
|
|
1733
|
+
// snapshot update for the same event happens in the same tick.
|
|
1734
|
+
if (REFRESH_EVENT_TYPES.has(ev.type)) {
|
|
1735
|
+
try {
|
|
1736
|
+
logger.debug('[AEM][inspect] @xstate.event (refresh candidate)', {
|
|
1737
|
+
eventType: ev?.type,
|
|
1738
|
+
participantId: ev?.participantId,
|
|
1739
|
+
nonResponderIds: ev?.nonResponderIds
|
|
1740
|
+
})
|
|
1741
|
+
} catch (_e) {
|
|
1742
|
+
// logging should never interrupt state processing
|
|
1743
|
+
}
|
|
1744
|
+
lastRefreshEvent = ev
|
|
1745
|
+
lastRefreshHandledBySnapshot = false
|
|
1746
|
+
|
|
1747
|
+
defer(() => {
|
|
1748
|
+
if (!appActor) return
|
|
1749
|
+
if (lastRefreshEvent !== ev) return
|
|
1750
|
+
if (lastRefreshHandledBySnapshot) return
|
|
1751
|
+
|
|
1752
|
+
logger.debug('[AEM][inspect] refresh fallback firing (no snapshot update)', {
|
|
1753
|
+
eventType: ev?.type,
|
|
1754
|
+
participantId: ev?.participantId
|
|
1755
|
+
})
|
|
1756
|
+
try {
|
|
1757
|
+
appMachineTransition(appActor.getSnapshot(), ev)
|
|
1758
|
+
} catch (e) {
|
|
1759
|
+
logger.warn('[AEM] AppMachineTransition failed during inspect refresh fallback', e)
|
|
1760
|
+
}
|
|
1761
|
+
})
|
|
1762
|
+
}
|
|
1763
|
+
}
|
|
1713
1764
|
}
|
|
1714
|
-
|
|
1765
|
+
|
|
1766
|
+
const actorOptions = actorInput
|
|
1767
|
+
? { input: actorInput, inspect }
|
|
1768
|
+
: { inspect }
|
|
1769
|
+
|
|
1770
|
+
appActor = createActor(machineToStart, actorOptions)
|
|
1771
|
+
appActor.start()
|
|
1772
|
+
|
|
1773
|
+
// v5: inspection generally emits an init snapshot, but keep a safe fallback
|
|
1774
|
+
// to match v4 onTransition behavior in case an init snapshot isn't observed.
|
|
1775
|
+
defer(() => {
|
|
1776
|
+
if (sawAnySnapshot) return
|
|
1777
|
+
try {
|
|
1778
|
+
logger.debug('[AEM][inspect] init snapshot fallback (no @xstate.snapshot observed yet)')
|
|
1779
|
+
appMachineTransition(appActor.getSnapshot(), { type: 'xstate.init' })
|
|
1780
|
+
} catch (e) {
|
|
1781
|
+
logger.warn('[AEM] AppMachineTransition failed on initial snapshot fallback', e)
|
|
1782
|
+
}
|
|
1783
|
+
})
|
|
1715
1784
|
|
|
1716
1785
|
// Auto-cleanup when AppM reaches done.
|
|
1717
1786
|
appActor.subscribe((snapshot) => {
|
|
@@ -3,6 +3,23 @@
|
|
|
3
3
|
const logger = require('./Logger')
|
|
4
4
|
const RenderContextBuilder = require('./RenderContextBuilder')
|
|
5
5
|
|
|
6
|
+
// Events that should force a "refresh render" even if the AppM did not
|
|
7
|
+
// transition (no-op/internal transitions, presence refreshes, etc.).
|
|
8
|
+
const REFRESH_EVENT_TYPES = new Set([
|
|
9
|
+
'SPECTATOR_ENTER',
|
|
10
|
+
'PARTICIPANT_ENTER',
|
|
11
|
+
'HOST_ENTER',
|
|
12
|
+
// Browser refresh / quick leave+rejoin cases:
|
|
13
|
+
'PARTICIPANT_RECONNECT',
|
|
14
|
+
'HOST_RECONNECT',
|
|
15
|
+
// Temporary disconnects (useful for "halt/pause" and UI indicators):
|
|
16
|
+
'PARTICIPANT_DISCONNECT',
|
|
17
|
+
'HOST_DISCONNECT',
|
|
18
|
+
// Location/heartbeat updates can also want a "refresh current view":
|
|
19
|
+
'PARTICIPANT_UPDATE',
|
|
20
|
+
'HOST_UPDATE'
|
|
21
|
+
])
|
|
22
|
+
|
|
6
23
|
/**
|
|
7
24
|
* AppMachineTransition:
|
|
8
25
|
* - Runs inside your appEventMachine onTransition.
|
|
@@ -19,17 +36,6 @@ const AppMachineTransition = (anearEvent) => {
|
|
|
19
36
|
// - Never re-render on the RENDERED ack (prevents ping-pong loops)
|
|
20
37
|
let lastBaseSignature = null
|
|
21
38
|
|
|
22
|
-
const REFRESH_EVENT_TYPES = new Set([
|
|
23
|
-
'SPECTATOR_ENTER',
|
|
24
|
-
'PARTICIPANT_ENTER',
|
|
25
|
-
// Browser refresh / quick leave+rejoin cases:
|
|
26
|
-
'PARTICIPANT_RECONNECT',
|
|
27
|
-
'HOST_RECONNECT',
|
|
28
|
-
// Location/heartbeat updates can also want a "refresh current view":
|
|
29
|
-
'PARTICIPANT_UPDATE',
|
|
30
|
-
'HOST_UPDATE'
|
|
31
|
-
])
|
|
32
|
-
|
|
33
39
|
return (appEventMachineState, triggeringEvent = null) => {
|
|
34
40
|
// Handle potential XState version differences and missing properties
|
|
35
41
|
const stateObj = appEventMachineState || {}
|
|
@@ -156,7 +162,7 @@ const AppMachineTransition = (anearEvent) => {
|
|
|
156
162
|
const { viewPath, props } = _extractViewAndProps(meta.eachParticipant)
|
|
157
163
|
const timeoutFn = RenderContextBuilder.buildTimeoutFn('participant', meta.eachParticipant.timeout)
|
|
158
164
|
|
|
159
|
-
const renderContext = RenderContextBuilder.buildAppRenderContext(appContext, appStateName, event.type, 'eachParticipant', timeoutFn)
|
|
165
|
+
const renderContext = RenderContextBuilder.buildAppRenderContext(appContext, appStateName, event.type, 'eachParticipant', timeoutFn)
|
|
160
166
|
displayEvent = RenderContextBuilder.buildDisplayEvent(
|
|
161
167
|
viewPath,
|
|
162
168
|
renderContext,
|
|
@@ -237,4 +243,7 @@ const _stringifiedState = (stateValue) => {
|
|
|
237
243
|
return Object.entries(stateValue).map(([k, v]) => `${k}.${_stringifiedState(v)}`).join('.')
|
|
238
244
|
}
|
|
239
245
|
|
|
246
|
+
// Export the refresh-event allowlist so AEM can share it (prevents drift).
|
|
247
|
+
AppMachineTransition.REFRESH_EVENT_TYPES = REFRESH_EVENT_TYPES
|
|
248
|
+
|
|
240
249
|
module.exports = AppMachineTransition
|