screentinker/server/lib/sync-backend.js
ScreenTinker 4ed7954f84 Drop the user-agent fallback — it could never fire
isBrightSignDevice() fell back to device.user_agent to catch panels paired
before this port existed, which registered as "Chrome 120" with a BrightSign
user agent. `devices` has no user_agent column, so the field is always undefined
on a row read from the database. The branch was unreachable in production and
passed only in a test that fabricated the field — which is precisely how dead
code survives review.

Two agents flagged it independently while working on unrelated areas, and the
schema confirms it: zero matches for user_agent in the devices table.

Those pre-port panels are recognised the moment they re-register on a build
carrying the host, which every one of them gets on its next update. Identifying
them sooner would mean persisting the user agent, and a column added solely to
track a population that disappears on its own is not worth carrying.

The test now asserts the honest behaviour: a fabricated user_agent does NOT
create a match, and a group containing such a panel reads as mixed until it
re-registers.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uaeo9MvzKoyXuN6ZsbhtkL
2026-08-05 10:30:10 -05:00

135 lines
5.8 KiB
JavaScript

'use strict';
/*
* Which synchronisation protocol a group runs.
*
* ScreenTinker has its own group sync: every member derives its position from a shared clock,
* so it needs no leader, survives a server outage, and works across Android, web, Tizen and
* BrightSign alike. BrightSign has its own — BrightWall — which is native, frame-accurate, and
* only exists between BrightSign players.
*
* The choice is therefore not "which is better" but "what is in this group":
*
* screentinker works everywhere, mixed fleets included; sync is to the second, not the frame
* brightsign frame-accurate video walls; requires EVERY member to be a BrightSign
*
* `auto` picks the strongest protocol the group can actually run, which is what an operator
* means when they say "just make the wall work". Explicit settings are honoured, except the one
* that cannot physically work (native sync with a non-BrightSign member) — that downgrades and
* says why, rather than silently doing nothing on the screens that can't participate.
*
* Kept pure so the decision is testable without a fleet: callers pass plain device rows.
*/
const BACKENDS = ['auto', 'screentinker', 'brightsign'];
/*
* A device is a BrightSign if it said so: the player sends ?platform=brightsign (autorun.brs puts
* it there), which lands in devices.platform.
*
* There is deliberately NO user-agent fallback. An earlier version had one, to catch panels paired
* before this port existed — which registered as "Chrome 120" with a BrightSign user agent. It
* could never fire: `devices` has no user_agent column, so the field is always undefined on a row
* read from the database. It read as defensive and was dead code.
*
* Those pre-port panels are identified the moment they re-register on a build that carries the
* host, which every one of them gets on its next update. Recognising them earlier would mean
* persisting the user agent, and a column added solely to identify a population that disappears on
* its own is not worth carrying.
*/
function isBrightSignDevice(device) {
if (!device) return false;
return String(device.platform || '').toLowerCase().includes('brightsign');
}
/*
* SyncManager is MULTICAST (224.0.126.10:1539), so every member must share one L2 network. A group
* spanning sites or VLANs cannot use it — and the failure is silent: each subnet would sync neatly
* within itself while drifting from the others.
*
* The evidence available server-side is the address each device connects from. Comparing the /24
* is a heuristic, not proof — two VLANs can share a public IP, and one subnet can span a routed
* boundary that blocks multicast. So it is used in ONE direction only: differing networks are
* treated as evidence against native sync, while matching ones are never treated as proof for it.
* Unknown addresses block nothing, because "we cannot see it" must not read as "it is broken".
*/
function networkOf(device) {
const ip = String((device && (device.ip_address || device.last_ip)) || '').trim();
if (!ip) return null;
if (ip.includes(':')) { // IPv6: compare the /64
const parts = ip.split(':');
return parts.slice(0, 4).join(':').toLowerCase();
}
const octets = ip.split('.');
if (octets.length !== 4) return null;
return octets.slice(0, 3).join('.');
}
function networksDiffer(members) {
const nets = members.map(networkOf).filter(Boolean);
if (nets.length < 2) return false; // nothing to contradict
return new Set(nets).size > 1;
}
/**
* @param {string} setting 'auto' | 'screentinker' | 'brightsign' (unknown values read as auto)
* @param {Array} members device rows in the group
* @returns {{backend: 'screentinker'|'brightsign', reason: string, downgraded: boolean}}
*/
function resolveSyncBackend(setting, members) {
const list = Array.isArray(members) ? members.filter(Boolean) : [];
const requested = BACKENDS.includes(setting) ? setting : 'auto';
const brightsignCount = list.filter(isBrightSignDevice).length;
const allBrightSign = list.length > 0 && brightsignCount === list.length;
const split = networksDiffer(list);
if (requested === 'screentinker') {
return { backend: 'screentinker', reason: 'explicitly selected', downgraded: false };
}
if (requested === 'brightsign') {
if (allBrightSign && !split) {
return { backend: 'brightsign', reason: 'explicitly selected', downgraded: false };
}
if (allBrightSign && split) {
// Every member is a BrightSign, but they are not on one network. Native sync would appear
// to work inside each subnet while the subnets drifted apart — worse than not using it.
return {
backend: 'screentinker',
reason: 'displays are on different networks — native sync is multicast and cannot cross them',
downgraded: true
};
}
// Refusing to pretend: BrightWall cannot include a non-BrightSign screen, and a group that
// half-syncs is worse than one that syncs to the second everywhere.
const others = list.length - brightsignCount;
return {
backend: 'screentinker',
reason: list.length === 0
? 'group is empty — native sync needs BrightSign members'
: `group has ${others} non-BrightSign display${others === 1 ? '' : 's'}`,
downgraded: true
};
}
// auto
if (allBrightSign && !split) {
return { backend: 'brightsign', reason: 'every display is a BrightSign', downgraded: false };
}
if (allBrightSign && split) {
return {
backend: 'screentinker',
reason: 'displays are on different networks',
downgraded: false
};
}
return {
backend: 'screentinker',
reason: list.length === 0 ? 'no displays in the group' : 'mixed fleet',
downgraded: false
};
}
module.exports = { resolveSyncBackend, isBrightSignDevice, networksDiffer, BACKENDS };