@tomorrowos/sdk 0.3.3 → 0.3.4

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.
@@ -13,13 +13,10 @@ export interface BuiltDevicePolicy {
13
13
  };
14
14
  revision?: number;
15
15
  syncMode?: "latest" | "snapshot";
16
- /** One-shot: player may start outside schedule right after publish. */
17
- playNow?: boolean;
18
16
  };
19
17
  }
20
18
  export interface PolicyBuildOptions {
21
19
  useLatest?: boolean;
22
- playNow?: boolean;
23
20
  mediaBaseUrl?: string;
24
21
  }
25
22
  export declare class PlaylistCatalog {
@@ -1 +1 @@
1
- {"version":3,"file":"playlist-catalog.d.ts","sourceRoot":"","sources":["../src/playlist-catalog.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,wBAAwB,EACxB,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,EACzB,cAAc,EACd,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE;QACN,SAAS,EAAE,yBAAyB,EAAE,CAAC;QACvC,QAAQ,EAAE;YAAE,IAAI,EAAE,OAAO,CAAA;SAAE,CAAC;QAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;QACjC,uEAAuE;QACvE,OAAO,CAAC,EAAE,OAAO,CAAC;KACnB,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA+CD,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,eAAe;IAE7C,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAK1C,6BAA6B,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAI1D,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAI5D,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC;IAgC/D,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAcnD,oBAAoB,CACxB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAIhC,wBAAwB,CAC5B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EAAE,EACrB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;IAkCvB,wBAAwB,CAC5B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC;IAOvB,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;YAKf,0BAA0B;IAmCxC,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGpE"}
1
+ {"version":3,"file":"playlist-catalog.d.ts","sourceRoot":"","sources":["../src/playlist-catalog.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,wBAAwB,EACxB,kBAAkB,EAClB,gBAAgB,EAChB,yBAAyB,EACzB,cAAc,EACd,eAAe,EAChB,MAAM,kBAAkB,CAAC;AAE1B,MAAM,WAAW,iBAAiB;IAChC,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,gBAAgB,CAAC;IAC5B,KAAK,EAAE,kBAAkB,EAAE,CAAC;CAC7B;AAED,MAAM,WAAW,iBAAiB;IAChC,MAAM,EAAE;QACN,SAAS,EAAE,yBAAyB,EAAE,CAAC;QACvC,QAAQ,EAAE;YAAE,IAAI,EAAE,OAAO,CAAA;SAAE,CAAC;QAC5B,QAAQ,CAAC,EAAE,MAAM,CAAC;QAClB,QAAQ,CAAC,EAAE,QAAQ,GAAG,UAAU,CAAC;KAClC,CAAC;CACH;AAED,MAAM,WAAW,kBAAkB;IACjC,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA+CD,qBAAa,eAAe;IACd,OAAO,CAAC,QAAQ,CAAC,KAAK;gBAAL,KAAK,EAAE,eAAe;IAE7C,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAK1C,6BAA6B,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAI1D,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAI5D,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAAC,cAAc,CAAC;IAgC/D,cAAc,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAcnD,oBAAoB,CACxB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAIhC,wBAAwB,CAC5B,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,MAAM,EAAE,EACrB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;IA2CvB,wBAAwB,CAC5B,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,GACjB,OAAO,CAAC,iBAAiB,CAAC;IAOvB,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,OAAO,GAAE,kBAAuB,GAC/B,OAAO,CAAC,iBAAiB,CAAC;YAKf,0BAA0B;IAgCxC,6BAA6B,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;CAGpE"}
@@ -101,14 +101,23 @@ export class PlaylistCatalog {
101
101
  return this.store.getDeviceAssignments(deviceId);
102
102
  }
103
103
  async publishPlaylistsToDevice(deviceId, playlistIds, options = {}) {
104
- const ids = [...new Set(playlistIds.map((x) => String(x).trim()).filter(Boolean))];
105
- if (ids.length === 0) {
104
+ const incoming = [...new Set(playlistIds.map((x) => String(x).trim()).filter(Boolean))];
105
+ if (incoming.length === 0) {
106
106
  throw Object.assign(new Error("Select at least one playlist"), {
107
107
  code: "PLAYLIST_INVALID"
108
108
  });
109
109
  }
110
+ const existing = await this.store.getDeviceAssignments(deviceId);
111
+ const existingById = new Map(existing.map((a) => [a.playlistId, a]));
112
+ const allIds = [...new Set([...existing.map((a) => a.playlistId), ...incoming])];
110
113
  const assignments = [];
111
- for (const playlistId of ids) {
114
+ for (const playlistId of allIds) {
115
+ if (!incoming.includes(playlistId)) {
116
+ const kept = existingById.get(playlistId);
117
+ if (kept)
118
+ assignments.push(kept);
119
+ continue;
120
+ }
112
121
  const playlist = await this.store.getPlaylist(playlistId);
113
122
  if (!playlist || playlist.retired) {
114
123
  throw Object.assign(new Error(`Playlist not available: ${playlistId}`), {
@@ -125,7 +134,6 @@ export class PlaylistCatalog {
125
134
  await this.store.setDeviceAssignments(deviceId, assignments);
126
135
  return this.buildPolicyFromAssignments(assignments, {
127
136
  useLatest: false,
128
- playNow: true,
129
137
  mediaBaseUrl: options.mediaBaseUrl
130
138
  });
131
139
  }
@@ -156,16 +164,14 @@ export class PlaylistCatalog {
156
164
  items: absolutizePlaylistItems(assignment.snapshot.items, mediaBaseUrl)
157
165
  });
158
166
  }
159
- const policy = {
160
- playlists,
161
- fallback: { type: "brand" },
162
- revision: Date.now(),
163
- syncMode: useLatest ? "latest" : "snapshot"
167
+ return {
168
+ policy: {
169
+ playlists,
170
+ fallback: { type: "brand" },
171
+ revision: Date.now(),
172
+ syncMode: useLatest ? "latest" : "snapshot"
173
+ }
164
174
  };
165
- if (options.playNow === true) {
166
- policy.playNow = true;
167
- }
168
- return { policy };
169
175
  }
170
176
  canPublishPlaylistToNewDevice(playlistId) {
171
177
  return this.store.getPlaylist(playlistId).then((p) => !!p && !p?.retired);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "0.3.3",
3
+ "version": "0.3.4",
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.3.3",
3
+ "version": "0.3.4",
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.3"
13
+ "@tomorrowos/sdk": "^0.3.4"
14
14
  },
15
15
  "devDependencies": {
16
16
  "@types/node": "^20.0.0",
@@ -64,7 +64,10 @@
64
64
  </label>
65
65
 
66
66
  <h3 class="subheading">When this playlist plays</h3>
67
- <p class="hint">Leave blank for always on (device local time).</p>
67
+ <p class="hint">
68
+ Leave blank for always on. Uses the <strong>TV/device local clock</strong> (not CMS server time).
69
+ “Until” includes that minute (e.g. Until 18:00 plays through 18:00).
70
+ </p>
68
71
  <div class="schedule-grid">
69
72
  <label>
70
73
  Start date
@@ -124,7 +127,7 @@
124
127
  <div class="modal-backdrop" data-close-modal="1"></div>
125
128
  <div class="modal-card">
126
129
  <h2>Publish to device</h2>
127
- <p class="hint" id="publishModalHint">Select playlists to deploy. Updates apply on publish; reboot pulls latest saved versions.</p>
130
+ <p class="hint" id="publishModalHint">Select playlists to add to this device. Already-published playlists are not listed here.</p>
128
131
  <div id="publishChecklist" class="publish-checklist"></div>
129
132
  <div class="modal-actions">
130
133
  <button type="button" id="publishConfirmBtn" class="primary">Publish selected</button>
@@ -217,7 +217,7 @@ async function fetchPlaylists() {
217
217
  showResult({
218
218
  status: "failed",
219
219
  error:
220
- "CMS server is missing /playlists. Restart CMS with @tomorrowos/sdk 0.3.3 or newer."
220
+ "CMS server is missing /playlists. Restart CMS with @tomorrowos/sdk 0.3.4 or newer."
221
221
  });
222
222
  }
223
223
  return;
@@ -626,17 +626,24 @@ function openPublishModal(deviceId) {
626
626
  return;
627
627
  }
628
628
 
629
- hint.textContent = `Device ${deviceId} select playlists to publish (snapshot at publish time).`;
629
+ const pubs = devicesCache.find((d) => d.deviceId === deviceId)?.publishedPlaylists || [];
630
+ const publishedIds = new Set(pubs.map((p) => p.playlistId));
631
+ const unpublished = playlistsCatalog.filter((pl) => !publishedIds.has(pl.id));
632
+
633
+ if (unpublished.length === 0) {
634
+ alert("All playlists are already published to this device. Use Remove on the card to unpublish one first.");
635
+ return;
636
+ }
637
+
638
+ hint.textContent = `Device ${deviceId} — add playlists not yet on this device (snapshot at publish time).`;
630
639
  checklist.innerHTML = "";
631
640
 
632
- for (const pl of playlistsCatalog) {
641
+ for (const pl of unpublished) {
633
642
  const label = document.createElement("label");
634
643
  const cb = document.createElement("input");
635
644
  cb.type = "checkbox";
636
645
  cb.value = pl.id;
637
646
  cb.dataset.name = pl.name;
638
- const pubs = devicesCache.find((d) => d.deviceId === deviceId)?.publishedPlaylists || [];
639
- if (pubs.some((p) => p.playlistId === pl.id)) cb.checked = true;
640
647
  label.appendChild(cb);
641
648
  label.appendChild(document.createTextNode(` ${pl.name} (v${pl.version})`));
642
649
  checklist.appendChild(label);
@@ -680,7 +687,11 @@ async function confirmPublishModal() {
680
687
  return;
681
688
  }
682
689
 
683
- const publishBody = { playlistIds: ids };
690
+ const device = devicesCache.find((d) => d.deviceId === publishModalDeviceId);
691
+ const alreadyOnDevice = (device?.publishedPlaylists || []).map((p) => p.playlistId);
692
+ const playlistIds = [...new Set([...alreadyOnDevice, ...ids])];
693
+
694
+ const publishBody = { playlistIds };
684
695
  if (mediaBaseUrl) publishBody.mediaBaseUrl = mediaBaseUrl;
685
696
 
686
697
  const res = await fetch(