@screenly/edge-apps 1.2.1 → 1.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/README.md CHANGED
@@ -140,16 +140,100 @@ signalReady()
140
140
  - `addUTMParams(url, params?)` - Add UTM parameters to URL
141
141
  - `addUTMParamsIf(url, enabled, params?)` - Conditionally add UTM parameters
142
142
 
143
+ ### Edge App Cache
144
+
145
+ - `readEdgeAppCache(namespace, key)` - Read a `localStorage`-backed, namespaced last-known-good value
146
+ - `writeEdgeAppCache(namespace, key, value)` - Write a `localStorage`-backed, namespaced last-known-good value
147
+
143
148
  ### Error Reporting (Sentry)
144
149
 
145
150
  - `setupSentry(app, contexts?)` - Initialize Sentry using the `sentry_dsn` setting; sets the `edge_app` tag, hostname, and any additional contexts. No-ops if `sentry_dsn` is not configured.
146
151
  - `scrubSensitiveData(event)` - Sentry `beforeSend` hook that redacts values of settings keys matching `token`, `secret`, `password`, or `credential` with `[REDACTED]`. Drops the event if it cannot be safely serialized.
147
152
  - `reportError(error, context?)` - Capture an exception via Sentry with optional extra context.
148
153
 
154
+ ## Edge App Cache
155
+
156
+ When an Edge App fetches data from a backend, a failure shouldn't necessarily
157
+ break the display — falling back to the last-known-good value is often better
158
+ than showing an error.
159
+
160
+ ### `readEdgeAppCache(namespace, key)` / `writeEdgeAppCache(namespace, key, value)`
161
+
162
+ Read/write a `localStorage`-backed value under `namespace`, so different
163
+ apps/caches don't collide. There is no TTL: it's meant to store the
164
+ last-known-good value and be consulted only after a genuine fetch failure, not
165
+ as a general-purpose expiring cache — and `localStorage` itself isn't
166
+ guaranteed to survive a device reboot. Both fail silently (return `null` /
167
+ no-op) if storage is unavailable, disabled, or full — so it's safe to use
168
+ without extra error handling around it.
169
+
170
+ `writeEdgeAppCache()` accepts any JSON-serializable value (object, array,
171
+ string, number, etc.) — `WeatherData` below is just a stand-in name for
172
+ "whatever your fetch returns," not a type this library exports.
173
+
174
+ ```typescript
175
+ import {
176
+ readEdgeAppCache,
177
+ writeEdgeAppCache,
178
+ getSettingWithDefault,
179
+ } from '@screenly/edge-apps'
180
+
181
+ async function loadWeather() {
182
+ const displayErrors = getSettingWithDefault('display_errors', false)
183
+
184
+ try {
185
+ const response = await fetch(weatherApiUrl)
186
+ if (!response.ok) throw new Error(`Weather API returned ${response.status}`)
187
+
188
+ // weatherData can be any JSON-serializable shape, e.g.:
189
+ // { temperature: 18, description: 'Cloudy', unit: 'metric' }
190
+ const weatherData: WeatherData = await response.json()
191
+ writeEdgeAppCache('my-edge-app', 'weather', weatherData)
192
+ return weatherData
193
+ } catch (error) {
194
+ // When `display_errors` is on, the raw error always wins, by design, so
195
+ // operators can diagnose real problems instead of seeing stale data.
196
+ if (!displayErrors) {
197
+ return readEdgeAppCache<WeatherData>('my-edge-app', 'weather')
198
+ }
199
+ throw error
200
+ }
201
+ }
202
+ ```
203
+
149
204
  ## Web Components
150
205
 
151
206
  This library includes reusable web components for building consistent Edge Apps. See the [components documentation](https://github.com/Screenly/edge-apps-library/blob/main/docs/components.md) for usage details.
152
207
 
208
+ ## Styling with Tailwind CSS
209
+
210
+ Edge Apps scaffolded with `create` come with [Tailwind CSS](https://tailwindcss.com/) enabled out of the box via `@tailwindcss/vite` — no extra install or config file needed. Just add the import to your app's stylesheet:
211
+
212
+ ```css
213
+ @layer theme, base, utilities;
214
+
215
+ @import 'tailwindcss/theme.css' layer(theme);
216
+ @import '@screenly/edge-apps/styles' layer(base);
217
+ @import 'tailwindcss/utilities.css' layer(utilities);
218
+ ```
219
+
220
+ > [!IMPORTANT]
221
+ > Declare the layer order up front with `@layer theme, base, utilities;`, and import `@screenly/edge-apps/styles` (and any other unlayered base CSS) into the `base` layer. Per the CSS Cascade Layers spec, unlayered CSS always beats layered CSS regardless of selector specificity — so without this, `@screenly/edge-apps/styles`'s base rules (e.g. its `user-select: none` reset) would silently override Tailwind utility classes.
222
+
223
+ Then use utility classes directly in your markup instead of writing custom CSS:
224
+
225
+ ```html
226
+ <main class="flex h-full w-full items-center justify-center">
227
+ <h1 class="text-6xl portrait:text-4xl">Hello, Screenly!</h1>
228
+ </main>
229
+ ```
230
+
231
+ > [!IMPORTANT]
232
+ > Inside `<auto-scaler>`, use `h-full`/`w-full` instead of `h-screen`/`w-screen`. `<auto-scaler>` renders its content into a fixed-size box (the `reference-width`/`reference-height` you pass it) and scales that box to fit the real viewport with a CSS transform. Viewport-relative utilities (`h-screen`, `w-screen`, or arbitrary `vh`/`vw` values) measure the real viewport, not the scaled box, so they won't line up with the rest of your layout. The `portrait:`/`landscape:` variants are unaffected since they're based on device orientation, which `<auto-scaler>` uses the same way.
233
+
234
+ > [!WARNING]
235
+ > Avoid `@import 'tailwindcss';` (the full package) in an Edge App. It includes Preflight, which resets `border`, `margin`, and `padding` to `0` on every element via a universal selector — including custom-element hosts. Components like `<app-header>` style themselves through `:host` in their own shadow DOM (border, padding, etc.), and Preflight's reset silently overrides that styling from outside, since the host tag itself is a normal element in your page's light DOM. Import `tailwindcss/theme.css` and `tailwindcss/utilities.css` directly instead, as shown above, to get Tailwind's utility classes without Preflight.
236
+
153
237
  ## Edge Apps Scripts CLI
154
238
 
155
239
  This package provides the `edge-apps-scripts` CLI tool for running shared development commands across all Edge Apps. It includes centralized ESLint configuration to avoid duplication.
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Reads and JSON-parses the value stored under `${namespace}:${key}` in
3
+ * `localStorage`, or `null` if missing, unreadable, or storage is
4
+ * unavailable. Meant for last-known-good fallback data for Edge Apps:
5
+ * consult it only after a genuine fetch failure, not as a general-purpose
6
+ * expiring cache — there is no TTL, and `localStorage` itself isn't
7
+ * guaranteed to survive a device reboot.
8
+ */
9
+ export declare function readEdgeAppCache<T>(namespace: string, key: string): T | null;
10
+ /**
11
+ * JSON-serializes and stores `value` under `${namespace}:${key}` in
12
+ * `localStorage`. Fails silently if storage is unavailable, disabled, or
13
+ * full.
14
+ */
15
+ export declare function writeEdgeAppCache(namespace: string, key: string, value: unknown): void;
16
+ //# sourceMappingURL=edge-app-cache.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"edge-app-cache.d.ts","sourceRoot":"","sources":["../../src/utils/edge-app-cache.ts"],"names":[],"mappings":"AASA;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,CAAC,GAAG,IAAI,CAU5E;AAED;;;;GAIG;AACH,wBAAgB,iBAAiB,CAC/B,SAAS,EAAE,MAAM,EACjB,GAAG,EAAE,MAAM,EACX,KAAK,EAAE,OAAO,GACb,IAAI,CAWN"}
@@ -0,0 +1,49 @@
1
+ function getStorage() {
2
+ try {
3
+ if (typeof localStorage === 'undefined')
4
+ return null;
5
+ return localStorage;
6
+ }
7
+ catch {
8
+ return null;
9
+ }
10
+ }
11
+ /**
12
+ * Reads and JSON-parses the value stored under `${namespace}:${key}` in
13
+ * `localStorage`, or `null` if missing, unreadable, or storage is
14
+ * unavailable. Meant for last-known-good fallback data for Edge Apps:
15
+ * consult it only after a genuine fetch failure, not as a general-purpose
16
+ * expiring cache — there is no TTL, and `localStorage` itself isn't
17
+ * guaranteed to survive a device reboot.
18
+ */
19
+ export function readEdgeAppCache(namespace, key) {
20
+ const storage = getStorage();
21
+ if (!storage)
22
+ return null;
23
+ try {
24
+ const raw = storage.getItem(`${namespace}:${key}`);
25
+ return raw ? JSON.parse(raw) : null;
26
+ }
27
+ catch {
28
+ return null;
29
+ }
30
+ }
31
+ /**
32
+ * JSON-serializes and stores `value` under `${namespace}:${key}` in
33
+ * `localStorage`. Fails silently if storage is unavailable, disabled, or
34
+ * full.
35
+ */
36
+ export function writeEdgeAppCache(namespace, key, value) {
37
+ const storage = getStorage();
38
+ if (!storage)
39
+ return;
40
+ try {
41
+ const serialized = JSON.stringify(value);
42
+ if (serialized === undefined)
43
+ return;
44
+ storage.setItem(`${namespace}:${key}`, serialized);
45
+ }
46
+ catch {
47
+ // Storage disabled or quota exceeded; caller simply has nothing cached.
48
+ }
49
+ }
@@ -1,5 +1,6 @@
1
1
  export * from './calendar.js';
2
2
  export * from './error-handling.js';
3
+ export * from './edge-app-cache.js';
3
4
  export * from './html.js';
4
5
  export * from './theme.js';
5
6
  export * from './locale.js';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/utils/index.ts"],"names":[],"mappings":"AAAA,cAAc,eAAe,CAAA;AAC7B,cAAc,qBAAqB,CAAA;AACnC,cAAc,qBAAqB,CAAA;AACnC,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,YAAY,CAAA;AAC1B,cAAc,aAAa,CAAA;AAC3B,cAAc,eAAe,CAAA;AAC7B,cAAc,aAAa,CAAA;AAC3B,cAAc,cAAc,CAAA;AAC5B,cAAc,UAAU,CAAA;AACxB,cAAc,eAAe,CAAA"}
@@ -1,5 +1,6 @@
1
1
  export * from './calendar.js';
2
2
  export * from './error-handling.js';
3
+ export * from './edge-app-cache.js';
3
4
  export * from './html.js';
4
5
  export * from './theme.js';
5
6
  export * from './locale.js';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@screenly/edge-apps",
3
- "version": "1.2.1",
3
+ "version": "1.4.0",
4
4
  "description": "A TypeScript library for interfacing with Screenly Edge Apps API",
5
5
  "type": "module",
6
6
  "sideEffects": [
@@ -104,7 +104,7 @@
104
104
  "jsdom": "^28.1.0",
105
105
  "offline-geocode-city": "^1.0.2",
106
106
  "panic-overlay": "^1.0.51",
107
- "sharp": "^0.34.5",
107
+ "sharp": "^0.35.3",
108
108
  "tailwindcss": "^4.2.1",
109
109
  "typescript": "^5.9.3",
110
110
  "typescript-eslint": "^8.57.0",
@@ -16,6 +16,8 @@ Install dependencies:
16
16
  {{PM_RUN}} dev
17
17
  ```
18
18
 
19
+ Styling uses [Tailwind CSS](https://tailwindcss.com/) utility classes, enabled via the `tailwindcss/theme.css` and `tailwindcss/utilities.css` imports in `src/style.css`. Inside `<auto-scaler>`, use `h-full`/`w-full` rather than `h-screen`/`w-screen` — see the [`@screenly/edge-apps` README](https://github.com/Screenly/edge-apps-library#styling-with-tailwind-css) for details.
20
+
19
21
  ## Build
20
22
 
21
23
  ```bash
@@ -12,10 +12,10 @@
12
12
  reference-height="1080"
13
13
  orientation="auto"
14
14
  >
15
- <div id="app">
15
+ <div id="app" class="flex h-full w-full flex-col">
16
16
  <app-header show-date></app-header>
17
- <main class="content">
18
- <h1 id="message"></h1>
17
+ <main class="flex flex-1 items-center justify-center">
18
+ <h1 id="message" class="text-6xl portrait:text-4xl"></h1>
19
19
  </main>
20
20
  </div>
21
21
  </auto-scaler>
@@ -1,30 +1,22 @@
1
- @import '@screenly/edge-apps/styles';
1
+ @layer theme, base, utilities;
2
2
 
3
- * {
4
- box-sizing: border-box;
5
- }
3
+ @import 'tailwindcss/theme.css' layer(theme);
4
+ @import '@screenly/edge-apps/styles' layer(base);
5
+ @import 'tailwindcss/utilities.css' layer(utilities);
6
6
 
7
- body {
8
- margin: 0;
9
- padding: 0;
10
- overflow: hidden;
11
- font-family: 'Inter', system-ui, sans-serif;
12
- }
7
+ @layer base {
8
+ * {
9
+ box-sizing: border-box;
10
+ }
13
11
 
14
- #app {
15
- display: flex;
16
- flex-direction: column;
17
- width: 100%;
18
- height: 100%;
19
- }
20
-
21
- .content {
22
- flex: 1;
23
- display: flex;
24
- align-items: center;
25
- justify-content: center;
26
- }
12
+ body {
13
+ margin: 0;
14
+ padding: 0;
15
+ overflow: hidden;
16
+ font-family: 'Inter', system-ui, sans-serif;
17
+ }
27
18
 
28
- #message {
29
- color: var(--theme-color-primary, #972eff);
19
+ #message {
20
+ color: var(--theme-color-primary, #972eff);
21
+ }
30
22
  }