mirror of
https://github.com/screentinker/screentinker.git
synced 2026-08-13 22:03:13 -06:00
The dashboard offered every control to every display, so a browser tab showed a reboot button that could never work. server/lib/player-capabilities.js defines the vocabulary; this makes the web player actually speak it. The declaration is computed, not constant, because the same index.html is BOTH the browser player and the BrightSign player. system.reboot / display.power / display.resolution / system.self_update are claimed only when BS.hasHost() answers — deliberately hasHost() and not isBrightSign(), since the UA check is also true for a widget built without node integration, which can reach none of them. Screenshots, offline cache, transitions and native sync are each probed the same way. Capabilities were never persisted: the column and the handler did not exist, so a declaration would have been sent and silently dropped. Added the migration and applyCapabilities(). An ABSENT declaration leaves the column NULL so the baseline still applies — several hundred fielded displays declare nothing and would otherwise lose every control at once — while an EMPTY declaration is stored as '[]' and honoured. docs/player-parity.md records every capability against all four players with a reason for each "no", and flags three Tizen baseline errors found while verifying it. Tests: 1109/1109. Both inline <script> blocks in index.html parse clean.
58 lines
2.8 KiB
JavaScript
58 lines
2.8 KiB
JavaScript
'use strict';
|
|
|
|
// A player declares what it can do; the dashboard hides the controls it cannot honour.
|
|
//
|
|
// The failure this guards is the one that would hit hardest: several hundred displays are already
|
|
// in the field and declare NOTHING. If an absent declaration were persisted as "supports nothing"
|
|
// they would all lose their controls the moment this shipped. So absent must leave the column NULL
|
|
// (baseline applies) while an EMPTY declaration is stored as '[]' and honoured — a real statement
|
|
// from, say, a BrightSign widget with no host bridge.
|
|
//
|
|
// Those two cases differ by one character in the payload and by an entire dashboard in effect.
|
|
|
|
const { test } = require('node:test');
|
|
const assert = require('node:assert/strict');
|
|
const caps = require('../lib/player-capabilities');
|
|
|
|
// The exact filter the socket handler applies before writing.
|
|
const persistedValue = (raw) => {
|
|
const declared = caps.parseDeclared(raw);
|
|
return declared === null ? null : JSON.stringify(declared);
|
|
};
|
|
|
|
test('THE FLEET CASE: an absent declaration is not persisted, so the baseline still applies', () => {
|
|
assert.equal(persistedValue(undefined), null);
|
|
assert.equal(persistedValue(null), null);
|
|
const legacy = { client_type: 'apk' }; // column stays NULL
|
|
assert.ok(caps.supports(legacy, 'system.reboot'), 'legacy Android keeps its controls');
|
|
});
|
|
|
|
test('an EMPTY declaration IS persisted and is honoured as "nothing"', () => {
|
|
assert.equal(persistedValue([]), '[]');
|
|
assert.deepEqual(caps.capabilitiesFor({ client_type: 'apk', capabilities: '[]' }), []);
|
|
});
|
|
|
|
test('a hostile or malformed declaration never reaches the dashboard', () => {
|
|
// Not persisted at all -> the device keeps its baseline rather than gaining anything.
|
|
for (const bad of ['not json', '{"a":1}', 42, ' ']) assert.equal(persistedValue(bad), null);
|
|
});
|
|
|
|
test('unknown capability names are dropped, known ones survive', () => {
|
|
// A newer player declaring something this server has never heard of must not lose the rest.
|
|
assert.equal(persistedValue(['playback.video', 'quantum.teleport']), '["playback.video"]');
|
|
});
|
|
|
|
test('a stored declaration overrides the baseline in both directions', () => {
|
|
const stripped = { client_type: 'apk', capabilities: persistedValue(['playback.video']) };
|
|
assert.equal(caps.supports(stripped, 'system.reboot'), false, 'declared set wins over baseline');
|
|
|
|
const hosted = { platform: 'brightsign', capabilities: persistedValue(['system.reboot', 'sync.native']) };
|
|
assert.ok(caps.supports(hosted, 'sync.native'), 'a BrightSign with SyncManager can declare it');
|
|
});
|
|
|
|
test('the round trip is stable — persisted output re-parses to the same set', () => {
|
|
const declared = ['playback.video', 'audio.mute', 'system.reboot'];
|
|
const stored = persistedValue(declared);
|
|
assert.deepEqual(caps.capabilitiesFor({ capabilities: stored }), declared);
|
|
});
|