@tomorrowos/sdk 0.9.11 → 0.9.16

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/REPLIT_SETUP.md CHANGED
@@ -230,7 +230,7 @@ Allowed extras (optional): `"build-player": "tomorrowos build --platform tizen"`
230
230
  "node": ">=20"
231
231
  },
232
232
  "dependencies": {
233
- "@tomorrowos/sdk": "^0.9.11",
233
+ "@tomorrowos/sdk": "^0.9.12",
234
234
  "dotenv": "^17.2.3",
235
235
  "tsx": "^4.19.0"
236
236
  },
package/dist/index.d.ts CHANGED
@@ -3,7 +3,7 @@ export type { DeviceListItem, DeviceScreenshotInfo, ListenOptions, TomorrowOSBra
3
3
  export type { ConnectorState, ConnectorStatus, ServerStatusReport, StatusBlocker } from "./server-status.js";
4
4
  export { buildServerStatus } from "./server-status.js";
5
5
  export { getSdkPackageVersion } from "./sdk-version.js";
6
- export type { DevicePlaylistAssignment, DeviceRegistryRecord, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, DeviceRegistryEntry, PlaylistItemRecord, PlaylistSchedule, PublishedPlaylistSnapshot, StoredPlaylist, TomorrowOSDataSnapshot, TomorrowOSMigratableStore, TomorrowOSStore } from "./store/types.js";
6
+ export type { DeviceOnOffTimer, DevicePlaylistAssignment, DeviceRegistryRecord, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, DeviceRegistryEntry, PlaylistItemRecord, PlaylistSchedule, PublishedPlaylistSnapshot, StoredPlaylist, TomorrowOSDataSnapshot, TomorrowOSMigratableStore, TomorrowOSStore } from "./store/types.js";
7
7
  export { PlaylistCatalog } from "./playlist-catalog.js";
8
8
  export type { BuiltDevicePolicy, SavePlaylistInput } from "./playlist-catalog.js";
9
9
  export { DEFAULT_PAIRING_CODE_SECRET, generateDeterministicPairingCode, generateRandomPairingCode, isValidPairingCodeFormat, normalizePairingCode, PAIRING_CODE_ALPHABET, PAIRING_CODE_LENGTH, resolvePairingCodeSecret } from "./pairing-code.js";
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,aAAa,EACd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,YAAY,EACV,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,EACzB,cAAc,EACd,sBAAsB,EACtB,yBAAyB,EACzB,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EACL,2BAA2B,EAC3B,gCAAgC,EAChC,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,wBAAwB,EACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,YAAY,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,4BAA4B,EAC5B,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,YAAY,EACV,cAAc,EACd,oBAAoB,EACpB,aAAa,EACb,eAAe,EACf,iBAAiB,EAClB,MAAM,iBAAiB,CAAC;AACzB,YAAY,EACV,cAAc,EACd,eAAe,EACf,kBAAkB,EAClB,aAAa,EACd,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAC;AACvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAExD,YAAY,EACV,gBAAgB,EAChB,wBAAwB,EACxB,oBAAoB,EACpB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EACjB,mBAAmB,EACnB,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,EACzB,cAAc,EACd,sBAAsB,EACtB,yBAAyB,EACzB,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AACxD,YAAY,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAClF,OAAO,EACL,2BAA2B,EAC3B,gCAAgC,EAChC,yBAAyB,EACzB,wBAAwB,EACxB,oBAAoB,EACpB,qBAAqB,EACrB,mBAAmB,EACnB,wBAAwB,EACzB,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EAAE,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAC1D,YAAY,EAAE,oBAAoB,EAAE,MAAM,2BAA2B,CAAC;AACtE,OAAO,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AACtD,OAAO,EACL,qBAAqB,EACrB,iBAAiB,EAClB,MAAM,oBAAoB,CAAC;AAC5B,YAAY,EACV,4BAA4B,EAC5B,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACpB,qBAAqB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,YAAY,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC"}
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Best-effort primary non-loopback IPv4 for local development.
3
+ * Returns null when no suitable interface is found.
4
+ */
5
+ export declare function detectPrimaryLanIpv4(): string | null;
6
+ //# sourceMappingURL=lan-address.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lan-address.d.ts","sourceRoot":"","sources":["../src/lan-address.ts"],"names":[],"mappings":"AAUA;;;GAGG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,GAAG,IAAI,CAcpD"}
@@ -0,0 +1,32 @@
1
+ import os from "os";
2
+ /** Prefer common private LAN ranges for local CMS / player media base URLs. */
3
+ function scoreLanIpv4(ip) {
4
+ if (ip.startsWith("192.168."))
5
+ return 3;
6
+ if (ip.startsWith("10."))
7
+ return 2;
8
+ if (/^172\.(1[6-9]|2\d|3[0-1])\./.test(ip))
9
+ return 2;
10
+ return 1;
11
+ }
12
+ /**
13
+ * Best-effort primary non-loopback IPv4 for local development.
14
+ * Returns null when no suitable interface is found.
15
+ */
16
+ export function detectPrimaryLanIpv4() {
17
+ const nets = os.networkInterfaces();
18
+ const addresses = [];
19
+ for (const entries of Object.values(nets)) {
20
+ if (!entries)
21
+ continue;
22
+ for (const ent of entries) {
23
+ const family = ent.family;
24
+ const isV4 = family === "IPv4" || family === 4;
25
+ if (!isV4 || ent.internal)
26
+ continue;
27
+ addresses.push(ent.address);
28
+ }
29
+ }
30
+ addresses.sort((a, b) => scoreLanIpv4(b) - scoreLanIpv4(a));
31
+ return addresses[0] ?? null;
32
+ }
@@ -36,14 +36,14 @@ function resolveDatabaseProvider(env) {
36
36
  };
37
37
  }
38
38
  if (driver === "memory") {
39
- return { provider: "memory", label: "Memory Store", configured: true };
39
+ return { provider: "memory", label: "Database", configured: true };
40
40
  }
41
41
  if (driver === "sqlite" || !driver) {
42
- return { provider: "sqlite", label: "SQLite", configured: true };
42
+ return { provider: "sqlite", label: "Database", configured: true };
43
43
  }
44
44
  return {
45
45
  provider: driver || "unknown",
46
- label: driver ? `Store (${driver})` : "Database",
46
+ label: "Database",
47
47
  configured: hasUrl || driver === "sqlite" || driver === "memory"
48
48
  };
49
49
  }
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../src/store/postgres-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../src/store/postgres-store.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,IAAI,CAAC;AAE3C,OAAO,KAAK,EAEV,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EAGjB,cAAc,EACd,yBAAyB,EACzB,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAGpB,MAAM,WAAW,oBAAoB;IACnC,gBAAgB,EAAE,MAAM,CAAC;IACzB,GAAG,CAAC,EAAE,UAAU,CAAC,KAAK,CAAC,CAAC;CACzB;AA0ED;;;GAGG;AACH,qBAAa,aAAc,YAAW,yBAAyB;IAC7D,OAAO,CAAC,QAAQ,CAAC,IAAI,CAAO;IAC5B,OAAO,CAAC,KAAK,CAAC,CAAgB;gBAElB,OAAO,EAAE,oBAAoB,GAAG,MAAM;IAY5C,IAAI,IAAI,OAAO,CAAC,IAAI,CAAC;YA0Gb,uCAAuC;YAoEvC,iCAAiC;IAoDzC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC;YAId,WAAW;IAKnB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAWtE,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAepE,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAiB/C,iBAAiB,CACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;IAWtC,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;IA+BV,uBAAuB,CAC3B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,oBAAoB,CAAA;KAAE,GAAG,SAAS,CAAC;IAepE,kBAAkB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAapD,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,IAAI,CAAC;IAuDV,eAAe,CACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAWpC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAOnD,iBAAiB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAajD,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAU1C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAW5D,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IA+BlD,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAevE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAWtE,wBAAwB,CAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAYrC,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAuC5D,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAK9C,kBAAkB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAUpD,oBAAoB,CACxB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAwBhC,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,wBAAwB,EAAE,GACtC,OAAO,CAAC,IAAI,CAAC;IAqChB,OAAO,CAAC,oBAAoB;IAW5B,OAAO,CAAC,kBAAkB;IAuB1B,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,mBAAmB;CAc5B"}
@@ -125,6 +125,9 @@ export class PostgresStore {
125
125
  ALTER TABLE paired_devices
126
126
  ADD COLUMN IF NOT EXISTS last_screenshot_captured_at TEXT;
127
127
 
128
+ ALTER TABLE paired_devices
129
+ ADD COLUMN IF NOT EXISTS on_off_timer_json TEXT;
130
+
128
131
  ALTER TABLE device_registry
129
132
  ADD COLUMN IF NOT EXISTS display_name TEXT;
130
133
  `);
@@ -363,9 +366,10 @@ export class PostgresStore {
363
366
  last_offline_at,
364
367
  last_policy_push_at,
365
368
  last_screenshot_asset_id,
366
- last_screenshot_captured_at
369
+ last_screenshot_captured_at,
370
+ on_off_timer_json
367
371
  )
368
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
372
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14, $15)
369
373
  ON CONFLICT (device_id) DO UPDATE SET
370
374
  pairing_token = EXCLUDED.pairing_token,
371
375
  paired_at = EXCLUDED.paired_at,
@@ -379,7 +383,8 @@ export class PostgresStore {
379
383
  last_offline_at = EXCLUDED.last_offline_at,
380
384
  last_policy_push_at = EXCLUDED.last_policy_push_at,
381
385
  last_screenshot_asset_id = EXCLUDED.last_screenshot_asset_id,
382
- last_screenshot_captured_at = EXCLUDED.last_screenshot_captured_at
386
+ last_screenshot_captured_at = EXCLUDED.last_screenshot_captured_at,
387
+ on_off_timer_json = EXCLUDED.on_off_timer_json
383
388
  `, [
384
389
  deviceId,
385
390
  record.pairingToken,
@@ -394,7 +399,8 @@ export class PostgresStore {
394
399
  record.lastOfflineAt ?? null,
395
400
  record.lastPolicyPushAt ?? null,
396
401
  record.lastScreenshotAssetId ?? null,
397
- record.lastScreenshotCapturedAt ?? null
402
+ record.lastScreenshotCapturedAt ?? null,
403
+ record.onOffTimer ? JSON.stringify(record.onOffTimer) : null
398
404
  ]);
399
405
  }
400
406
  async getPairedDevice(deviceId) {
@@ -626,6 +632,7 @@ export class PostgresStore {
626
632
  };
627
633
  }
628
634
  mapPairedDeviceRow(row) {
635
+ const onOffTimer = parseJson(row.on_off_timer_json, null);
629
636
  return {
630
637
  pairingToken: row.pairing_token,
631
638
  pairedAt: row.paired_at,
@@ -639,7 +646,8 @@ export class PostgresStore {
639
646
  lastOfflineAt: optionalString(row.last_offline_at),
640
647
  lastPolicyPushAt: optionalString(row.last_policy_push_at),
641
648
  lastScreenshotAssetId: optionalString(row.last_screenshot_asset_id),
642
- lastScreenshotCapturedAt: optionalString(row.last_screenshot_captured_at)
649
+ lastScreenshotCapturedAt: optionalString(row.last_screenshot_captured_at),
650
+ ...(onOffTimer ? { onOffTimer } : {})
643
651
  };
644
652
  }
645
653
  mapPlaylistRow(row) {
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../src/store/sqlite-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../src/store/sqlite-store.ts"],"names":[],"mappings":"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"}
@@ -122,6 +122,7 @@ export class SQLiteStore {
122
122
  this.addColumnIfMissing("paired_devices", "system_version TEXT");
123
123
  this.addColumnIfMissing("paired_devices", "last_screenshot_asset_id TEXT");
124
124
  this.addColumnIfMissing("paired_devices", "last_screenshot_captured_at TEXT");
125
+ this.addColumnIfMissing("paired_devices", "on_off_timer_json TEXT");
125
126
  this.addColumnIfMissing("device_registry", "display_name TEXT");
126
127
  this.migrateDropPlaylistVersionColumns();
127
128
  this.migrateDropUploadedAssetStorageProvider();
@@ -413,9 +414,10 @@ export class SQLiteStore {
413
414
  last_offline_at,
414
415
  last_policy_push_at,
415
416
  last_screenshot_asset_id,
416
- last_screenshot_captured_at
417
+ last_screenshot_captured_at,
418
+ on_off_timer_json
417
419
  )
418
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
420
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
419
421
  ON CONFLICT(device_id) DO UPDATE SET
420
422
  pairing_token = excluded.pairing_token,
421
423
  paired_at = excluded.paired_at,
@@ -429,8 +431,9 @@ export class SQLiteStore {
429
431
  last_offline_at = excluded.last_offline_at,
430
432
  last_policy_push_at = excluded.last_policy_push_at,
431
433
  last_screenshot_asset_id = excluded.last_screenshot_asset_id,
432
- last_screenshot_captured_at = excluded.last_screenshot_captured_at
433
- `).run(deviceId, record.pairingToken, record.pairedAt, record.deviceName ?? null, record.platform ?? null, record.system ?? null, record.playerVersion ?? null, record.systemVersion ?? null, record.lastBootAt ?? null, record.lastOnlineAt ?? null, record.lastOfflineAt ?? null, record.lastPolicyPushAt ?? null, record.lastScreenshotAssetId ?? null, record.lastScreenshotCapturedAt ?? null);
434
+ last_screenshot_captured_at = excluded.last_screenshot_captured_at,
435
+ on_off_timer_json = excluded.on_off_timer_json
436
+ `).run(deviceId, record.pairingToken, record.pairedAt, record.deviceName ?? null, record.platform ?? null, record.system ?? null, record.playerVersion ?? null, record.systemVersion ?? null, record.lastBootAt ?? null, record.lastOnlineAt ?? null, record.lastOfflineAt ?? null, record.lastPolicyPushAt ?? null, record.lastScreenshotAssetId ?? null, record.lastScreenshotCapturedAt ?? null, record.onOffTimer ? JSON.stringify(record.onOffTimer) : null);
434
437
  }
435
438
  async getPairedDevice(deviceId) {
436
439
  const row = this.db.prepare(`
@@ -606,6 +609,7 @@ export class SQLiteStore {
606
609
  };
607
610
  }
608
611
  mapPairedDeviceRow(row) {
612
+ const onOffTimer = parseJson(row.on_off_timer_json, null);
609
613
  return {
610
614
  pairingToken: row.pairing_token,
611
615
  pairedAt: row.paired_at,
@@ -619,7 +623,8 @@ export class SQLiteStore {
619
623
  lastOfflineAt: optionalString(row.last_offline_at),
620
624
  lastPolicyPushAt: optionalString(row.last_policy_push_at),
621
625
  lastScreenshotAssetId: optionalString(row.last_screenshot_asset_id),
622
- lastScreenshotCapturedAt: optionalString(row.last_screenshot_captured_at)
626
+ lastScreenshotCapturedAt: optionalString(row.last_screenshot_captured_at),
627
+ ...(onOffTimer ? { onOffTimer } : {})
623
628
  };
624
629
  }
625
630
  mapPlaylistRow(row) {
@@ -16,6 +16,14 @@ export interface DeviceRegistryRecord {
16
16
  /** CMS operator display name — survives unpair / re-pair. */
17
17
  displayName?: string;
18
18
  }
19
+ /** Device-local daily screen on/off window (HH:mm, device clock). Off = panel mute. */
20
+ export interface DeviceOnOffTimer {
21
+ enabled: boolean;
22
+ /** HH:mm — unmute / screen on */
23
+ turnOnAt: string;
24
+ /** HH:mm — panel mute / screen off (device stays CMS-connected) */
25
+ turnOffAt: string;
26
+ }
19
27
  export interface PairedDeviceRecord {
20
28
  pairingToken: string;
21
29
  pairedAt: string;
@@ -31,6 +39,8 @@ export interface PairedDeviceRecord {
31
39
  /** Latest device screenshot stored via uploaded_assets (local or Cloudinary). */
32
40
  lastScreenshotAssetId?: string;
33
41
  lastScreenshotCapturedAt?: string;
42
+ /** Tizen screen mute schedule (panel mute; not power off). */
43
+ onOffTimer?: DeviceOnOffTimer;
34
44
  }
35
45
  export interface PairedDeviceEntry {
36
46
  deviceId: string;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wFAAwF;AACxF,MAAM,WAAW,oBAAoB;IACnC,oBAAoB,EAAE,MAAM,CAAC;IAC7B,aAAa,EAAE,MAAM,CAAC;IACtB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6DAA6D;IAC7D,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,uFAAuF;AACvF,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,iCAAiC;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,mEAAmE;IACnE,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,kBAAkB;IACjC,YAAY,EAAE,MAAM,CAAC;IACrB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,iFAAiF;IACjF,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B,wBAAwB,CAAC,EAAE,MAAM,CAAC;IAClC,8DAA8D;IAC9D,UAAU,CAAC,EAAE,gBAAgB,CAAC;CAC/B;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,kBAAkB,CAAC;CAC5B;AAED,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,iBAAiB,CAAC;CAC3B;AAED,MAAM,WAAW,mBAAmB;IAClC,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,oBAAoB,CAAC;CAC9B;AAED,MAAM,WAAW,kBAAkB;IACjC,GAAG,EAAE,MAAM,CAAC;IACZ,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,mBAAmB;IAClC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,GAAG,EAAE,MAAM,CAAC;IACZ,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,wGAAwG;AACxG,MAAM,WAAW,gBAAgB;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,qFAAqF;IACrF,UAAU,CAAC,EAAE,MAAM,EAAE,CAAC;IACtB,6DAA6D;IAC7D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,wEAAwE;IACxE,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,kBAAkB,EAAE,CAAC;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,yBAAyB;IACxC,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,wBAAwB;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,yBAAyB,CAAC;CACrC;AAED;;;GAGG;AACH,MAAM,WAAW,eAAe;IAC9B,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACvE,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC,CAAC;IACrE,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC,CAAC;IAC/E,iBAAiB,CACf,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,uBAAuB,CACrB,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,oBAAoB,CAAA;KAAE,GAAG,SAAS,CAAC,CAAC;IAC3E,eAAe,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7E,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC,CAAC;IAC3E,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpD,iBAAiB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC,CAAC;IAClD,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAC3C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC,CAAC;IAC7D,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACnD,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;IACxE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAC;IACvE,wBAAwB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC,CAAC;IACnF,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC7D,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAC/C,oBAAoB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,wBAAwB,EAAE,CAAC,CAAC;IAC5E,oBAAoB,CAClB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,wBAAwB,EAAE,GACtC,OAAO,CAAC,IAAI,CAAC,CAAC;CAClB;AAED,MAAM,WAAW,sBAAsB;IACrC,YAAY,EAAE,gBAAgB,EAAE,CAAC;IACjC,cAAc,EAAE,mBAAmB,EAAE,CAAC;IACtC,aAAa,EAAE,iBAAiB,EAAE,CAAC;IACnC,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,cAAc,EAAE,mBAAmB,EAAE,CAAC;IACtC,iBAAiB,EAAE,KAAK,CAAC;QACvB,QAAQ,EAAE,MAAM,CAAC;QACjB,WAAW,EAAE,wBAAwB,EAAE,CAAC;KACzC,CAAC,CAAC;CACJ;AAED,MAAM,WAAW,yBAA0B,SAAQ,eAAe;IAChE,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAC;IAChD,kBAAkB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;IACrD,kBAAkB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC,CAAC;CACtD"}
@@ -1,7 +1,7 @@
1
1
  import { EventEmitter } from "events";
2
2
  import http from "http";
3
3
  import { PlaylistCatalog, type BuiltDevicePolicy } from "./playlist-catalog.js";
4
- import type { TomorrowOSStore } from "./store/types.js";
4
+ import type { DeviceOnOffTimer, TomorrowOSStore } from "./store/types.js";
5
5
  export type { ConnectorState, ConnectorStatus, ServerStatusReport, StatusBlocker } from "./server-status.js";
6
6
  export interface TomorrowOSBrand {
7
7
  name?: string;
@@ -66,6 +66,8 @@ export interface DeviceListItem {
66
66
  url: string;
67
67
  capturedAt: string;
68
68
  } | null;
69
+ /** Tizen daily screen mute schedule (panel mute; device stays connected). */
70
+ onOffTimer: DeviceOnOffTimer | null;
69
71
  }
70
72
  export interface DeviceLogEntry {
71
73
  timestamp: string;
@@ -96,6 +98,8 @@ export declare class TomorrowOS extends EventEmitter {
96
98
  private staticIndexFile;
97
99
  /** Set when listen() starts — exposed to CMS panel for post-restart reconnect grace. */
98
100
  private serverStartedAt;
101
+ /** Port passed to listen(); used for suggested LAN CMS URL. */
102
+ private listenPort;
99
103
  constructor(options: TomorrowOSOptions);
100
104
  /** Push all device assignments using latest playlist definitions (e.g. after reboot). */
101
105
  pushLatestPolicyToDevice(deviceId: string): Promise<{
@@ -123,6 +127,16 @@ export declare class TomorrowOS extends EventEmitter {
123
127
  };
124
128
  /** List paired devices with live connection + timing fields for the CMS panel. */
125
129
  listDevices(): Promise<DeviceListItem[]>;
130
+ /**
131
+ * Persist a daily on/off timer (Tizen panel mute). Saved even if offline;
132
+ * pushed to the player when connected.
133
+ */
134
+ setDeviceOnOffTimer(deviceId: string, timerInput: unknown): Promise<{
135
+ deviceId: string;
136
+ onOffTimer: DeviceOnOffTimer;
137
+ pushed: boolean;
138
+ }>;
139
+ private pushOnOffTimerToDevice;
126
140
  setDeviceName(deviceId: string, deviceName: string): Promise<{
127
141
  deviceId: string;
128
142
  deviceName: string;
@@ -153,6 +167,8 @@ export declare class TomorrowOS extends EventEmitter {
153
167
  }>;
154
168
  };
155
169
  private sendCommandToSocket;
170
+ /** Best-effort LAN URL for local screens (e.g. http://192.168.1.10:3000). */
171
+ private getSuggestedCmsUrl;
156
172
  listen(options: ListenOptions): http.Server;
157
173
  private handleMediaUpload;
158
174
  private storeUploadedMediaAsset;
@@ -1 +1 @@
1
- {"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"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"}
@@ -11,6 +11,7 @@ import { resolveBrandLogoPath, syncProjectAssetsToStaticRoot } from "./brand-ass
11
11
  import { deleteCloudinaryAsset, resolveCloudinaryConfig, uploadBufferToCloudinary } from "./cloudinary-storage.js";
12
12
  import { probeVideoDurationMs } from "./media-probe.js";
13
13
  import { contentHashHex, storeUploadIfNeeded } from "./upload-storage.js";
14
+ import { detectPrimaryLanIpv4 } from "./lan-address.js";
14
15
  import { buildServerStatus } from "./server-status.js";
15
16
  function normalizeDevicePlatform(platform, system) {
16
17
  const explicit = typeof platform === "string" ? platform.trim().toLowerCase() : "";
@@ -45,6 +46,23 @@ function normalizeDeviceSystemLabel(platform, system) {
45
46
  const label = typeof system === "string" ? system.trim() : "";
46
47
  return label || undefined;
47
48
  }
49
+ const ON_OFF_TIME_RE = /^([01]\d|2[0-3]):[0-5]\d$/;
50
+ function normalizeOnOffTimer(input) {
51
+ if (!input || typeof input !== "object") {
52
+ throw new Error("onOffTimer object is required");
53
+ }
54
+ const body = input;
55
+ const enabled = Boolean(body.enabled);
56
+ const turnOnAt = String(body.turnOnAt ?? "").trim();
57
+ const turnOffAt = String(body.turnOffAt ?? "").trim();
58
+ if (!ON_OFF_TIME_RE.test(turnOnAt) || !ON_OFF_TIME_RE.test(turnOffAt)) {
59
+ throw new Error("turnOnAt and turnOffAt must be HH:mm (24h)");
60
+ }
61
+ if (turnOnAt === turnOffAt) {
62
+ throw new Error("turnOnAt and turnOffAt must be different");
63
+ }
64
+ return { enabled, turnOnAt, turnOffAt };
65
+ }
48
66
  function isRemoteUploadedAssetUrl(url) {
49
67
  return /^https?:\/\//i.test(String(url || "").trim());
50
68
  }
@@ -196,6 +214,8 @@ export class TomorrowOS extends EventEmitter {
196
214
  staticIndexFile = "index.html";
197
215
  /** Set when listen() starts — exposed to CMS panel for post-restart reconnect grace. */
198
216
  serverStartedAt = null;
217
+ /** Port passed to listen(); used for suggested LAN CMS URL. */
218
+ listenPort = null;
199
219
  constructor(options) {
200
220
  super();
201
221
  this.brand = options.brand;
@@ -284,6 +304,9 @@ export class TomorrowOS extends EventEmitter {
284
304
  void this.pushLatestPolicyToDevice(deviceId).catch((err) => {
285
305
  console.error("[TomorrowOS] pushLatestPolicy on paired failed:", err);
286
306
  });
307
+ void this.pushOnOffTimerToDevice(deviceId).catch((err) => {
308
+ console.error("[TomorrowOS] pushOnOffTimer on paired failed:", err);
309
+ });
287
310
  }
288
311
  this.emit("device.paired", { deviceId });
289
312
  return { deviceId };
@@ -384,10 +407,48 @@ export class TomorrowOS extends EventEmitter {
384
407
  screenOnlineSince,
385
408
  latestErrorAt: latestError?.timestamp ?? null,
386
409
  latestErrorMessage: latestError?.message ?? null,
387
- latestScreenshot
410
+ latestScreenshot,
411
+ onOffTimer: record.onOffTimer ?? null
388
412
  };
389
413
  }));
390
414
  }
415
+ /**
416
+ * Persist a daily on/off timer (Tizen panel mute). Saved even if offline;
417
+ * pushed to the player when connected.
418
+ */
419
+ async setDeviceOnOffTimer(deviceId, timerInput) {
420
+ const id = String(deviceId || "").trim();
421
+ if (!id) {
422
+ throw new Error("deviceId is required");
423
+ }
424
+ const existing = await this.store.getPairedDevice(id);
425
+ if (!existing) {
426
+ throw new Error("Device is not paired");
427
+ }
428
+ const onOffTimer = normalizeOnOffTimer(timerInput);
429
+ await this.store.setPairedDevice(id, {
430
+ ...existing,
431
+ onOffTimer
432
+ });
433
+ const pushed = await this.pushOnOffTimerToDevice(id);
434
+ return { deviceId: id, onOffTimer, pushed };
435
+ }
436
+ async pushOnOffTimerToDevice(deviceId) {
437
+ const existing = await this.store.getPairedDevice(deviceId);
438
+ if (!existing?.onOffTimer)
439
+ return false;
440
+ const ws = this.devices.get(deviceId);
441
+ if (!ws || ws.readyState !== WebSocket.OPEN)
442
+ return false;
443
+ try {
444
+ const result = await this.sendCommandToSocket(ws, deviceId, "device.display.setOnOffTimer", { onOffTimer: existing.onOffTimer }, 15_000);
445
+ return result.status === "success" || result.status === "accepted";
446
+ }
447
+ catch (err) {
448
+ console.error("[TomorrowOS] pushOnOffTimer failed:", err);
449
+ return false;
450
+ }
451
+ }
391
452
  async setDeviceName(deviceId, deviceName) {
392
453
  const id = String(deviceId || "").trim();
393
454
  if (!id) {
@@ -658,8 +719,17 @@ export class TomorrowOS extends EventEmitter {
658
719
  }));
659
720
  });
660
721
  }
722
+ /** Best-effort LAN URL for local screens (e.g. http://192.168.1.10:3000). */
723
+ getSuggestedCmsUrl() {
724
+ const ip = detectPrimaryLanIpv4();
725
+ const port = this.listenPort;
726
+ if (!ip || port == null || !Number.isFinite(port))
727
+ return null;
728
+ return `http://${ip}:${port}`;
729
+ }
661
730
  listen(options) {
662
731
  const { port, host = "0.0.0.0", staticRoot, staticIndex } = options;
732
+ this.listenPort = port;
663
733
  this.staticRoot = staticRoot ?? null;
664
734
  if (this.staticRoot) {
665
735
  const idx = (staticIndex ?? "index.html").trim().replace(/^[\\/]+/, "") || "index.html";
@@ -1030,7 +1100,11 @@ export class TomorrowOS extends EventEmitter {
1030
1100
  store: this.store,
1031
1101
  staticRoot: this.staticRoot
1032
1102
  });
1033
- sendJson(res, 200, report);
1103
+ const suggestedCmsUrl = this.getSuggestedCmsUrl();
1104
+ sendJson(res, 200, {
1105
+ ...report,
1106
+ ...(suggestedCmsUrl ? { suggestedCmsUrl } : {})
1107
+ });
1034
1108
  return;
1035
1109
  }
1036
1110
  if (req.method === "POST" && pathname === "/media/upload") {
@@ -1091,6 +1165,20 @@ export class TomorrowOS extends EventEmitter {
1091
1165
  }
1092
1166
  return;
1093
1167
  }
1168
+ const deviceOnOffTimer = /^\/device\/([^/]+)\/on-off-timer$/.exec(pathname);
1169
+ if (req.method === "POST" && deviceOnOffTimer) {
1170
+ const deviceId = decodeURIComponent(deviceOnOffTimer[1]);
1171
+ const body = (await readJsonBody(req));
1172
+ try {
1173
+ const result = await this.setDeviceOnOffTimer(deviceId, body.onOffTimer ?? body);
1174
+ sendJson(res, 200, { status: "success", ...result });
1175
+ }
1176
+ catch (e) {
1177
+ const msg = e instanceof Error ? e.message : "On/off timer failed";
1178
+ sendJson(res, 400, { status: "failed", error: msg });
1179
+ }
1180
+ return;
1181
+ }
1094
1182
  if (req.method === "GET" && pathname === "/playlists") {
1095
1183
  const playlists = await this.playlists.listPlaylists();
1096
1184
  sendJson(res, 200, { status: "success", playlists });
@@ -1477,6 +1565,9 @@ export class TomorrowOS extends EventEmitter {
1477
1565
  void this.pushLatestPolicyToDevice(deviceId).catch((err) => {
1478
1566
  console.error("[TomorrowOS] pushLatestPolicy on resume failed:", err);
1479
1567
  });
1568
+ void this.pushOnOffTimerToDevice(deviceId).catch((err) => {
1569
+ console.error("[TomorrowOS] pushOnOffTimer on resume failed:", err);
1570
+ });
1480
1571
  })();
1481
1572
  return;
1482
1573
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "0.9.11",
3
+ "version": "0.9.16",
4
4
  "description": "TomorrowOS CMS server SDK - WebSocket transport, pairing, device commands, optional static CMS UI. Includes CLI (tomorrowos init / build) and starter templates.",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-cms",
3
- "version": "0.9.11",
3
+ "version": "0.9.16",
4
4
  "description": "CMS server on @tomorrowos/sdk. Add your UI (React, static files, etc.) alongside this server.",
5
5
  "private": true,
6
6
  "type": "module",
@@ -13,7 +13,7 @@
13
13
  "build-player": "tomorrowos build --platform tizen"
14
14
  },
15
15
  "dependencies": {
16
- "@tomorrowos/sdk": "^0.9.11",
16
+ "@tomorrowos/sdk": "^0.9.16",
17
17
  "dotenv": "^17.2.3",
18
18
  "tsx": "^4.19.0"
19
19
  },
@@ -66,15 +66,16 @@
66
66
  <section class="card" id="cmsUrlSection">
67
67
  <h2>CMS URL for screens</h2>
68
68
  <p class="hint">
69
- <strong>Local development only.</strong> Optional LAN override so TVs reach your PC (not localhost).
70
- Hosted CMS (e.g. Replit) uses its public URL automatically leave this empty there.
69
+ <strong>Local development only.</strong> Defaults to this PC’s LAN IP and listen port so TVs
70
+ can reach media (not localhost). Change and Save if the auto-detected address is wrong.
71
+ Hosted CMS (e.g. Replit) uses its public URL automatically — this section is hidden there.
71
72
  </p>
72
73
  <div class="row">
73
74
  <input
74
75
  id="cmsDeviceBaseUrl"
75
76
  type="text"
76
77
  style="flex: 1; min-width: 16rem"
77
- placeholder="http://192.168.1.105:3000"
78
+ placeholder="Detecting LAN address…"
78
79
  />
79
80
  <button type="button" onclick="saveCmsDeviceBaseUrl()">Save</button>
80
81
  </div>
@@ -180,6 +181,30 @@
180
181
  </div>
181
182
  </div>
182
183
 
184
+ <div id="onOffTimerModal" class="modal hidden" role="dialog" aria-modal="true">
185
+ <div class="modal-backdrop" data-close-on-off-timer-modal="1"></div>
186
+ <div class="modal-card">
187
+ <h2>On / off timer</h2>
188
+ <p class="hint" id="onOffTimerModalHint">
189
+ Daily on/off times follow the device clock. Off blacks the screen; the player stays powered and connected to the CMS.
190
+ </p>
191
+ <div class="on-off-timer-grid">
192
+ <label class="field-label">
193
+ Turn on (unmute)
194
+ <input type="time" id="onOffTimerTurnOnAt" value="06:00" />
195
+ </label>
196
+ <label class="field-label">
197
+ Turn off (mute)
198
+ <input type="time" id="onOffTimerTurnOffAt" value="18:00" />
199
+ </label>
200
+ </div>
201
+ <div class="modal-actions">
202
+ <button type="button" id="onOffTimerSaveBtn" class="primary">Save</button>
203
+ <button type="button" data-close-on-off-timer-modal="1">Cancel</button>
204
+ </div>
205
+ </div>
206
+ </div>
207
+
183
208
  <div id="downloadFailedModal" class="modal hidden" role="dialog" aria-modal="true">
184
209
  <div class="modal-backdrop" data-close-download-failed-modal="1"></div>
185
210
  <div class="modal-card">
@@ -204,7 +229,7 @@
204
229
  <p class="hint">Current installed SDK version:</p>
205
230
  <p class="update-sdk-version" id="updateSdkVersionLabel">Loading…</p>
206
231
  <p class="hint">
207
- To upgrade on Replit (or another Agent platform), paste this prompt into the Agent chat:
232
+ To upgrade on AI agent platforms (Replit, Vercel, etc.), paste this prompt into the Agent chat:
208
233
  </p>
209
234
  <pre class="update-sdk-prompt" id="updateSdkPromptText">Follow @tomorrowos/sdk REPLIT_UPGRADE.md to upgrade my CMS with the latest SDK.</pre>
210
235
  <div class="modal-actions update-sdk-modal-actions">
@@ -190,6 +190,28 @@ function getExplicitLanMediaBase() {
190
190
  return normalizeMediaBaseUrl(localStorage.getItem(PANEL_MEDIA_BASE_KEY) || "");
191
191
  }
192
192
 
193
+ /**
194
+ * Prefill CMS URL for screens from server-detected LAN IP:port when running on localhost
195
+ * and the operator has not saved a value yet. Auto-persists so thumbs/publish work without Save.
196
+ */
197
+ function applyDefaultCmsUrlForScreens(suggested) {
198
+ if (!isLocalPanelHost(window.location.hostname)) return;
199
+ const normalized = normalizeMediaBaseUrl(suggested);
200
+ if (!normalized) return;
201
+
202
+ const cmsBaseInput = document.getElementById("cmsDeviceBaseUrl");
203
+ const saved = getExplicitLanMediaBase();
204
+ if (saved) {
205
+ if (cmsBaseInput && !String(cmsBaseInput.value || "").trim()) {
206
+ cmsBaseInput.value = saved;
207
+ }
208
+ return;
209
+ }
210
+
211
+ if (cmsBaseInput) cmsBaseInput.value = normalized;
212
+ localStorage.setItem(PANEL_MEDIA_BASE_KEY, normalized);
213
+ }
214
+
193
215
  function playlistHasRelativeMediaUrls(playlist) {
194
216
  return (playlist?.items || []).some((item) => {
195
217
  const url = String(item?.url || "").trim();
@@ -1207,7 +1229,6 @@ function renderDeviceCards() {
1207
1229
  const primaryRows = [
1208
1230
  ["Device ID", device.deviceId],
1209
1231
  ["System", device.system || device.platform || "—"],
1210
- ["System version", device.systemVersion || "—"],
1211
1232
  ["Player version", device.playerVersion || "—"]
1212
1233
  ];
1213
1234
  for (const [label, value] of primaryRows) {
@@ -1279,6 +1300,12 @@ function renderDeviceCards() {
1279
1300
  latestScreenshotBtn.textContent = "Last screen";
1280
1301
  latestScreenshotBtn.addEventListener("click", () => viewLatestScreenshot(device.deviceId));
1281
1302
 
1303
+ const timerBtn = document.createElement("button");
1304
+ timerBtn.type = "button";
1305
+ timerBtn.textContent = "Timer";
1306
+ timerBtn.title = "Daily screen on/off timer";
1307
+ timerBtn.addEventListener("click", () => openOnOffTimerModal(device.deviceId));
1308
+
1282
1309
  const unpairBtn = document.createElement("button");
1283
1310
  unpairBtn.type = "button";
1284
1311
  unpairBtn.className = "danger";
@@ -1293,6 +1320,9 @@ function renderDeviceCards() {
1293
1320
  actions.appendChild(logsBtn);
1294
1321
  actions.appendChild(screenshotBtn);
1295
1322
  actions.appendChild(latestScreenshotBtn);
1323
+ if (isTizenDevice(device)) {
1324
+ actions.appendChild(timerBtn);
1325
+ }
1296
1326
  actions.appendChild(unpairBtn);
1297
1327
 
1298
1328
  card.appendChild(header);
@@ -1785,6 +1815,92 @@ function closeScreenshotModal() {
1785
1815
  modal?.classList.add("hidden");
1786
1816
  }
1787
1817
 
1818
+ function isTizenDevice(device) {
1819
+ const platform = String(device?.platform || "").toLowerCase();
1820
+ const system = String(device?.system || "").toLowerCase();
1821
+ return platform.includes("tizen") || system.includes("tizen");
1822
+ }
1823
+
1824
+ function normalizeTimeInputValue(value, fallback) {
1825
+ const raw = String(value || "").trim();
1826
+ const match = /^(\d{1,2}):(\d{2})/.exec(raw);
1827
+ if (!match) return fallback;
1828
+ const hh = Math.min(23, Math.max(0, Number(match[1])));
1829
+ const mm = Math.min(59, Math.max(0, Number(match[2])));
1830
+ if (Number.isNaN(hh) || Number.isNaN(mm)) return fallback;
1831
+ return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}`;
1832
+ }
1833
+
1834
+ let onOffTimerModalDeviceId = null;
1835
+
1836
+ function openOnOffTimerModal(deviceId) {
1837
+ const device = devicesCache.find((d) => d.deviceId === deviceId);
1838
+ if (!device) return;
1839
+ if (!isTizenDevice(device)) {
1840
+ alert("On/off timer is available for Tizen players only.");
1841
+ return;
1842
+ }
1843
+
1844
+ onOffTimerModalDeviceId = deviceId;
1845
+ const modal = document.getElementById("onOffTimerModal");
1846
+ const onEl = document.getElementById("onOffTimerTurnOnAt");
1847
+ const offEl = document.getElementById("onOffTimerTurnOffAt");
1848
+ if (!modal || !onEl || !offEl) return;
1849
+
1850
+ const timer = device.onOffTimer || {};
1851
+ onEl.value = normalizeTimeInputValue(timer.turnOnAt, "06:00");
1852
+ offEl.value = normalizeTimeInputValue(timer.turnOffAt, "18:00");
1853
+ modal.classList.remove("hidden");
1854
+ }
1855
+
1856
+ function closeOnOffTimerModal() {
1857
+ const modal = document.getElementById("onOffTimerModal");
1858
+ if (modal) modal.classList.add("hidden");
1859
+ onOffTimerModalDeviceId = null;
1860
+ }
1861
+
1862
+ async function saveOnOffTimerModal() {
1863
+ const deviceId = onOffTimerModalDeviceId;
1864
+ if (!deviceId) return;
1865
+
1866
+ const onEl = document.getElementById("onOffTimerTurnOnAt");
1867
+ const offEl = document.getElementById("onOffTimerTurnOffAt");
1868
+ const saveBtn = document.getElementById("onOffTimerSaveBtn");
1869
+ if (!onEl || !offEl) return;
1870
+
1871
+ const onOffTimer = {
1872
+ enabled: true,
1873
+ turnOnAt: normalizeTimeInputValue(onEl.value, "06:00"),
1874
+ turnOffAt: normalizeTimeInputValue(offEl.value, "18:00")
1875
+ };
1876
+
1877
+ if (onOffTimer.turnOnAt === onOffTimer.turnOffAt) {
1878
+ alert("Turn on and turn off times must be different.");
1879
+ return;
1880
+ }
1881
+
1882
+ if (saveBtn) saveBtn.disabled = true;
1883
+ try {
1884
+ const res = await fetch(`/device/${encodeURIComponent(deviceId)}/on-off-timer`, {
1885
+ method: "POST",
1886
+ headers: { "Content-Type": "application/json" },
1887
+ body: JSON.stringify({ onOffTimer })
1888
+ });
1889
+ const data = await res.json();
1890
+ if (!res.ok || data.status === "failed") {
1891
+ alert(data.error || "Could not save on/off timer");
1892
+ return;
1893
+ }
1894
+ showResult({ deviceId, onOffTimer: data.onOffTimer, pushed: data.pushed });
1895
+ closeOnOffTimerModal();
1896
+ await fetchDevices();
1897
+ } catch (err) {
1898
+ alert(err?.message || "Could not save on/off timer");
1899
+ } finally {
1900
+ if (saveBtn) saveBtn.disabled = false;
1901
+ }
1902
+ }
1903
+
1788
1904
  function openDownloadFailedModal(detail) {
1789
1905
  const modal = document.getElementById("downloadFailedModal");
1790
1906
  const message = document.getElementById("downloadFailedModalMessage");
@@ -2035,6 +2151,9 @@ async function fetchServerStatus() {
2035
2151
  if (typeof data.sdkVersion === "string" && data.sdkVersion.trim()) {
2036
2152
  cachedSdkVersion = data.sdkVersion.trim();
2037
2153
  }
2154
+ if (typeof data.suggestedCmsUrl === "string") {
2155
+ applyDefaultCmsUrlForScreens(data.suggestedCmsUrl);
2156
+ }
2038
2157
  renderServerStatus(data);
2039
2158
  } catch (err) {
2040
2159
  renderServerStatus({
@@ -2101,6 +2220,12 @@ document.addEventListener("DOMContentLoaded", () => {
2101
2220
  document.querySelectorAll("[data-close-screenshot-modal]").forEach((el) => {
2102
2221
  el.addEventListener("click", closeScreenshotModal);
2103
2222
  });
2223
+ document.querySelectorAll("[data-close-on-off-timer-modal]").forEach((el) => {
2224
+ el.addEventListener("click", closeOnOffTimerModal);
2225
+ });
2226
+ document
2227
+ .getElementById("onOffTimerSaveBtn")
2228
+ ?.addEventListener("click", () => void saveOnOffTimerModal());
2104
2229
  document.querySelectorAll("[data-close-download-failed-modal]").forEach((el) => {
2105
2230
  el.addEventListener("click", closeDownloadFailedModal);
2106
2231
  });
@@ -829,6 +829,19 @@ button.danger {
829
829
  width: min(94vw, 860px);
830
830
  }
831
831
 
832
+ .on-off-timer-grid {
833
+ display: grid;
834
+ grid-template-columns: 1fr 1fr;
835
+ gap: 0.75rem;
836
+ margin-top: 0.85rem;
837
+ }
838
+
839
+ @media (max-width: 520px) {
840
+ .on-off-timer-grid {
841
+ grid-template-columns: 1fr;
842
+ }
843
+ }
844
+
832
845
  .screenshot-preview {
833
846
  display: block;
834
847
  width: 100%;