anear-js-api 1.2.3 → 1.3.0
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.
|
@@ -167,7 +167,8 @@ const AnearEventMachineContext = (
|
|
|
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
169
|
pendingCancelAction: null, // ACTION to forward after cancellation completes: { participantId, appEventName, payload }
|
|
170
|
-
|
|
170
|
+
consecutiveTimeoutCount: 0, // Global counter tracking consecutive timeouts across all participants for dead-man switch
|
|
171
|
+
consecutiveAllParticipantsTimeoutCount: 0 // Counter tracking consecutive allParticipants timeouts where ALL participants timed out
|
|
171
172
|
})
|
|
172
173
|
|
|
173
174
|
const ActiveEventGlobalEvents = {
|
|
@@ -683,7 +684,7 @@ const ActiveEventStatesConfig = {
|
|
|
683
684
|
},
|
|
684
685
|
on: {
|
|
685
686
|
ACTION: {
|
|
686
|
-
actions: ['processParticipantAction']
|
|
687
|
+
actions: ['resetParticipantTimeoutCount', 'processParticipantAction']
|
|
687
688
|
},
|
|
688
689
|
// Forward per-participant timeouts (from APM) to the AppM
|
|
689
690
|
PARTICIPANT_TIMEOUT: {
|
|
@@ -741,7 +742,7 @@ const ActiveEventStatesConfig = {
|
|
|
741
742
|
always: {
|
|
742
743
|
guard: 'allParticipantsResponded',
|
|
743
744
|
target: 'waitingForActions',
|
|
744
|
-
actions: ['clearParticipantsTimeout']
|
|
745
|
+
actions: ['sendActionsCompleteToAppM', 'clearParticipantsTimeout']
|
|
745
746
|
},
|
|
746
747
|
after: {
|
|
747
748
|
participantsActionTimeout: {
|
|
@@ -1839,14 +1840,17 @@ const AnearEventMachineFunctions = ({
|
|
|
1839
1840
|
},
|
|
1840
1841
|
resetParticipantTimeoutCount: assign(({ context, event }) => {
|
|
1841
1842
|
const participantId = event.data.participantId
|
|
1842
|
-
// Reset consecutive timeout
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
return { consecutiveTimeoutCounts: newCounts }
|
|
1843
|
+
// Reset global consecutive timeout counts when ANY participant sends an action
|
|
1844
|
+
const updates = {}
|
|
1845
|
+
if (context.consecutiveTimeoutCount > 0) {
|
|
1846
|
+
logger.debug(`[AEM] Resetting global timeout count from ${context.consecutiveTimeoutCount} to 0 (action received from ${participantId})`)
|
|
1847
|
+
updates.consecutiveTimeoutCount = 0
|
|
1848
1848
|
}
|
|
1849
|
-
|
|
1849
|
+
if (context.consecutiveAllParticipantsTimeoutCount > 0) {
|
|
1850
|
+
logger.debug(`[AEM] Resetting allParticipants timeout count from ${context.consecutiveAllParticipantsTimeoutCount} to 0 (action received from ${participantId})`)
|
|
1851
|
+
updates.consecutiveAllParticipantsTimeoutCount = 0
|
|
1852
|
+
}
|
|
1853
|
+
return updates
|
|
1850
1854
|
}),
|
|
1851
1855
|
processParticipantAction: ({ context, event, self }) => {
|
|
1852
1856
|
// event.data.participantId,
|
|
@@ -1935,16 +1939,13 @@ const AnearEventMachineFunctions = ({
|
|
|
1935
1939
|
}),
|
|
1936
1940
|
trackParticipantTimeout: assign(({ context, event }) => {
|
|
1937
1941
|
const participantId = event.participantId
|
|
1938
|
-
const currentCount = context.
|
|
1942
|
+
const currentCount = context.consecutiveTimeoutCount || 0
|
|
1939
1943
|
const newCount = currentCount + 1
|
|
1940
1944
|
|
|
1941
|
-
logger.debug(`[AEM]
|
|
1945
|
+
logger.debug(`[AEM] Global timeout count (any participant): ${currentCount} -> ${newCount} (timeout from ${participantId})`)
|
|
1942
1946
|
|
|
1943
1947
|
return {
|
|
1944
|
-
|
|
1945
|
-
...context.consecutiveTimeoutCounts,
|
|
1946
|
-
[participantId]: newCount
|
|
1947
|
-
}
|
|
1948
|
+
consecutiveTimeoutCount: newCount
|
|
1948
1949
|
}
|
|
1949
1950
|
}),
|
|
1950
1951
|
processParticipantTimeout: ({ context, event, self }) => {
|
|
@@ -1957,12 +1958,13 @@ const AnearEventMachineFunctions = ({
|
|
|
1957
1958
|
}
|
|
1958
1959
|
|
|
1959
1960
|
const participantId = event.participantId
|
|
1960
|
-
const timeoutCount = context.
|
|
1961
|
+
const timeoutCount = context.consecutiveTimeoutCount || 0
|
|
1961
1962
|
const maxConsecutiveTimeouts = C.DEAD_MAN_SWITCH.MAX_CONSECUTIVE_PARTICIPANT_TIMEOUTS
|
|
1962
1963
|
|
|
1963
|
-
// Check dead-man switch: if
|
|
1964
|
+
// Check dead-man switch: if global consecutive timeout threshold reached, cancel event
|
|
1965
|
+
// This detects when the game is "dead" (no actions happening) across all participants
|
|
1964
1966
|
if (timeoutCount >= maxConsecutiveTimeouts) {
|
|
1965
|
-
logger.warn(`[AEM] Dead-man switch triggered:
|
|
1967
|
+
logger.warn(`[AEM] Dead-man switch triggered: ${timeoutCount} consecutive timeouts across all participants (threshold: ${maxConsecutiveTimeouts}). Auto-canceling event.`)
|
|
1966
1968
|
self.send({ type: 'CANCEL' })
|
|
1967
1969
|
return
|
|
1968
1970
|
}
|
|
@@ -2044,30 +2046,70 @@ const AnearEventMachineFunctions = ({
|
|
|
2044
2046
|
}
|
|
2045
2047
|
};
|
|
2046
2048
|
}),
|
|
2047
|
-
|
|
2049
|
+
sendActionsCompleteToAppM: assign(({ context }) => {
|
|
2050
|
+
// When all participants respond, the final ACTION already has finalAction: true
|
|
2051
|
+
// No need to send a separate ACTIONS_COMPLETE event - the finalAction flag handles it
|
|
2052
|
+
// Reset allParticipants timeout counter when all participants respond
|
|
2053
|
+
if (context.consecutiveAllParticipantsTimeoutCount > 0) {
|
|
2054
|
+
logger.debug(`[AEM] Resetting allParticipants timeout count from ${context.consecutiveAllParticipantsTimeoutCount} to 0 (all participants responded)`)
|
|
2055
|
+
return { consecutiveAllParticipantsTimeoutCount: 0 }
|
|
2056
|
+
}
|
|
2057
|
+
return {}
|
|
2058
|
+
}),
|
|
2059
|
+
sendActionsTimeoutToAppM: assign(({ context, self }) => {
|
|
2048
2060
|
const { nonResponders, msecs } = context.participantsActionTimeout
|
|
2049
2061
|
const nonResponderIds = [...nonResponders]
|
|
2050
2062
|
const allActiveParticipantIds = getActiveParticipantIds(context)
|
|
2051
2063
|
|
|
2052
2064
|
logger.info(`[AEM] allParticipants timeout expired after ${msecs}ms. Non-responders: ${nonResponderIds.join(', ')}`)
|
|
2053
2065
|
|
|
2054
|
-
//
|
|
2055
|
-
|
|
2056
|
-
|
|
2057
|
-
|
|
2058
|
-
|
|
2066
|
+
// Track consecutive allParticipants timeouts where ALL participants timed out
|
|
2067
|
+
const allTimedOut = nonResponders.size === allActiveParticipantIds.length && allActiveParticipantIds.length > 0
|
|
2068
|
+
const updates = {}
|
|
2069
|
+
|
|
2070
|
+
if (allTimedOut) {
|
|
2071
|
+
const currentCount = context.consecutiveAllParticipantsTimeoutCount || 0
|
|
2072
|
+
const newCount = currentCount + 1
|
|
2073
|
+
logger.debug(`[AEM] All participants timed out in allParticipants timeout. Count: ${currentCount} -> ${newCount}`)
|
|
2074
|
+
updates.consecutiveAllParticipantsTimeoutCount = newCount
|
|
2075
|
+
|
|
2076
|
+
// Dead-man switch: if 4 consecutive allParticipants timeouts where all timed out, cancel event
|
|
2077
|
+
const maxConsecutiveTimeouts = C.DEAD_MAN_SWITCH.MAX_CONSECUTIVE_PARTICIPANT_TIMEOUTS
|
|
2078
|
+
if (newCount >= maxConsecutiveTimeouts) {
|
|
2079
|
+
logger.warn(`[AEM] Dead-man switch triggered: ${maxConsecutiveTimeouts} consecutive allParticipants timeouts where all participants timed out. Auto-canceling event.`)
|
|
2080
|
+
self.send({ type: 'CANCEL' })
|
|
2081
|
+
return updates
|
|
2082
|
+
}
|
|
2083
|
+
} else {
|
|
2084
|
+
// Not all timed out - reset the counter
|
|
2085
|
+
if (context.consecutiveAllParticipantsTimeoutCount > 0) {
|
|
2086
|
+
logger.debug(`[AEM] Resetting allParticipants timeout count from ${context.consecutiveAllParticipantsTimeoutCount} to 0 (not all participants timed out)`)
|
|
2087
|
+
updates.consecutiveAllParticipantsTimeoutCount = 0
|
|
2088
|
+
}
|
|
2059
2089
|
}
|
|
2060
2090
|
|
|
2061
2091
|
if (context.appEventMachine) {
|
|
2092
|
+
// Send ACTIONS_TIMEOUT when timeout expires
|
|
2093
|
+
logger.info(`[AEM] Sending ACTIONS_TIMEOUT (timeout expired)`)
|
|
2062
2094
|
context.appEventMachine.send({ type: 'ACTIONS_TIMEOUT', timeout: msecs, nonResponderIds })
|
|
2063
2095
|
}
|
|
2064
|
-
|
|
2096
|
+
|
|
2097
|
+
return updates
|
|
2098
|
+
}),
|
|
2065
2099
|
sendActionsTimeoutToAppMCanceled: ({ context }) => {
|
|
2066
2100
|
const { nonResponders, msecs } = context.participantsActionTimeout
|
|
2067
2101
|
const nonResponderIds = [...nonResponders]
|
|
2068
2102
|
logger.info(`[AEM] allParticipants timeout cancelled after ${msecs}ms. Non-responders at cancellation: ${nonResponderIds.join(', ')}`)
|
|
2069
2103
|
|
|
2070
2104
|
if (context.appEventMachine) {
|
|
2105
|
+
// Send ACTIONS_TIMEOUT when timeout is canceled (backward compatibility)
|
|
2106
|
+
context.appEventMachine.send({
|
|
2107
|
+
type: 'ACTIONS_TIMEOUT',
|
|
2108
|
+
timeout: msecs,
|
|
2109
|
+
nonResponderIds,
|
|
2110
|
+
canceled: true
|
|
2111
|
+
})
|
|
2112
|
+
// Keep ACTIONS_TIMEOUT for backward compatibility (deprecated)
|
|
2071
2113
|
context.appEventMachine.send({ type: 'ACTIONS_TIMEOUT', timeout: msecs, nonResponderIds, canceled: true })
|
|
2072
2114
|
}
|
|
2073
2115
|
},
|
|
@@ -2100,9 +2142,6 @@ const AnearEventMachineFunctions = ({
|
|
|
2100
2142
|
|
|
2101
2143
|
logger.debug(`[AEM] cleaning up exiting participant ${participantId}`)
|
|
2102
2144
|
|
|
2103
|
-
// Clean up timeout counts for exiting participant
|
|
2104
|
-
const { [participantId]: removedTimeoutCount, ...remainingTimeoutCounts } = context.consecutiveTimeoutCounts
|
|
2105
|
-
|
|
2106
2145
|
if (participant) {
|
|
2107
2146
|
const isOpenHouse = context.anearEvent.openHouse || false
|
|
2108
2147
|
|
|
@@ -2125,8 +2164,7 @@ const AnearEventMachineFunctions = ({
|
|
|
2125
2164
|
...context.participants,
|
|
2126
2165
|
[participantId]: updatedParticipant
|
|
2127
2166
|
},
|
|
2128
|
-
participantMachines: remainingMachines
|
|
2129
|
-
consecutiveTimeoutCounts: remainingTimeoutCounts
|
|
2167
|
+
participantMachines: remainingMachines
|
|
2130
2168
|
}
|
|
2131
2169
|
} else {
|
|
2132
2170
|
// For non-open-house events: remove both participant and machine (existing behavior)
|
|
@@ -2142,14 +2180,11 @@ const AnearEventMachineFunctions = ({
|
|
|
2142
2180
|
|
|
2143
2181
|
return {
|
|
2144
2182
|
participants: remainingParticipants,
|
|
2145
|
-
participantMachines: remainingMachines
|
|
2146
|
-
consecutiveTimeoutCounts: remainingTimeoutCounts
|
|
2183
|
+
participantMachines: remainingMachines
|
|
2147
2184
|
}
|
|
2148
2185
|
}
|
|
2149
2186
|
} else {
|
|
2150
|
-
return {
|
|
2151
|
-
consecutiveTimeoutCounts: remainingTimeoutCounts
|
|
2152
|
-
}
|
|
2187
|
+
return {}
|
|
2153
2188
|
}
|
|
2154
2189
|
}),
|
|
2155
2190
|
notifyCoreServiceMachineExit: ({ context }) => {
|
|
@@ -365,9 +365,20 @@ class DisplayEventProcessor {
|
|
|
365
365
|
if (timeoutFn) {
|
|
366
366
|
// For participant displays, any non-null / positive timeout value should
|
|
367
367
|
// start a real per-participant timer on that participant's APM.
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
368
|
+
if (!participantStruct) {
|
|
369
|
+
logger.warn(`[DisplayEventProcessor] Participant struct not found for ${participantId}, skipping timeout calculation`)
|
|
370
|
+
} else {
|
|
371
|
+
try {
|
|
372
|
+
const msecs = timeoutFn(appCtx, participantStruct)
|
|
373
|
+
if (typeof msecs === 'number' && msecs > 0) {
|
|
374
|
+
timeout = msecs
|
|
375
|
+
} else {
|
|
376
|
+
// Log when timeout function returns null/undefined to help diagnose issues
|
|
377
|
+
logger.debug(`[DisplayEventProcessor] Timeout function returned ${msecs === null ? 'null' : msecs === undefined ? 'undefined' : `non-numeric value: ${msecs}`} for ${participantId}`)
|
|
378
|
+
}
|
|
379
|
+
} catch (error) {
|
|
380
|
+
logger.warn(`[DisplayEventProcessor] Error calculating timeout for ${participantId}: ${error.message}`)
|
|
381
|
+
}
|
|
371
382
|
}
|
|
372
383
|
}
|
|
373
384
|
const privateRenderContext = {
|