@tomorrowos/sdk 0.8.2 → 0.8.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.
package/REPLIT_SETUP.md CHANGED
@@ -12,6 +12,7 @@ When a human pastes a prompt like this, **read this file and start Question 1 im
12
12
  ```text
13
13
  Follow @tomorrowos/sdk REPLIT_SETUP.md and set up my TomorrowOS CMS.
14
14
  Ask only the questions listed in REPLIT_SETUP.md, in order. Do not use LLM_PROMPT.md.
15
+ Artifact kind must be **web** (never **api**). Question 4 only updates **brand.json**.
15
16
  After all setup questions are answered (including Cloudinary Secrets if you chose Cloudinary), configure and start — minimal verification only (Web app type + Preview at /). **Never test Supabase connection** — save `SUPABASE_URL` and move on. No Supabase or WebSocket tests.
16
17
  Do not skip steps.
17
18
  ```
@@ -23,7 +24,31 @@ Alternate triggers that also activate this protocol:
23
24
 
24
25
  When any of these triggers fire, **ignore `LLM_PROMPT.md` entirely** for the Q&A phase.
25
26
 
26
- **Before Question 1:** ensure the Replit workspace is a **Web app** project (see **Deployability: Web app artifact**). If the project is API-only or not publishable, fix that first — do not continue the questionnaire on a broken project type.
27
+ **Before Question 1:** ensure the Replit workspace primary artifact **`kind` is `web`** (see **IRON RULE: `web` kind only**). If the project is `api` / API-only or not publishable, **fix artifact kind first** — do not continue the questionnaire on a broken project type.
28
+
29
+ ---
30
+
31
+ ## IRON RULE: `web` kind only (Publish will fail on `api`)
32
+
33
+ > **This is the #1 deployability rule. Non-negotiable.**
34
+
35
+ TomorrowOS CMS serves a **browser Control Panel at `/`** plus WebSocket for TVs. Replit’s publish detector treats **`kind = "api"`** artifacts as **not deployable** as a user-facing app — Publish may only see design mockups / libs and fail with *“api-server artifact isn't being recognized as deployable”*.
36
+
37
+ | Required | Forbidden |
38
+ |----------|-----------|
39
+ | Primary artifact **`kind = "web"`** | **`kind = "api"`** on the CMS artifact |
40
+ | Homepage / project type: **Web app** | **API server** as the only publishable artifact |
41
+ | `GET /` → Control Panel HTML (`public/index.html`) | `artifacts/api-server` with `kind = "api"` for this CMS |
42
+ | `.replit` + `npm run start` → live Node (`tsx server.ts`) | Splitting CMS into a non-publishable API artifact |
43
+
44
+ **Agent must enforce before and after Q&A:**
45
+
46
+ 1. If `.replit-artifact/artifact.toml` (or artifact metadata) shows **`kind = "api"`** → change to **`kind = "web"`** immediately (validated temp-file flow). **Never** leave the CMS on `api` kind.
47
+ 2. **Do not** scaffold TomorrowOS CMS under `artifacts/api-server` with `kind = "api"`. The CMS lives at the **project root** (or a **`web`** artifact at `/`).
48
+ 3. **Do not** create a separate `api` artifact “for the backend” — TomorrowOS is one **web** app (HTTP + WebSocket in `server.ts`).
49
+ 4. After setup, confirm publish detector sees a **web** artifact serving `/` — not api-only.
50
+
51
+ **If Publish says api-kind / api-server not deployable:** fix `kind` to **`web`** first, then `.replit` ports + `deploymentTarget = "autoscale"`. Creating a **new Web app** Repl is the fastest fix if Agent locked the project as API-only.
27
52
 
28
53
  ---
29
54
 
@@ -64,6 +89,7 @@ Then ask **Question 1** — do not switch to `LLM_PROMPT.md`.
64
89
  7. After Q&A, **configure and start** — do **not** run a long test suite (see **Post-Q&A: minimal verification only**).
65
90
  8. **Follow the Runtime & Replit publish rules below exactly.** A project that cannot Deploy / Publish on Replit is incomplete.
66
91
  9. **Supabase: configure only — never test.** Save `SUPABASE_URL` + `TOMORROWOS_STORE=supabase` and proceed. **Do not** ping Postgres, run `psql`, start the server solely to verify DB connectivity, or treat log errors like `ENOTFOUND` / `database connection failure` in **Replit dev / Preview** as a failed setup. Supabase often works only after **Publish** (production networking), not in the dev shell.
92
+ 10. **Artifact kind = `web` only.** **`kind = "api"` is forbidden** for the TomorrowOS CMS artifact. See **IRON RULE** above. Setup is incomplete if Publish would still see an `api` artifact.
67
93
 
68
94
  **Question order after Q1:**
69
95
 
@@ -76,7 +102,9 @@ Never insert extra questions between these steps. When the user chooses Cloudina
76
102
 
77
103
  ## Deployability: Web app artifact (mandatory — fixes “api-kind not deployable”)
78
104
 
79
- TomorrowOS CMS is a **user-facing Web app** (Control Panel at `/` + WebSocket for TVs). Replit must treat it as a **Web app** artifact, **not** an API-only / backend-library project.
105
+ > **Reminder:** Re-read **IRON RULE: `web` kind only** `api` kind is never acceptable for this CMS.
106
+
107
+ TomorrowOS CMS is a **user-facing Web app** (Control Panel at `/` + WebSocket for TVs). Replit must treat it as a **`web`** artifact, **not** `api` / API-only.
80
108
 
81
109
  ### Create the project correctly (do this first)
82
110
 
@@ -89,6 +117,7 @@ When scaffolding a **new** Repl:
89
117
 
90
118
  **Forbidden setups that cause publish failure:**
91
119
 
120
+ - ❌ **`kind = "api"`** on the CMS artifact (`api-server`, `artifacts/api-server`, etc.)
92
121
  - ❌ Project / primary artifact classified as **API** (`api-kind` artifact)
93
122
  - ❌ Backend-only Express/Fastify service with **no** `staticRoot` / Control Panel at `/`
94
123
  - ❌ Splitting CMS into a non-publishable API artifact + separate unpublished frontend
@@ -133,7 +162,8 @@ ignorePorts = false
133
162
  - Set `entrypoint = "server.ts"` (not `index.js` unless that file exists and starts TomorrowOS).
134
163
  - Set `deploymentTarget = "autoscale"` (WebSocket + long-lived connections; Static Deploy is wrong).
135
164
  - Keep exactly **one** primary `[[ports]]` entry with `externalPort = 80` for the CMS server.
136
- - If `.replit` contains `[[artifacts]]` with **only** `artifacts/api-server` and Publish fails, **convert to Web app layout** or create a **new Web app** Repl and migrate — do not tell the user publish succeeded.
165
+ - Ensure `.replit-artifact/artifact.toml` has **`kind = "web"`** (shipped in cms-starter). If `kind = "api"`, change to **`web`** before Publish.
166
+ - If `.replit` contains `[[artifacts]]` with **only** `artifacts/api-server` and Publish fails, set artifact **`kind = "web"`** or create a **new Web app** Repl and migrate — do not tell the user publish succeeded.
137
167
 
138
168
  If Replit assigns a different `localPort` via `PORT`, update `localPort` to match the port in `[TomorrowOS] listening on http://0.0.0.0:<PORT>` logs, but keep `externalPort = 80`.
139
169
 
@@ -154,14 +184,15 @@ After Publish, the public URL is:
154
184
 
155
185
  **Do not** keep retrying publish unchanged. Fix in this order:
156
186
 
157
- 1. Verify homepage artifact type is **Web app**; if the Repl was created as API-only, **create a new Web app Repl** and re-run this setup (fastest fix).
158
- 2. Replace `.replit` with the template above (`entrypoint`, `modules`, `[[ports]]`, `deploymentTarget = "autoscale"`).
159
- 3. Confirm `server.ts` calls `tomorrowos.listen({ staticRoot: join(__dirname, "public"), host: "0.0.0.0", port: Number(process.env.PORT) || 3000 })`.
160
- 4. Confirm `GET /` returns Control Panel HTML (not JSON-only API root).
161
- 5. In Publishing, switch to **Autoscale** if currently Static.
162
- 6. Restart workspace `npm run start` Publish again.
187
+ 1. Open `.replit-artifact/artifact.toml` (or artifact config) set **`kind = "web"`** (never `api`). This is the most common fix.
188
+ 2. Verify homepage artifact type is **Web app**; if the Repl was created as API-only, **create a new Web app Repl** and re-run this setup (fastest fix).
189
+ 3. Replace `.replit` with the template above (`entrypoint`, `modules`, `[[ports]]`, `deploymentTarget = "autoscale"`).
190
+ 4. Confirm `server.ts` calls `tomorrowos.listen({ staticRoot: join(__dirname, "public"), host: "0.0.0.0", port: Number(process.env.PORT) || 3000 })`.
191
+ 5. Confirm `GET /` returns Control Panel HTML (not JSON-only API root).
192
+ 6. In Publishing, switch to **Autoscale** if currently Static.
193
+ 7. Restart workspace → `npm run start` → Publish again.
163
194
 
164
- Tell the user plainly if step 1 requires a new Repl — API-only projects cannot publish a browser CMS without reclassification.
195
+ Tell the user plainly if step 2 requires a new Repl — API-only projects cannot publish a browser CMS without **`kind = "web"`**.
165
196
 
166
197
  ---
167
198
 
@@ -201,7 +232,7 @@ Allowed extras (optional): `"build-player": "tomorrowos build --platform tizen"`
201
232
  "node": ">=20"
202
233
  },
203
234
  "dependencies": {
204
- "@tomorrowos/sdk": "^0.8.2",
235
+ "@tomorrowos/sdk": "^0.8.4",
205
236
  "dotenv": "^17.2.3",
206
237
  "tsx": "^4.19.0"
207
238
  },
@@ -255,7 +286,7 @@ If the workspace already has a `.replit`, **merge** these keys — do not wipe u
255
286
 
256
287
  **Only these three checks matter after setup.** Do not add more unless Publish fails.
257
288
 
258
- 1. **Web app, not API** — project / primary artifact is **Web app** (not `api-kind` / API-only).
289
+ 1. **Web app, not API** — primary artifact **`kind = "web"`** (not `api` / API-only).
259
290
  2. **`.replit` is correct** — `entrypoint = "server.ts"`, `modules = ["nodejs-20"]`, `[[ports]]` with `externalPort = 80`, `deploymentTarget = "autoscale"`, Run/Deploy = `npm run start`.
260
291
  3. **Preview at `/`** — after `npm run start`, logs show `[TomorrowOS] listening on http://0.0.0.0:...` and Preview shows the Control Panel (HTML), not empty / API JSON.
261
292
 
@@ -486,7 +517,11 @@ The SDK auto-detects these env vars and uploads media to Cloudinary. New uploads
486
517
 
487
518
  ---
488
519
 
489
- ## Question 4 — Brand / TomorrowOS app look
520
+ ## Question 4 — Brand / TomorrowOS app look (`brand.json` only)
521
+
522
+ > **Scope:** Question 4 answers **only** update **`brand.json`** — colours, name, tagline, logo path, `cms.expectedScreens`, etc. They configure how the **TomorrowOS player app** looks and metadata flags.
523
+ >
524
+ > **Question 4 does NOT affect:** Replit Publish, artifact `kind`, `.replit`, `server.ts`, Secrets, Supabase, Cloudinary, ports, deployment target, or CMS server wiring. **Never** change deploy/runtime config based on branding answers.
490
525
 
491
526
  > **This is the only branding / platform / use-case question block.** Do not ask separate LLM_PROMPT “target platform”, “use case”, or “hosting” questions before this.
492
527
 
@@ -505,7 +540,7 @@ The SDK auto-detects these env vars and uploads media to Cloudinary. New uploads
505
540
 
506
541
  If the user only gives a name and primary colour, use defaults for the rest and say what you assumed.
507
542
 
508
- **Then write / update `brand.json`** at the project root (validate mentally against `brand.schema.json` in the SDK). Minimum example:
543
+ **Then write / update `brand.json` only** at the project root (validate mentally against `brand.schema.json` in the SDK). **Do not** modify `.replit`, `server.ts`, Secrets, or deployment settings from these answers. Minimum example:
509
544
 
510
545
  ```json
511
546
  {
@@ -541,9 +576,9 @@ Set `cms.expectedScreens` from Question 1 (do not ask screen count again). Alway
541
576
 
542
577
  Run these steps in order. Tick mentally; do not skip.
543
578
 
544
- ### A. Seed the project (Web app first)
579
+ ### A. Seed the project (Web app + `kind = "web"` first)
545
580
 
546
- **If creating a new Repl:** choose **Web app** on the Replit homepage before `init`.
581
+ **If creating a new Repl:** choose **Web app** on the Replit homepage before `init`. Confirm `.replit-artifact/artifact.toml` has **`kind = "web"`** after init.
547
582
 
548
583
  ```bash
549
584
  npx @tomorrowos/sdk@latest init .
@@ -657,6 +692,8 @@ Do **not**:
657
692
  - Use Canvas preview as the primary CMS verification surface
658
693
  - Serve a different app or API-only response at `/` instead of the Control Panel
659
694
  - Create the Repl as **API-only** or publish with **Static Deployment** only
695
+ - Set or leave artifact **`kind = "api"`** for the TomorrowOS CMS
696
+ - Change `.replit`, `server.ts`, Secrets, or deployment config based on **Question 4** branding answers (those go in **`brand.json` only**)
660
697
  - Omit `[[ports]]` / `deploymentTarget` from `.replit` when setting up for Publish
661
698
  - Run **Supabase connection tests**, **WebSocket console tests**, or other post-setup smoke tests unless Preview fails or the user asks
662
699
  - **Debug or “fix” Supabase** because dev logs show `getaddrinfo ENOTFOUND` or `database connection failure` — save config and finish setup instead
@@ -685,7 +722,7 @@ TOMORROWOS_STORE=sqlite
685
722
 
686
723
  | Symptom | Likely cause | Fix |
687
724
  |---------|--------------|-----|
688
- | Publish: **api-kind artifact not deployable** | Repl created as API-only, or missing Web app + ports | New **Web app** Repl **or** fix `.replit` (`entrypoint`, `[[ports]]`, `deploymentTarget=autoscale`); serve Control Panel at `/` |
725
+ | Publish: **api-kind artifact not deployable** | CMS artifact `kind = "api"` or API-only Repl | Set **`kind = "web"`** in `.replit-artifact/artifact.toml`; new **Web app** Repl if needed; `.replit` ports + `deploymentTarget=autoscale` |
689
726
  | Preview empty / wrong app at `/` | Project type API, wrong staticRoot, or stale process | Set project to **Web app**; confirm `staticRoot: public`; stop old servers; restart workspace |
690
727
  | TV / browser: WebSocket handshake timeout | Node CMS not running or crash-loop | Fix Run → `npm run start`; check logs for `listening` |
691
728
  | Deploy/Publish fails / `tsx: not found` | `tsx` only in devDependencies | Move `tsx` to `dependencies`, reinstall, keep `start`: `tsx server.ts` |
@@ -701,11 +738,13 @@ TOMORROWOS_STORE=sqlite
701
738
 
702
739
  ## Protocol version
703
740
 
704
- `replit-setup/1.6` — aligned with TomorrowOS protocol `1.0` and `@tomorrowos/sdk` store drivers `sqlite` | `supabase` | `postgres` | `memory`.
741
+ `replit-setup/1.7` — aligned with TomorrowOS protocol `1.0` and `@tomorrowos/sdk` store drivers `sqlite` | `supabase` | `postgres` | `memory`.
742
+
743
+ **Changelog 1.7:** **IRON RULE** — artifact **`kind = "web"` only** (never `api`); Question 4 **only** writes `brand.json` (no deploy/server changes).
705
744
 
706
- **Changelog 1.6:** **Never test Supabase** in Replit dev — `ENOTFOUND` / connection errors in Preview logs are expected; setup completes when Control Panel loads.
745
+ **Changelog 1.6:** **Never test Supabase** in Replit dev — `ENOTFOUND` in Preview logs is expected.
707
746
 
708
- **Changelog 1.5:** Cloudinary Secrets collected **inside Question 3** — no separate “3b” step; show Secrets form immediately when user picks Cloudinary.
747
+ **Changelog 1.5:** Cloudinary Secrets inside Question 3 — no “3b” step.
709
748
 
710
749
  **Changelog 1.4:** mandatory Cloudinary credential collection before Question 4 (superseded by 1.5 flow).
711
750
 
@@ -29,7 +29,7 @@ export declare class MemoryStore implements TomorrowOSStore {
29
29
  setPlaylist(record: StoredPlaylist): Promise<void>;
30
30
  isPlaylistNameTaken(name: string, excludeId?: string): Promise<boolean>;
31
31
  getUploadedAsset(id: string): Promise<UploadedAssetRecord | undefined>;
32
- getUploadedAssetBySha256(sha256: string, storageProvider?: UploadedAssetRecord["storageProvider"]): Promise<UploadedAssetRecord | undefined>;
32
+ getUploadedAssetBySha256(sha256: string): Promise<UploadedAssetRecord | undefined>;
33
33
  setUploadedAsset(record: UploadedAssetRecord): Promise<void>;
34
34
  deleteUploadedAsset(id: string): Promise<void>;
35
35
  getDeviceAssignments(deviceId: string): Promise<DevicePlaylistAssignment[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../src/store/memory-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,qBAAa,WAAY,YAAW,eAAe;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwC;IACrE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2C;IAC1E,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyC;IACvE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqC;IAC/D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA0C;IACzE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAiD;IAE7E,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAIpE,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,iBAAiB,CACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;IAItC,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;IASV,uBAAuB,CAC3B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,oBAAoB,CAAA;KAAE,GAAG,SAAS,CAAC;IAQpE,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,IAAI,CAAC;IAIV,eAAe,CACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAIpC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD,iBAAiB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAOjD,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAI1C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAI5D,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUvE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAItE,wBAAwB,CAC5B,MAAM,EAAE,MAAM,EACd,eAAe,CAAC,EAAE,mBAAmB,CAAC,iBAAiB,CAAC,GACvD,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAYrC,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,oBAAoB,CACxB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAIhC,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,wBAAwB,EAAE,GACtC,OAAO,CAAC,IAAI,CAAC;CASjB"}
1
+ {"version":3,"file":"memory-store.d.ts","sourceRoot":"","sources":["../../src/store/memory-store.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,wBAAwB,EACxB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,iBAAiB,EACjB,cAAc,EACd,eAAe,EACf,mBAAmB,EACpB,MAAM,YAAY,CAAC;AAEpB;;;GAGG;AACH,qBAAa,WAAY,YAAW,eAAe;IACjD,OAAO,CAAC,QAAQ,CAAC,YAAY,CAAwC;IACrE,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA2C;IAC1E,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA6B;IAC5D,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAyC;IACvE,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAqC;IAC/D,OAAO,CAAC,QAAQ,CAAC,cAAc,CAA0C;IACzE,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAiD;IAE7E,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAItE,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAIpE,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,iBAAiB,CACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;IAItC,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;IASV,uBAAuB,CAC3B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,oBAAoB,CAAA;KAAE,GAAG,SAAS,CAAC;IAQpE,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,IAAI,CAAC;IAIV,eAAe,CACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IAIpC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD,iBAAiB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAOjD,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAI1C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAI5D,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IAIlD,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAUvE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAItE,wBAAwB,CAC5B,MAAM,EAAE,MAAM,GACb,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IASrC,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAI5D,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,oBAAoB,CACxB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAIhC,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,wBAAwB,EAAE,GACtC,OAAO,CAAC,IAAI,CAAC;CASjB"}
@@ -78,10 +78,9 @@ export class MemoryStore {
78
78
  async getUploadedAsset(id) {
79
79
  return this.uploadedAssets.get(id);
80
80
  }
81
- async getUploadedAssetBySha256(sha256, storageProvider) {
81
+ async getUploadedAssetBySha256(sha256) {
82
82
  for (const asset of this.uploadedAssets.values()) {
83
- if (asset.sha256 === sha256 &&
84
- (!storageProvider || asset.storageProvider === storageProvider)) {
83
+ if (asset.sha256 === sha256) {
85
84
  return asset;
86
85
  }
87
86
  }
@@ -1,5 +1,5 @@
1
1
  import { type PoolConfig } from "pg";
2
- import type { DevicePlaylistAssignment, DeviceRegistryEntry, DeviceRegistryRecord, PairedDeviceEntry, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, StoredPlaylist, TomorrowOSMigratableStore, UploadedAssetRecord, UploadedAssetStorageProvider } from "./types.js";
2
+ import type { DevicePlaylistAssignment, DeviceRegistryEntry, DeviceRegistryRecord, PairedDeviceEntry, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, StoredPlaylist, TomorrowOSMigratableStore, UploadedAssetRecord } from "./types.js";
3
3
  export interface PostgresStoreOptions {
4
4
  connectionString: string;
5
5
  ssl?: PoolConfig["ssl"];
@@ -13,6 +13,7 @@ export declare class PostgresStore implements TomorrowOSMigratableStore {
13
13
  private ready?;
14
14
  constructor(options: PostgresStoreOptions | string);
15
15
  init(): Promise<void>;
16
+ private migrateDropUploadedAssetStorageProvider;
16
17
  private migrateDropPlaylistVersionColumns;
17
18
  close(): Promise<void>;
18
19
  private ensureReady;
@@ -36,7 +37,7 @@ export declare class PostgresStore implements TomorrowOSMigratableStore {
36
37
  setPlaylist(record: StoredPlaylist): Promise<void>;
37
38
  isPlaylistNameTaken(name: string, excludeId?: string): Promise<boolean>;
38
39
  getUploadedAsset(id: string): Promise<UploadedAssetRecord | undefined>;
39
- getUploadedAssetBySha256(sha256: string, storageProvider?: UploadedAssetStorageProvider): Promise<UploadedAssetRecord | undefined>;
40
+ getUploadedAssetBySha256(sha256: string): Promise<UploadedAssetRecord | undefined>;
40
41
  setUploadedAsset(record: UploadedAssetRecord): Promise<void>;
41
42
  deleteUploadedAsset(id: string): Promise<void>;
42
43
  listUploadedAssets(): Promise<UploadedAssetRecord[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../src/store/postgres-store.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"postgres-store.d.ts","sourceRoot":"","sources":["../../src/store/postgres-store.ts"],"names":[],"mappings":"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"}
@@ -95,8 +95,7 @@ export class PostgresStore {
95
95
 
96
96
  CREATE TABLE IF NOT EXISTS uploaded_assets (
97
97
  id TEXT PRIMARY KEY,
98
- sha256 TEXT NOT NULL,
99
- storage_provider TEXT NOT NULL,
98
+ sha256 TEXT NOT NULL UNIQUE,
100
99
  storage_key TEXT NOT NULL,
101
100
  url TEXT NOT NULL,
102
101
  original_filename TEXT,
@@ -104,8 +103,7 @@ export class PostgresStore {
104
103
  resource_type TEXT,
105
104
  bytes BIGINT,
106
105
  created_at TEXT NOT NULL,
107
- updated_at TEXT NOT NULL,
108
- UNIQUE (storage_provider, sha256)
106
+ updated_at TEXT NOT NULL
109
107
  );
110
108
 
111
109
  CREATE INDEX IF NOT EXISTS uploaded_assets_sha256_idx
@@ -120,8 +118,79 @@ export class PostgresStore {
120
118
 
121
119
  ALTER TABLE paired_devices
122
120
  ADD COLUMN IF NOT EXISTS system_version TEXT;
121
+
122
+ ALTER TABLE paired_devices
123
+ ADD COLUMN IF NOT EXISTS last_screenshot_asset_id TEXT;
124
+
125
+ ALTER TABLE paired_devices
126
+ ADD COLUMN IF NOT EXISTS last_screenshot_captured_at TEXT;
123
127
  `);
124
128
  await this.migrateDropPlaylistVersionColumns();
129
+ await this.migrateDropUploadedAssetStorageProvider();
130
+ }
131
+ async migrateDropUploadedAssetStorageProvider() {
132
+ const applied = await this.pool.query(`
133
+ SELECT 1
134
+ FROM schema_migrations
135
+ WHERE name = 'drop_uploaded_asset_storage_provider'
136
+ LIMIT 1
137
+ `);
138
+ if (applied.rows.length > 0)
139
+ return;
140
+ const columnExists = await this.pool.query(`
141
+ SELECT EXISTS (
142
+ SELECT 1
143
+ FROM information_schema.columns
144
+ WHERE table_name = 'uploaded_assets'
145
+ AND column_name = 'storage_provider'
146
+ ) AS exists
147
+ `);
148
+ if (!columnExists.rows[0]?.exists) {
149
+ await this.pool.query(`
150
+ INSERT INTO schema_migrations (id, name)
151
+ VALUES (2, 'drop_uploaded_asset_storage_provider')
152
+ ON CONFLICT (id) DO NOTHING
153
+ `);
154
+ return;
155
+ }
156
+ await this.pool.query(`
157
+ DELETE FROM uploaded_assets a
158
+ USING uploaded_assets b
159
+ WHERE a.sha256 = b.sha256
160
+ AND (
161
+ CASE WHEN a.url LIKE 'http%' THEN 0 ELSE 1 END,
162
+ a.updated_at
163
+ ) > (
164
+ CASE WHEN b.url LIKE 'http%' THEN 0 ELSE 1 END,
165
+ b.updated_at
166
+ )
167
+ `);
168
+ await this.pool.query(`
169
+ ALTER TABLE uploaded_assets
170
+ DROP CONSTRAINT IF EXISTS uploaded_assets_storage_provider_sha256_key
171
+ `);
172
+ await this.pool.query(`
173
+ ALTER TABLE uploaded_assets
174
+ DROP COLUMN IF EXISTS storage_provider
175
+ `);
176
+ await this.pool.query(`
177
+ DO $$
178
+ BEGIN
179
+ IF NOT EXISTS (
180
+ SELECT 1
181
+ FROM pg_constraint
182
+ WHERE conname = 'uploaded_assets_sha256_key'
183
+ ) THEN
184
+ ALTER TABLE uploaded_assets
185
+ ADD CONSTRAINT uploaded_assets_sha256_key UNIQUE (sha256);
186
+ END IF;
187
+ END $$;
188
+ `);
189
+ await this.pool.query(`
190
+ INSERT INTO schema_migrations (id, name)
191
+ VALUES (2, 'drop_uploaded_asset_storage_provider')
192
+ ON CONFLICT (id) DO NOTHING
193
+ `);
125
194
  }
126
195
  async migrateDropPlaylistVersionColumns() {
127
196
  const applied = await this.pool.query(`
@@ -286,9 +355,11 @@ export class PostgresStore {
286
355
  last_boot_at,
287
356
  last_online_at,
288
357
  last_offline_at,
289
- last_policy_push_at
358
+ last_policy_push_at,
359
+ last_screenshot_asset_id,
360
+ last_screenshot_captured_at
290
361
  )
291
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12)
362
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)
292
363
  ON CONFLICT (device_id) DO UPDATE SET
293
364
  pairing_token = EXCLUDED.pairing_token,
294
365
  paired_at = EXCLUDED.paired_at,
@@ -300,7 +371,9 @@ export class PostgresStore {
300
371
  last_boot_at = EXCLUDED.last_boot_at,
301
372
  last_online_at = EXCLUDED.last_online_at,
302
373
  last_offline_at = EXCLUDED.last_offline_at,
303
- last_policy_push_at = EXCLUDED.last_policy_push_at
374
+ last_policy_push_at = EXCLUDED.last_policy_push_at,
375
+ last_screenshot_asset_id = EXCLUDED.last_screenshot_asset_id,
376
+ last_screenshot_captured_at = EXCLUDED.last_screenshot_captured_at
304
377
  `, [
305
378
  deviceId,
306
379
  record.pairingToken,
@@ -313,7 +386,9 @@ export class PostgresStore {
313
386
  record.lastBootAt ?? null,
314
387
  record.lastOnlineAt ?? null,
315
388
  record.lastOfflineAt ?? null,
316
- record.lastPolicyPushAt ?? null
389
+ record.lastPolicyPushAt ?? null,
390
+ record.lastScreenshotAssetId ?? null,
391
+ record.lastScreenshotCapturedAt ?? null
317
392
  ]);
318
393
  }
319
394
  async getPairedDevice(deviceId) {
@@ -418,15 +493,14 @@ export class PostgresStore {
418
493
  const row = result.rows[0];
419
494
  return row ? this.mapUploadedAssetRow(row) : undefined;
420
495
  }
421
- async getUploadedAssetBySha256(sha256, storageProvider) {
496
+ async getUploadedAssetBySha256(sha256) {
422
497
  await this.ensureReady();
423
498
  const result = await this.pool.query(`
424
499
  SELECT *
425
500
  FROM uploaded_assets
426
501
  WHERE sha256 = $1
427
- AND ($2::text IS NULL OR storage_provider = $2)
428
502
  LIMIT 1
429
- `, [sha256, storageProvider ?? null]);
503
+ `, [sha256]);
430
504
  const row = result.rows[0];
431
505
  return row ? this.mapUploadedAssetRow(row) : undefined;
432
506
  }
@@ -436,7 +510,6 @@ export class PostgresStore {
436
510
  INSERT INTO uploaded_assets (
437
511
  id,
438
512
  sha256,
439
- storage_provider,
440
513
  storage_key,
441
514
  url,
442
515
  original_filename,
@@ -446,10 +519,9 @@ export class PostgresStore {
446
519
  created_at,
447
520
  updated_at
448
521
  )
449
- VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11)
522
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)
450
523
  ON CONFLICT (id) DO UPDATE SET
451
524
  sha256 = EXCLUDED.sha256,
452
- storage_provider = EXCLUDED.storage_provider,
453
525
  storage_key = EXCLUDED.storage_key,
454
526
  url = EXCLUDED.url,
455
527
  original_filename = EXCLUDED.original_filename,
@@ -460,7 +532,6 @@ export class PostgresStore {
460
532
  `, [
461
533
  record.id,
462
534
  record.sha256,
463
- record.storageProvider,
464
535
  record.storageKey,
465
536
  record.url,
466
537
  record.originalFilename ?? null,
@@ -559,7 +630,9 @@ export class PostgresStore {
559
630
  lastBootAt: optionalString(row.last_boot_at),
560
631
  lastOnlineAt: optionalString(row.last_online_at),
561
632
  lastOfflineAt: optionalString(row.last_offline_at),
562
- lastPolicyPushAt: optionalString(row.last_policy_push_at)
633
+ lastPolicyPushAt: optionalString(row.last_policy_push_at),
634
+ lastScreenshotAssetId: optionalString(row.last_screenshot_asset_id),
635
+ lastScreenshotCapturedAt: optionalString(row.last_screenshot_captured_at)
563
636
  };
564
637
  }
565
638
  mapPlaylistRow(row) {
@@ -577,7 +650,6 @@ export class PostgresStore {
577
650
  return {
578
651
  id: row.id,
579
652
  sha256: row.sha256,
580
- storageProvider: row.storage_provider,
581
653
  storageKey: row.storage_key,
582
654
  url: row.url,
583
655
  originalFilename: optionalString(row.original_filename),
@@ -1,4 +1,4 @@
1
- import type { DevicePlaylistAssignment, DeviceRegistryEntry, DeviceRegistryRecord, PairedDeviceEntry, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, StoredPlaylist, TomorrowOSMigratableStore, UploadedAssetRecord, UploadedAssetStorageProvider } from "./types.js";
1
+ import type { DevicePlaylistAssignment, DeviceRegistryEntry, DeviceRegistryRecord, PairedDeviceEntry, PairedDeviceRecord, PendingCodeEntry, PendingCodeRecord, StoredPlaylist, TomorrowOSMigratableStore, UploadedAssetRecord } from "./types.js";
2
2
  interface SQLiteStoreOptions {
3
3
  databasePath?: string;
4
4
  }
@@ -11,6 +11,7 @@ export declare class SQLiteStore implements TomorrowOSMigratableStore {
11
11
  private readonly db;
12
12
  constructor(options?: SQLiteStoreOptions | string);
13
13
  init(): void;
14
+ private migrateDropUploadedAssetStorageProvider;
14
15
  private tableHasColumn;
15
16
  private migrateDropPlaylistVersionColumns;
16
17
  private addColumnIfMissing;
@@ -35,7 +36,7 @@ export declare class SQLiteStore implements TomorrowOSMigratableStore {
35
36
  setPlaylist(record: StoredPlaylist): Promise<void>;
36
37
  isPlaylistNameTaken(name: string, excludeId?: string): Promise<boolean>;
37
38
  getUploadedAsset(id: string): Promise<UploadedAssetRecord | undefined>;
38
- getUploadedAssetBySha256(sha256: string, storageProvider?: UploadedAssetStorageProvider): Promise<UploadedAssetRecord | undefined>;
39
+ getUploadedAssetBySha256(sha256: string): Promise<UploadedAssetRecord | undefined>;
39
40
  setUploadedAsset(record: UploadedAssetRecord): Promise<void>;
40
41
  deleteUploadedAsset(id: string): Promise<void>;
41
42
  listUploadedAssets(): Promise<UploadedAssetRecord[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../src/store/sqlite-store.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EACV,wBAAwB,EACxB,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,EAClB,gBAAgB,EAChB,iBAAiB,EAGjB,cAAc,EACd,yBAAyB,EACzB,mBAAmB,EACnB,4BAA4B,EAC7B,MAAM,YAAY,CAAC;AAGpB,UAAU,kBAAkB;IAC1B,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AA6ED;;;GAGG;AACH,qBAAa,WAAY,YAAW,yBAAyB;IAC3D,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAC;IAC9B,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAoB;gBAE3B,OAAO,GAAE,kBAAkB,GAAG,MAAW;IAerD,IAAI,IAAI,IAAI;IA6FZ,OAAO,CAAC,cAAc;IAKtB,OAAO,CAAC,iCAAiC;IA4GzC,OAAO,CAAC,kBAAkB;IAS1B,KAAK,IAAI,IAAI;IAIP,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,iBAAiB,GAAG,OAAO,CAAC,IAAI,CAAC;IAUtE,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,GAAG,SAAS,CAAC;IAUpE,iBAAiB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,gBAAgB,IAAI,OAAO,CAAC,gBAAgB,EAAE,CAAC;IAY/C,iBAAiB,CACrB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,oBAAoB,GAAG,SAAS,CAAC;IAStC,iBAAiB,CACrB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,oBAAoB,GAC3B,OAAO,CAAC,IAAI,CAAC;IA2BV,uBAAuB,CAC3B,IAAI,EAAE,MAAM,GACX,OAAO,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,oBAAoB,CAAA;KAAE,GAAG,SAAS,CAAC;IAapE,kBAAkB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IAYpD,eAAe,CACnB,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,IAAI,CAAC;IA6CV,eAAe,CACnB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,kBAAkB,GAAG,SAAS,CAAC;IASpC,kBAAkB,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAInD,iBAAiB,IAAI,OAAO,CAAC,iBAAiB,EAAE,CAAC;IAYjD,aAAa,IAAI,OAAO,CAAC,cAAc,EAAE,CAAC;IAS1C,WAAW,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,GAAG,SAAS,CAAC;IAS5D,WAAW,CAAC,MAAM,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC;IA8BlD,mBAAmB,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAcvE,gBAAgB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAStE,wBAAwB,CAC5B,MAAM,EAAE,MAAM,EACd,eAAe,CAAC,EAAE,4BAA4B,GAC7C,OAAO,CAAC,mBAAmB,GAAG,SAAS,CAAC;IAerC,gBAAgB,CAAC,MAAM,EAAE,mBAAmB,GAAG,OAAO,CAAC,IAAI,CAAC;IAyC5D,mBAAmB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;IAI9C,kBAAkB,IAAI,OAAO,CAAC,mBAAmB,EAAE,CAAC;IASpD,oBAAoB,CACxB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,EAAE,CAAC;IAuBhC,oBAAoB,CACxB,QAAQ,EAAE,MAAM,EAChB,WAAW,EAAE,wBAAwB,EAAE,GACtC,OAAO,CAAC,IAAI,CAAC;IA4BhB,OAAO,CAAC,oBAAoB;IAU5B,OAAO,CAAC,kBAAkB;IAgB1B,OAAO,CAAC,cAAc;IAYtB,OAAO,CAAC,mBAAmB;CAe5B"}
1
+ {"version":3,"file":"sqlite-store.d.ts","sourceRoot":"","sources":["../../src/store/sqlite-store.ts"],"names":[],"mappings":"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"}
@@ -101,8 +101,7 @@ export class SQLiteStore {
101
101
 
102
102
  CREATE TABLE IF NOT EXISTS uploaded_assets (
103
103
  id TEXT PRIMARY KEY,
104
- sha256 TEXT NOT NULL,
105
- storage_provider TEXT NOT NULL,
104
+ sha256 TEXT NOT NULL UNIQUE,
106
105
  storage_key TEXT NOT NULL,
107
106
  url TEXT NOT NULL,
108
107
  original_filename TEXT,
@@ -110,8 +109,7 @@ export class SQLiteStore {
110
109
  resource_type TEXT,
111
110
  bytes INTEGER,
112
111
  created_at TEXT NOT NULL,
113
- updated_at TEXT NOT NULL,
114
- UNIQUE (storage_provider, sha256)
112
+ updated_at TEXT NOT NULL
115
113
  );
116
114
 
117
115
  CREATE INDEX IF NOT EXISTS uploaded_assets_sha256_idx
@@ -122,7 +120,94 @@ export class SQLiteStore {
122
120
  `);
123
121
  this.addColumnIfMissing("paired_devices", "player_version TEXT");
124
122
  this.addColumnIfMissing("paired_devices", "system_version TEXT");
123
+ this.addColumnIfMissing("paired_devices", "last_screenshot_asset_id TEXT");
124
+ this.addColumnIfMissing("paired_devices", "last_screenshot_captured_at TEXT");
125
125
  this.migrateDropPlaylistVersionColumns();
126
+ this.migrateDropUploadedAssetStorageProvider();
127
+ }
128
+ migrateDropUploadedAssetStorageProvider() {
129
+ const alreadyApplied = this.db.prepare(`
130
+ SELECT 1
131
+ FROM schema_migrations
132
+ WHERE name = 'drop_uploaded_asset_storage_provider'
133
+ LIMIT 1
134
+ `).get();
135
+ if (alreadyApplied)
136
+ return;
137
+ if (!this.tableHasColumn("uploaded_assets", "storage_provider")) {
138
+ this.db.prepare(`
139
+ INSERT OR IGNORE INTO schema_migrations (id, name)
140
+ VALUES (2, 'drop_uploaded_asset_storage_provider')
141
+ `).run();
142
+ return;
143
+ }
144
+ this.db.exec(`
145
+ CREATE TABLE uploaded_assets_new (
146
+ id TEXT PRIMARY KEY,
147
+ sha256 TEXT NOT NULL UNIQUE,
148
+ storage_key TEXT NOT NULL,
149
+ url TEXT NOT NULL,
150
+ original_filename TEXT,
151
+ mime_type TEXT,
152
+ resource_type TEXT,
153
+ bytes INTEGER,
154
+ created_at TEXT NOT NULL,
155
+ updated_at TEXT NOT NULL
156
+ );
157
+
158
+ INSERT INTO uploaded_assets_new (
159
+ id,
160
+ sha256,
161
+ storage_key,
162
+ url,
163
+ original_filename,
164
+ mime_type,
165
+ resource_type,
166
+ bytes,
167
+ created_at,
168
+ updated_at
169
+ )
170
+ SELECT
171
+ id,
172
+ sha256,
173
+ storage_key,
174
+ url,
175
+ original_filename,
176
+ mime_type,
177
+ resource_type,
178
+ bytes,
179
+ created_at,
180
+ updated_at
181
+ FROM (
182
+ SELECT
183
+ id,
184
+ sha256,
185
+ storage_key,
186
+ url,
187
+ original_filename,
188
+ mime_type,
189
+ resource_type,
190
+ bytes,
191
+ created_at,
192
+ updated_at,
193
+ ROW_NUMBER() OVER (
194
+ PARTITION BY sha256
195
+ ORDER BY CASE WHEN url LIKE 'http%' THEN 0 ELSE 1 END, updated_at DESC
196
+ ) AS rn
197
+ FROM uploaded_assets
198
+ )
199
+ WHERE rn = 1;
200
+
201
+ DROP TABLE uploaded_assets;
202
+ ALTER TABLE uploaded_assets_new RENAME TO uploaded_assets;
203
+
204
+ CREATE INDEX IF NOT EXISTS uploaded_assets_sha256_idx
205
+ ON uploaded_assets (sha256);
206
+ `);
207
+ this.db.prepare(`
208
+ INSERT OR IGNORE INTO schema_migrations (id, name)
209
+ VALUES (2, 'drop_uploaded_asset_storage_provider')
210
+ `).run();
126
211
  }
127
212
  tableHasColumn(table, column) {
128
213
  const rows = this.db.pragma(`table_info(${table})`);
@@ -323,9 +408,11 @@ export class SQLiteStore {
323
408
  last_boot_at,
324
409
  last_online_at,
325
410
  last_offline_at,
326
- last_policy_push_at
411
+ last_policy_push_at,
412
+ last_screenshot_asset_id,
413
+ last_screenshot_captured_at
327
414
  )
328
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
415
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
329
416
  ON CONFLICT(device_id) DO UPDATE SET
330
417
  pairing_token = excluded.pairing_token,
331
418
  paired_at = excluded.paired_at,
@@ -337,8 +424,10 @@ export class SQLiteStore {
337
424
  last_boot_at = excluded.last_boot_at,
338
425
  last_online_at = excluded.last_online_at,
339
426
  last_offline_at = excluded.last_offline_at,
340
- last_policy_push_at = excluded.last_policy_push_at
341
- `).run(deviceId, record.pairingToken, record.pairedAt, record.deviceName ?? null, record.platform ?? null, record.system ?? null, record.playerVersion ?? null, record.systemVersion ?? null, record.lastBootAt ?? null, record.lastOnlineAt ?? null, record.lastOfflineAt ?? null, record.lastPolicyPushAt ?? null);
427
+ last_policy_push_at = excluded.last_policy_push_at,
428
+ last_screenshot_asset_id = excluded.last_screenshot_asset_id,
429
+ last_screenshot_captured_at = excluded.last_screenshot_captured_at
430
+ `).run(deviceId, record.pairingToken, record.pairedAt, record.deviceName ?? null, record.platform ?? null, record.system ?? null, record.playerVersion ?? null, record.systemVersion ?? null, record.lastBootAt ?? null, record.lastOnlineAt ?? null, record.lastOfflineAt ?? null, record.lastPolicyPushAt ?? null, record.lastScreenshotAssetId ?? null, record.lastScreenshotCapturedAt ?? null);
342
431
  }
343
432
  async getPairedDevice(deviceId) {
344
433
  const row = this.db.prepare(`
@@ -421,14 +510,13 @@ export class SQLiteStore {
421
510
  `).get(id);
422
511
  return row ? this.mapUploadedAssetRow(row) : undefined;
423
512
  }
424
- async getUploadedAssetBySha256(sha256, storageProvider) {
513
+ async getUploadedAssetBySha256(sha256) {
425
514
  const row = this.db.prepare(`
426
515
  SELECT *
427
516
  FROM uploaded_assets
428
517
  WHERE sha256 = ?
429
- AND (? IS NULL OR storage_provider = ?)
430
518
  LIMIT 1
431
- `).get(sha256, storageProvider ?? null, storageProvider ?? null);
519
+ `).get(sha256);
432
520
  return row ? this.mapUploadedAssetRow(row) : undefined;
433
521
  }
434
522
  async setUploadedAsset(record) {
@@ -436,7 +524,6 @@ export class SQLiteStore {
436
524
  INSERT INTO uploaded_assets (
437
525
  id,
438
526
  sha256,
439
- storage_provider,
440
527
  storage_key,
441
528
  url,
442
529
  original_filename,
@@ -446,10 +533,9 @@ export class SQLiteStore {
446
533
  created_at,
447
534
  updated_at
448
535
  )
449
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
536
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
450
537
  ON CONFLICT(id) DO UPDATE SET
451
538
  sha256 = excluded.sha256,
452
- storage_provider = excluded.storage_provider,
453
539
  storage_key = excluded.storage_key,
454
540
  url = excluded.url,
455
541
  original_filename = excluded.original_filename,
@@ -457,7 +543,7 @@ export class SQLiteStore {
457
543
  resource_type = excluded.resource_type,
458
544
  bytes = excluded.bytes,
459
545
  updated_at = excluded.updated_at
460
- `).run(record.id, record.sha256, record.storageProvider, record.storageKey, record.url, record.originalFilename ?? null, record.mimeType ?? null, record.resourceType ?? null, record.bytes ?? null, record.createdAt, record.updatedAt);
546
+ `).run(record.id, record.sha256, record.storageKey, record.url, record.originalFilename ?? null, record.mimeType ?? null, record.resourceType ?? null, record.bytes ?? null, record.createdAt, record.updatedAt);
461
547
  }
462
548
  async deleteUploadedAsset(id) {
463
549
  this.db.prepare("DELETE FROM uploaded_assets WHERE id = ?").run(id);
@@ -527,7 +613,9 @@ export class SQLiteStore {
527
613
  lastBootAt: optionalString(row.last_boot_at),
528
614
  lastOnlineAt: optionalString(row.last_online_at),
529
615
  lastOfflineAt: optionalString(row.last_offline_at),
530
- lastPolicyPushAt: optionalString(row.last_policy_push_at)
616
+ lastPolicyPushAt: optionalString(row.last_policy_push_at),
617
+ lastScreenshotAssetId: optionalString(row.last_screenshot_asset_id),
618
+ lastScreenshotCapturedAt: optionalString(row.last_screenshot_captured_at)
531
619
  };
532
620
  }
533
621
  mapPlaylistRow(row) {
@@ -545,7 +633,6 @@ export class SQLiteStore {
545
633
  return {
546
634
  id: row.id,
547
635
  sha256: row.sha256,
548
- storageProvider: row.storage_provider,
549
636
  storageKey: row.storage_key,
550
637
  url: row.url,
551
638
  originalFilename: optionalString(row.original_filename),
@@ -26,6 +26,9 @@ export interface PairedDeviceRecord {
26
26
  lastOnlineAt?: string;
27
27
  lastOfflineAt?: string;
28
28
  lastPolicyPushAt?: string;
29
+ /** Latest device screenshot stored via uploaded_assets (local or Cloudinary). */
30
+ lastScreenshotAssetId?: string;
31
+ lastScreenshotCapturedAt?: string;
29
32
  }
30
33
  export interface PairedDeviceEntry {
31
34
  deviceId: string;
@@ -45,11 +48,9 @@ export interface PlaylistItemRecord {
45
48
  type?: string;
46
49
  durationMs?: number;
47
50
  }
48
- export type UploadedAssetStorageProvider = "local" | "cloudinary";
49
51
  export interface UploadedAssetRecord {
50
52
  id: string;
51
53
  sha256: string;
52
- storageProvider: UploadedAssetStorageProvider;
53
54
  storageKey: string;
54
55
  url: string;
55
56
  originalFilename?: string;
@@ -113,7 +114,7 @@ export interface TomorrowOSStore {
113
114
  setPlaylist(record: StoredPlaylist): Promise<void>;
114
115
  isPlaylistNameTaken(name: string, excludeId?: string): Promise<boolean>;
115
116
  getUploadedAsset(id: string): Promise<UploadedAssetRecord | undefined>;
116
- getUploadedAssetBySha256(sha256: string, storageProvider?: UploadedAssetStorageProvider): Promise<UploadedAssetRecord | undefined>;
117
+ getUploadedAssetBySha256(sha256: string): Promise<UploadedAssetRecord | undefined>;
117
118
  setUploadedAsset(record: UploadedAssetRecord): Promise<void>;
118
119
  deleteUploadedAsset(id: string): Promise<void>;
119
120
  getDeviceAssignments(deviceId: string): Promise<DevicePlaylistAssignment[]>;
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/store/types.ts"],"names":[],"mappings":"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"}
@@ -78,6 +78,7 @@ export interface DeviceScreenshotInfo {
78
78
  url: string;
79
79
  capturedAt: string;
80
80
  mimeType: string;
81
+ assetId?: string;
81
82
  width?: number;
82
83
  height?: number;
83
84
  }
@@ -150,12 +151,13 @@ export declare class TomorrowOS extends EventEmitter {
150
151
  private getAssetIdsFromItems;
151
152
  private isUploadedAssetReferenced;
152
153
  private releaseRemovedPlaylistAssets;
154
+ private isUploadedAssetReferencedAsDeviceScreenshot;
153
155
  private deleteUploadedAssetIfUnreferenced;
154
156
  private refreshResolvedBrandLogo;
155
157
  private tryServeStatic;
156
- private getScreenshotsDir;
157
- private screenshotBaseName;
158
+ private getLegacyScreenshotsDir;
158
159
  private screenshotExtension;
160
+ private getLegacyDeviceScreenshot;
159
161
  private saveDeviceScreenshot;
160
162
  private getLatestDeviceScreenshot;
161
163
  private captureDeviceScreenshot;
@@ -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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"}
@@ -44,6 +44,9 @@ function normalizeDeviceSystemLabel(platform, system) {
44
44
  const label = typeof system === "string" ? system.trim() : "";
45
45
  return label || undefined;
46
46
  }
47
+ function isRemoteUploadedAssetUrl(url) {
48
+ return /^https?:\/\//i.test(String(url || "").trim());
49
+ }
47
50
  function formatDurationMs(ms) {
48
51
  if (!Number.isFinite(ms) || ms < 0)
49
52
  return "0s";
@@ -286,8 +289,13 @@ export class TomorrowOS extends EventEmitter {
286
289
  err.code = "PAIRING_INVALID";
287
290
  throw err;
288
291
  }
292
+ const paired = await this.store.getPairedDevice(id);
293
+ const screenshotAssetId = paired?.lastScreenshotAssetId;
289
294
  await this.store.deletePairedDevice(id);
290
295
  this.pendingDeviceMeta.delete(id);
296
+ if (screenshotAssetId) {
297
+ await this.deleteUploadedAssetIfUnreferenced(screenshotAssetId);
298
+ }
291
299
  const ws = this.devices.get(id);
292
300
  let notified = false;
293
301
  if (ws && ws.readyState === WebSocket.OPEN) {
@@ -625,9 +633,7 @@ export class TomorrowOS extends EventEmitter {
625
633
  }
626
634
  this.staticIndexFile = idx;
627
635
  const uploadsDir = path.join(path.resolve(this.staticRoot), "uploads");
628
- const screenshotsDir = path.join(path.resolve(this.staticRoot), "screenshots");
629
636
  void fs.mkdir(uploadsDir, { recursive: true });
630
- void fs.mkdir(screenshotsDir, { recursive: true });
631
637
  void this.refreshResolvedBrandLogo();
632
638
  }
633
639
  else {
@@ -685,10 +691,9 @@ export class TomorrowOS extends EventEmitter {
685
691
  status: "success",
686
692
  url: asset.url,
687
693
  assetId: asset.id,
688
- filename: asset.storageProvider === "local" ? asset.storageKey : safeName,
694
+ filename: isRemoteUploadedAssetUrl(asset.url) ? safeName : asset.storageKey,
689
695
  size: asset.bytes ?? body.length,
690
696
  contentHash: asset.sha256,
691
- storageProvider: asset.storageProvider,
692
697
  deduplicated
693
698
  };
694
699
  if (durationMs != null)
@@ -703,12 +708,9 @@ export class TomorrowOS extends EventEmitter {
703
708
  async storeUploadedMediaAsset(body, safeName, mimeType) {
704
709
  const sha256 = contentHashHex(body);
705
710
  const cloudinaryConfig = resolveCloudinaryConfig();
706
- const storageProvider = cloudinaryConfig
707
- ? "cloudinary"
708
- : "local";
709
- const existing = await this.store.getUploadedAssetBySha256(sha256, storageProvider);
711
+ const existing = await this.store.getUploadedAssetBySha256(sha256);
710
712
  if (existing) {
711
- if (existing.storageProvider === "cloudinary") {
713
+ if (isRemoteUploadedAssetUrl(existing.url)) {
712
714
  return { asset: existing, deduplicated: true };
713
715
  }
714
716
  const existingLocalPath = path.join(path.resolve(this.staticRoot || "."), "uploads", existing.storageKey);
@@ -733,7 +735,6 @@ export class TomorrowOS extends EventEmitter {
733
735
  const asset = {
734
736
  id: randomUUID(),
735
737
  sha256,
736
- storageProvider: "cloudinary",
737
738
  storageKey: uploaded.publicId,
738
739
  url: uploaded.secureUrl,
739
740
  originalFilename: safeName,
@@ -752,7 +753,6 @@ export class TomorrowOS extends EventEmitter {
752
753
  const asset = {
753
754
  id: existing?.id ?? randomUUID(),
754
755
  sha256,
755
- storageProvider: "local",
756
756
  storageKey: storedName,
757
757
  url: `/uploads/${storedName}`,
758
758
  originalFilename: safeName,
@@ -790,14 +790,20 @@ export class TomorrowOS extends EventEmitter {
790
790
  await this.deleteUploadedAssetIfUnreferenced(assetId);
791
791
  }
792
792
  }
793
+ async isUploadedAssetReferencedAsDeviceScreenshot(assetId) {
794
+ const devices = await this.store.listPairedDevices();
795
+ return devices.some((entry) => entry.record.lastScreenshotAssetId === assetId);
796
+ }
793
797
  async deleteUploadedAssetIfUnreferenced(assetId) {
794
798
  try {
795
799
  if (await this.isUploadedAssetReferenced(assetId))
796
800
  return;
801
+ if (await this.isUploadedAssetReferencedAsDeviceScreenshot(assetId))
802
+ return;
797
803
  const asset = await this.store.getUploadedAsset(assetId);
798
804
  if (!asset)
799
805
  return;
800
- if (asset.storageProvider === "cloudinary") {
806
+ if (isRemoteUploadedAssetUrl(asset.url)) {
801
807
  const cloudinaryConfig = resolveCloudinaryConfig();
802
808
  if (!cloudinaryConfig) {
803
809
  console.warn(`[TomorrowOS] Cloudinary config missing; cannot delete asset ${asset.id}.`);
@@ -871,15 +877,12 @@ export class TomorrowOS extends EventEmitter {
871
877
  return false;
872
878
  }
873
879
  }
874
- getScreenshotsDir() {
880
+ getLegacyScreenshotsDir() {
875
881
  if (!this.staticRoot) {
876
882
  throw new Error("Screenshot storage requires listen({ staticRoot })");
877
883
  }
878
884
  return path.join(path.resolve(this.staticRoot), "screenshots");
879
885
  }
880
- screenshotBaseName(deviceId) {
881
- return sanitizeStorageSegment(deviceId);
882
- }
883
886
  screenshotExtension(mimeType) {
884
887
  const normalized = mimeType.toLowerCase();
885
888
  if (normalized === "image/png")
@@ -888,6 +891,29 @@ export class TomorrowOS extends EventEmitter {
888
891
  return ".webp";
889
892
  return ".jpg";
890
893
  }
894
+ async getLegacyDeviceScreenshot(deviceId) {
895
+ if (!this.staticRoot)
896
+ return null;
897
+ const base = sanitizeStorageSegment(deviceId);
898
+ const metaPath = path.join(this.getLegacyScreenshotsDir(), `${base}.json`);
899
+ try {
900
+ const raw = await fs.readFile(metaPath, "utf8");
901
+ const parsed = JSON.parse(raw);
902
+ if (!parsed || typeof parsed.url !== "string")
903
+ return null;
904
+ return {
905
+ deviceId,
906
+ url: parsed.url,
907
+ capturedAt: parsed.capturedAt,
908
+ mimeType: parsed.mimeType || "image/jpeg",
909
+ width: parsed.width,
910
+ height: parsed.height
911
+ };
912
+ }
913
+ catch {
914
+ return null;
915
+ }
916
+ }
891
917
  async saveDeviceScreenshot(deviceId, payload) {
892
918
  const mimeType = typeof payload.mimeType === "string" && payload.mimeType.startsWith("image/")
893
919
  ? payload.mimeType
@@ -896,42 +922,51 @@ export class TomorrowOS extends EventEmitter {
896
922
  if (!dataBase64.trim()) {
897
923
  throw new Error("Screenshot payload missing dataBase64");
898
924
  }
899
- const dir = this.getScreenshotsDir();
900
- await fs.mkdir(dir, { recursive: true });
901
- const base = this.screenshotBaseName(deviceId);
902
- const ext = this.screenshotExtension(mimeType);
903
- const filename = `${base}${ext}`;
904
- const filePath = path.join(dir, filename);
905
- const metaPath = path.join(dir, `${base}.json`);
906
925
  const capturedAt = typeof payload.capturedAt === "string" && payload.capturedAt.trim()
907
926
  ? payload.capturedAt
908
927
  : new Date().toISOString();
909
- await fs.writeFile(filePath, Buffer.from(dataBase64, "base64"));
910
- const info = {
928
+ const body = Buffer.from(dataBase64, "base64");
929
+ const safeName = sanitizeUploadFilename(`screenshot-${sanitizeStorageSegment(deviceId)}-${Date.now()}${this.screenshotExtension(mimeType)}`);
930
+ const paired = await this.store.getPairedDevice(deviceId);
931
+ const previousAssetId = paired?.lastScreenshotAssetId;
932
+ const { asset } = await this.storeUploadedMediaAsset(body, safeName, mimeType);
933
+ if (paired) {
934
+ await this.store.setPairedDevice(deviceId, {
935
+ ...paired,
936
+ lastScreenshotAssetId: asset.id,
937
+ lastScreenshotCapturedAt: capturedAt
938
+ });
939
+ }
940
+ if (previousAssetId && previousAssetId !== asset.id) {
941
+ await this.deleteUploadedAssetIfUnreferenced(previousAssetId);
942
+ }
943
+ return {
911
944
  deviceId,
912
- url: `/screenshots/${filename}`,
945
+ assetId: asset.id,
946
+ url: asset.url,
913
947
  capturedAt,
914
- mimeType,
948
+ mimeType: asset.mimeType || mimeType,
915
949
  width: typeof payload.width === "number" ? payload.width : undefined,
916
950
  height: typeof payload.height === "number" ? payload.height : undefined
917
951
  };
918
- await fs.writeFile(metaPath, JSON.stringify(info, null, 2));
919
- return info;
920
952
  }
921
953
  async getLatestDeviceScreenshot(deviceId) {
922
- const dir = this.getScreenshotsDir();
923
- const base = this.screenshotBaseName(deviceId);
924
- const metaPath = path.join(dir, `${base}.json`);
925
- try {
926
- const raw = await fs.readFile(metaPath, "utf8");
927
- const parsed = JSON.parse(raw);
928
- if (!parsed || typeof parsed.url !== "string")
929
- return null;
930
- return parsed;
931
- }
932
- catch {
933
- return null;
954
+ const paired = await this.store.getPairedDevice(deviceId);
955
+ const assetId = paired?.lastScreenshotAssetId;
956
+ const capturedAt = paired?.lastScreenshotCapturedAt;
957
+ if (assetId && capturedAt) {
958
+ const asset = await this.store.getUploadedAsset(assetId);
959
+ if (asset) {
960
+ return {
961
+ deviceId,
962
+ assetId: asset.id,
963
+ url: asset.url,
964
+ capturedAt,
965
+ mimeType: asset.mimeType || "image/jpeg"
966
+ };
967
+ }
934
968
  }
969
+ return this.getLegacyDeviceScreenshot(deviceId);
935
970
  }
936
971
  async captureDeviceScreenshot(deviceId) {
937
972
  const result = await this.sendDeviceCommand(deviceId, "device.captureScreen", {});
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tomorrowos/sdk",
3
- "version": "0.8.2",
3
+ "version": "0.8.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",
@@ -0,0 +1,5 @@
1
+ # TomorrowOS CMS — must be "web" for Replit Publish (never "api").
2
+ kind = "web"
3
+ previewPath = "/"
4
+ title = "TomorrowOS CMS"
5
+ version = "1.0.0"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "my-cms",
3
- "version": "0.8.2",
3
+ "version": "0.8.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",
@@ -13,7 +13,7 @@
13
13
  "build-player": "tomorrowos build --platform tizen"
14
14
  },
15
15
  "dependencies": {
16
- "@tomorrowos/sdk": "^0.8.2",
16
+ "@tomorrowos/sdk": "^0.8.4",
17
17
  "dotenv": "^17.2.3",
18
18
  "tsx": "^4.19.0"
19
19
  },