anear-js-api 0.5.9 → 0.6.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/lib/models/AnearEvent.js
CHANGED
|
@@ -154,7 +154,11 @@ class AnearEvent extends JsonApiResource {
|
|
|
154
154
|
}
|
|
155
155
|
|
|
156
156
|
allowsSpectators() {
|
|
157
|
-
|
|
157
|
+
//
|
|
158
|
+
// Canonical spectators decision uses the ANAPI-provided boolean when present.
|
|
159
|
+
// Fallback to legacy negative flag for older payloads.
|
|
160
|
+
//
|
|
161
|
+
return this.attributes['spectators-allowed']
|
|
158
162
|
}
|
|
159
163
|
|
|
160
164
|
isParticipantEventCreator(participant) {
|
|
@@ -209,6 +209,16 @@ const AnearParticipantMachineConfig = participantId => ({
|
|
|
209
209
|
]
|
|
210
210
|
},
|
|
211
211
|
on: {
|
|
212
|
+
// Even while we're waiting for a reconnect, the AppM may
|
|
213
|
+
// re-trigger a display (e.g. a refreshed PlayableGameBoard).
|
|
214
|
+
// In that case, re-publish the view but keep the timeout
|
|
215
|
+
// semantics governed by the stored remaining time.
|
|
216
|
+
RENDER_DISPLAY: {
|
|
217
|
+
target: '#renderDisplay'
|
|
218
|
+
},
|
|
219
|
+
PRIVATE_DISPLAY: {
|
|
220
|
+
target: '#renderDisplay'
|
|
221
|
+
},
|
|
212
222
|
PARTICIPANT_EXIT: {
|
|
213
223
|
target: '#cleanupAndExit'
|
|
214
224
|
},
|
|
@@ -258,6 +268,16 @@ const AnearParticipantMachineConfig = participantId => ({
|
|
|
258
268
|
}
|
|
259
269
|
},
|
|
260
270
|
on: {
|
|
271
|
+
RENDER_DISPLAY: {
|
|
272
|
+
// Re-render the current prompt for this participant (e.g. after reconnect)
|
|
273
|
+
// without resetting the original timeout start. The updateActionTimeout
|
|
274
|
+
// action will recompute the remaining time based on the original
|
|
275
|
+
// actionTimeoutStart so the participant does not get extra time.
|
|
276
|
+
target: '#renderDisplay'
|
|
277
|
+
},
|
|
278
|
+
PRIVATE_DISPLAY: {
|
|
279
|
+
target: '#renderDisplay'
|
|
280
|
+
},
|
|
261
281
|
PARTICIPANT_EXIT: {
|
|
262
282
|
actions: 'logExit',
|
|
263
283
|
target: '#cleanupAndExit'
|
|
@@ -404,22 +424,33 @@ const AnearParticipantMachineFunctions = {
|
|
|
404
424
|
lastSeen: context => CurrentDateTimestamp()
|
|
405
425
|
}),
|
|
406
426
|
updateActionTimeout: assign((context, event) => {
|
|
407
|
-
const
|
|
427
|
+
const timeoutFromEvent = event.data.timeout
|
|
428
|
+
const now = CurrentDateTimestamp()
|
|
408
429
|
|
|
430
|
+
// If we already have a running timer (e.g. after a disconnect/reconnect
|
|
431
|
+
// cycle or a re-render), treat actionTimeoutMsecs as the remaining
|
|
432
|
+
// duration for this leg and subtract only the time since the last
|
|
433
|
+
// (re)start. This avoids double-subtracting elapsed time across
|
|
434
|
+
// multiple renders.
|
|
409
435
|
if (context.actionTimeoutStart) {
|
|
410
|
-
const elapsed =
|
|
411
|
-
const
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
}
|
|
416
|
-
} else {
|
|
417
|
-
logger.debug(`[APM] Starting new timer for ${context.anearParticipant.id} with ${newTimeoutDuration}ms`)
|
|
436
|
+
const elapsed = now - context.actionTimeoutStart
|
|
437
|
+
const base = context.actionTimeoutMsecs != null ? context.actionTimeoutMsecs : timeoutFromEvent
|
|
438
|
+
const remaining = base - elapsed
|
|
439
|
+
const remainingMsecs = remaining > 0 ? remaining : 0
|
|
440
|
+
|
|
441
|
+
logger.debug(`[APM] Resuming timer for ${context.anearParticipant.id} with ${remainingMsecs}ms remaining`)
|
|
418
442
|
return {
|
|
419
|
-
actionTimeoutMsecs:
|
|
420
|
-
actionTimeoutStart:
|
|
443
|
+
actionTimeoutMsecs: remainingMsecs,
|
|
444
|
+
actionTimeoutStart: now
|
|
421
445
|
}
|
|
422
446
|
}
|
|
447
|
+
|
|
448
|
+
// First time this participant sees this prompt: start a fresh timer.
|
|
449
|
+
logger.debug(`[APM] Starting new timer for ${context.anearParticipant.id} with ${timeoutFromEvent}ms`)
|
|
450
|
+
return {
|
|
451
|
+
actionTimeoutMsecs: timeoutFromEvent,
|
|
452
|
+
actionTimeoutStart: now
|
|
453
|
+
}
|
|
423
454
|
}),
|
|
424
455
|
nullActionTimeout: assign({
|
|
425
456
|
actionTimeoutMsecs: _c => null,
|
|
@@ -427,12 +458,16 @@ const AnearParticipantMachineFunctions = {
|
|
|
427
458
|
}),
|
|
428
459
|
updateRemainingTimeoutOnDisconnect: assign((context, event) => {
|
|
429
460
|
if (context.actionTimeoutStart) {
|
|
430
|
-
const
|
|
461
|
+
const now = CurrentDateTimestamp()
|
|
462
|
+
const elapsed = now - context.actionTimeoutStart
|
|
431
463
|
const remaining = context.actionTimeoutMsecs - elapsed
|
|
432
464
|
const remainingMsecs = remaining > 0 ? remaining : 0
|
|
433
465
|
logger.debug(`[APM] Participant disconnected mid-turn. Storing remaining timeout of ${remainingMsecs}ms`)
|
|
434
466
|
return {
|
|
435
|
-
actionTimeoutMsecs: remainingMsecs
|
|
467
|
+
actionTimeoutMsecs: remainingMsecs,
|
|
468
|
+
// Reset the start baseline to "now" so that subsequent resumptions
|
|
469
|
+
// measure elapsed time from the disconnect point forward.
|
|
470
|
+
actionTimeoutStart: now
|
|
436
471
|
}
|
|
437
472
|
}
|
|
438
473
|
return {}
|
|
@@ -336,18 +336,75 @@ class DisplayEventProcessor {
|
|
|
336
336
|
let timeout = null
|
|
337
337
|
|
|
338
338
|
const participantStruct = this.participantsIndex.get(participantId)
|
|
339
|
+
const appCtx = templateRenderContext.app || {}
|
|
340
|
+
|
|
341
|
+
// Determine which participant (if any) is currently timed according to
|
|
342
|
+
// the AppM context (e.g., Tic-Tac-Toe currentPlayerToken).
|
|
343
|
+
const currentToken = appCtx.currentPlayerToken
|
|
344
|
+
const currentPlayerId = currentToken && appCtx.playerIds ? appCtx.playerIds[currentToken] : null
|
|
345
|
+
const isCurrentPlayer = currentPlayerId && participantId === currentPlayerId
|
|
339
346
|
|
|
340
347
|
if (timeoutFn) {
|
|
341
|
-
timeout
|
|
348
|
+
// Only the current player should receive a real timeout value that
|
|
349
|
+
// starts an APM timer. For other participants we keep `timeout` null
|
|
350
|
+
// so their APMs never start per-participant timers.
|
|
351
|
+
if (isCurrentPlayer) {
|
|
352
|
+
timeout = timeoutFn(appCtx, participantStruct)
|
|
353
|
+
}
|
|
342
354
|
}
|
|
343
355
|
const privateRenderContext = {
|
|
344
356
|
...templateRenderContext,
|
|
345
357
|
participant: participantStruct
|
|
346
358
|
}
|
|
347
359
|
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
360
|
+
// Build visual timeout (meta.timeout) used by the countdown bar.
|
|
361
|
+
// For the current player, derive from their own APM state.
|
|
362
|
+
// For opponents / other participants, mirror the current player's timer
|
|
363
|
+
// so everyone sees the same countdown without starting extra APM timers.
|
|
364
|
+
let visualTimeout = null
|
|
365
|
+
try {
|
|
366
|
+
// Helper to compute remaining from a given participant machine
|
|
367
|
+
const computeRemainingFromMachine = (pm) => {
|
|
368
|
+
const state = pm?.state
|
|
369
|
+
const ctx = state?.context
|
|
370
|
+
if (!ctx || !ctx.actionTimeoutStart || ctx.actionTimeoutMsecs == null) return null
|
|
371
|
+
const now = Date.now()
|
|
372
|
+
const elapsed = now - ctx.actionTimeoutStart
|
|
373
|
+
const base = ctx.actionTimeoutMsecs
|
|
374
|
+
const remaining = base - elapsed
|
|
375
|
+
return remaining > 0 ? remaining : 0
|
|
376
|
+
}
|
|
377
|
+
|
|
378
|
+
if (isCurrentPlayer) {
|
|
379
|
+
if (timeout !== null) {
|
|
380
|
+
const remainingMsecs = computeRemainingFromMachine(participantMachine) ?? timeout
|
|
381
|
+
visualTimeout = { msecs: timeout, remainingMsecs }
|
|
382
|
+
}
|
|
383
|
+
} else if (currentPlayerId) {
|
|
384
|
+
// Mirror the current player's timer for non-current participants.
|
|
385
|
+
const currentPm = this.participantMachines[currentPlayerId]
|
|
386
|
+
const remainingMsecs = computeRemainingFromMachine(currentPm)
|
|
387
|
+
|
|
388
|
+
if (remainingMsecs != null) {
|
|
389
|
+
// Prefer the app-configured move timeout if available; otherwise
|
|
390
|
+
// fall back to the remaining time as the total for visuals.
|
|
391
|
+
const moveTimeout =
|
|
392
|
+
(appCtx.C && typeof appCtx.C.MOVE_TIMEOUT_MSECS === 'number' && appCtx.C.MOVE_TIMEOUT_MSECS > 0)
|
|
393
|
+
? appCtx.C.MOVE_TIMEOUT_MSECS
|
|
394
|
+
: remainingMsecs
|
|
395
|
+
visualTimeout = { msecs: moveTimeout, remainingMsecs }
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
} catch (_e) {
|
|
399
|
+
// If anything goes wrong computing visualTimeout, we simply omit it and
|
|
400
|
+
// let the templates fall back to their default behavior.
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (visualTimeout) {
|
|
404
|
+
privateRenderContext.meta = {
|
|
405
|
+
...(privateRenderContext.meta || {}),
|
|
406
|
+
timeout: visualTimeout
|
|
407
|
+
}
|
|
351
408
|
}
|
|
352
409
|
|
|
353
410
|
const privateHtml = template(privateRenderContext)
|