@tomorrowos/sdk 0.2.0 → 0.2.2

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.
@@ -40,6 +40,8 @@ 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). */
44
+ screenOnlineSince: string | null;
43
45
  }
44
46
  export declare class TomorrowOS extends EventEmitter {
45
47
  readonly brand: TomorrowOSBrand;
@@ -76,6 +78,8 @@ export declare class TomorrowOS extends EventEmitter {
76
78
  private mergePairedRecord;
77
79
  private touchPairedOnline;
78
80
  private touchPairedOffline;
81
+ /** Mark device offline immediately (e.g. reboot) before WebSocket close propagates. */
82
+ private forceDeviceOffline;
79
83
  private recordPolicyPush;
80
84
  private refreshPairedDeviceInfo;
81
85
  device(deviceId: string): {
@@ -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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"}
@@ -16,12 +16,11 @@ function formatDurationMs(ms) {
16
16
  const parts = [];
17
17
  if (days > 0)
18
18
  parts.push(`${days}d`);
19
- if (hours > 0)
19
+ if (days > 0 || hours > 0)
20
20
  parts.push(`${hours}h`);
21
- if (minutes > 0)
21
+ if (days > 0 || hours > 0 || minutes > 0)
22
22
  parts.push(`${minutes}m`);
23
- if (parts.length === 0)
24
- parts.push(`${seconds}s`);
23
+ parts.push(`${seconds}s`);
25
24
  return parts.join(" ");
26
25
  }
27
26
  function createSixDigitCode() {
@@ -161,6 +160,7 @@ export class TomorrowOS extends EventEmitter {
161
160
  screenOnlineLabel = formatDurationMs(now - since);
162
161
  }
163
162
  }
163
+ const screenOnlineSince = connected && record.lastOnlineAt ? record.lastOnlineAt : null;
164
164
  return {
165
165
  deviceId,
166
166
  connected,
@@ -173,7 +173,8 @@ export class TomorrowOS extends EventEmitter {
173
173
  lastOfflineAt: record.lastOfflineAt ?? null,
174
174
  lastPolicyPushAt: record.lastPolicyPushAt ?? null,
175
175
  screenOnlineActive,
176
- screenOnlineLabel
176
+ screenOnlineLabel,
177
+ screenOnlineSince
177
178
  };
178
179
  });
179
180
  }
@@ -222,7 +223,28 @@ export class TomorrowOS extends EventEmitter {
222
223
  return;
223
224
  await this.store.setPairedDevice(deviceId, {
224
225
  ...existing,
225
- lastOfflineAt: new Date().toISOString()
226
+ lastOfflineAt: new Date().toISOString(),
227
+ lastOnlineAt: undefined
228
+ });
229
+ }
230
+ /** Mark device offline immediately (e.g. reboot) before WebSocket close propagates. */
231
+ forceDeviceOffline(deviceId) {
232
+ const ws = this.devices.get(deviceId);
233
+ if (ws) {
234
+ try {
235
+ ws.close();
236
+ }
237
+ catch {
238
+ /* ignore */
239
+ }
240
+ if (this.devices.get(deviceId) === ws) {
241
+ this.devices.delete(deviceId);
242
+ }
243
+ }
244
+ void this.touchPairedOffline(deviceId);
245
+ this.emit("device.offline", {
246
+ deviceId,
247
+ lastSeen: new Date().toISOString()
226
248
  });
227
249
  }
228
250
  async recordPolicyPush(deviceId) {
@@ -540,6 +562,10 @@ export class TomorrowOS extends EventEmitter {
540
562
  if (action === "content/set-policy" && result.status === "success") {
541
563
  await this.recordPolicyPush(deviceId);
542
564
  }
565
+ if (action === "reboot" &&
566
+ (result.status === "success" || result.status === "accepted")) {
567
+ this.forceDeviceOffline(deviceId);
568
+ }
543
569
  sendJson(res, 200, { status: result.status, data: result.data });
544
570
  }
545
571
  catch (e) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "0.2.0",
3
+ "version": "0.2.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
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-cms",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
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",
@@ -10,7 +10,7 @@
10
10
  "build-player": "tomorrowos build --platform tizen"
11
11
  },
12
12
  "dependencies": {
13
- "@tomorrowos/sdk": "^0.2.0"
13
+ "@tomorrowos/sdk": "^0.2.2"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@types/node": "^20.0.0",
@@ -28,9 +28,9 @@ function formatDurationMs(ms) {
28
28
  const seconds = totalSec % 60;
29
29
  const parts = [];
30
30
  if (days > 0) parts.push(`${days}d`);
31
- if (hours > 0) parts.push(`${hours}h`);
32
- if (minutes > 0) parts.push(`${minutes}m`);
33
- if (parts.length === 0) parts.push(`${seconds}s`);
31
+ if (days > 0 || hours > 0) parts.push(`${hours}h`);
32
+ if (days > 0 || hours > 0 || minutes > 0) parts.push(`${minutes}m`);
33
+ parts.push(`${seconds}s`);
34
34
  return parts.join(" ");
35
35
  }
36
36
 
@@ -48,9 +48,12 @@ function formatDateTimeSeconds(iso) {
48
48
  });
49
49
  }
50
50
 
51
- function formatScreenOnlineLabel(device) {
52
- if (!device.connected || !device.lastOnlineAt) return "Not active";
53
- const since = new Date(device.lastOnlineAt).getTime();
51
+ /** TV online duration since last WebSocket active (device.lastOnlineAt), not this browser tab. */
52
+ function formatDeviceOnlineLabel(device) {
53
+ 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();
54
57
  if (Number.isNaN(since)) return "Not active";
55
58
  return formatDurationMs(Date.now() - since);
56
59
  }
@@ -61,7 +64,7 @@ function updateDeviceUptimeLabels() {
61
64
  const device = devicesCache.find((d) => d.deviceId === deviceId);
62
65
  if (!device) return;
63
66
  const el = card.querySelector(".device-online-time");
64
- if (el) el.textContent = formatScreenOnlineLabel(device);
67
+ if (el) el.textContent = formatDeviceOnlineLabel(device);
65
68
  });
66
69
  }
67
70
 
@@ -117,7 +120,10 @@ function renderDeviceCards() {
117
120
  const rows = [
118
121
  ["Device ID", device.deviceId],
119
122
  ["System", device.system || device.platform || "—"],
120
- ["Screen online", formatScreenOnlineLabel(device)],
123
+ [
124
+ "Device online",
125
+ formatDeviceOnlineLabel(device)
126
+ ],
121
127
  ["Last boot", formatDateTimeSeconds(device.lastBootAt)],
122
128
  ["Latest content push", formatDateTimeSeconds(device.lastPolicyPushAt)]
123
129
  ];
@@ -128,8 +134,10 @@ function renderDeviceCards() {
128
134
  const dt = document.createElement("dt");
129
135
  dt.textContent = label;
130
136
  const dd = document.createElement("dd");
131
- if (label === "Screen online") {
137
+ if (label === "Device online") {
132
138
  dd.className = "device-online-time";
139
+ dd.title =
140
+ "Time since this TV's WebSocket session became active (updates every minute)";
133
141
  }
134
142
  dd.textContent = value;
135
143
  row.appendChild(dt);
@@ -151,6 +159,13 @@ function renderDeviceCards() {
151
159
  infoBtn.textContent = "Info";
152
160
  infoBtn.addEventListener("click", () => deviceAction(device.deviceId, "get-info"));
153
161
 
162
+ const capsBtn = document.createElement("button");
163
+ capsBtn.type = "button";
164
+ capsBtn.textContent = "Capabilities";
165
+ capsBtn.addEventListener("click", () =>
166
+ deviceAction(device.deviceId, "get-capabilities")
167
+ );
168
+
154
169
  const rebootBtn = document.createElement("button");
155
170
  rebootBtn.type = "button";
156
171
  rebootBtn.textContent = "Reboot";
@@ -169,6 +184,7 @@ function renderDeviceCards() {
169
184
 
170
185
  actions.appendChild(publishBtn);
171
186
  actions.appendChild(infoBtn);
187
+ actions.appendChild(capsBtn);
172
188
  actions.appendChild(rebootBtn);
173
189
  actions.appendChild(clearBtn);
174
190
  actions.appendChild(unpairBtn);
@@ -585,7 +601,12 @@ async function deviceAction(deviceId, action) {
585
601
  const res = await fetch(`/device/${encodeURIComponent(deviceId)}/${action}`, {
586
602
  method: "POST"
587
603
  });
588
- showResult(await res.json());
604
+ const data = await res.json();
605
+ showResult({ deviceId, action, ...data });
606
+
607
+ if (action === "reboot" && res.ok) {
608
+ await fetchDevices();
609
+ }
589
610
  }
590
611
 
591
612
  document.addEventListener("DOMContentLoaded", () => {