@tomorrowos/sdk 0.3.7 → 0.3.9

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.
@@ -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;AAUxB,OAAO,EAAE,eAAe,EAAE,KAAK,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAChF,OAAO,KAAK,EAIV,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAO1B,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;AAuBD,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,2EAA2E;IAC3E,WAAW,EAAE,MAAM,GAAG,IAAI,CAAC;IAC3B,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,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,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;IACjC,kBAAkB,EAAE,KAAK,CAAC;QACxB,UAAU,EAAE,MAAM,CAAC;QACnB,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,WAAW,EAAE,MAAM,CAAC;KACrB,CAAC,CAAC;CACJ;AA6GD,qBAAa,UAAW,SAAQ,YAAY;IAC1C,QAAQ,CAAC,KAAK,EAAE,eAAe,CAAC;IAChC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAkB;IACxC,QAAQ,CAAC,SAAS,EAAE,eAAe,CAAC;IACpC,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;IAOtC,yFAAyF;IACnF,wBAAwB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;QACxD,MAAM,EAAE,OAAO,CAAC;QAChB,MAAM,CAAC,EAAE,iBAAiB,CAAC,QAAQ,CAAC,CAAC;KACtC,CAAC;YAkBY,iBAAiB;IAY/B,6EAA6E;IACvE,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAA;KAAE,CAAC;IA0DhE,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;sBAxFgC,MAAM;;2BAyFxC,MAAM;sBA9BgC,MAAM;sBAAY,OAAO;;MA+BlF;IAEF,kFAAkF;IAC5E,WAAW,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAkD9C,OAAO,CAAC,iBAAiB;IAKzB,OAAO,CAAC,gBAAgB;YAaV,+BAA+B;YAiC/B,iBAAiB;YASjB,iBAAiB;YA+BjB,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;YAmD7B,iBAAiB;YAqCjB,wBAAwB;YAiBxB,cAAc;YAyCd,UAAU;YA0LV,gBAAgB;IA2E9B,OAAO,CAAC,iBAAiB;IAWzB,OAAO,CAAC,gBAAgB;CAuGzB"}
1
+ {"version":3,"file":"tomorrowos.d.ts","sourceRoot":"","sources":["../src/tomorrowos.ts"],"names":[],"mappings":"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"}
@@ -32,6 +32,29 @@ function parseBootIsoMs(iso) {
32
32
  const ms = new Date(iso).getTime();
33
33
  return Number.isFinite(ms) ? ms : null;
34
34
  }
35
+ function parseBootUptimeSec(msg) {
36
+ const raw = msg.bootUptimeSec ?? msg.bootUptimeSeconds;
37
+ if (typeof raw === "number" && Number.isFinite(raw) && raw >= 0)
38
+ return raw;
39
+ if (typeof raw === "string" && raw.trim()) {
40
+ const n = Number(raw);
41
+ if (Number.isFinite(n) && n >= 0)
42
+ return n;
43
+ }
44
+ return null;
45
+ }
46
+ /** Derive boot instant from device uptime + CMS clock (avoids wrong TV wall clock). */
47
+ function bootAtFromUptimeSec(uptimeSec) {
48
+ return new Date(Date.now() - uptimeSec * 1000).toISOString();
49
+ }
50
+ function lastBootAtFromHandshake(msg, existing) {
51
+ const uptimeSec = parseBootUptimeSec(msg);
52
+ if (uptimeSec != null) {
53
+ return resolveLastBootAt(bootAtFromUptimeSec(uptimeSec), existing);
54
+ }
55
+ const bootedAt = typeof msg.bootedAt === "string" ? msg.bootedAt : undefined;
56
+ return resolveLastBootAt(bootedAt, existing);
57
+ }
35
58
  /** Merge device-reported boot time with stored value (ignore page-reload noise, accept reboots). */
36
59
  function resolveLastBootAt(incoming, existing) {
37
60
  const inMs = parseBootIsoMs(incoming);
@@ -168,7 +191,9 @@ export class TomorrowOS extends EventEmitter {
168
191
  const pairedAt = new Date().toISOString();
169
192
  const meta = this.pendingDeviceMeta.get(deviceId);
170
193
  const now = pairedAt;
171
- const lastBootAt = resolveLastBootAt(meta?.bootedAt, undefined);
194
+ const lastBootAt = meta?.bootUptimeSec != null
195
+ ? resolveLastBootAt(bootAtFromUptimeSec(meta.bootUptimeSec), undefined)
196
+ : resolveLastBootAt(meta?.bootedAt, undefined);
172
197
  await this.store.setPairedDevice(deviceId, {
173
198
  pairingToken,
174
199
  pairedAt,
@@ -268,11 +293,13 @@ export class TomorrowOS extends EventEmitter {
268
293
  return !!ws && ws.readyState === WebSocket.OPEN;
269
294
  }
270
295
  captureHelloMeta(deviceId, msg) {
296
+ const bootUptimeSec = parseBootUptimeSec(msg);
271
297
  this.pendingDeviceMeta.set(deviceId, {
272
298
  platform: typeof msg.platform === "string" ? msg.platform : undefined,
273
299
  deviceName: typeof msg.deviceName === "string" ? msg.deviceName : undefined,
274
300
  system: typeof msg.system === "string" ? msg.system : undefined,
275
301
  bootedAt: typeof msg.bootedAt === "string" ? msg.bootedAt : undefined,
302
+ bootUptimeSec: bootUptimeSec ?? undefined,
276
303
  playerVersion: typeof msg.playerVersion === "string" ? msg.playerVersion : undefined,
277
304
  serialNumber: typeof msg.serialNumber === "string" ? msg.serialNumber : deviceId
278
305
  });
@@ -313,11 +340,10 @@ export class TomorrowOS extends EventEmitter {
313
340
  async touchPairedOnline(deviceId, msg) {
314
341
  this.captureHelloMeta(deviceId, msg);
315
342
  const now = new Date().toISOString();
316
- const bootedAt = typeof msg.bootedAt === "string" ? msg.bootedAt : undefined;
317
343
  const existing = await this.store.getPairedDevice(deviceId);
318
344
  if (!existing)
319
345
  return;
320
- const lastBootAt = resolveLastBootAt(bootedAt, existing.lastBootAt);
346
+ const lastBootAt = lastBootAtFromHandshake(msg, existing.lastBootAt);
321
347
  await this.store.setPairedDevice(deviceId, {
322
348
  ...existing,
323
349
  deviceName: (typeof msg.deviceName === "string" ? msg.deviceName : undefined) ??
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
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,23 +1,23 @@
1
- {
2
- "name": "My Venue",
3
- "tagline": "Digital signage",
4
- "targetPlatforms": ["tizen"],
5
- "primaryColor": "#FF8A3D",
6
- "secondaryColor": "#F5F3EF",
7
- "backgroundColor": "#FAFAF9",
8
- "textColor": "#0A0908",
9
- "logoPath": "./assets/logo.svg",
10
- "fontFamily": "Inter",
11
- "cms": {
12
- "useCase": "other",
13
- "hostingTarget": "self-hosted",
14
- "expectedScreens": 5,
15
- "features": {
16
- "bulkCommands": false,
17
- "proofOfPlay": false,
18
- "contentScheduling": true,
19
- "userManagement": false
20
- }
21
- },
22
- "protocolVersion": "1.0"
23
- }
1
+ {
2
+ "name": "My Venue",
3
+ "tagline": "Digital signage",
4
+ "targetPlatforms": ["tizen"],
5
+ "primaryColor": "#FF8A3D",
6
+ "secondaryColor": "#F5F3EF",
7
+ "backgroundColor": "#FAFAF9",
8
+ "textColor": "#0A0908",
9
+ "logoPath": "./assets/logo.svg",
10
+ "fontFamily": "Inter",
11
+ "cms": {
12
+ "useCase": "other",
13
+ "hostingTarget": "self-hosted",
14
+ "expectedScreens": 5,
15
+ "features": {
16
+ "bulkCommands": false,
17
+ "proofOfPlay": false,
18
+ "contentScheduling": true,
19
+ "userManagement": false
20
+ }
21
+ },
22
+ "protocolVersion": "1.0"
23
+ }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-cms",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
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.3.7"
13
+ "@tomorrowos/sdk": "^0.3.9"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@types/node": "^20.0.0",
@@ -55,7 +55,7 @@
55
55
  </div>
56
56
  </section>
57
57
 
58
- <section class="card" id="playlistEditorSection">
58
+ <section class="card hidden" id="playlistEditorSection">
59
59
  <h2 id="editorTitle">Playlist editor</h2>
60
60
  <p class="hint">Save confirms changes. Names must be unique. Schedule applies to this playlist only.</p>
61
61
  <label class="field-label">
@@ -225,7 +225,7 @@ async function fetchPlaylists() {
225
225
  showResult({
226
226
  status: "failed",
227
227
  error:
228
- "CMS server is missing /playlists. Restart CMS with @tomorrowos/sdk 0.3.7 or newer."
228
+ "CMS server is missing /playlists. Restart CMS with @tomorrowos/sdk 0.3.9 or newer."
229
229
  });
230
230
  }
231
231
  return;
@@ -234,7 +234,8 @@ async function fetchPlaylists() {
234
234
  playlistsCatalog = data.playlists;
235
235
  renderPlaylistCatalog();
236
236
  if (selectedPlaylistId && !getSelectedPlaylist()) {
237
- selectedPlaylistId = playlistsCatalog[0]?.id || null;
237
+ selectedPlaylistId = null;
238
+ playlistDraftActive = false;
238
239
  loadEditorFromSelection();
239
240
  }
240
241
  }
@@ -261,7 +262,7 @@ function renderPlaylistCatalog() {
261
262
  const li = document.createElement("li");
262
263
  li.className = "playlist-catalog-item";
263
264
  if (pl.id === selectedPlaylistId) li.classList.add("playlist-catalog-item--active");
264
- li.innerHTML = `<strong>${escapeHtml(pl.name)}</strong><small>v${pl.version} · ${(pl.items || []).length} items</small>`;
265
+ li.innerHTML = `<strong>${escapeHtml(pl.name)}</strong><small>${(pl.items || []).length} items</small>`;
265
266
  li.addEventListener("click", () => {
266
267
  playlistDraftActive = false;
267
268
  selectedPlaylistId = pl.id;
@@ -276,6 +277,12 @@ function isPlaylistEditorOpen() {
276
277
  return playlistDraftActive || !!selectedPlaylistId;
277
278
  }
278
279
 
280
+ function updatePlaylistEditorVisibility() {
281
+ const section = document.getElementById("playlistEditorSection");
282
+ if (!section) return;
283
+ section.classList.toggle("hidden", !isPlaylistEditorOpen());
284
+ }
285
+
279
286
  function loadEditorFromSelection() {
280
287
  const pl = getSelectedPlaylist();
281
288
  const nameInput = document.getElementById("playlistName");
@@ -284,6 +291,7 @@ function loadEditorFromSelection() {
284
291
  if (!pl) {
285
292
  if (playlistDraftActive) {
286
293
  if (editorTitle) editorTitle.textContent = "New playlist";
294
+ updatePlaylistEditorVisibility();
287
295
  renderEditorAssets();
288
296
  return;
289
297
  }
@@ -292,6 +300,7 @@ function loadEditorFromSelection() {
292
300
  if (nameInput) nameInput.value = "";
293
301
  editorItems = [];
294
302
  loadScheduleIntoForm(null);
303
+ updatePlaylistEditorVisibility();
295
304
  renderEditorAssets();
296
305
  return;
297
306
  }
@@ -308,6 +317,7 @@ function loadEditorFromSelection() {
308
317
  durationMs: normalizeDurationMs(item)
309
318
  }));
310
319
  renderEditorAssets();
320
+ updatePlaylistEditorVisibility();
311
321
  }
312
322
 
313
323
  function renderEditorAssets() {
@@ -478,6 +488,7 @@ function newPlaylistDraft() {
478
488
  editorItems = [];
479
489
  renderPlaylistCatalog();
480
490
  renderEditorAssets();
491
+ updatePlaylistEditorVisibility();
481
492
  const editorTitle = document.getElementById("editorTitle");
482
493
  if (editorTitle) editorTitle.textContent = "New playlist";
483
494
  document.getElementById("playlistEditorSection")?.scrollIntoView({ behavior: "smooth", block: "start" });
@@ -538,7 +549,7 @@ function renderDeviceCards() {
538
549
  for (const p of pubs) {
539
550
  const li = document.createElement("li");
540
551
  const label = document.createElement("span");
541
- label.textContent = `${p.name} (v${p.version})`;
552
+ label.textContent = p.name;
542
553
  const rm = document.createElement("button");
543
554
  rm.type = "button";
544
555
  rm.textContent = "Remove";
@@ -653,7 +664,7 @@ function openPublishModal(deviceId) {
653
664
  cb.value = pl.id;
654
665
  cb.dataset.name = pl.name;
655
666
  label.appendChild(cb);
656
- label.appendChild(document.createTextNode(` ${pl.name} (v${pl.version})`));
667
+ label.appendChild(document.createTextNode(` ${pl.name}`));
657
668
  checklist.appendChild(label);
658
669
  }
659
670
 
@@ -830,6 +841,7 @@ document.addEventListener("DOMContentLoaded", () => {
830
841
  const cmsBaseInput = document.getElementById("cmsDeviceBaseUrl");
831
842
  if (savedMediaBase && cmsBaseInput) cmsBaseInput.value = savedMediaBase;
832
843
 
844
+ updatePlaylistEditorVisibility();
833
845
  void fetchPlaylists();
834
846
  startDevicePolling();
835
847