mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-11 11:06:16 -08:00
fix(dashboard): keep rechecking timestamp-expired sources hourly
A badge derived from a timestamp in the past can still change if the system clock is corrected backward; the minute heartbeat used to recover from that. Keep the hourly recheck for such facts. A portal-reported expiry stays terminal and arms no timer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
a989eca4bf
commit
e474555726
3 files changed
+34
-10
No files matched your search
@@ -57,10 +57,12 @@ export function resolveSourceExpiryBadge(
|
||||
|
||||
/**
|
||||
* The next instant (ms) at which {@link resolveSourceExpiryBadge} would answer
|
||||
* differently for these facts, or null when it never will again. The badge
|
||||
* moves only at day granularity: it appears `warningDays` days before
|
||||
* expiry, counts down once per day and turns into "expired" at expiry, so a
|
||||
* consumer can wait for that boundary instead of polling the clock.
|
||||
* differently for these facts, or null when no boundary lies ahead: a
|
||||
* portal-reported expiry, or a timestamp already in the past (for as long as
|
||||
* the system clock only moves forward). The badge moves only at day
|
||||
* granularity: it appears `warningDays` days before expiry, counts down once
|
||||
* per day and turns into "expired" at expiry, so a consumer can wait for that
|
||||
* boundary instead of polling the clock.
|
||||
*/
|
||||
export function nextSourceExpiryChangeMs(
|
||||
facts: SourceExpiryFacts | null | undefined,
|
||||
|
||||
@@ -143,4 +143,15 @@ describe('createSourceExpiryClock', () => {
|
||||
expect(clock()).toBe(Date.now());
|
||||
expect(clock()).toBeGreaterThan(before);
|
||||
});
|
||||
|
||||
it('keeps rechecking an expired timestamp hourly in case the clock is corrected', () => {
|
||||
facts.set(new Map([['xtream', expiringIn(-60_000)]]));
|
||||
TestBed.tick();
|
||||
expect(jest.getTimerCount()).toBe(1);
|
||||
const before = clock();
|
||||
|
||||
jest.advanceTimersByTime(SOURCE_EXPIRY_MAX_WAIT_MS);
|
||||
TestBed.tick();
|
||||
expect(clock()).toBe(before + SOURCE_EXPIRY_MAX_WAIT_MS);
|
||||
});
|
||||
});
|
||||
@@ -21,7 +21,8 @@ const BOUNDARY_SLACK_MS = 1_000;
|
||||
*
|
||||
* Badges move at day granularity, so instead of polling the clock this arms
|
||||
* one timer for the earliest badge boundary among the known facts (none at
|
||||
* all when no badge can change), capped at {@link SOURCE_EXPIRY_MAX_WAIT_MS}
|
||||
* all when no badge can change; an hourly recheck for a timestamp that has
|
||||
* already passed, in case the system clock is corrected backward), capped at {@link SOURCE_EXPIRY_MAX_WAIT_MS}
|
||||
* because timers do not follow system sleep or clock changes. No timer is
|
||||
* armed while the document is hidden or `active` is false; becoming visible
|
||||
* re-reads the clock at once. Must be created in
|
||||
@@ -45,18 +46,28 @@ export function createSourceExpiryClock(
|
||||
if (!visible() || !active()) return;
|
||||
const nowMs = Date.now();
|
||||
let next: number | null = null;
|
||||
// A badge derived from a timestamp can still change if the system
|
||||
// clock is corrected backward, so it keeps the hourly recheck even
|
||||
// with no boundary ahead. A portal-reported expiry is terminal.
|
||||
let clockDependent = false;
|
||||
for (const entry of facts().values()) {
|
||||
if (!entry.reportedExpired && (entry.expiresAtSeconds ?? 0) > 0) {
|
||||
clockDependent = true;
|
||||
}
|
||||
const change = nextSourceExpiryChangeMs(entry, nowMs);
|
||||
if (change !== null && (next === null || change < next)) {
|
||||
next = change;
|
||||
}
|
||||
}
|
||||
// No badge can change any more: new facts re-run this effect.
|
||||
if (next === null) return;
|
||||
const delay = Math.min(
|
||||
Math.max(next - nowMs, 0) + BOUNDARY_SLACK_MS,
|
||||
SOURCE_EXPIRY_MAX_WAIT_MS
|
||||
);
|
||||
if (next === null && !clockDependent) return;
|
||||
const delay =
|
||||
next === null
|
||||
? SOURCE_EXPIRY_MAX_WAIT_MS
|
||||
: Math.min(
|
||||
Math.max(next - nowMs, 0) + BOUNDARY_SLACK_MS,
|
||||
SOURCE_EXPIRY_MAX_WAIT_MS
|
||||
);
|
||||
const timer = setTimeout(() => now.set(Date.now()), delay);
|
||||
onCleanup(() => clearTimeout(timer));
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user