mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 22:03:13 -06:00
Persist the device token, not just the device id
The bridge stored device_id in the registry and the display still came back as a NEW device on the next boot. The id is not an identity on its own: the server authenticates a claim to an existing display with the token, so an id presented without one reads as a brand-new player and gets a fresh row. device_token now sits alongside device_id in the registry, getConfig adopts both, and clearIdentity forgets both — a stale token must not outlive the identity it belongs to. Found on an XT245, not in a test, which is why the three new cases name the symptom rather than the mechanism. 951 pass. Also worth recording from the same session: the duplicate rows had a second cause. The widget's storage_path was pointing nowhere useful, so localStorage had no persistent home and the per-install fingerprint salt was regenerated on every boot. With storage_path set correctly the cache directory now exists on the player and the fingerprint is stable, which is what stopped the churn; the registry identity is the belt to that pair of braces. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
This commit is contained in:
parent
5cd56344d2
commit
58641e7bbe
|
|
@ -102,7 +102,11 @@
|
|||
* which would register a "[object Promise]" display.
|
||||
*/
|
||||
var SECTION = 'screentinker';
|
||||
var CACHED_KEYS = ['device_id', 'server_url', 'sync_backend'];
|
||||
// device_token belongs here as much as device_id: the server authenticates the claim to an
|
||||
// existing display with the token, so an id presented without one reads as a NEW display and
|
||||
// gets a fresh row. Persisting the id alone looked correct and still spawned a duplicate on
|
||||
// every boot — found on hardware, not in a test.
|
||||
var CACHED_KEYS = ['device_id', 'device_token', 'server_url', 'sync_backend'];
|
||||
var cache = {};
|
||||
var ready = false;
|
||||
var readyWaiters = [];
|
||||
|
|
@ -238,11 +242,16 @@
|
|||
try { return global.localStorage.getItem('st_device_id'); } catch (e) { return null; }
|
||||
},
|
||||
|
||||
/* The credential that proves this player IS that display. Useless without deviceId, and
|
||||
deviceId is useless without it. */
|
||||
deviceToken: function () { return regGet('device_token', null); },
|
||||
|
||||
/* Called once pairing completes, so a reboot comes back as the same display. */
|
||||
setIdentity: function (deviceId, serverUrl) {
|
||||
setIdentity: function (deviceId, serverUrl, deviceToken) {
|
||||
var values = {};
|
||||
if (deviceId) values.device_id = deviceId;
|
||||
if (serverUrl) values.server_url = serverUrl;
|
||||
if (deviceToken) values.device_token = deviceToken;
|
||||
regSet(values);
|
||||
post({ type: 'identity', device_id: deviceId || null, server_url: serverUrl || null });
|
||||
},
|
||||
|
|
@ -253,7 +262,7 @@
|
|||
* the same identity on its next boot — a reset that resets nothing.
|
||||
*/
|
||||
clearIdentity: function () {
|
||||
regSet({ device_id: '' });
|
||||
regSet({ device_id: '', device_token: '' });
|
||||
return post({ type: 'identity', clear: true });
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -371,7 +371,14 @@
|
|||
if (BS && !cfg.deviceId) {
|
||||
try {
|
||||
const known = BS.deviceId();
|
||||
if (known) { cfg.deviceId = known; cfg.paired = true; }
|
||||
if (known) {
|
||||
cfg.deviceId = known;
|
||||
// Without the token the server cannot verify the claim and issues a NEW display,
|
||||
// so the id alone is not an identity.
|
||||
const tok = BS.deviceToken();
|
||||
if (tok) cfg.deviceToken = tok;
|
||||
cfg.paired = true;
|
||||
}
|
||||
} catch (e) { /* registry unavailable — carry on unpaired */ }
|
||||
}
|
||||
return cfg;
|
||||
|
|
@ -381,7 +388,7 @@
|
|||
// Mirror identity into the registry so it survives a reboot, a content update or a storage
|
||||
// wipe. Best-effort by design: failing to persist here must never break pairing itself.
|
||||
if (BS && cfg && cfg.deviceId) {
|
||||
try { BS.setIdentity(cfg.deviceId, cfg.serverUrl || null); } catch (e) { /* ignore */ }
|
||||
try { BS.setIdentity(cfg.deviceId, cfg.serverUrl || null, cfg.deviceToken || null); } catch (e) { /* ignore */ }
|
||||
}
|
||||
}
|
||||
const PLAYLIST_CACHE_KEY = 'rd_playlist_cache' + SCREEN_SUFFIX;
|
||||
|
|
@ -1165,8 +1172,10 @@
|
|||
const known = BS.deviceId();
|
||||
if (known && !config.deviceId) {
|
||||
config.deviceId = known;
|
||||
const tok = BS.deviceToken();
|
||||
if (tok) config.deviceToken = tok;
|
||||
config.paired = true;
|
||||
console.log('[bs] adopted identity from registry:', known);
|
||||
console.log('[bs] adopted identity from registry:', known, tok ? '(with token)' : '(NO TOKEN — will re-pair)');
|
||||
}
|
||||
} catch (e) { /* carry on unpaired rather than not at all */ }
|
||||
connect(serverUrl);
|
||||
|
|
|
|||
|
|
@ -215,3 +215,33 @@ test('a rejected registry read still lets the player boot', async () => {
|
|||
await ready;
|
||||
assert.doesNotThrow(() => api.deviceId());
|
||||
});
|
||||
|
||||
test('THE DUPLICATE-ROW BUG: the token is persisted alongside the id', async () => {
|
||||
// Persisting device_id alone looked correct and still spawned a new device row on every boot:
|
||||
// the server authenticates a claim to an existing display with the TOKEN, so an id presented
|
||||
// without one reads as a brand-new display. Found on an XT245, not in a test — hence this one.
|
||||
const { api, ready } = load({ mods: true });
|
||||
await ready;
|
||||
api.setIdentity('dev-9', 'https://alpha.screentinker.com', 'tok-abc123');
|
||||
assert.equal(api.deviceId(), 'dev-9');
|
||||
assert.equal(api.deviceToken(), 'tok-abc123', 'without this the display re-pairs every boot');
|
||||
});
|
||||
|
||||
test('an id with no token is still stored — it is better than nothing', async () => {
|
||||
// An unpaired display has no token yet; the server issues one at pairing. Storing the id alone
|
||||
// must not throw or wipe anything.
|
||||
const { api, ready } = load({ mods: true });
|
||||
await ready;
|
||||
api.setIdentity('dev-10', null, null);
|
||||
assert.equal(api.deviceId(), 'dev-10');
|
||||
assert.equal(api.deviceToken(), null);
|
||||
});
|
||||
|
||||
test('clearIdentity forgets the token too, or the reset leaks a credential', async () => {
|
||||
const { api, ready } = load({ mods: true });
|
||||
await ready;
|
||||
api.setIdentity('dev-11', null, 'tok-xyz');
|
||||
api.clearIdentity();
|
||||
assert.equal(api.deviceId(), null);
|
||||
assert.equal(api.deviceToken(), null, 'a stale token must not outlive the identity it belongs to');
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue