@tomorrowos/sdk 0.8.7 → 0.8.8
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/REPLIT_SETUP.md +1 -1
- package/dist/tomorrowos.d.ts +2 -0
- package/dist/tomorrowos.d.ts.map +1 -1
- package/dist/tomorrowos.js +8 -1
- package/package.json +1 -1
- package/templates/cms-starter/package.json +2 -2
- package/templates/cms-starter/public/methods.js +68 -4
- package/templates/cms-starter/public/panel.css +14 -0
package/REPLIT_SETUP.md
CHANGED
package/dist/tomorrowos.d.ts
CHANGED
|
@@ -93,6 +93,8 @@ export declare class TomorrowOS extends EventEmitter {
|
|
|
93
93
|
private wss;
|
|
94
94
|
private staticRoot;
|
|
95
95
|
private staticIndexFile;
|
|
96
|
+
/** Set when listen() starts — exposed to CMS panel for post-restart reconnect grace. */
|
|
97
|
+
private serverStartedAt;
|
|
96
98
|
constructor(options: TomorrowOSOptions);
|
|
97
99
|
/** Push all device assignments using latest playlist definitions (e.g. after reboot). */
|
|
98
100
|
pushLatestPolicyToDevice(deviceId: string): Promise<{
|
package/dist/tomorrowos.d.ts.map
CHANGED
|
@@ -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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"}
|
package/dist/tomorrowos.js
CHANGED
|
@@ -193,6 +193,8 @@ export class TomorrowOS extends EventEmitter {
|
|
|
193
193
|
wss = null;
|
|
194
194
|
staticRoot = null;
|
|
195
195
|
staticIndexFile = "index.html";
|
|
196
|
+
/** Set when listen() starts — exposed to CMS panel for post-restart reconnect grace. */
|
|
197
|
+
serverStartedAt = null;
|
|
196
198
|
constructor(options) {
|
|
197
199
|
super();
|
|
198
200
|
this.brand = options.brand;
|
|
@@ -661,6 +663,7 @@ export class TomorrowOS extends EventEmitter {
|
|
|
661
663
|
wss.on("connection", (ws) => {
|
|
662
664
|
this.handleConnection(ws);
|
|
663
665
|
});
|
|
666
|
+
this.serverStartedAt = new Date().toISOString();
|
|
664
667
|
server.listen(port, host, () => {
|
|
665
668
|
// eslint-disable-next-line no-console
|
|
666
669
|
console.log(`[TomorrowOS] listening on http://${host}:${port}`);
|
|
@@ -1026,7 +1029,11 @@ export class TomorrowOS extends EventEmitter {
|
|
|
1026
1029
|
}
|
|
1027
1030
|
if (req.method === "GET" && pathname === "/devices") {
|
|
1028
1031
|
const devices = await this.listDevices();
|
|
1029
|
-
sendJson(res, 200, {
|
|
1032
|
+
sendJson(res, 200, {
|
|
1033
|
+
status: "success",
|
|
1034
|
+
devices,
|
|
1035
|
+
serverStartedAt: this.serverStartedAt
|
|
1036
|
+
});
|
|
1030
1037
|
return;
|
|
1031
1038
|
}
|
|
1032
1039
|
const deviceNamePatch = /^\/device\/([^/]+)\/name$/.exec(pathname);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tomorrowos/sdk",
|
|
3
|
-
"version": "0.8.
|
|
3
|
+
"version": "0.8.8",
|
|
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.8.
|
|
3
|
+
"version": "0.8.8",
|
|
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.8.
|
|
16
|
+
"@tomorrowos/sdk": "^0.8.8",
|
|
17
17
|
"dotenv": "^17.2.3",
|
|
18
18
|
"tsx": "^4.19.0"
|
|
19
19
|
},
|
|
@@ -27,10 +27,15 @@ let editingDeviceNameId = null;
|
|
|
27
27
|
let editingDeviceNameValue = "";
|
|
28
28
|
|
|
29
29
|
let devicePollTimer = null;
|
|
30
|
+
/** @type {number|null} CMS server boot time (ms) from GET /devices. */
|
|
31
|
+
let serverStartedAtMs = null;
|
|
32
|
+
/** @type {ReturnType<typeof setTimeout>|null} */
|
|
33
|
+
let reconnectGraceTimer = null;
|
|
30
34
|
let uploadQueue = [];
|
|
31
35
|
let uploadInProgress = false;
|
|
32
36
|
|
|
33
37
|
const UPLOAD_MAX_RETRIES = 3;
|
|
38
|
+
const DEVICE_RECONNECT_GRACE_MS = 60000;
|
|
34
39
|
const UPLOAD_TIMEOUT_MS = 120000;
|
|
35
40
|
|
|
36
41
|
function escapeHtml(value) {
|
|
@@ -71,8 +76,30 @@ function formatDateTimeSeconds(iso) {
|
|
|
71
76
|
});
|
|
72
77
|
}
|
|
73
78
|
|
|
74
|
-
function
|
|
75
|
-
if (
|
|
79
|
+
function resolveDeviceConnectionState(device) {
|
|
80
|
+
if (device.connected) return "online";
|
|
81
|
+
if (serverStartedAtMs != null) {
|
|
82
|
+
const elapsed = Date.now() - serverStartedAtMs;
|
|
83
|
+
if (elapsed >= 0 && elapsed < DEVICE_RECONNECT_GRACE_MS) return "loading";
|
|
84
|
+
}
|
|
85
|
+
return "offline";
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function statusLedClass(connectionState) {
|
|
89
|
+
if (connectionState === "online") return "status-led status-led--online";
|
|
90
|
+
if (connectionState === "loading") return "status-led status-led--loading";
|
|
91
|
+
return "status-led status-led--offline";
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function statusLedTitle(connectionState) {
|
|
95
|
+
if (connectionState === "online") return "Device connected";
|
|
96
|
+
if (connectionState === "loading") return "Waiting for device to reconnect";
|
|
97
|
+
return "Device not connected";
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
function formatDeviceOnlineLabel(device, connectionState) {
|
|
101
|
+
if (connectionState === "loading") return "Reconnecting…";
|
|
102
|
+
if (connectionState !== "online") return "Not active";
|
|
76
103
|
const bootIso = device.lastBootAt;
|
|
77
104
|
if (!bootIso) return "Not active";
|
|
78
105
|
const bootMs = new Date(bootIso).getTime();
|
|
@@ -80,6 +107,30 @@ function formatDeviceOnlineLabel(device) {
|
|
|
80
107
|
return formatDurationMs(Date.now() - bootMs);
|
|
81
108
|
}
|
|
82
109
|
|
|
110
|
+
function scheduleReconnectGraceRerender() {
|
|
111
|
+
if (reconnectGraceTimer) {
|
|
112
|
+
clearTimeout(reconnectGraceTimer);
|
|
113
|
+
reconnectGraceTimer = null;
|
|
114
|
+
}
|
|
115
|
+
if (serverStartedAtMs == null) return;
|
|
116
|
+
|
|
117
|
+
const hasLoadingDevice = devicesCache.some(
|
|
118
|
+
(device) => resolveDeviceConnectionState(device) === "loading"
|
|
119
|
+
);
|
|
120
|
+
if (!hasLoadingDevice) return;
|
|
121
|
+
|
|
122
|
+
const remaining = DEVICE_RECONNECT_GRACE_MS - (Date.now() - serverStartedAtMs);
|
|
123
|
+
if (remaining <= 0) {
|
|
124
|
+
renderDeviceCards();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
reconnectGraceTimer = setTimeout(() => {
|
|
129
|
+
reconnectGraceTimer = null;
|
|
130
|
+
renderDeviceCards();
|
|
131
|
+
}, remaining + 50);
|
|
132
|
+
}
|
|
133
|
+
|
|
83
134
|
function showResult(data) {
|
|
84
135
|
document.getElementById("result").textContent = JSON.stringify(data, null, 2);
|
|
85
136
|
}
|
|
@@ -942,9 +993,18 @@ async function fetchDevices() {
|
|
|
942
993
|
try {
|
|
943
994
|
const res = await fetch("/devices");
|
|
944
995
|
const data = await res.json();
|
|
996
|
+
if (typeof data.serverStartedAt === "string") {
|
|
997
|
+
const parsed = new Date(data.serverStartedAt).getTime();
|
|
998
|
+
if (!Number.isNaN(parsed)) {
|
|
999
|
+
const prev = serverStartedAtMs;
|
|
1000
|
+
serverStartedAtMs = parsed;
|
|
1001
|
+
if (prev !== parsed) scheduleReconnectGraceRerender();
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
945
1004
|
if (Array.isArray(data.devices)) {
|
|
946
1005
|
devicesCache = data.devices;
|
|
947
1006
|
renderDeviceCards();
|
|
1007
|
+
scheduleReconnectGraceRerender();
|
|
948
1008
|
}
|
|
949
1009
|
} catch (err) {
|
|
950
1010
|
showResult({ status: "failed", error: err.message });
|
|
@@ -1010,8 +1070,12 @@ function renderDeviceCards() {
|
|
|
1010
1070
|
header.className = "device-card-header";
|
|
1011
1071
|
const headerMain = document.createElement("div");
|
|
1012
1072
|
headerMain.className = "device-card-header-main";
|
|
1073
|
+
const connectionState = resolveDeviceConnectionState(device);
|
|
1013
1074
|
const led = document.createElement("span");
|
|
1014
|
-
led.className =
|
|
1075
|
+
led.className = statusLedClass(connectionState);
|
|
1076
|
+
led.title = statusLedTitle(connectionState);
|
|
1077
|
+
led.setAttribute("role", "status");
|
|
1078
|
+
led.setAttribute("aria-label", statusLedTitle(connectionState));
|
|
1015
1079
|
const title = document.createElement("h3");
|
|
1016
1080
|
title.className = "device-card-title";
|
|
1017
1081
|
const isEditingName = editingDeviceNameId === device.deviceId;
|
|
@@ -1124,7 +1188,7 @@ function renderDeviceCards() {
|
|
|
1124
1188
|
const metaSecondary = document.createElement("dl");
|
|
1125
1189
|
metaSecondary.className = "device-meta device-meta--secondary";
|
|
1126
1190
|
const secondaryRows = [
|
|
1127
|
-
["Device online", formatDeviceOnlineLabel(device)],
|
|
1191
|
+
["Device online", formatDeviceOnlineLabel(device, connectionState)],
|
|
1128
1192
|
["Last boot", formatDateTimeSeconds(device.lastBootAt)],
|
|
1129
1193
|
["Latest push", formatDateTimeSeconds(device.lastPolicyPushAt)],
|
|
1130
1194
|
["Latest error", device.latestErrorMessage || "—"],
|
|
@@ -200,6 +200,20 @@ body {
|
|
|
200
200
|
box-shadow: 0 0 8px rgba(240, 68, 56, 0.45);
|
|
201
201
|
}
|
|
202
202
|
|
|
203
|
+
.status-led--loading {
|
|
204
|
+
background: transparent;
|
|
205
|
+
border: 2px solid #dcd9d4;
|
|
206
|
+
border-top-color: #6e6b67;
|
|
207
|
+
box-shadow: none;
|
|
208
|
+
animation: status-led-spin 0.75s linear infinite;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
@keyframes status-led-spin {
|
|
212
|
+
to {
|
|
213
|
+
transform: rotate(360deg);
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
|
|
203
217
|
.device-meta {
|
|
204
218
|
display: grid;
|
|
205
219
|
gap: 0.35rem;
|