@tomorrowos/sdk 0.1.8 → 0.1.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "0.1.8",
3
+ "version": "0.1.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,6 +1,6 @@
1
1
  {
2
2
  "name": "my-cms",
3
- "version": "0.1.8",
3
+ "version": "0.1.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.1.8"
13
+ "@tomorrowos/sdk": "^0.1.9"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@types/node": "^20.0.0",
@@ -23,6 +23,23 @@
23
23
  <input type="hidden" id="deviceId" />
24
24
  </section>
25
25
 
26
+ <section class="card">
27
+ <h2>CMS URL for screens</h2>
28
+ <p style="margin: 0 0 0.5rem; font-size: 0.8rem; color: #666">
29
+ HTTP(S) base URL your TV uses to download uploads (same host as <code>ws://</code> on the
30
+ device, not <code>localhost</code>).
31
+ </p>
32
+ <div class="row">
33
+ <input
34
+ id="cmsDeviceBaseUrl"
35
+ type="text"
36
+ style="flex: 1; min-width: 16rem"
37
+ placeholder="http://192.168.1.105:3000"
38
+ />
39
+ <button type="button" onclick="saveCmsDeviceBaseUrl()">Save</button>
40
+ </div>
41
+ </section>
42
+
26
43
  <section class="card">
27
44
  <h2>When this playlist plays</h2>
28
45
  <p style="margin: 0 0 0.75rem; font-size: 0.8rem; color: #666">
@@ -1,6 +1,7 @@
1
1
  const PANEL_DEVICE_ID_KEY = "tomorrowos.panel.deviceId";
2
2
  const PANEL_PLAYLIST_KEY = "tomorrowos.panel.playlistDraft";
3
3
  const PANEL_SCHEDULE_KEY = "tomorrowos.panel.scheduleDraft";
4
+ const PANEL_MEDIA_BASE_KEY = "tomorrowos.panel.mediaBaseUrl";
4
5
 
5
6
  /** @type {{ id: string, url: string, name: string, type: string, durationMs: number }[]} */
6
7
  let playlistItems = [];
@@ -25,11 +26,65 @@ function showResult(data) {
25
26
  document.getElementById("result").textContent = JSON.stringify(data, null, 2);
26
27
  }
27
28
 
29
+ function isLocalPanelHost(hostname) {
30
+ const h = String(hostname || "").toLowerCase();
31
+ return h === "localhost" || h === "127.0.0.1" || h === "[::1]";
32
+ }
33
+
34
+ function normalizeMediaBaseUrl(raw) {
35
+ let s = String(raw || "").trim();
36
+ if (!s) return "";
37
+ if (!/^https?:\/\//i.test(s)) {
38
+ s = `http://${s}`;
39
+ }
40
+ try {
41
+ const u = new URL(s);
42
+ return u.origin;
43
+ } catch {
44
+ return "";
45
+ }
46
+ }
47
+
48
+ function getMediaBaseOrigin() {
49
+ const fromInput = normalizeMediaBaseUrl(
50
+ document.getElementById("cmsDeviceBaseUrl")?.value ||
51
+ localStorage.getItem(PANEL_MEDIA_BASE_KEY) ||
52
+ ""
53
+ );
54
+ if (fromInput) return fromInput;
55
+
56
+ if (!isLocalPanelHost(window.location.hostname)) {
57
+ return window.location.origin;
58
+ }
59
+
60
+ return "";
61
+ }
62
+
63
+ function saveCmsDeviceBaseUrl() {
64
+ const normalized = normalizeMediaBaseUrl(
65
+ document.getElementById("cmsDeviceBaseUrl")?.value
66
+ );
67
+ if (!normalized) {
68
+ alert("Enter a valid URL, e.g. http://192.168.1.105:3000");
69
+ return;
70
+ }
71
+ document.getElementById("cmsDeviceBaseUrl").value = normalized;
72
+ localStorage.setItem(PANEL_MEDIA_BASE_KEY, normalized);
73
+ showResult({ status: "saved", mediaBaseUrl: normalized });
74
+ }
75
+
28
76
  function absoluteMediaUrl(path) {
29
77
  const p = String(path || "").trim();
30
78
  if (!p) return "";
31
79
  if (/^https?:\/\//i.test(p)) return p;
32
- return `${window.location.origin}${p.startsWith("/") ? p : `/${p}`}`;
80
+
81
+ const base = getMediaBaseOrigin();
82
+ if (!base) {
83
+ throw new Error(
84
+ "Set CMS URL for screens (use your PC LAN IP or Replit https URL, not localhost)."
85
+ );
86
+ }
87
+ return `${base}${p.startsWith("/") ? p : `/${p}`}`;
33
88
  }
34
89
 
35
90
  function inferMediaType(filename, mime) {
@@ -271,8 +326,28 @@ async function publish() {
271
326
  return;
272
327
  }
273
328
 
329
+ const mediaBase = getMediaBaseOrigin();
330
+ if (!mediaBase) {
331
+ alert(
332
+ "Set CMS URL for screens first (e.g. http://192.168.1.105:3000 — same machine as the TV ws:// address)."
333
+ );
334
+ return;
335
+ }
336
+ if (isLocalPanelHost(new URL(mediaBase).hostname)) {
337
+ const ok = confirm(
338
+ "Media URLs use localhost. TVs cannot download from localhost unless the player runs on this PC. Continue anyway?"
339
+ );
340
+ if (!ok) return;
341
+ }
342
+
274
343
  saveScheduleDraft();
275
- const payload = buildPolicyPayload();
344
+ let payload;
345
+ try {
346
+ payload = buildPolicyPayload();
347
+ } catch (err) {
348
+ alert(err.message);
349
+ return;
350
+ }
276
351
 
277
352
  const res = await fetch(`/device/${deviceId}/content/set-policy`, {
278
353
  method: "POST",
@@ -330,6 +405,14 @@ document.addEventListener("DOMContentLoaded", () => {
330
405
  const saved = localStorage.getItem(PANEL_DEVICE_ID_KEY);
331
406
  if (saved) setPanelDeviceId(saved);
332
407
 
408
+ const savedMediaBase = localStorage.getItem(PANEL_MEDIA_BASE_KEY);
409
+ const cmsBaseInput = document.getElementById("cmsDeviceBaseUrl");
410
+ if (savedMediaBase && cmsBaseInput) {
411
+ cmsBaseInput.value = savedMediaBase;
412
+ } else if (cmsBaseInput && !isLocalPanelHost(window.location.hostname)) {
413
+ cmsBaseInput.value = window.location.origin;
414
+ }
415
+
333
416
  loadPlaylistDraft();
334
417
  loadScheduleDraft();
335
418