fix(timer): dismiss actually cancels on server + shorter retention
All checks were successful
Deploy / deploy (push) Successful in 3m10s
All checks were successful
Deploy / deploy (push) Successful in 3m10s
Bug: после перезагрузки страницы оверлей «Таймер прозвенел» открывался
снова и снова. Две причины:
- dismissTimer в TimerWidget удалял таймер только из локального
useState, но /data/tablet-timers.json оставался нетронутым. После
reload таймер возвращался в список и firedRef (которая пустая после
reload) снова триггерила alarm.
- lib/timers.ts держал просроченные таймеры 30 минут, давая им шанс
повторно сработать при каждом reload в этом окне.
Фикс:
- dismissTimer теперь POST /api/voice/timer {action:cancel, id} через
cookie auth (endpoint с прошлого коммита принимает и cookie, и bearer).
- Retention в listActive снижена до 30 секунд — этого хватает чтобы
клиент увидел свежий звонок; старше = самоудаление.
- TimerWidget клиентский фильтр тоже 30 секунд.
This commit is contained in:
@@ -120,21 +120,27 @@ export default function TimerWidget() {
|
|||||||
}, [timers, tick])
|
}, [timers, tick])
|
||||||
|
|
||||||
const dismissTimer = async (id: string) => {
|
const dismissTimer = async (id: string) => {
|
||||||
|
firedRef.current.delete(id)
|
||||||
|
setFiredIds(new Set(firedRef.current))
|
||||||
|
setTimers(ts => ts.filter(t => t.id !== id))
|
||||||
try {
|
try {
|
||||||
firedRef.current.delete(id)
|
// Fire-and-forget server cancel. API теперь принимает cookie auth, так что
|
||||||
setFiredIds(new Set(firedRef.current))
|
// браузерный запрос проходит middleware.
|
||||||
// We use POST with bearer — but widget runs with cookie auth.
|
await fetch('/api/voice/timer', {
|
||||||
// Cancel endpoint only accepts bearer; for user-dismissal we use DELETE-style via... hmm.
|
method: 'POST',
|
||||||
// For simplicity, tell server to cancel via a plain GET-less POST flow — skip server call here.
|
headers: { 'Content-Type': 'application/json' },
|
||||||
// (The timer will be cleaned up on next listActive when expired >30min ago.)
|
body: JSON.stringify({ action: 'cancel', id }),
|
||||||
setTimers(ts => ts.filter(t => t.id !== id))
|
credentials: 'include',
|
||||||
|
})
|
||||||
} catch {}
|
} catch {}
|
||||||
}
|
}
|
||||||
|
|
||||||
const now = Date.now()
|
const now = Date.now()
|
||||||
const active = timers.filter(t => {
|
const active = timers.filter(t => {
|
||||||
const remain = new Date(t.endsAt).getTime() - now
|
const remain = new Date(t.endsAt).getTime() - now
|
||||||
return remain > -30 * 60 * 1000 // keep expired ones visible for 30 min max
|
// Keep expired for only 30 sec — иначе при перезагрузке давно прошедший
|
||||||
|
// таймер снова начнёт трезвонить.
|
||||||
|
return remain > -30 * 1000
|
||||||
})
|
})
|
||||||
|
|
||||||
if (active.length === 0) return null
|
if (active.length === 0) return null
|
||||||
|
|||||||
@@ -28,8 +28,10 @@ function save(list: Timer[]) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function cleanup(list: Timer[]): Timer[] {
|
function cleanup(list: Timer[]): Timer[] {
|
||||||
// Drop items expired more than 30 min ago
|
// Drop items expired more than 30 seconds ago. Достаточно чтобы показать
|
||||||
const cutoff = Date.now() - 30 * 60 * 1000
|
// «звенит» при активной странице, но не воскрешать alarm после перезагрузки
|
||||||
|
// через час.
|
||||||
|
const cutoff = Date.now() - 30 * 1000
|
||||||
return list.filter(t => new Date(t.endsAt).getTime() > cutoff)
|
return list.filter(t => new Date(t.endsAt).getTime() > cutoff)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user