anear-js-api 1.2.2 → 1.2.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.
|
@@ -166,7 +166,8 @@ const AnearEventMachineContext = (
|
|
|
166
166
|
suppressParticipantTimeouts: false, // Flag to suppress PARTICIPANT_TIMEOUT events (cleared when new timers are set up via render)
|
|
167
167
|
eachParticipantCancelActionType: null, // ACTION type that triggers cancellation for eachParticipant timeouts
|
|
168
168
|
pendingCancelConfirmations: null, // Set of participantIds waiting for TIMER_CANCELED confirmation
|
|
169
|
-
pendingCancelAction: null // ACTION to forward after cancellation completes: { participantId, appEventName, payload }
|
|
169
|
+
pendingCancelAction: null, // ACTION to forward after cancellation completes: { participantId, appEventName, payload }
|
|
170
|
+
consecutiveTimeoutCounts: {} // { participantId: count } tracking consecutive timeouts per participant for dead-man switch
|
|
170
171
|
})
|
|
171
172
|
|
|
172
173
|
const ActiveEventGlobalEvents = {
|
|
@@ -278,7 +279,7 @@ const ActiveEventStatesConfig = {
|
|
|
278
279
|
],
|
|
279
280
|
on: {
|
|
280
281
|
ACTION: {
|
|
281
|
-
actions: ['processParticipantAction']
|
|
282
|
+
actions: ['resetParticipantTimeoutCount', 'processParticipantAction']
|
|
282
283
|
},
|
|
283
284
|
RENDER_DISPLAY: {
|
|
284
285
|
target: '#createdRendering'
|
|
@@ -447,7 +448,7 @@ const ActiveEventStatesConfig = {
|
|
|
447
448
|
initial: 'transitioning',
|
|
448
449
|
on: {
|
|
449
450
|
ACTION: {
|
|
450
|
-
actions: ['processParticipantAction']
|
|
451
|
+
actions: ['resetParticipantTimeoutCount', 'processParticipantAction']
|
|
451
452
|
},
|
|
452
453
|
RENDER_DISPLAY: {
|
|
453
454
|
target: '#announceRendering'
|
|
@@ -629,6 +630,11 @@ const ActiveEventStatesConfig = {
|
|
|
629
630
|
},
|
|
630
631
|
CANCEL_ALL_PARTICIPANT_TIMEOUTS: {
|
|
631
632
|
actions: 'cancelAllParticipantTimeouts'
|
|
633
|
+
},
|
|
634
|
+
// Handle PARTICIPANT_TIMEOUT even when in liveRendering state
|
|
635
|
+
// (events bubble up from child states, so we need to handle them here)
|
|
636
|
+
PARTICIPANT_TIMEOUT: {
|
|
637
|
+
actions: ['trackParticipantTimeout', 'processParticipantTimeout']
|
|
632
638
|
}
|
|
633
639
|
},
|
|
634
640
|
states: {
|
|
@@ -681,7 +687,7 @@ const ActiveEventStatesConfig = {
|
|
|
681
687
|
},
|
|
682
688
|
// Forward per-participant timeouts (from APM) to the AppM
|
|
683
689
|
PARTICIPANT_TIMEOUT: {
|
|
684
|
-
actions: ['processParticipantTimeout'],
|
|
690
|
+
actions: ['trackParticipantTimeout', 'processParticipantTimeout'],
|
|
685
691
|
// v5 note: internal transitions are the default (v4 had `internal: true`)
|
|
686
692
|
},
|
|
687
693
|
TRIGGER_CANCEL_SEQUENCE: {
|
|
@@ -794,7 +800,7 @@ const ActiveEventStatesConfig = {
|
|
|
794
800
|
target: 'handleParticipantsTimeoutCanceled'
|
|
795
801
|
},
|
|
796
802
|
{
|
|
797
|
-
actions: ['processAndForwardAction', 'processParticipantResponse'],
|
|
803
|
+
actions: ['resetParticipantTimeoutCount', 'processAndForwardAction', 'processParticipantResponse'],
|
|
798
804
|
// v5 note: internal transitions are the default (v4 had `internal: true`)
|
|
799
805
|
}
|
|
800
806
|
],
|
|
@@ -1831,6 +1837,17 @@ const AnearEventMachineFunctions = ({
|
|
|
1831
1837
|
const eventName = getPresenceEventName(participant, 'UPDATE');
|
|
1832
1838
|
context.appEventMachine.send({ type: eventName, data: event.data });
|
|
1833
1839
|
},
|
|
1840
|
+
resetParticipantTimeoutCount: assign(({ context, event }) => {
|
|
1841
|
+
const participantId = event.data.participantId
|
|
1842
|
+
// Reset consecutive timeout count when participant sends an action
|
|
1843
|
+
if (context.consecutiveTimeoutCounts[participantId]) {
|
|
1844
|
+
logger.debug(`[AEM] Resetting timeout count for participant ${participantId} (action received)`)
|
|
1845
|
+
const newCounts = { ...context.consecutiveTimeoutCounts }
|
|
1846
|
+
delete newCounts[participantId]
|
|
1847
|
+
return { consecutiveTimeoutCounts: newCounts }
|
|
1848
|
+
}
|
|
1849
|
+
return {}
|
|
1850
|
+
}),
|
|
1834
1851
|
processParticipantAction: ({ context, event, self }) => {
|
|
1835
1852
|
// event.data.participantId,
|
|
1836
1853
|
// event.data.payload: {"appEventMachineACTION": {action event keys and values}}
|
|
@@ -1916,7 +1933,21 @@ const AnearEventMachineFunctions = ({
|
|
|
1916
1933
|
}
|
|
1917
1934
|
}
|
|
1918
1935
|
}),
|
|
1919
|
-
|
|
1936
|
+
trackParticipantTimeout: assign(({ context, event }) => {
|
|
1937
|
+
const participantId = event.participantId
|
|
1938
|
+
const currentCount = context.consecutiveTimeoutCounts[participantId] || 0
|
|
1939
|
+
const newCount = currentCount + 1
|
|
1940
|
+
|
|
1941
|
+
logger.debug(`[AEM] Participant ${participantId} timeout count: ${currentCount} -> ${newCount}`)
|
|
1942
|
+
|
|
1943
|
+
return {
|
|
1944
|
+
consecutiveTimeoutCounts: {
|
|
1945
|
+
...context.consecutiveTimeoutCounts,
|
|
1946
|
+
[participantId]: newCount
|
|
1947
|
+
}
|
|
1948
|
+
}
|
|
1949
|
+
}),
|
|
1950
|
+
processParticipantTimeout: ({ context, event, self }) => {
|
|
1920
1951
|
// Suppress PARTICIPANT_TIMEOUT events after cancelAllParticipantTimeouts() until new timers
|
|
1921
1952
|
// are set up via render. This handles race conditions where an APM's timer fires just
|
|
1922
1953
|
// before receiving CANCEL_TIMEOUT. The flag is cleared when renders complete (new timers set up).
|
|
@@ -1924,7 +1955,19 @@ const AnearEventMachineFunctions = ({
|
|
|
1924
1955
|
logger.debug(`[AEM] Suppressing PARTICIPANT_TIMEOUT from ${event.participantId} (cancelled timers, waiting for new render)`)
|
|
1925
1956
|
return
|
|
1926
1957
|
}
|
|
1927
|
-
|
|
1958
|
+
|
|
1959
|
+
const participantId = event.participantId
|
|
1960
|
+
const timeoutCount = context.consecutiveTimeoutCounts[participantId] || 0
|
|
1961
|
+
const maxConsecutiveTimeouts = C.DEAD_MAN_SWITCH.MAX_CONSECUTIVE_PARTICIPANT_TIMEOUTS
|
|
1962
|
+
|
|
1963
|
+
// Check dead-man switch: if participant has exceeded consecutive timeout threshold, cancel event
|
|
1964
|
+
if (timeoutCount >= maxConsecutiveTimeouts) {
|
|
1965
|
+
logger.warn(`[AEM] Dead-man switch triggered: Participant ${participantId} has ${timeoutCount} consecutive timeouts (threshold: ${maxConsecutiveTimeouts}). Auto-canceling event.`)
|
|
1966
|
+
self.send({ type: 'CANCEL' })
|
|
1967
|
+
return
|
|
1968
|
+
}
|
|
1969
|
+
|
|
1970
|
+
context.appEventMachine.send({ type: 'PARTICIPANT_TIMEOUT', participantId })
|
|
1928
1971
|
},
|
|
1929
1972
|
setupParticipantsTimeout: assign(({ context, event }) => {
|
|
1930
1973
|
// Only set up a new timeout if one is provided by the display render workflow
|
|
@@ -2001,11 +2044,20 @@ const AnearEventMachineFunctions = ({
|
|
|
2001
2044
|
}
|
|
2002
2045
|
};
|
|
2003
2046
|
}),
|
|
2004
|
-
sendActionsTimeoutToAppM: ({ context }) => {
|
|
2047
|
+
sendActionsTimeoutToAppM: ({ context, self }) => {
|
|
2005
2048
|
const { nonResponders, msecs } = context.participantsActionTimeout
|
|
2006
2049
|
const nonResponderIds = [...nonResponders]
|
|
2050
|
+
const allActiveParticipantIds = getActiveParticipantIds(context)
|
|
2051
|
+
|
|
2007
2052
|
logger.info(`[AEM] allParticipants timeout expired after ${msecs}ms. Non-responders: ${nonResponderIds.join(', ')}`)
|
|
2008
2053
|
|
|
2054
|
+
// Dead-man switch: if all participants timed out, auto-cancel the event
|
|
2055
|
+
if (nonResponders.size === allActiveParticipantIds.length && allActiveParticipantIds.length > 0) {
|
|
2056
|
+
logger.warn(`[AEM] Dead-man switch triggered: All ${allActiveParticipantIds.length} participants timed out in allParticipants timeout. Auto-canceling event.`)
|
|
2057
|
+
self.send({ type: 'CANCEL' })
|
|
2058
|
+
return
|
|
2059
|
+
}
|
|
2060
|
+
|
|
2009
2061
|
if (context.appEventMachine) {
|
|
2010
2062
|
context.appEventMachine.send({ type: 'ACTIONS_TIMEOUT', timeout: msecs, nonResponderIds })
|
|
2011
2063
|
}
|
|
@@ -2048,6 +2100,9 @@ const AnearEventMachineFunctions = ({
|
|
|
2048
2100
|
|
|
2049
2101
|
logger.debug(`[AEM] cleaning up exiting participant ${participantId}`)
|
|
2050
2102
|
|
|
2103
|
+
// Clean up timeout counts for exiting participant
|
|
2104
|
+
const { [participantId]: removedTimeoutCount, ...remainingTimeoutCounts } = context.consecutiveTimeoutCounts
|
|
2105
|
+
|
|
2051
2106
|
if (participant) {
|
|
2052
2107
|
const isOpenHouse = context.anearEvent.openHouse || false
|
|
2053
2108
|
|
|
@@ -2070,7 +2125,8 @@ const AnearEventMachineFunctions = ({
|
|
|
2070
2125
|
...context.participants,
|
|
2071
2126
|
[participantId]: updatedParticipant
|
|
2072
2127
|
},
|
|
2073
|
-
participantMachines: remainingMachines
|
|
2128
|
+
participantMachines: remainingMachines,
|
|
2129
|
+
consecutiveTimeoutCounts: remainingTimeoutCounts
|
|
2074
2130
|
}
|
|
2075
2131
|
} else {
|
|
2076
2132
|
// For non-open-house events: remove both participant and machine (existing behavior)
|
|
@@ -2086,11 +2142,14 @@ const AnearEventMachineFunctions = ({
|
|
|
2086
2142
|
|
|
2087
2143
|
return {
|
|
2088
2144
|
participants: remainingParticipants,
|
|
2089
|
-
participantMachines: remainingMachines
|
|
2145
|
+
participantMachines: remainingMachines,
|
|
2146
|
+
consecutiveTimeoutCounts: remainingTimeoutCounts
|
|
2090
2147
|
}
|
|
2091
2148
|
}
|
|
2092
2149
|
} else {
|
|
2093
|
-
return {
|
|
2150
|
+
return {
|
|
2151
|
+
consecutiveTimeoutCounts: remainingTimeoutCounts
|
|
2152
|
+
}
|
|
2094
2153
|
}
|
|
2095
2154
|
}),
|
|
2096
2155
|
notifyCoreServiceMachineExit: ({ context }) => {
|
package/lib/utils/Constants.js
CHANGED
|
@@ -16,6 +16,10 @@ module.exports = {
|
|
|
16
16
|
RECONNECT: 30 * 1000, // 30 seconds
|
|
17
17
|
BOOT_EXIT: 2 * 1000 // 5 seconds
|
|
18
18
|
},
|
|
19
|
+
DEAD_MAN_SWITCH: {
|
|
20
|
+
// Maximum consecutive timeouts per participant before auto-canceling event
|
|
21
|
+
MAX_CONSECUTIVE_PARTICIPANT_TIMEOUTS: 4
|
|
22
|
+
},
|
|
19
23
|
EventStates: {
|
|
20
24
|
CREATED: 'created',
|
|
21
25
|
STARTED: 'started',
|