src.nth.io/

summaryrefslogtreecommitdiff
path: root/scrypted/scrypted-viewport.ts
diff options
context:
space:
mode:
authorLuke Hoersten <[email protected]>2026-06-14 20:31:36 -0500
committerLuke Hoersten <[email protected]>2026-06-14 20:31:36 -0500
commitf069dd6692f18b95b70f87ce0207a284da320b61 (patch)
tree0356db8018765533931f43e91578afda2cc2f310 /scrypted/scrypted-viewport.ts
parent97a177b3dd134b5457c5a96688626f7cdfe53a6d (diff)
scrypted: guard against empty v.name in register + log paths
After the first camera event fires on a freshly-created viewport, v.name sometimes resolves as "" (Scrypted device-record load racing with event delivery). registerViewport then POSTs /config with viewport="" and the firmware returns 400 "viewport must be a non-empty string". The handler retries on the next callback so it eventually self-heals, but the log is noisy and we'd needlessly fail one register cycle. Mirror the canonical display name into device storage (display_name) on createDevice + on every successful register, and have registerViewport + attachListener fall back to that storage value when v.name is empty. No more empty-name POSTs to the firmware.
Diffstat (limited to 'scrypted/scrypted-viewport.ts')
-rw-r--r--scrypted/scrypted-viewport.ts31
1 files changed, 25 insertions, 6 deletions
diff --git a/scrypted/scrypted-viewport.ts b/scrypted/scrypted-viewport.ts
index fd846ae..0a0ca6e 100644
--- a/scrypted/scrypted-viewport.ts
+++ b/scrypted/scrypted-viewport.ts
@@ -325,7 +325,12 @@ class ScryptedViewportProvider extends ScryptedDeviceBase
});
// 2. Now safe to seed the child's storage from the form values.
+ // display_name is the canonical user-facing name; v.name (the
+ // ScryptedDeviceBase one) is async-loaded from Scrypted's record
+ // and races with our first registerViewport call, so we mirror
+ // it into storage as a stable fallback for register/log paths.
const childStore = deviceManager.getDeviceStorage(nativeId);
+ childStore.setItem("display_name", name);
childStore.setItem("host", String(settings.host || ""));
childStore.setItem("cameraId", String(settings.cameraId || ""));
childStore.setItem("orientation", String(settings.orientation || "portrait"));
@@ -358,8 +363,9 @@ class ScryptedViewportProvider extends ScryptedDeviceBase
private attachListener(v: Viewport) {
if (!v.cameraId) return;
const cam = systemManager.getDeviceById(v.cameraId);
+ const tag = v.name || v.storage.getItem("display_name") || v.nativeId;
if (!cam) {
- this.console.warn(`viewport "${v.name}": camera ${v.cameraId} not found`);
+ this.console.warn(`viewport "${tag}": camera ${v.cameraId} not found`);
return;
}
const ifaces = [
@@ -371,7 +377,7 @@ class ScryptedViewportProvider extends ScryptedDeviceBase
this.handleCameraEvent(v, details, data);
});
this.listeners.set(v.nativeId!, reg);
- this.console.log(`viewport "${v.name}": subscribed to "${cam.name}"`);
+ this.console.log(`viewport "${tag}": subscribed to "${cam.name}"`);
}
private detachListener(nativeId: string) {
@@ -391,22 +397,35 @@ class ScryptedViewportProvider extends ScryptedDeviceBase
}
private async registerViewport(v: Viewport) {
+ // Guard against transient empty names. Scrypted occasionally hands
+ // us a Viewport whose `.name` hasn't resolved yet (race between
+ // device-record load and event delivery); POSTing /config with an
+ // empty viewport just gets a 400 from the firmware. Fall back to
+ // the stored display name from storage if it's there, else skip.
+ const name = (v.name && v.name.trim()) || v.storage.getItem("display_name") || "";
+ if (!name) {
+ this.console.warn(`register skipped — empty name on ${v.nativeId}; will retry on next event`);
+ return;
+ }
await this.refreshHostFromMdns(v);
if (!v.host) {
- this.console.warn(`register "${v.name}" skipped — no host (set one manually or check mDNS)`);
+ this.console.warn(`register "${name}" skipped — no host (set one manually or check mDNS)`);
return;
}
try {
await this.postJSON(`http://${v.host}/config`, {
- viewport: v.name,
+ viewport: name,
scrypted: this.scryptedBase,
idle_timeout_ms: v.idleTimeoutMs,
orientation: v.orientation,
brightness: v.brightness,
});
- this.console.log(`registered "${v.name}" (${v.host})`);
+ // Cache the name in storage so a future empty-.name event can
+ // still find it. createDevice + putSetting always update this.
+ v.storage.setItem("display_name", name);
+ this.console.log(`registered "${name}" (${v.host})`);
} catch (e) {
- this.console.warn(`register "${v.name}" failed:`, (e as Error).message);
+ this.console.warn(`register "${name}" failed:`, (e as Error).message);
}
}