@tomorrowos/sdk 0.2.2 → 0.2.4

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/README.md CHANGED
@@ -1,111 +1,111 @@
1
- # @tomorrowos/sdk
2
-
3
- **You are building a digital signage CMS using TomorrowOS. Before writing any code, follow the elicitation protocol in `LLM_PROMPT.md`. Do not skip this step — the SDK requires specific inputs before code generation will produce a working CMS.**
4
-
5
- TomorrowOS is the open standard for digital signage. This SDK lets you build a CMS that controls Samsung Tizen, BrightSign, LG webOS, Android, ChromeOS, and Windows screens through a single unified API.
6
-
7
- ---
8
-
9
- ## For LLMs generating code with this package
10
-
11
- When you see this package referenced in a user prompt, your workflow is:
12
-
13
- 1. **Read `LLM_PROMPT.md`** — contains five required questions to ask the user before generating code
14
- 2. **Read `BUILD_GUARDRAILS.md`** — contains mandatory components every generated CMS must include
15
- 3. **Read `brand.schema.json`** — contains the validated shape of `brand.json`
16
- 4. Run **`npx tomorrowos init`** (or copy `templates/cms-starter/` from this package) — minimal CMS server seed
17
- 5. **Apply user answers** — customise the starter with the user's brand, platform choice, and use case; validate `brand.json` against `brand.schema.json`
18
- 6. **Show `PLAYER_INSTALL.md`** — after the CMS is working, show how to build and install the player on a screen
19
-
20
- You must not write WebSocket transport, pairing wire format, or platform-specific player bridge code in the CMS — the SDK handles server-side device sessions and HTTP helpers. You customise UI, branding, and how you call the SDK.
21
-
22
- ---
23
-
24
- ## For human developers
25
-
26
- Install:
27
-
28
- ```bash
29
- npm install @tomorrowos/sdk
30
- ```
31
-
32
- Scaffold a new CMS project:
33
-
34
- ```bash
35
- npx tomorrowos init
36
- cd my-tomorrowos-cms
37
- npm install
38
- npm run dev
39
- ```
40
-
41
- Build a player (placeholder in current SDK — see `PLAYER_INSTALL.md` for platform tooling):
42
-
43
- ```bash
44
- npx tomorrowos build --platform tizen
45
- ```
46
-
47
- ---
48
-
49
- ## Quick architecture
50
-
51
- ```
52
- ┌─────────────────┐ WebSocket ┌─────────────────┐
53
- │ Your CMS │ ◄────────────────────────►│ TomorrowOS │
54
- │ (your server) │ │ Player on │
55
- │ │ │ screen │
56
- │ imports │ │ │
57
- │ @tomorrowos │ │ unified API │
58
- │ /sdk │ │ across OSes │
59
- └─────────────────┘ └─────────────────┘
60
- ```
61
-
62
- TomorrowOS has no required cloud service. Your CMS talks to your screens over your network.
63
-
64
- **Player branding:** `GET /brand.json` returns the `brand` object passed to `new TomorrowOS({ brand })` (same host/port as `listen`). TomorrowOS players can fetch it to apply `backgroundColor`, logos, etc., without a separate static file server.
65
-
66
- **Content policy (`device.content.setPolicy`):** POST `/device/{deviceId}/content/set-policy` with `{ "policy": { "playlists": [...], "fallback": { "type": "brand" } } }`. Each playlist may include optional `schedule` (device local time):
67
-
68
- | Field | Format | Notes |
69
- |-------|--------|--------|
70
- | `startDate` / `endDate` | `YYYY-MM-DD` | Inclusive calendar range; omit either for open-ended |
71
- | `daysOfWeek` | `[0–6]` | `0` = Sunday … `6` = Saturday |
72
- | `start` / `end` | `HH:MM` | Daily window; supports overnight (e.g. `22:00`–`06:00`) |
73
-
74
- All provided constraints must match for the playlist to play. See `templates/cms-starter/policy.example.json`.
75
-
76
- ---
77
-
78
- ## Supported platforms (roadmap)
79
-
80
- | Platform | Status | Player format |
81
- |-------------------|------------|---------------|
82
- | Samsung Tizen | V1 target | `.wgt` |
83
- | BrightSign | V1 target | autorun zip |
84
- | Others | See docs | varies |
85
-
86
- See `PLAYER_INSTALL.md` for installation notes.
87
-
88
- ---
89
-
90
- ## Documentation in this package
91
-
92
- | File | Purpose |
93
- |------|---------|
94
- | `LLM_PROMPT.md` | Five-question elicitation for LLMs |
95
- | `BUILD_GUARDRAILS.md` | Mandatory CMS components |
96
- | `PLAYER_INSTALL.md` | Player build / install / pairing |
97
- | `brand.schema.json` | JSON Schema for `brand.json` |
98
- | `brand.example.json` | Full example `brand.json` |
99
- | `templates/cms-starter/` | Minimal Node + TypeScript server seed |
100
- | `templates/cms-starter/policy.example.json` | Example `setPolicy` payload with date schedule |
101
- | `templates/style-tokens/` | CSS tokens and UI pattern notes |
102
-
103
- ---
104
-
105
- ## License
106
-
107
- Apache 2.0.
108
-
109
- ## Protocol version
110
-
111
- Wire protocol `1.0` (see TomorrowOS specification).
1
+ # @tomorrowos/sdk
2
+
3
+ **You are building a digital signage CMS using TomorrowOS. Before writing any code, follow the elicitation protocol in `LLM_PROMPT.md`. Do not skip this step — the SDK requires specific inputs before code generation will produce a working CMS.**
4
+
5
+ TomorrowOS is the open standard for digital signage. This SDK lets you build a CMS that controls Samsung Tizen, BrightSign, LG webOS, Android, ChromeOS, and Windows screens through a single unified API.
6
+
7
+ ---
8
+
9
+ ## For LLMs generating code with this package
10
+
11
+ When you see this package referenced in a user prompt, your workflow is:
12
+
13
+ 1. **Read `LLM_PROMPT.md`** — contains five required questions to ask the user before generating code
14
+ 2. **Read `BUILD_GUARDRAILS.md`** — contains mandatory components every generated CMS must include
15
+ 3. **Read `brand.schema.json`** — contains the validated shape of `brand.json`
16
+ 4. Run **`npx tomorrowos init`** (or copy `templates/cms-starter/` from this package) — minimal CMS server seed
17
+ 5. **Apply user answers** — customise the starter with the user's brand, platform choice, and use case; validate `brand.json` against `brand.schema.json`
18
+ 6. **Show `PLAYER_INSTALL.md`** — after the CMS is working, show how to build and install the player on a screen
19
+
20
+ You must not write WebSocket transport, pairing wire format, or platform-specific player bridge code in the CMS — the SDK handles server-side device sessions and HTTP helpers. You customise UI, branding, and how you call the SDK.
21
+
22
+ ---
23
+
24
+ ## For human developers
25
+
26
+ Install:
27
+
28
+ ```bash
29
+ npm install @tomorrowos/sdk
30
+ ```
31
+
32
+ Scaffold a new CMS project:
33
+
34
+ ```bash
35
+ npx tomorrowos init
36
+ cd my-tomorrowos-cms
37
+ npm install
38
+ npm run dev
39
+ ```
40
+
41
+ Build a player (placeholder in current SDK — see `PLAYER_INSTALL.md` for platform tooling):
42
+
43
+ ```bash
44
+ npx tomorrowos build --platform tizen
45
+ ```
46
+
47
+ ---
48
+
49
+ ## Quick architecture
50
+
51
+ ```
52
+ ┌─────────────────┐ WebSocket ┌─────────────────┐
53
+ │ Your CMS │ ◄────────────────────────►│ TomorrowOS │
54
+ │ (your server) │ │ Player on │
55
+ │ │ │ screen │
56
+ │ imports │ │ │
57
+ │ @tomorrowos │ │ unified API │
58
+ │ /sdk │ │ across OSes │
59
+ └─────────────────┘ └─────────────────┘
60
+ ```
61
+
62
+ TomorrowOS has no required cloud service. Your CMS talks to your screens over your network.
63
+
64
+ **Player branding:** `GET /brand.json` returns the `brand` object passed to `new TomorrowOS({ brand })` (same host/port as `listen`). TomorrowOS players can fetch it to apply `backgroundColor`, logos, etc., without a separate static file server.
65
+
66
+ **Content policy (`device.content.setPolicy`):** POST `/device/{deviceId}/content/set-policy` with `{ "policy": { "playlists": [...], "fallback": { "type": "brand" } } }`. Each playlist may include optional `schedule` (device local time):
67
+
68
+ | Field | Format | Notes |
69
+ |-------|--------|--------|
70
+ | `startDate` / `endDate` | `YYYY-MM-DD` | Inclusive calendar range; omit either for open-ended |
71
+ | `daysOfWeek` | `[0–6]` | `0` = Sunday … `6` = Saturday |
72
+ | `start` / `end` | `HH:MM` | Daily window; supports overnight (e.g. `22:00`–`06:00`) |
73
+
74
+ All provided constraints must match for the playlist to play. See `templates/cms-starter/policy.example.json`.
75
+
76
+ ---
77
+
78
+ ## Supported platforms (roadmap)
79
+
80
+ | Platform | Status | Player format |
81
+ |-------------------|------------|---------------|
82
+ | Samsung Tizen | V1 target | `.wgt` |
83
+ | BrightSign | V1 target | autorun zip |
84
+ | Others | See docs | varies |
85
+
86
+ See `PLAYER_INSTALL.md` for installation notes.
87
+
88
+ ---
89
+
90
+ ## Documentation in this package
91
+
92
+ | File | Purpose |
93
+ |------|---------|
94
+ | `LLM_PROMPT.md` | Five-question elicitation for LLMs |
95
+ | `BUILD_GUARDRAILS.md` | Mandatory CMS components |
96
+ | `PLAYER_INSTALL.md` | Player build / install / pairing |
97
+ | `brand.schema.json` | JSON Schema for `brand.json` |
98
+ | `brand.example.json` | Full example `brand.json` |
99
+ | `templates/cms-starter/` | Minimal Node + TypeScript server seed |
100
+ | `templates/cms-starter/policy.example.json` | Example `setPolicy` payload with date schedule |
101
+ | `templates/style-tokens/` | CSS tokens and UI pattern notes |
102
+
103
+ ---
104
+
105
+ ## License
106
+
107
+ Apache 2.0.
108
+
109
+ ## Protocol version
110
+
111
+ Wire protocol `1.0` (see TomorrowOS specification).
package/dist/cli.js CHANGED
@@ -56,18 +56,18 @@ function cmdBuild(argv) {
56
56
  process.exitCode = 0;
57
57
  }
58
58
  function printHelp() {
59
- console.log(`tomorrowos — TomorrowOS SDK CLI
60
-
61
- Usage:
62
- tomorrowos init [directory] Copy cms-starter template (default: my-tomorrowos-cms)
63
- tomorrowos build --platform … Placeholder; player packaging lives in the player repo
64
-
65
- Options:
66
- --force With init: copy into a non-empty directory
67
-
68
- Examples:
69
- npx @tomorrowos/sdk init
70
- npx @tomorrowos/sdk init ./my-cms
59
+ console.log(`tomorrowos — TomorrowOS SDK CLI
60
+
61
+ Usage:
62
+ tomorrowos init [directory] Copy cms-starter template (default: my-tomorrowos-cms)
63
+ tomorrowos build --platform … Placeholder; player packaging lives in the player repo
64
+
65
+ Options:
66
+ --force With init: copy into a non-empty directory
67
+
68
+ Examples:
69
+ npx @tomorrowos/sdk init
70
+ npx @tomorrowos/sdk init ./my-cms
71
71
  `);
72
72
  }
73
73
  function main() {
@@ -40,7 +40,7 @@ export interface DeviceListItem {
40
40
  lastPolicyPushAt: string | null;
41
41
  screenOnlineActive: boolean;
42
42
  screenOnlineLabel: string;
43
- /** ISO time when the TV WebSocket session last became active (not CMS browser). */
43
+ /** Same as lastBootAt while connected; used for uptime display. */
44
44
  screenOnlineSince: string | null;
45
45
  }
46
46
  export declare class TomorrowOS extends EventEmitter {
@@ -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":"AACA,OAAO,EAAE,YAAY,EAAE,MAAM,QAAQ,CAAC;AAEtC,OAAO,IAAI,MAAM,MAAM,CAAC;AAKxB,OAAO,KAAK,EAAsB,eAAe,EAAE,MAAM,kBAAkB,CAAC;AAG5E,MAAM,WAAW,eAAe;IAC9B,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,eAAe,CAAC;IACvB,6EAA6E;IAC7E,KAAK,CAAC,EAAE,eAAe,CAAC;CACzB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd;;;;;OAKG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB;;;OAGG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAsBD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,OAAO,CAAC;IACnB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,gBAAgB,EAAE,MAAM,GAAG,IAAI,CAAC;IAChC,kBAAkB,EAAE,OAAO,CAAC;IAC5B,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mEAAmE;IACnE,iBAAiB,EAAE,MAAM,GAAG,IAAI,CAAC;CAClC;AA4ED,qBAAa,UAAW,SAAQ,YAAY;IAC1C,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAmC;IAC3D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAsC;IACxE,OAAO,CAAC,UAAU,CAA4B;IAC9C,OAAO,CAAC,GAAG,CAAgC;IAC3C,OAAO,CAAC,UAAU,CAAuB;IACzC,OAAO,CAAC,eAAe,CAAgB;gBAE3B,OAAO,EAAE,iBAAiB;IAMtC,oEAAoE;IAC9D,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IA0ChE,6EAA6E;IACvE,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAA;KAAE,CAAC;IA4BvF,OAAO;uBACU,MAAM;sBAxEgC,MAAM;;2BAyExC,MAAM;sBA9BgC,MAAM;sBAAY,OAAO;;MA+BlF;IAEF,kFAAkF;IAC5E,WAAW,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAsC9C,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,gBAAgB;YAWV,iBAAiB;YASjB,iBAAiB;YA6BjB,kBAAkB;IAUhC,uFAAuF;IACvF,OAAO,CAAC,kBAAkB;YAmBZ,gBAAgB;YAMhB,uBAAuB;IAoCrC,MAAM,CAAC,QAAQ,EAAE,MAAM;oBAGD,CAAC,oBACT,MAAM,WACN,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC;YAAE,MAAM,EAAE,MAAM,CAAC;YAAC,IAAI,CAAC,EAAE,CAAC,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAC;YAAC,KAAK,CAAC,EAAE,MAAM,CAAA;SAAE,CAAC;;IAU5E,OAAO,CAAC,mBAAmB;IA6D3B,MAAM,CAAC,OAAO,EAAE,aAAa,GAAG,IAAI,CAAC,MAAM;YAkD7B,iBAAiB;YAqCjB,cAAc;YAmCd,UAAU;YAwEV,gBAAgB;IA2E9B,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,gBAAgB;CAwFzB"}
@@ -153,14 +153,14 @@ export class TomorrowOS extends EventEmitter {
153
153
  const connected = this.isDeviceConnected(deviceId);
154
154
  let screenOnlineActive = false;
155
155
  let screenOnlineLabel = "Not active";
156
- if (connected && record.lastOnlineAt) {
157
- const since = new Date(record.lastOnlineAt).getTime();
158
- if (!Number.isNaN(since)) {
156
+ if (connected && record.lastBootAt) {
157
+ const bootMs = new Date(record.lastBootAt).getTime();
158
+ if (!Number.isNaN(bootMs)) {
159
159
  screenOnlineActive = true;
160
- screenOnlineLabel = formatDurationMs(now - since);
160
+ screenOnlineLabel = formatDurationMs(now - bootMs);
161
161
  }
162
162
  }
163
- const screenOnlineSince = connected && record.lastOnlineAt ? record.lastOnlineAt : null;
163
+ const screenOnlineSince = connected && record.lastBootAt ? record.lastBootAt : null;
164
164
  return {
165
165
  deviceId,
166
166
  connected,
package/package.json CHANGED
@@ -1,43 +1,43 @@
1
- {
2
- "name": "@tomorrowos/sdk",
3
- "version": "0.2.2",
4
- "description": "TomorrowOS CMS server SDK — WebSocket transport, pairing, device commands, optional static CMS UI. Includes CLI (tomorrowos init / build) and starter templates.",
5
- "type": "module",
6
- "main": "./dist/index.js",
7
- "types": "./dist/index.d.ts",
8
- "exports": {
9
- ".": {
10
- "types": "./dist/index.d.ts",
11
- "import": "./dist/index.js"
12
- }
13
- },
14
- "bin": {
15
- "tomorrowos": "./dist/cli.js"
16
- },
17
- "files": [
18
- "dist",
19
- "templates",
20
- "README.md",
21
- "LLM_PROMPT.md",
22
- "BUILD_GUARDRAILS.md",
23
- "PLAYER_INSTALL.md",
24
- "brand.schema.json",
25
- "brand.example.json"
26
- ],
27
- "scripts": {
28
- "build": "tsc",
29
- "prepublishOnly": "npm run build"
30
- },
31
- "engines": {
32
- "node": ">=18"
33
- },
34
- "dependencies": {
35
- "ws": "^8.18.0"
36
- },
37
- "devDependencies": {
38
- "@types/node": "^20.19.41",
39
- "@types/ws": "^8.5.10",
40
- "typescript": "^5.5.0"
41
- },
42
- "license": "Apache-2.0"
43
- }
1
+ {
2
+ "name": "@tomorrowos/sdk",
3
+ "version": "0.2.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
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "bin": {
15
+ "tomorrowos": "./dist/cli.js"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "templates",
20
+ "README.md",
21
+ "LLM_PROMPT.md",
22
+ "BUILD_GUARDRAILS.md",
23
+ "PLAYER_INSTALL.md",
24
+ "brand.schema.json",
25
+ "brand.example.json"
26
+ ],
27
+ "scripts": {
28
+ "build": "tsc",
29
+ "prepublishOnly": "npm run build"
30
+ },
31
+ "engines": {
32
+ "node": ">=18"
33
+ },
34
+ "dependencies": {
35
+ "ws": "^8.18.0"
36
+ },
37
+ "devDependencies": {
38
+ "@types/node": "^20.19.41",
39
+ "@types/ws": "^8.5.10",
40
+ "typescript": "^5.5.0"
41
+ },
42
+ "license": "Apache-2.0"
43
+ }
@@ -1,20 +1,20 @@
1
- {
2
- "name": "my-cms",
3
- "version": "0.2.2",
4
- "description": "CMS server on @tomorrowos/sdk. Add your UI (React, static files, etc.) alongside this server.",
5
- "private": true,
6
- "type": "module",
7
- "scripts": {
8
- "dev": "tsx watch server.ts",
9
- "start": "tsx server.ts",
10
- "build-player": "tomorrowos build --platform tizen"
11
- },
12
- "dependencies": {
13
- "@tomorrowos/sdk": "^0.2.2"
14
- },
15
- "devDependencies": {
16
- "@types/node": "^20.0.0",
17
- "tsx": "^4.19.0",
18
- "typescript": "^5.5.0"
19
- }
20
- }
1
+ {
2
+ "name": "my-cms",
3
+ "version": "0.2.4",
4
+ "description": "CMS server on @tomorrowos/sdk. Add your UI (React, static files, etc.) alongside this server.",
5
+ "private": true,
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "tsx watch server.ts",
9
+ "start": "tsx server.ts",
10
+ "build-player": "tomorrowos build --platform tizen"
11
+ },
12
+ "dependencies": {
13
+ "@tomorrowos/sdk": "^0.2.4"
14
+ },
15
+ "devDependencies": {
16
+ "@types/node": "^20.0.0",
17
+ "tsx": "^4.19.0",
18
+ "typescript": "^5.5.0"
19
+ }
20
+ }
@@ -1,30 +1,30 @@
1
- {
2
- "policy": {
3
- "playlists": [
4
- {
5
- "id": "weekday-promo",
6
- "name": "Weekday promo",
7
- "schedule": {
8
- "startDate": "2026-05-01",
9
- "endDate": "2026-05-31",
10
- "daysOfWeek": [1, 2, 3, 4, 5],
11
- "start": "09:00",
12
- "end": "17:00"
13
- },
14
- "items": [
15
- {
16
- "url": "https://example.com/hero.jpg",
17
- "type": "image",
18
- "durationMs": 10000
19
- },
20
- {
21
- "url": "https://example.com/spot.mp4",
22
- "type": "video",
23
- "durationMs": 30000
24
- }
25
- ]
26
- }
27
- ],
28
- "fallback": { "type": "brand" }
29
- }
30
- }
1
+ {
2
+ "policy": {
3
+ "playlists": [
4
+ {
5
+ "id": "weekday-promo",
6
+ "name": "Weekday promo",
7
+ "schedule": {
8
+ "startDate": "2026-05-01",
9
+ "endDate": "2026-05-31",
10
+ "daysOfWeek": [1, 2, 3, 4, 5],
11
+ "start": "09:00",
12
+ "end": "17:00"
13
+ },
14
+ "items": [
15
+ {
16
+ "url": "https://example.com/hero.jpg",
17
+ "type": "image",
18
+ "durationMs": 10000
19
+ },
20
+ {
21
+ "url": "https://example.com/spot.mp4",
22
+ "type": "video",
23
+ "durationMs": 30000
24
+ }
25
+ ]
26
+ }
27
+ ],
28
+ "fallback": { "type": "brand" }
29
+ }
30
+ }
@@ -30,11 +30,13 @@
30
30
  </div>
31
31
  </section>
32
32
 
33
- <section class="card">
33
+ <section class="card" id="cmsUrlSection">
34
34
  <h2>CMS URL for screens</h2>
35
35
  <p class="hint">
36
- HTTP(S) base URL your TVs use to download uploads (same host as <code>ws://</code>, not
37
- <code>localhost</code>).
36
+ <strong>Local development only.</strong> Fill this in when you run the CMS on your PC and
37
+ TVs are on the same LAN (e.g. <code>http://192.168.1.105:3000</code> — same machine as your
38
+ <code>ws://</code> address, not <code>localhost</code>). On hosted CMS (Replit, etc.) you
39
+ normally do <strong>not</strong> need to change this.
38
40
  </p>
39
41
  <div class="row">
40
42
  <input
@@ -9,7 +9,6 @@ let playlistItems = [];
9
9
  let devicesCache = [];
10
10
 
11
11
  let devicePollTimer = null;
12
- let deviceUptimeTimer = null;
13
12
 
14
13
  function escapeHtml(value) {
15
14
  return String(value ?? "")
@@ -48,24 +47,14 @@ function formatDateTimeSeconds(iso) {
48
47
  });
49
48
  }
50
49
 
51
- /** TV online duration since last WebSocket active (device.lastOnlineAt), not this browser tab. */
50
+ /** TV uptime: now minus last boot time (device.lastBootAt). Not active when app is offline. */
52
51
  function formatDeviceOnlineLabel(device) {
53
52
  if (!device.connected) return "Not active";
54
- const sinceIso = device.screenOnlineSince || device.lastOnlineAt;
55
- if (!sinceIso) return "Not active";
56
- const since = new Date(sinceIso).getTime();
57
- if (Number.isNaN(since)) return "Not active";
58
- return formatDurationMs(Date.now() - since);
59
- }
60
-
61
- function updateDeviceUptimeLabels() {
62
- document.querySelectorAll(".device-card[data-device-id]").forEach((card) => {
63
- const deviceId = card.dataset.deviceId;
64
- const device = devicesCache.find((d) => d.deviceId === deviceId);
65
- if (!device) return;
66
- const el = card.querySelector(".device-online-time");
67
- if (el) el.textContent = formatDeviceOnlineLabel(device);
68
- });
53
+ const bootIso = device.lastBootAt;
54
+ if (!bootIso) return "Not active";
55
+ const bootMs = new Date(bootIso).getTime();
56
+ if (Number.isNaN(bootMs)) return "Not active";
57
+ return formatDurationMs(Date.now() - bootMs);
69
58
  }
70
59
 
71
60
  async function fetchDevices() {
@@ -136,8 +125,7 @@ function renderDeviceCards() {
136
125
  const dd = document.createElement("dd");
137
126
  if (label === "Device online") {
138
127
  dd.className = "device-online-time";
139
- dd.title =
140
- "Time since this TV's WebSocket session became active (updates every minute)";
128
+ dd.title = "Current time minus this TV last boot time";
141
129
  }
142
130
  dd.textContent = value;
143
131
  row.appendChild(dt);
@@ -198,14 +186,11 @@ function renderDeviceCards() {
198
186
 
199
187
  function startDevicePolling() {
200
188
  if (devicePollTimer) clearInterval(devicePollTimer);
201
- if (deviceUptimeTimer) clearInterval(deviceUptimeTimer);
202
189
 
203
190
  void fetchDevices();
204
191
  devicePollTimer = setInterval(() => {
205
192
  void fetchDevices();
206
193
  }, 8000);
207
-
208
- deviceUptimeTimer = setInterval(updateDeviceUptimeLabels, 60_000);
209
194
  }
210
195
 
211
196
  function showResult(data) {
@@ -267,7 +252,7 @@ function absoluteMediaUrl(path) {
267
252
  const base = getMediaBaseOrigin();
268
253
  if (!base) {
269
254
  throw new Error(
270
- "Set CMS URL for screens (use your PC LAN IP or Replit https URL, not localhost)."
255
+ "Local CMS only: set CMS URL for screens (your PC LAN IP, e.g. http://192.168.1.105:3000 not localhost)."
271
256
  );
272
257
  }
273
258
  return `${base}${p.startsWith("/") ? p : `/${p}`}`;
@@ -558,7 +543,7 @@ async function publishToDevice(deviceId) {
558
543
  const mediaBase = getMediaBaseOrigin();
559
544
  if (!mediaBase) {
560
545
  alert(
561
- "Set CMS URL for screens first (e.g. http://192.168.1.105:3000 — same machine as the TV ws:// address)."
546
+ "Local CMS only: set CMS URL for screens first (e.g. http://192.168.1.105:3000 — your PC LAN IP, not localhost)."
562
547
  );
563
548
  return;
564
549
  }