mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-13 04:26:16 -08:00
fix(settings): re-ask the PIN before a relocked backup merge and keep the PIN cooldown across prompts
A backup merge now asks for the PIN again right before it replaces a playlist's locks when the app relocked during the import, instead of relying on the answer given at the start. The wrong-PIN count and the 30-second pause move from the dialog into the lock service, so dismissing and reopening the prompt no longer resets them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
451b373b1d
commit
8ad8907ef9
11 files changed
+237
-19
No files matched your search
@@ -29,6 +29,7 @@ export * from './lib/language.enum';
|
||||
export * from './lib/m3u-favorite-channel.interface';
|
||||
export * from './lib/parental-lock.util';
|
||||
export * from './lib/parental-lock-pin.util';
|
||||
export * from './lib/parental-lock-pin-throttle.util';
|
||||
export * from './lib/parsed-playlist.interface';
|
||||
export * from './lib/performance-phase.interface';
|
||||
export * from './lib/playback-position.interface';
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
import {
|
||||
createParentalLockPinThrottle,
|
||||
PARENTAL_LOCK_PIN_COOLDOWN_MS,
|
||||
PARENTAL_LOCK_PIN_MAX_ATTEMPTS,
|
||||
} from './parental-lock-pin-throttle.util';
|
||||
|
||||
describe('createParentalLockPinThrottle', () => {
|
||||
it('starts a cooldown after the maximum failures and ends it on time', () => {
|
||||
let clock = 1_000;
|
||||
const throttle = createParentalLockPinThrottle(() => clock);
|
||||
for (let i = 1; i < PARENTAL_LOCK_PIN_MAX_ATTEMPTS; i++) {
|
||||
expect(throttle.recordFailure()).toBe(false);
|
||||
}
|
||||
expect(throttle.cooldownUntil()).toBe(0);
|
||||
|
||||
expect(throttle.recordFailure()).toBe(true);
|
||||
expect(throttle.cooldownUntil()).toBe(
|
||||
1_000 + PARENTAL_LOCK_PIN_COOLDOWN_MS
|
||||
);
|
||||
|
||||
clock += PARENTAL_LOCK_PIN_COOLDOWN_MS;
|
||||
expect(throttle.cooldownUntil()).toBe(0);
|
||||
});
|
||||
|
||||
it('clears the count and the cooldown after the right PIN', () => {
|
||||
const throttle = createParentalLockPinThrottle(() => 0);
|
||||
for (let i = 1; i < PARENTAL_LOCK_PIN_MAX_ATTEMPTS; i++) {
|
||||
throttle.recordFailure();
|
||||
}
|
||||
throttle.recordSuccess();
|
||||
expect(throttle.recordFailure()).toBe(false);
|
||||
expect(throttle.cooldownUntil()).toBe(0);
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,40 @@
|
||||
/** Failed PIN attempts before the prompt pauses for the cooldown. */
|
||||
export const PARENTAL_LOCK_PIN_MAX_ATTEMPTS = 5;
|
||||
export const PARENTAL_LOCK_PIN_COOLDOWN_MS = 30_000;
|
||||
|
||||
/**
|
||||
* Wrong-PIN bookkeeping that outlives a single PIN dialog. Kept by the lock
|
||||
* service and handed to every unlock prompt, so dismissing the dialog during
|
||||
* a cooldown and opening it again neither resets the count nor the pause.
|
||||
*/
|
||||
export interface ParentalLockPinThrottle {
|
||||
/** Epoch milliseconds until which no PIN may be tried; 0 when none. */
|
||||
cooldownUntil(): number;
|
||||
/** Records a wrong PIN; true when this failure starts a cooldown. */
|
||||
recordFailure(): boolean;
|
||||
/** Clears the count after the right PIN. */
|
||||
recordSuccess(): void;
|
||||
}
|
||||
|
||||
export function createParentalLockPinThrottle(
|
||||
now: () => number = Date.now
|
||||
): ParentalLockPinThrottle {
|
||||
let failedAttempts = 0;
|
||||
let cooldownUntil = 0;
|
||||
return {
|
||||
cooldownUntil: () => (cooldownUntil > now() ? cooldownUntil : 0),
|
||||
recordFailure: () => {
|
||||
failedAttempts += 1;
|
||||
if (failedAttempts < PARENTAL_LOCK_PIN_MAX_ATTEMPTS) {
|
||||
return false;
|
||||
}
|
||||
failedAttempts = 0;
|
||||
cooldownUntil = now() + PARENTAL_LOCK_PIN_COOLDOWN_MS;
|
||||
return true;
|
||||
},
|
||||
recordSuccess: () => {
|
||||
failedAttempts = 0;
|
||||
cooldownUntil = 0;
|
||||
},
|
||||
};
|
||||
}
|
||||
Reference in new issue
Block a user