fix(server-storage): forget the token for another server, listen before sign-in, adopt only stale records

- A server that answers as another backend URL or instance id than the
  remembered one makes the browser forget the stored token, so server A's
  token never reaches server B.
- The session installs its page-hide and heartbeat listeners before it
  returns for sign-in, so a page holding another page load's writes still
  marks itself closed on unload.
- Closed tabs are found by stale records only again: saved writes without
  a record may belong to a running tab whose record storage refused, and
  are left to that tab's own reload.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
4grayandClaude Opus 5.5 committed 2026-10-13 20:08:33 +02:00
1 parent cf715a7573
commit 4908803ceb
7 files changed
+94 -41

No files matched your search

@@ -45,6 +45,28 @@ describe('resolveServerStorageStart', () => {
).toBeNull();
});
it('keeps the token only for the same server and database', () => {
const token = 'iptvnator-server:access-token';
resolveServerStorageStart({ kind: 'server', instance }, '/api');
localStorage.setItem(token, 'secret');
resolveServerStorageStart({ kind: 'server', instance }, '/api/');
expect(localStorage.getItem(token)).toBe('secret');
resolveServerStorageStart(
{ kind: 'server', instance: { ...instance, instanceId: 'i-2' } },
'/api'
);
expect(localStorage.getItem(token)).toBeNull();
localStorage.setItem(token, 'secret');
resolveServerStorageStart(
{ kind: 'server', instance: { ...instance, instanceId: 'i-2' } },
'/other'
);
expect(localStorage.getItem(token)).toBeNull();
});
it('forgets the token of a server that no longer offers storage', () => {
localStorage.setItem('iptvnator-server:access-token', 'old');
@@ -32,9 +32,21 @@ export function resolveServerStorageStart(
> | null = safeLocalStorage()
): ServerStorageStart | null {
switch (probe.kind) {
case 'server':
case 'server': {
const remembered = read(storage);
// Another server, or another database behind this one: the
// stored token was not given for it.
if (
!remembered ||
backendIdentity(remembered.baseUrl) !==
backendIdentity(baseUrl) ||
remembered.instance.instanceId !== probe.instance.instanceId
) {
clearServerAccessToken();
}
write(storage, { baseUrl, instance: probe.instance });
return { instance: probe.instance, startedOffline: false };
}
case 'none':
remove(storage);
// The token belonged to that server; another must not see it.