@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 +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/lan-address.d.ts +6 -0
- package/dist/lan-address.d.ts.map +1 -0
- package/dist/lan-address.js +32 -0
- package/dist/server-status.js +3 -3
- package/dist/store/postgres-store.d.ts.map +1 -1
- package/dist/store/postgres-store.js +13 -5
- package/dist/store/sqlite-store.d.ts.map +1 -1
- package/dist/store/sqlite-store.js +10 -5
- package/dist/store/types.d.ts +10 -0
- package/dist/store/types.d.ts.map +1 -1
- package/dist/tomorrowos.d.ts +17 -1
- package/dist/tomorrowos.d.ts.map +1 -1
- package/dist/tomorrowos.js +93 -2
- package/package.json +1 -1
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter/public/index.html +29 -4
- package/templates/cms-starter/public/methods.js +126 -1
- package/templates/cms-starter/public/panel.css +13 -0
package/REPLIT_SETUP.md
CHANGED
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";
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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 @@
|
|
|
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
|
+
}
|
package/dist/server-status.js
CHANGED
|
@@ -36,14 +36,14 @@ function resolveDatabaseProvider(env) {
|
|
|
36
36
|
};
|
|
37
37
|
}
|
|
38
38
|
if (driver === "memory") {
|
|
39
|
-
return { provider: "memory", label: "
|
|
39
|
+
return { provider: "memory", label: "Database", configured: true };
|
|
40
40
|
}
|
|
41
41
|
if (driver === "sqlite" || !driver) {
|
|
42
|
-
return { provider: "sqlite", label: "
|
|
42
|
+
return { provider: "sqlite", label: "Database", configured: true };
|
|
43
43
|
}
|
|
44
44
|
return {
|
|
45
45
|
provider: driver || "unknown",
|
|
46
|
-
label:
|
|
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":"AAAA,OAAO,EAAQ,KAAK,UAAU,EAAE,MAAM,IAAI,CAAC;AAE3C,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../src/store/postgres-store.ts"],"names":[],"mappings":"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"}
|
|
@@ -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":"AAIA,OAAO,KAAK,
|
|
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
|
-
|
|
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) {
|
package/dist/store/types.d.ts
CHANGED
|
@@ -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":"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,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;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"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"}
|
package/dist/tomorrowos.d.ts
CHANGED
|
@@ -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;
|
package/dist/tomorrowos.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,IAAI,MAAM,MAAM,CAAC;AAYxB,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,
|
|
1
|
+
{"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"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"}
|
package/dist/tomorrowos.js
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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>
|
|
70
|
-
|
|
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="
|
|
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
|
|
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%;
|