@screenly/edge-apps 1.0.0 → 1.1.1

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
@@ -97,6 +97,12 @@ signalReady()
97
97
  - `addUTMParams(url, params?)` - Add UTM parameters to URL
98
98
  - `addUTMParamsIf(url, enabled, params?)` - Conditionally add UTM parameters
99
99
 
100
+ ### Error Reporting (Sentry)
101
+
102
+ - `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.
103
+ - `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.
104
+ - `reportError(error, context?)` - Capture an exception via Sentry with optional extra context.
105
+
100
106
  ## Web Components
101
107
 
102
108
  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.
@@ -1,3 +1,2 @@
1
1
  export * from './mock.js';
2
- export * from './setup.js';
3
2
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AAAA,cAAc,WAAW,CAAA;AACzB,cAAc,YAAY,CAAA"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/test/index.ts"],"names":[],"mappings":"AA+BA,cAAc,WAAW,CAAA"}
@@ -1,2 +1,18 @@
1
+ import { JSDOM } from 'jsdom';
2
+ // Preserve the side-effect import contract of `@screenly/edge-apps/test`
3
+ // for downstream Bun tests that need DOM globals.
4
+ const g = globalThis;
5
+ if (typeof g.window === 'undefined' || typeof g.document === 'undefined') {
6
+ const dom = new JSDOM('<!DOCTYPE html><html><head></head><body></body></html>', {
7
+ url: 'http://localhost',
8
+ });
9
+ g.window = dom.window;
10
+ g.document = dom.window.document;
11
+ g.navigator = dom.window.navigator;
12
+ g.Node = dom.window.Node;
13
+ g.HTMLElement = dom.window.HTMLElement;
14
+ g.customElements = dom.window.customElements;
15
+ g.getComputedStyle = dom.window.getComputedStyle;
16
+ g.SVGSVGElement = dom.window.SVGSVGElement;
17
+ }
1
18
  export * from './mock.js';
2
- export * from './setup.js';
@@ -5,6 +5,7 @@ export * from './theme.js';
5
5
  export * from './locale.js';
6
6
  export * from './metadata.js';
7
7
  export * from './oauth.js';
8
+ export * from './sentry.js';
8
9
  export * from './settings.js';
9
10
  export * from './screen.js';
10
11
  export * from './weather.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,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,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"}
@@ -5,6 +5,7 @@ export * from './theme.js';
5
5
  export * from './locale.js';
6
6
  export * from './metadata.js';
7
7
  export * from './oauth.js';
8
+ export * from './sentry.js';
8
9
  export * from './settings.js';
9
10
  export * from './screen.js';
10
11
  export * from './weather.js';
@@ -0,0 +1,5 @@
1
+ import type { ErrorEvent } from '@sentry/browser';
2
+ export declare function scrubSensitiveData(event: ErrorEvent): ErrorEvent | null;
3
+ export declare function setupSentry(app: string, contexts?: Record<string, Record<string, unknown>>): void;
4
+ export declare function reportError(error: unknown, context?: Record<string, unknown>): void;
5
+ //# sourceMappingURL=sentry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sentry.d.ts","sourceRoot":"","sources":["../../src/utils/sentry.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAA;AAWjD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,UAAU,GAAG,IAAI,CAwBvE;AAED,wBAAgB,WAAW,CACzB,GAAG,EAAE,MAAM,EACX,QAAQ,GAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAM,GACrD,IAAI,CAqBN;AAED,wBAAgB,WAAW,CACzB,KAAK,EAAE,OAAO,EACd,OAAO,GAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAM,GACpC,IAAI,CAEN"}
@@ -0,0 +1,55 @@
1
+ import * as Sentry from '@sentry/browser';
2
+ import { getHostname } from './metadata.js';
3
+ import { getSetting, getSettings } from './settings.js';
4
+ const SENSITIVE_KEY_PATTERNS = ['token', 'secret', 'password', 'credential'];
5
+ function isSensitiveKey(key) {
6
+ const lowerKey = key.toLowerCase();
7
+ return SENSITIVE_KEY_PATTERNS.some((pattern) => lowerKey.includes(pattern));
8
+ }
9
+ export function scrubSensitiveData(event) {
10
+ const settings = getSettings();
11
+ const secrets = Object.keys(settings)
12
+ .filter(isSensitiveKey)
13
+ .map((key) => settings[key])
14
+ .filter((value) => typeof value === 'string' && value !== '');
15
+ if (secrets.length === 0) {
16
+ return event;
17
+ }
18
+ try {
19
+ let serialized = JSON.stringify(event);
20
+ for (const secret of secrets) {
21
+ serialized = serialized.split(secret).join('[REDACTED]');
22
+ }
23
+ return JSON.parse(serialized);
24
+ }
25
+ catch {
26
+ // If the event can't be serialized to scrub it, drop it rather than risk
27
+ // sending an unredacted secret.
28
+ return null;
29
+ }
30
+ }
31
+ export function setupSentry(app, contexts = {}) {
32
+ const dsn = getSetting('sentry_dsn');
33
+ if (!dsn) {
34
+ return;
35
+ }
36
+ try {
37
+ Sentry.init({
38
+ dsn,
39
+ tracesSampleRate: 0,
40
+ beforeSend: scrubSensitiveData,
41
+ });
42
+ Sentry.setTag('edge_app', app);
43
+ Sentry.setTag('hostname', getHostname());
44
+ Sentry.setUser({ id: getHostname() });
45
+ for (const [name, data] of Object.entries(contexts)) {
46
+ Sentry.setContext(name, data);
47
+ }
48
+ }
49
+ catch (error) {
50
+ console.error('Sentry init failed', error);
51
+ }
52
+ }
53
+ export function reportError(error, context = {}) {
54
+ Sentry.captureException(error, { extra: context });
55
+ }
package/package.json CHANGED
@@ -1,8 +1,9 @@
1
1
  {
2
2
  "name": "@screenly/edge-apps",
3
- "version": "1.0.0",
3
+ "version": "1.1.1",
4
4
  "description": "A TypeScript library for interfacing with Screenly Edge Apps API",
5
5
  "type": "module",
6
+ "sideEffects": ["**/*.css", "**/register.js", "**/buffer-shim.js"],
6
7
  "engines": {
7
8
  "node": ">=20.6.0"
8
9
  },
@@ -87,19 +88,21 @@
87
88
  "access": "public"
88
89
  },
89
90
  "dependencies": {
91
+ "@eslint/js": "^10.0.1",
90
92
  "@photostructure/tz-lookup": "^11.5.0",
93
+ "@sentry/browser": "^10.59.0",
91
94
  "@tailwindcss/vite": "^4.2.1",
92
95
  "country-locale-map": "^1.9.12",
93
96
  "dayjs": "^1.11.19",
94
- "@eslint/js": "^10.0.1",
95
97
  "eslint": "^10.0.3",
96
98
  "jiti": "^2.6.1",
99
+ "jsdom": "^28.1.0",
97
100
  "offline-geocode-city": "^1.0.2",
98
- "typescript-eslint": "^8.57.0",
99
101
  "panic-overlay": "^1.0.51",
100
102
  "sharp": "^0.34.5",
101
103
  "tailwindcss": "^4.2.1",
102
104
  "typescript": "^5.9.3",
105
+ "typescript-eslint": "^8.57.0",
103
106
  "vite": "^7.3.1",
104
107
  "yaml": "^2.8.2"
105
108
  },
@@ -117,7 +120,6 @@
117
120
  "@types/sharp": "^0.32.0",
118
121
  "@vitest/coverage-v8": "^3.2.4",
119
122
  "globals": "^13.24.0",
120
- "jsdom": "^28.1.0",
121
123
  "prettier": "^3.8.1",
122
124
  "vitest": "^3.2.4"
123
125
  }
package/tsconfig.json CHANGED
@@ -14,7 +14,7 @@
14
14
  "forceConsistentCasingInFileNames": true,
15
15
  "resolveJsonModule": true,
16
16
  "allowSyntheticDefaultImports": true,
17
- "types": ["vite/client"]
17
+ "types": ["vite/client", "node"]
18
18
  },
19
19
  "include": ["src/**/*"],
20
20
  "exclude": ["node_modules", "dist", "**/*.test.ts"]
@@ -1,2 +0,0 @@
1
- export {};
2
- //# sourceMappingURL=setup.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"setup.d.ts","sourceRoot":"","sources":["../../src/test/setup.ts"],"names":[],"mappings":""}
@@ -1,13 +0,0 @@
1
- import { JSDOM } from 'jsdom';
2
- // Setup jsdom for DOM operations in tests
3
- const dom = new JSDOM('<!DOCTYPE html><html><head></head><body></body></html>', {
4
- url: 'http://localhost',
5
- });
6
- global.document = dom.window.document;
7
- global.window = dom.window;
8
- global.navigator = dom.window.navigator;
9
- global.Node = dom.window.Node;
10
- global.HTMLElement = dom.window.HTMLElement;
11
- global.customElements = dom.window.customElements;
12
- global.getComputedStyle = dom.window.getComputedStyle;
13
- global.SVGSVGElement = dom.window.SVGSVGElement;