mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-14 14:23:14 -06:00
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
135 lines
5.8 KiB
JavaScript
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 };
|