@tomorrowos/sdk 0.9.13 → 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/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/tomorrowos.d.ts +4 -0
- package/dist/tomorrowos.d.ts.map +1 -1
- package/dist/tomorrowos.js +17 -1
- package/package.json +1 -1
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter/public/index.html +6 -10
- package/templates/cms-starter/public/methods.js +31 -20
- package/templates/cms-starter/public/panel.css +1 -12
|
@@ -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
|
}
|
package/dist/tomorrowos.d.ts
CHANGED
|
@@ -98,6 +98,8 @@ export declare class TomorrowOS extends EventEmitter {
|
|
|
98
98
|
private staticIndexFile;
|
|
99
99
|
/** Set when listen() starts — exposed to CMS panel for post-restart reconnect grace. */
|
|
100
100
|
private serverStartedAt;
|
|
101
|
+
/** Port passed to listen(); used for suggested LAN CMS URL. */
|
|
102
|
+
private listenPort;
|
|
101
103
|
constructor(options: TomorrowOSOptions);
|
|
102
104
|
/** Push all device assignments using latest playlist definitions (e.g. after reboot). */
|
|
103
105
|
pushLatestPolicyToDevice(deviceId: string): Promise<{
|
|
@@ -165,6 +167,8 @@ export declare class TomorrowOS extends EventEmitter {
|
|
|
165
167
|
}>;
|
|
166
168
|
};
|
|
167
169
|
private sendCommandToSocket;
|
|
170
|
+
/** Best-effort LAN URL for local screens (e.g. http://192.168.1.10:3000). */
|
|
171
|
+
private getSuggestedCmsUrl;
|
|
168
172
|
listen(options: ListenOptions): http.Server;
|
|
169
173
|
private handleMediaUpload;
|
|
170
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,EACV,gBAAgB,EAKhB,eAAe,EAEhB,MAAM,kBAAkB,CAAC;
|
|
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() : "";
|
|
@@ -213,6 +214,8 @@ export class TomorrowOS extends EventEmitter {
|
|
|
213
214
|
staticIndexFile = "index.html";
|
|
214
215
|
/** Set when listen() starts — exposed to CMS panel for post-restart reconnect grace. */
|
|
215
216
|
serverStartedAt = null;
|
|
217
|
+
/** Port passed to listen(); used for suggested LAN CMS URL. */
|
|
218
|
+
listenPort = null;
|
|
216
219
|
constructor(options) {
|
|
217
220
|
super();
|
|
218
221
|
this.brand = options.brand;
|
|
@@ -716,8 +719,17 @@ export class TomorrowOS extends EventEmitter {
|
|
|
716
719
|
}));
|
|
717
720
|
});
|
|
718
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
|
+
}
|
|
719
730
|
listen(options) {
|
|
720
731
|
const { port, host = "0.0.0.0", staticRoot, staticIndex } = options;
|
|
732
|
+
this.listenPort = port;
|
|
721
733
|
this.staticRoot = staticRoot ?? null;
|
|
722
734
|
if (this.staticRoot) {
|
|
723
735
|
const idx = (staticIndex ?? "index.html").trim().replace(/^[\\/]+/, "") || "index.html";
|
|
@@ -1088,7 +1100,11 @@ export class TomorrowOS extends EventEmitter {
|
|
|
1088
1100
|
store: this.store,
|
|
1089
1101
|
staticRoot: this.staticRoot
|
|
1090
1102
|
});
|
|
1091
|
-
|
|
1103
|
+
const suggestedCmsUrl = this.getSuggestedCmsUrl();
|
|
1104
|
+
sendJson(res, 200, {
|
|
1105
|
+
...report,
|
|
1106
|
+
...(suggestedCmsUrl ? { suggestedCmsUrl } : {})
|
|
1107
|
+
});
|
|
1092
1108
|
return;
|
|
1093
1109
|
}
|
|
1094
1110
|
if (req.method === "POST" && pathname === "/media/upload") {
|
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>
|
|
@@ -185,12 +186,8 @@
|
|
|
185
186
|
<div class="modal-card">
|
|
186
187
|
<h2>On / off timer</h2>
|
|
187
188
|
<p class="hint" id="onOffTimerModalHint">
|
|
188
|
-
Daily
|
|
189
|
+
Daily on/off times follow the device clock. Off blacks the screen; the player stays powered and connected to the CMS.
|
|
189
190
|
</p>
|
|
190
|
-
<label class="field-label on-off-timer-enable">
|
|
191
|
-
<input type="checkbox" id="onOffTimerEnabled" />
|
|
192
|
-
Enable daily timer
|
|
193
|
-
</label>
|
|
194
191
|
<div class="on-off-timer-grid">
|
|
195
192
|
<label class="field-label">
|
|
196
193
|
Turn on (unmute)
|
|
@@ -201,7 +198,6 @@
|
|
|
201
198
|
<input type="time" id="onOffTimerTurnOffAt" value="18:00" />
|
|
202
199
|
</label>
|
|
203
200
|
</div>
|
|
204
|
-
<p class="hint" id="onOffTimerModalStatus"></p>
|
|
205
201
|
<div class="modal-actions">
|
|
206
202
|
<button type="button" id="onOffTimerSaveBtn" class="primary">Save</button>
|
|
207
203
|
<button type="button" data-close-on-off-timer-modal="1">Cancel</button>
|
|
@@ -233,7 +229,7 @@
|
|
|
233
229
|
<p class="hint">Current installed SDK version:</p>
|
|
234
230
|
<p class="update-sdk-version" id="updateSdkVersionLabel">Loading…</p>
|
|
235
231
|
<p class="hint">
|
|
236
|
-
To upgrade on
|
|
232
|
+
To upgrade on AI agent platforms (Replit, Vercel, etc.), paste this prompt into the Agent chat:
|
|
237
233
|
</p>
|
|
238
234
|
<pre class="update-sdk-prompt" id="updateSdkPromptText">Follow @tomorrowos/sdk REPLIT_UPGRADE.md to upgrade my CMS with the latest SDK.</pre>
|
|
239
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();
|
|
@@ -1280,8 +1302,8 @@ function renderDeviceCards() {
|
|
|
1280
1302
|
|
|
1281
1303
|
const timerBtn = document.createElement("button");
|
|
1282
1304
|
timerBtn.type = "button";
|
|
1283
|
-
timerBtn.textContent =
|
|
1284
|
-
timerBtn.title = "Daily screen on/off
|
|
1305
|
+
timerBtn.textContent = "Timer";
|
|
1306
|
+
timerBtn.title = "Daily screen on/off timer";
|
|
1285
1307
|
timerBtn.addEventListener("click", () => openOnOffTimerModal(device.deviceId));
|
|
1286
1308
|
|
|
1287
1309
|
const unpairBtn = document.createElement("button");
|
|
@@ -1815,32 +1837,19 @@ function openOnOffTimerModal(deviceId) {
|
|
|
1815
1837
|
const device = devicesCache.find((d) => d.deviceId === deviceId);
|
|
1816
1838
|
if (!device) return;
|
|
1817
1839
|
if (!isTizenDevice(device)) {
|
|
1818
|
-
alert("On/off timer is available for
|
|
1840
|
+
alert("On/off timer is available for Tizen players only.");
|
|
1819
1841
|
return;
|
|
1820
1842
|
}
|
|
1821
1843
|
|
|
1822
1844
|
onOffTimerModalDeviceId = deviceId;
|
|
1823
1845
|
const modal = document.getElementById("onOffTimerModal");
|
|
1824
|
-
const hint = document.getElementById("onOffTimerModalHint");
|
|
1825
|
-
const status = document.getElementById("onOffTimerModalStatus");
|
|
1826
|
-
const enabledEl = document.getElementById("onOffTimerEnabled");
|
|
1827
1846
|
const onEl = document.getElementById("onOffTimerTurnOnAt");
|
|
1828
1847
|
const offEl = document.getElementById("onOffTimerTurnOffAt");
|
|
1829
|
-
if (!modal || !
|
|
1848
|
+
if (!modal || !onEl || !offEl) return;
|
|
1830
1849
|
|
|
1831
1850
|
const timer = device.onOffTimer || {};
|
|
1832
|
-
enabledEl.checked = Boolean(timer.enabled);
|
|
1833
1851
|
onEl.value = normalizeTimeInputValue(timer.turnOnAt, "06:00");
|
|
1834
1852
|
offEl.value = normalizeTimeInputValue(timer.turnOffAt, "18:00");
|
|
1835
|
-
if (hint) {
|
|
1836
|
-
hint.textContent =
|
|
1837
|
-
"Daily schedule on the device clock. Off uses Samsung panel mute (screen black); the player stays connected to the CMS.";
|
|
1838
|
-
}
|
|
1839
|
-
if (status) {
|
|
1840
|
-
status.textContent = device.connected
|
|
1841
|
-
? "Device is online — save will apply immediately."
|
|
1842
|
-
: "Device is offline — schedule will save and apply when it reconnects.";
|
|
1843
|
-
}
|
|
1844
1853
|
modal.classList.remove("hidden");
|
|
1845
1854
|
}
|
|
1846
1855
|
|
|
@@ -1854,14 +1863,13 @@ async function saveOnOffTimerModal() {
|
|
|
1854
1863
|
const deviceId = onOffTimerModalDeviceId;
|
|
1855
1864
|
if (!deviceId) return;
|
|
1856
1865
|
|
|
1857
|
-
const enabledEl = document.getElementById("onOffTimerEnabled");
|
|
1858
1866
|
const onEl = document.getElementById("onOffTimerTurnOnAt");
|
|
1859
1867
|
const offEl = document.getElementById("onOffTimerTurnOffAt");
|
|
1860
1868
|
const saveBtn = document.getElementById("onOffTimerSaveBtn");
|
|
1861
|
-
if (!
|
|
1869
|
+
if (!onEl || !offEl) return;
|
|
1862
1870
|
|
|
1863
1871
|
const onOffTimer = {
|
|
1864
|
-
enabled:
|
|
1872
|
+
enabled: true,
|
|
1865
1873
|
turnOnAt: normalizeTimeInputValue(onEl.value, "06:00"),
|
|
1866
1874
|
turnOffAt: normalizeTimeInputValue(offEl.value, "18:00")
|
|
1867
1875
|
};
|
|
@@ -2143,6 +2151,9 @@ async function fetchServerStatus() {
|
|
|
2143
2151
|
if (typeof data.sdkVersion === "string" && data.sdkVersion.trim()) {
|
|
2144
2152
|
cachedSdkVersion = data.sdkVersion.trim();
|
|
2145
2153
|
}
|
|
2154
|
+
if (typeof data.suggestedCmsUrl === "string") {
|
|
2155
|
+
applyDefaultCmsUrlForScreens(data.suggestedCmsUrl);
|
|
2156
|
+
}
|
|
2146
2157
|
renderServerStatus(data);
|
|
2147
2158
|
} catch (err) {
|
|
2148
2159
|
renderServerStatus({
|
|
@@ -829,22 +829,11 @@ button.danger {
|
|
|
829
829
|
width: min(94vw, 860px);
|
|
830
830
|
}
|
|
831
831
|
|
|
832
|
-
.on-off-timer-enable {
|
|
833
|
-
display: flex;
|
|
834
|
-
align-items: center;
|
|
835
|
-
gap: 0.5rem;
|
|
836
|
-
margin: 0.85rem 0 0.65rem;
|
|
837
|
-
}
|
|
838
|
-
|
|
839
|
-
.on-off-timer-enable input {
|
|
840
|
-
width: auto;
|
|
841
|
-
margin: 0;
|
|
842
|
-
}
|
|
843
|
-
|
|
844
832
|
.on-off-timer-grid {
|
|
845
833
|
display: grid;
|
|
846
834
|
grid-template-columns: 1fr 1fr;
|
|
847
835
|
gap: 0.75rem;
|
|
836
|
+
margin-top: 0.85rem;
|
|
848
837
|
}
|
|
849
838
|
|
|
850
839
|
@media (max-width: 520px) {
|