@rockhall/electron-offline-content 0.4.0
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/CHANGELOG.md +384 -0
- package/LICENSE +21 -0
- package/README.md +794 -0
- package/dist/internal/asset-file-name.cjs +13 -0
- package/dist/internal/asset-file-name.cjs.map +1 -0
- package/dist/internal/asset-file-name.d.cts +6 -0
- package/dist/internal/asset-file-name.d.cts.map +1 -0
- package/dist/internal/asset-file-name.d.ts +6 -0
- package/dist/internal/asset-file-name.d.ts.map +1 -0
- package/dist/internal/asset-file-name.js +12 -0
- package/dist/internal/asset-file-name.js.map +1 -0
- package/dist/internal/asset-key.cjs +30 -0
- package/dist/internal/asset-key.cjs.map +1 -0
- package/dist/internal/asset-key.d.cts +19 -0
- package/dist/internal/asset-key.d.cts.map +1 -0
- package/dist/internal/asset-key.d.ts +19 -0
- package/dist/internal/asset-key.d.ts.map +1 -0
- package/dist/internal/asset-key.js +27 -0
- package/dist/internal/asset-key.js.map +1 -0
- package/dist/internal/log-format.cjs +98 -0
- package/dist/internal/log-format.cjs.map +1 -0
- package/dist/internal/log-format.d.cts +10 -0
- package/dist/internal/log-format.d.cts.map +1 -0
- package/dist/internal/log-format.d.ts +10 -0
- package/dist/internal/log-format.d.ts.map +1 -0
- package/dist/internal/log-format.js +97 -0
- package/dist/internal/log-format.js.map +1 -0
- package/dist/internal/media-kind.cjs +46 -0
- package/dist/internal/media-kind.cjs.map +1 -0
- package/dist/internal/media-kind.d.cts +20 -0
- package/dist/internal/media-kind.d.cts.map +1 -0
- package/dist/internal/media-kind.d.ts +20 -0
- package/dist/internal/media-kind.d.ts.map +1 -0
- package/dist/internal/media-kind.js +45 -0
- package/dist/internal/media-kind.js.map +1 -0
- package/dist/internal/url-warn.cjs +14 -0
- package/dist/internal/url-warn.cjs.map +1 -0
- package/dist/internal/url-warn.d.cts +10 -0
- package/dist/internal/url-warn.d.cts.map +1 -0
- package/dist/internal/url-warn.d.ts +10 -0
- package/dist/internal/url-warn.d.ts.map +1 -0
- package/dist/internal/url-warn.js +13 -0
- package/dist/internal/url-warn.js.map +1 -0
- package/dist/internal/validation.cjs +222 -0
- package/dist/internal/validation.cjs.map +1 -0
- package/dist/internal/validation.d.cts +78 -0
- package/dist/internal/validation.d.cts.map +1 -0
- package/dist/internal/validation.d.ts +78 -0
- package/dist/internal/validation.d.ts.map +1 -0
- package/dist/internal/validation.js +196 -0
- package/dist/internal/validation.js.map +1 -0
- package/dist/main/asset-download.cjs +265 -0
- package/dist/main/asset-download.cjs.map +1 -0
- package/dist/main/asset-download.d.cts +12 -0
- package/dist/main/asset-download.d.cts.map +1 -0
- package/dist/main/asset-download.d.ts +12 -0
- package/dist/main/asset-download.d.ts.map +1 -0
- package/dist/main/asset-download.js +263 -0
- package/dist/main/asset-download.js.map +1 -0
- package/dist/main/database.cjs +473 -0
- package/dist/main/database.cjs.map +1 -0
- package/dist/main/database.d.cts +81 -0
- package/dist/main/database.d.cts.map +1 -0
- package/dist/main/database.d.ts +81 -0
- package/dist/main/database.d.ts.map +1 -0
- package/dist/main/database.js +472 -0
- package/dist/main/database.js.map +1 -0
- package/dist/main/index.cjs +22 -0
- package/dist/main/index.d.cts +7 -0
- package/dist/main/index.d.ts +7 -0
- package/dist/main/index.js +7 -0
- package/dist/main/media-cache.cjs +862 -0
- package/dist/main/media-cache.cjs.map +1 -0
- package/dist/main/media-cache.d.cts +134 -0
- package/dist/main/media-cache.d.cts.map +1 -0
- package/dist/main/media-cache.d.ts +134 -0
- package/dist/main/media-cache.d.ts.map +1 -0
- package/dist/main/media-cache.js +854 -0
- package/dist/main/media-cache.js.map +1 -0
- package/dist/main/storage-root-lock.cjs +124 -0
- package/dist/main/storage-root-lock.cjs.map +1 -0
- package/dist/main/storage-root-lock.d.cts +11 -0
- package/dist/main/storage-root-lock.d.cts.map +1 -0
- package/dist/main/storage-root-lock.d.ts +11 -0
- package/dist/main/storage-root-lock.d.ts.map +1 -0
- package/dist/main/storage-root-lock.js +120 -0
- package/dist/main/storage-root-lock.js.map +1 -0
- package/dist/main/store.cjs +197 -0
- package/dist/main/store.cjs.map +1 -0
- package/dist/main/store.d.cts +83 -0
- package/dist/main/store.d.cts.map +1 -0
- package/dist/main/store.d.ts +83 -0
- package/dist/main/store.d.ts.map +1 -0
- package/dist/main/store.js +195 -0
- package/dist/main/store.js.map +1 -0
- package/dist/preload/index.cjs +36 -0
- package/dist/preload/index.cjs.map +1 -0
- package/dist/preload/index.d.cts +14 -0
- package/dist/preload/index.d.cts.map +1 -0
- package/dist/preload/index.d.ts +14 -0
- package/dist/preload/index.d.ts.map +1 -0
- package/dist/preload/index.js +34 -0
- package/dist/preload/index.js.map +1 -0
- package/dist/react/index.cjs +199 -0
- package/dist/react/index.cjs.map +1 -0
- package/dist/react/index.d.cts +50 -0
- package/dist/react/index.d.cts.map +1 -0
- package/dist/react/index.d.ts +50 -0
- package/dist/react/index.d.ts.map +1 -0
- package/dist/react/index.js +191 -0
- package/dist/react/index.js.map +1 -0
- package/dist/renderer/helpers.cjs +36 -0
- package/dist/renderer/helpers.cjs.map +1 -0
- package/dist/renderer/helpers.d.cts +11 -0
- package/dist/renderer/helpers.d.cts.map +1 -0
- package/dist/renderer/helpers.d.ts +11 -0
- package/dist/renderer/helpers.d.ts.map +1 -0
- package/dist/renderer/helpers.js +35 -0
- package/dist/renderer/helpers.js.map +1 -0
- package/dist/renderer/index.cjs +20 -0
- package/dist/renderer/index.cjs.map +1 -0
- package/dist/renderer/index.d.cts +14 -0
- package/dist/renderer/index.d.cts.map +1 -0
- package/dist/renderer/index.d.ts +14 -0
- package/dist/renderer/index.d.ts.map +1 -0
- package/dist/renderer/index.js +14 -0
- package/dist/renderer/index.js.map +1 -0
- package/dist/renderer/runtime.cjs +278 -0
- package/dist/renderer/runtime.cjs.map +1 -0
- package/dist/renderer/runtime.d.cts +35 -0
- package/dist/renderer/runtime.d.cts.map +1 -0
- package/dist/renderer/runtime.d.ts +35 -0
- package/dist/renderer/runtime.d.ts.map +1 -0
- package/dist/renderer/runtime.js +273 -0
- package/dist/renderer/runtime.js.map +1 -0
- package/dist/renderer/window-globals.d.cts +9 -0
- package/dist/renderer/window-globals.d.cts.map +1 -0
- package/dist/renderer/window-globals.d.ts +9 -0
- package/dist/renderer/window-globals.d.ts.map +1 -0
- package/dist/shared/errors.cjs +102 -0
- package/dist/shared/errors.cjs.map +1 -0
- package/dist/shared/errors.d.cts +45 -0
- package/dist/shared/errors.d.cts.map +1 -0
- package/dist/shared/errors.d.ts +45 -0
- package/dist/shared/errors.d.ts.map +1 -0
- package/dist/shared/errors.js +93 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/ipc.cjs +14 -0
- package/dist/shared/ipc.cjs.map +1 -0
- package/dist/shared/ipc.d.cts +12 -0
- package/dist/shared/ipc.d.cts.map +1 -0
- package/dist/shared/ipc.d.ts +12 -0
- package/dist/shared/ipc.d.ts.map +1 -0
- package/dist/shared/ipc.js +13 -0
- package/dist/shared/ipc.js.map +1 -0
- package/dist/shared/normalize.cjs +19 -0
- package/dist/shared/normalize.cjs.map +1 -0
- package/dist/shared/normalize.d.cts +11 -0
- package/dist/shared/normalize.d.cts.map +1 -0
- package/dist/shared/normalize.d.ts +11 -0
- package/dist/shared/normalize.d.ts.map +1 -0
- package/dist/shared/normalize.js +18 -0
- package/dist/shared/normalize.js.map +1 -0
- package/dist/shared/pagination.cjs +32 -0
- package/dist/shared/pagination.cjs.map +1 -0
- package/dist/shared/pagination.d.cts +14 -0
- package/dist/shared/pagination.d.cts.map +1 -0
- package/dist/shared/pagination.d.ts +14 -0
- package/dist/shared/pagination.d.ts.map +1 -0
- package/dist/shared/pagination.js +28 -0
- package/dist/shared/pagination.js.map +1 -0
- package/dist/shared/stem.cjs +16 -0
- package/dist/shared/stem.cjs.map +1 -0
- package/dist/shared/stem.d.cts +6 -0
- package/dist/shared/stem.d.cts.map +1 -0
- package/dist/shared/stem.d.ts +6 -0
- package/dist/shared/stem.d.ts.map +1 -0
- package/dist/shared/stem.js +14 -0
- package/dist/shared/stem.js.map +1 -0
- package/dist/shared/types.cjs +15 -0
- package/dist/shared/types.cjs.map +1 -0
- package/dist/shared/types.d.cts +234 -0
- package/dist/shared/types.d.cts.map +1 -0
- package/dist/shared/types.d.ts +234 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/types.js +14 -0
- package/dist/shared/types.js.map +1 -0
- package/package.json +120 -0
- package/skills/authenticated-downloads/SKILL.md +203 -0
- package/skills/cache-configuration/SKILL.md +357 -0
- package/skills/cache-configuration/references/options.md +356 -0
- package/skills/getting-started/SKILL.md +407 -0
- package/skills/production-checklist/SKILL.md +397 -0
- package/skills/react-rendering/SKILL.md +424 -0
- package/skills/react-rendering/references/hooks.md +443 -0
- package/skills/store-authoring/SKILL.md +369 -0
|
@@ -0,0 +1,397 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: production-checklist
|
|
3
|
+
description: >
|
|
4
|
+
Go-live audit for kiosk deployment: verify devPassthrough disabled,
|
|
5
|
+
storage limits set for device hardware (maxCacheBytes, reserveFreeBytes),
|
|
6
|
+
structured logging wired to production sink, onSyncFailure mode chosen,
|
|
7
|
+
app.requestSingleInstanceLock in place, scope boundaries confirmed
|
|
8
|
+
(read-only presentation assets only), offline mode tested with real
|
|
9
|
+
storage path and media:// protocol URLs, disk space validated for
|
|
10
|
+
full catalog download.
|
|
11
|
+
type: lifecycle
|
|
12
|
+
library: electron-offline-content
|
|
13
|
+
library_version: "0.4.0"
|
|
14
|
+
requires:
|
|
15
|
+
- cache-configuration
|
|
16
|
+
sources:
|
|
17
|
+
- "rockhallweb/electron-offline-content:src/main/media-cache.ts"
|
|
18
|
+
- "rockhallweb/electron-offline-content:src/shared/types.ts"
|
|
19
|
+
- "rockhallweb/electron-offline-content:src/main/storage-root-lock.ts"
|
|
20
|
+
- "rockhallweb/electron-offline-content:README.md"
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
# Production Checklist
|
|
24
|
+
|
|
25
|
+
This skill builds on cache-configuration. Read it first for all createMediaCache options.
|
|
26
|
+
|
|
27
|
+
## Runtime Configuration Checks
|
|
28
|
+
|
|
29
|
+
### Check: devPassthrough is explicitly disabled
|
|
30
|
+
|
|
31
|
+
**Expected:**
|
|
32
|
+
|
|
33
|
+
```typescript
|
|
34
|
+
import { createMediaCache } from "@rockhall/electron-offline-content/main";
|
|
35
|
+
|
|
36
|
+
const mediaCache = createMediaCache({
|
|
37
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
38
|
+
devPassthrough: false,
|
|
39
|
+
resolveStore: async () => store,
|
|
40
|
+
});
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Fail condition:** `NODE_ENV` is `"development"` in production build, or `devPassthrough` is omitted and `NODE_ENV` is not set. Defaults to `false` when `NODE_ENV` is unset, but being explicit prevents silent regressions if build tooling sets `NODE_ENV`.
|
|
44
|
+
|
|
45
|
+
**Fix:** Set `devPassthrough: false` explicitly in production configuration.
|
|
46
|
+
|
|
47
|
+
### Check: onSyncFailure mode is chosen deliberately
|
|
48
|
+
|
|
49
|
+
**Expected — kiosk that must always show content:**
|
|
50
|
+
|
|
51
|
+
```typescript
|
|
52
|
+
const mediaCache = createMediaCache({
|
|
53
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
54
|
+
devPassthrough: false,
|
|
55
|
+
onSyncFailure: "serve-last-snapshot",
|
|
56
|
+
resolveStore: async () => store,
|
|
57
|
+
});
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Expected — app that needs fresh content:**
|
|
61
|
+
|
|
62
|
+
```typescript
|
|
63
|
+
const mediaCache = createMediaCache({
|
|
64
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
65
|
+
devPassthrough: false,
|
|
66
|
+
onSyncFailure: "throw",
|
|
67
|
+
resolveStore: async () => store,
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Fail condition:** `onSyncFailure` omitted — default may not match your use case. Stale content on a kiosk loop may be acceptable; stale content on an info display with time-sensitive data may not.
|
|
72
|
+
|
|
73
|
+
**Fix:** Set explicitly based on whether stale content or a blank screen is worse for your deployment.
|
|
74
|
+
|
|
75
|
+
### Check: app.requestSingleInstanceLock() is in place
|
|
76
|
+
|
|
77
|
+
**Expected:**
|
|
78
|
+
|
|
79
|
+
```typescript
|
|
80
|
+
import { app } from "electron";
|
|
81
|
+
import { createMediaCache } from "@rockhall/electron-offline-content/main";
|
|
82
|
+
|
|
83
|
+
if (!app.requestSingleInstanceLock()) {
|
|
84
|
+
app.exit(1);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const mediaCache = createMediaCache({
|
|
88
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
89
|
+
devPassthrough: false,
|
|
90
|
+
resolveStore: async () => store,
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
**Fail condition:** Second process launches and collides on the storage root. `StorageOwnershipError` thrown at `start()` time — kiosk may silently restart into a crash loop.
|
|
95
|
+
|
|
96
|
+
**Fix:** Acquire lock before `createMediaCache`. Exit immediately on failure.
|
|
97
|
+
|
|
98
|
+
## Storage Checks
|
|
99
|
+
|
|
100
|
+
### Check: Storage limits match device hardware
|
|
101
|
+
|
|
102
|
+
**Expected:** `maxCacheBytes` set from SSD capacity. Tune `reserveFreeBytes` for OS, logs, and other apps (the library defaults to **1 GiB** when omitted, but kiosks often need a larger explicit cushion).
|
|
103
|
+
|
|
104
|
+
```typescript
|
|
105
|
+
const mediaCache = createMediaCache({
|
|
106
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
107
|
+
devPassthrough: false,
|
|
108
|
+
maxCacheBytes: 10 * 1024 * 1024 * 1024,
|
|
109
|
+
reserveFreeBytes: 5 * 1024 * 1024 * 1024,
|
|
110
|
+
resolveStore: async () => store,
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
**Fail condition:** Cache consumes all disk on kiosk SSD. OS becomes unresponsive, other apps crash, kiosk requires physical intervention.
|
|
115
|
+
|
|
116
|
+
**Fix:** Set `maxCacheBytes` based on device SSD capacity. Set `reserveFreeBytes` explicitly when the default 1 GiB is too small for your platform. Example: 128 GB SSD kiosk → `maxCacheBytes: 80 * 1024 * 1024 * 1024`, `reserveFreeBytes: 5 * 1024 * 1024 * 1024`. Use **`reserveFreeBytes: 0`** only if you intentionally need the old no-reservation behavior.
|
|
117
|
+
|
|
118
|
+
### Check: storagePath targets a valid writable location
|
|
119
|
+
|
|
120
|
+
**Expected:** `storagePath` uses an `appPath` that is writable on the target platform after packaging.
|
|
121
|
+
|
|
122
|
+
**Fail condition:** Permission errors at runtime on certain platforms or packagers. `"userData"` is generally safe; other paths may not be writable in sandboxed or locked-down kiosk environments.
|
|
123
|
+
|
|
124
|
+
**Fix:** Build and run the packaged app on the target platform. Verify the resolved storage directory is writable. Check with `app.getPath("userData")` in the main process to confirm the actual path.
|
|
125
|
+
|
|
126
|
+
## Logging Checks
|
|
127
|
+
|
|
128
|
+
### Check: Structured logging wired to production sink
|
|
129
|
+
|
|
130
|
+
**Expected:**
|
|
131
|
+
|
|
132
|
+
```typescript
|
|
133
|
+
import pino from "pino";
|
|
134
|
+
import { createMediaCache } from "@rockhall/electron-offline-content/main";
|
|
135
|
+
|
|
136
|
+
const logger = pino({ name: "media-cache" });
|
|
137
|
+
|
|
138
|
+
const mediaCache = createMediaCache({
|
|
139
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
140
|
+
devPassthrough: false,
|
|
141
|
+
logging: {
|
|
142
|
+
level: "info",
|
|
143
|
+
onLog: (entry) => {
|
|
144
|
+
logger[entry.level === "debug" ? "debug" : entry.level](entry, entry.event);
|
|
145
|
+
},
|
|
146
|
+
},
|
|
147
|
+
resolveStore: async () => store,
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
**Fail condition:** Default console logging is invisible on headless kiosks. Sync failures, disk errors, and download issues go unnoticed until content goes stale.
|
|
152
|
+
|
|
153
|
+
**Fix:** Set `logging.onLog` with your production logger. Wire to pino, logtape, or your log aggregation service. Set `logging.level: "info"` — `"debug"` produces high volume in production.
|
|
154
|
+
|
|
155
|
+
## Scope Boundary Checks
|
|
156
|
+
|
|
157
|
+
### Check: Package used only for read-only presentation assets
|
|
158
|
+
|
|
159
|
+
**Expected:** Cache serves media for display — video, images, audio, documents. Content flows one direction: CMS → store → download → local storage → renderer display.
|
|
160
|
+
|
|
161
|
+
**Fail condition:** Package misused for user-generated content (webcam captures, user uploads, form attachments) or runtime data storage. No write operations are exposed to the renderer. The cache is read-only by design.
|
|
162
|
+
|
|
163
|
+
**Fix:** Use a different solution for user-generated content. Electron's `dialog.showSaveDialog`, IndexedDB, or a separate upload service are appropriate for user-created files.
|
|
164
|
+
|
|
165
|
+
## Testing Checks
|
|
166
|
+
|
|
167
|
+
### Check: Offline mode tested with real storage path and media:// URLs
|
|
168
|
+
|
|
169
|
+
**Expected:** App built and tested with `devPassthrough: false`. Content loads from `media://` protocol URLs. Renderer displays media correctly from local storage.
|
|
170
|
+
|
|
171
|
+
**Fail condition:**
|
|
172
|
+
|
|
173
|
+
- `storagePath` permissions fail on the target platform after packaging.
|
|
174
|
+
- DOM security policies block `media://` protocol URLs in `<video>`, `<img>`, or `<audio>` elements.
|
|
175
|
+
- Media rendering issues (codec support, file integrity) only surface when serving from disk.
|
|
176
|
+
- All of these are invisible in dev passthrough mode because assets load from remote `https://` URLs.
|
|
177
|
+
|
|
178
|
+
**Fix:** Build the production binary. Run it with `devPassthrough: false`. Navigate every content type. Verify `media://` URLs render in all media elements.
|
|
179
|
+
|
|
180
|
+
### Check: Full catalog download tested for disk space
|
|
181
|
+
|
|
182
|
+
**Expected:** Download the full store to verify it fits on the target device. Monitor disk usage during and after sync completes.
|
|
183
|
+
|
|
184
|
+
**Fail condition:** Catalog exceeds available disk space on kiosk hardware. Downloads stall or fail when `maxCacheBytes` or `reserveFreeBytes` limits are hit. Partial sync leaves kiosk with incomplete content.
|
|
185
|
+
|
|
186
|
+
**Fix:** For smoke tests, scope down the store to a subset. For production validation, verify the device has capacity for the full catalog plus `reserveFreeBytes` headroom. Calculate: total store size + reserve + OS needs < device disk capacity.
|
|
187
|
+
|
|
188
|
+
## Common Mistakes
|
|
189
|
+
|
|
190
|
+
### CRITICAL: devPassthrough left enabled in production
|
|
191
|
+
|
|
192
|
+
In production Electron builds, `NODE_ENV` may be unset — which defaults `devPassthrough` to `false`. But if `NODE_ENV` is explicitly set to `"development"` in a deployed build (common with misconfigured build tooling), all downloads are silently skipped and the app serves only remote URLs. The kiosk works on the network but fails offline.
|
|
193
|
+
|
|
194
|
+
Wrong — relying on `NODE_ENV`:
|
|
195
|
+
|
|
196
|
+
```typescript
|
|
197
|
+
const mediaCache = createMediaCache({
|
|
198
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
199
|
+
resolveStore: async () => store,
|
|
200
|
+
});
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
Correct — explicit:
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
const mediaCache = createMediaCache({
|
|
207
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
208
|
+
devPassthrough: false,
|
|
209
|
+
resolveStore: async () => store,
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Source: media-cache.ts; types.ts
|
|
214
|
+
Cross-skill: also in cache-configuration/SKILL.md § Common Mistakes
|
|
215
|
+
|
|
216
|
+
### CRITICAL: Shipping with only dev passthrough testing
|
|
217
|
+
|
|
218
|
+
`devPassthrough` bypasses real storage, protocol serving, and filesystem permissions. Platform-specific issues only surface in offline mode:
|
|
219
|
+
|
|
220
|
+
- `media://` protocol URLs may be blocked by CSP or DOM security in certain Electron configurations.
|
|
221
|
+
- Disk space for the full catalog is untested.
|
|
222
|
+
- Authenticated download URLs embedded in `resolveStore` are untested.
|
|
223
|
+
- `onSyncFailure: "serve-last-snapshot"` is overridden to `"throw"` — resilience logic is untested.
|
|
224
|
+
|
|
225
|
+
Wrong — testing only in dev mode:
|
|
226
|
+
|
|
227
|
+
```typescript
|
|
228
|
+
const mediaCache = createMediaCache({
|
|
229
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
230
|
+
devPassthrough: true,
|
|
231
|
+
resolveStore: async () => store,
|
|
232
|
+
});
|
|
233
|
+
// "It works!" — but only because assets load from remote URLs
|
|
234
|
+
```
|
|
235
|
+
|
|
236
|
+
Correct — test with production configuration before deploying:
|
|
237
|
+
|
|
238
|
+
```typescript
|
|
239
|
+
const mediaCache = createMediaCache({
|
|
240
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
241
|
+
devPassthrough: false,
|
|
242
|
+
onSyncFailure: "serve-last-snapshot",
|
|
243
|
+
resolveStore: async () => store,
|
|
244
|
+
});
|
|
245
|
+
// Build, run, verify media:// URLs render, verify sync completes
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Source: Maintainer interview
|
|
249
|
+
|
|
250
|
+
### HIGH: No soft cap on cache size (`maxCacheBytes`)
|
|
251
|
+
|
|
252
|
+
Without `maxCacheBytes`, the cache can grow to the full store size subject only to disk space and the default **1 GiB** `reserveFreeBytes` headroom. On kiosk SSDs (often 64–256 GB), a growing catalog can still consume almost all disk space. Set `maxCacheBytes` explicitly for a hard ceiling.
|
|
253
|
+
|
|
254
|
+
Wrong:
|
|
255
|
+
|
|
256
|
+
```typescript
|
|
257
|
+
const mediaCache = createMediaCache({
|
|
258
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
259
|
+
devPassthrough: false,
|
|
260
|
+
resolveStore: async () => store,
|
|
261
|
+
});
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Correct:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
const mediaCache = createMediaCache({
|
|
268
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
269
|
+
devPassthrough: false,
|
|
270
|
+
maxCacheBytes: 10 * 1024 * 1024 * 1024,
|
|
271
|
+
reserveFreeBytes: 1 * 1024 * 1024 * 1024,
|
|
272
|
+
resolveStore: async () => store,
|
|
273
|
+
});
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Source: README
|
|
277
|
+
|
|
278
|
+
### HIGH: Switching to offline mode without considering disk space
|
|
279
|
+
|
|
280
|
+
When disabling `devPassthrough` for the first time, the full catalog downloads to the local machine. Developers on laptops with limited free space may run out of disk during the first sync. Large production catalogs (tens of GB of video) amplify this.
|
|
281
|
+
|
|
282
|
+
Wrong — disabling passthrough without checking space:
|
|
283
|
+
|
|
284
|
+
```typescript
|
|
285
|
+
// Was devPassthrough: true during development, now switching for production testing
|
|
286
|
+
const mediaCache = createMediaCache({
|
|
287
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
288
|
+
devPassthrough: false,
|
|
289
|
+
resolveStore: async () => fullProductionStore,
|
|
290
|
+
});
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Correct — scope down store for local testing:
|
|
294
|
+
|
|
295
|
+
```typescript
|
|
296
|
+
const mediaCache = createMediaCache({
|
|
297
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
298
|
+
devPassthrough: false,
|
|
299
|
+
maxCacheBytes: 2 * 1024 * 1024 * 1024,
|
|
300
|
+
resolveStore: async () => scopedTestStore,
|
|
301
|
+
});
|
|
302
|
+
```
|
|
303
|
+
|
|
304
|
+
Source: Maintainer interview
|
|
305
|
+
|
|
306
|
+
### MEDIUM: Default console logging in production
|
|
307
|
+
|
|
308
|
+
When `logging.onLog` is omitted and `NODE_ENV !== "production"`, the package prints to `console`. On headless kiosks, console output goes nowhere. Sync failures, download errors, and storage warnings are silently lost.
|
|
309
|
+
|
|
310
|
+
Wrong:
|
|
311
|
+
|
|
312
|
+
```typescript
|
|
313
|
+
const mediaCache = createMediaCache({
|
|
314
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
315
|
+
devPassthrough: false,
|
|
316
|
+
resolveStore: async () => store,
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
Correct:
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
import pino from "pino";
|
|
324
|
+
|
|
325
|
+
const logger = pino({ name: "media-cache" });
|
|
326
|
+
|
|
327
|
+
const mediaCache = createMediaCache({
|
|
328
|
+
storagePath: { appPath: "userData", segments: ["offline-media"] },
|
|
329
|
+
devPassthrough: false,
|
|
330
|
+
logging: {
|
|
331
|
+
level: "info",
|
|
332
|
+
onLog: (entry) => {
|
|
333
|
+
logger[entry.level === "debug" ? "debug" : entry.level](entry, entry.event);
|
|
334
|
+
},
|
|
335
|
+
},
|
|
336
|
+
resolveStore: async () => store,
|
|
337
|
+
});
|
|
338
|
+
```
|
|
339
|
+
|
|
340
|
+
Source: media-cache.ts; README
|
|
341
|
+
|
|
342
|
+
### MEDIUM: Using package for user-generated content
|
|
343
|
+
|
|
344
|
+
Wrong:
|
|
345
|
+
|
|
346
|
+
```typescript
|
|
347
|
+
ipcMain.handle("save-webcam-photo", async (_event, photoBuffer: Buffer) => {
|
|
348
|
+
await mediaCache.writeAsset("user-photos", "selfie", photoBuffer);
|
|
349
|
+
});
|
|
350
|
+
```
|
|
351
|
+
|
|
352
|
+
Correct:
|
|
353
|
+
|
|
354
|
+
```typescript
|
|
355
|
+
import { writeFile } from "node:fs/promises";
|
|
356
|
+
import { app } from "electron";
|
|
357
|
+
import path from "node:path";
|
|
358
|
+
|
|
359
|
+
ipcMain.handle("save-webcam-photo", async (_event, photoBuffer: Buffer) => {
|
|
360
|
+
const userDataPath = path.join(app.getPath("userData"), "user-photos");
|
|
361
|
+
await writeFile(path.join(userDataPath, "selfie.jpg"), photoBuffer);
|
|
362
|
+
});
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
The cache is read-only for presentation assets. No write operations are exposed to the renderer. Use standard Node.js file APIs for user-generated content like webcam photos or uploads.
|
|
366
|
+
|
|
367
|
+
Source: Maintainer interview
|
|
368
|
+
|
|
369
|
+
### HIGH Tension: Dev passthrough simplicity vs production correctness
|
|
370
|
+
|
|
371
|
+
`devPassthrough` makes development fast but changes behavior: downloads are skipped, `onSyncFailure` is overridden to `"throw"`, URLs are remote. Code working in dev may break in production offline mode — especially authenticated downloads and sync failure resilience.
|
|
372
|
+
|
|
373
|
+
See also: cache-configuration/SKILL.md § Common Mistakes
|
|
374
|
+
|
|
375
|
+
### HIGH Tension: Sync resilience vs stale content
|
|
376
|
+
|
|
377
|
+
`"serve-last-snapshot"` never shows a blank screen but may serve outdated content indefinitely if syncs keep failing. `"throw"` is honest but leaves the UI empty. Choose based on whether stale content or no content is worse for your kiosk use case.
|
|
378
|
+
|
|
379
|
+
See also: cache-configuration/SKILL.md § Common Mistakes
|
|
380
|
+
|
|
381
|
+
## Pre-Deploy Summary
|
|
382
|
+
|
|
383
|
+
- [ ] `devPassthrough: false` set explicitly
|
|
384
|
+
- [ ] `onSyncFailure` mode chosen deliberately
|
|
385
|
+
- [ ] `app.requestSingleInstanceLock()` in place
|
|
386
|
+
- [ ] `maxCacheBytes` set for device; `reserveFreeBytes` tuned for hardware or default accepted
|
|
387
|
+
- [ ] `storagePath` writable on target platform
|
|
388
|
+
- [ ] `logging.onLog` wired to production log sink
|
|
389
|
+
- [ ] Package used only for read-only presentation assets
|
|
390
|
+
- [ ] Offline mode tested with real storage path and media:// URLs
|
|
391
|
+
- [ ] Full catalog fits on target device disk
|
|
392
|
+
|
|
393
|
+
---
|
|
394
|
+
|
|
395
|
+
See also: cache-configuration/SKILL.md — All createMediaCache options
|
|
396
|
+
See also: getting-started/SKILL.md — Initial wiring and setup
|
|
397
|
+
See also: authenticated-downloads/SKILL.md — Verify auth works in offline mode
|