mirror of
https://github.com/4gray/iptvnator.git
synced 2026-10-12 12:06:16 -08:00
feat(dashboard): warn on source cards when a portal subscription expires soon
Dashboard source cards now carry a passive expiry chip: amber "Expires in N d" within 7 days of the subscription lapsing, error-toned "Expired" once it has. Account details stay behind the card's ⋮ → Account info. Xtream expirations ride on the playlist switcher's cached PortalStatusService check — checkPortalStatusDetails() now surfaces the parsed exp_date from the same round-trip, so the dashboard adds no extra portal calls. Stalker expirations come from the stalkerAccountInfo snapshot persisted at import; it lives in the playlist payload (meta rows carry payload: null), so each Stalker source costs one full-playlist read memoized on the playlist's update timestamp. New i18n keys added to all 19 locales via the i18n-fill merger. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
6c065124ed
commit
e7d32d7c61
34 files changed
+849
-30
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import {
|
||||
extractXtreamCredentialsFromUrl,
|
||||
normalizeXtreamServerUrl,
|
||||
resolveXtreamPortalExpiration,
|
||||
resolveXtreamPortalStatus,
|
||||
} from './xtream-portal.utils';
|
||||
|
||||
@@ -104,4 +105,39 @@ describe('xtream portal utilities', () => {
|
||||
).toBe('expired');
|
||||
});
|
||||
});
|
||||
|
||||
describe('resolveXtreamPortalExpiration', () => {
|
||||
it('parses numeric-string exp_date into unix seconds', () => {
|
||||
expect(
|
||||
resolveXtreamPortalExpiration({
|
||||
user_info: { exp_date: '1790000000' },
|
||||
})
|
||||
).toBe(1_790_000_000);
|
||||
});
|
||||
|
||||
it('treats unlimited and absent expirations as null', () => {
|
||||
expect(
|
||||
resolveXtreamPortalExpiration({
|
||||
user_info: { exp_date: '0' },
|
||||
})
|
||||
).toBeNull();
|
||||
expect(
|
||||
resolveXtreamPortalExpiration({
|
||||
user_info: { exp_date: '' },
|
||||
})
|
||||
).toBeNull();
|
||||
expect(
|
||||
resolveXtreamPortalExpiration({ user_info: {} })
|
||||
).toBeNull();
|
||||
expect(resolveXtreamPortalExpiration(null)).toBeNull();
|
||||
});
|
||||
|
||||
it('rejects non-numeric exp_date values', () => {
|
||||
expect(
|
||||
resolveXtreamPortalExpiration({
|
||||
user_info: { exp_date: 'never' },
|
||||
})
|
||||
).toBeNull();
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -102,6 +102,17 @@ export function resolveXtreamPortalStatus(
|
||||
return 'active';
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the account expiration from an Xtream `get_account_info`-style
|
||||
* response as unix seconds. Returns null when the portal doesn't report one
|
||||
* (unlimited accounts answer with empty/zero/absent `exp_date`).
|
||||
*/
|
||||
export function resolveXtreamPortalExpiration(
|
||||
response: XtreamPortalStatusResponseLike | null | undefined
|
||||
): number | null {
|
||||
return parseXtreamExpiration(response?.user_info?.exp_date);
|
||||
}
|
||||
|
||||
function normalizeAuthValue(
|
||||
value: boolean | number | string | null | undefined
|
||||
): boolean | null {
|
||||
|
||||
Reference in new issue
Block a user