-
Notifications
You must be signed in to change notification settings - Fork 12
Adds Common Alerting Protocol (CAP) 1.2 Edge App #559
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Conversation
PR Reviewer Guide 🔍Here are some key observations to aid the review process:
|
PR Code Suggestions ✨Explore these optional code suggestions:
|
|
As discussed, I'll continue on this PR from this point forward (i.e., refine implementation, write tests, increase coverage). |
edge-apps/cap-alerting/screenly.yml
Outdated
| title: Offline Mode | ||
| optional: true | ||
| default_value: 'false' | ||
| help_text: When enabled, avoid network fetches and use cached data (true/false). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🤔 Let's ensure that our system can work offline by default. Otherwise, human factors could affect its reliability.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
libedgeapp
The change was made by Bun when `bun install` was executed.
- Include `cap-alerting/` in `.gitignore` - Have the unit tests be run via `bun run test:unit` instead
- Extract common build flags into shared scripts to reduce duplication - Add build:css:common and build:js:common scripts - Add watch mode for CSS and JS builds - Add npm-run-all2 dependency for parallel task execution
- Update isAnywhereScreen to check for empty string or undefined - Use isAnywhereScreen in cap-alerting demo mode logic
- Extract CAP type definitions into separate types/cap.ts file - Use getTags() helper instead of direct metadata access
… setting - Replace separate demo_mode and test_mode boolean settings with single mode enumeration - Mode supports three values: production, demo, test - Add CAPMode type for type-safe mode handling - Update screenly.yml and screenly_qc.yml with mode setting as select type - Update src/main.ts and index.ts to use CAPMode type
- Delete legacy index.ts entry point - Delete orphaned CAPFetcher implementation (fetcher.ts) - Delete comprehensive CAPFetcher test suite (fetcher.test.ts) - Update index.test.ts to use modern @screenly/edge-apps imports - Update package.json lint script to reflect active files only
- Create src/fetcher.ts with CAPFetcher class - Extract test, demo, and live data fetching logic - Create comprehensive test suite in src/fetcher.test.ts - Update main.ts to use CAPFetcher instead of inline logic - Remove DEMO_BASE_URL, fetchCapData, and related inline fetch code - Update package.json lint script to include fetcher files
…odule - Create src/parser.ts with parseCap function - Rename index.test.ts to src/parser.test.ts with CAP parsing tests - Update src/main.ts to import parseCap from parser module - Update package.json lint script to reference new parser files
- Use getSettingWithDefault for all settings parsing in main.ts - Extract getNearestExit, highlightKeywords, splitIntoSentences, and proxyUrl to separate files - Create render.ts for rendering utilities - Create utils.ts for shared utility functions - Add abbreviation support to splitIntoSentences for better sentence parsing - Move getNearestExit tests to utils.test.ts - Add comprehensive unit tests for splitIntoSentences and proxyUrl - Update package.json lint script to use wildcard pattern for TypeScript files - Fix type-check errors by replacing replaceAll with replace
- Rename audio_alert to mute_sound in both screenly.yml and screenly_qc.yml - Update help_text to use boolean type structure for toggle switch display - Invert logic in main.ts: playAudio = !getSettingWithDefault(mute_sound, false)
- Update all settings to use structured help_text format - Add type information (string, number, boolean, select) to all settings - Remove (true/false) notation from boolean settings - Ensures consistent UI presentation for all setting types
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR introduces a comprehensive Common Alerting Protocol (CAP) v1.2 edge app for displaying emergency alerts on Screenly digital signage screens. The implementation includes robust XML parsing, caching mechanisms, and multiple operating modes (test, demo, production).
Key changes:
- Complete CAP v1.2 parser with support for all standard fields and multi-language alerts
- Fetcher with caching, offline mode, and demo capabilities
- Responsive UI optimized for digital signage displays using viewport-based typography
Reviewed changes
Copilot reviewed 31 out of 33 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| edge-apps/edge-apps-library/src/utils/metadata.ts | Adds isAnywhereScreen() helper to detect Anywhere screens |
| edge-apps/cap-alerting/src/parser.ts | CAP XML parser supporting all v1.2 fields |
| edge-apps/cap-alerting/src/fetcher.ts | Feed fetcher with caching and multiple modes |
| edge-apps/cap-alerting/src/main.ts | Main app logic and alert rendering |
| edge-apps/cap-alerting/src/utils.ts | Utility functions for exit tags and text processing |
| edge-apps/cap-alerting/src/render.ts | Keyword highlighting for emergency instructions |
| edge-apps/cap-alerting/src/types/cap.ts | TypeScript interfaces for CAP data structures |
| edge-apps/cap-alerting/src/input.css | Viewport-optimized styles for digital signage |
| static/*.cap | Demo CAP alert files for various emergency scenarios |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| test('should return true when hardware is empty string', () => { | ||
| setupScreenlyMock({ | ||
| coordinates: [37.3861, -122.0839], | ||
| hostname: 'test-host', | ||
| location: 'Test Location', | ||
| hardware: '', | ||
| screenly_version: '1.2.3', | ||
| screen_name: 'Main Screen', | ||
| tags: [], | ||
| }) | ||
| expect(isAnywhereScreen()).toBe(true) | ||
| }) |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The isAnywhereScreen() function also checks for hardware === undefined, but there's no test case covering when hardware is undefined. Add a test case that validates the function returns true when hardware is undefined.
| } catch (_) { | ||
| const cached = localStorage.getItem('screenly_settings') | ||
| settings = cached | ||
| ? (JSON.parse(cached) as Partial<ReturnType<typeof getSettings>>) | ||
| : {} | ||
| } |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error is caught but ignored without logging. Consider logging the error to help diagnose issues when getSettings() fails, especially in production environments where debugging is more difficult.
| } catch (_) { | ||
| const cachedMeta = localStorage.getItem('screenly_metadata') | ||
| metadata = cachedMeta | ||
| ? (JSON.parse(cachedMeta) as Partial<ReturnType<typeof getMetadata>>) | ||
| : {} | ||
| } |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error is caught but ignored without logging. Consider logging the error to help diagnose issues when getMetadata() fails, especially in production environments where debugging is more difficult.
| "@photostructure/tz-lookup": "^11.3.0", | ||
| "country-locale-map": "^1.9.11", | ||
| "fast-xml-parser": "^5.3.2", | ||
| "offline-geocode-city": "^1.0.2" |
Copilot
AI
Dec 30, 2025
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The dependencies @photostructure/tz-lookup, country-locale-map, and offline-geocode-city are declared but not imported or used anywhere in the codebase. Consider removing these unused dependencies to reduce bundle size and maintenance overhead.
| "@photostructure/tz-lookup": "^11.3.0", | |
| "country-locale-map": "^1.9.11", | |
| "fast-xml-parser": "^5.3.2", | |
| "offline-geocode-city": "^1.0.2" | |
| "fast-xml-parser": "^5.3.2" |
| export function isAnywhereScreen(): boolean { | ||
| return ( | ||
| screenly.metadata.hardware === '' || | ||
| screenly.metadata.hardware === undefined | ||
| ) | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@rusko124, we should make the screen type part of the metadata
| const container = document.getElementById('alerts') | ||
| if (!container) return |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this display an error when the debug mode enabled, or will it silently die?
It looks like there could be more similar issues.
Fail fast, at least in debug mode.
| import { getNearestExit, splitIntoSentences, proxyUrl } from './utils' | ||
| import { highlightKeywords } from './render' | ||
|
|
||
| function renderAlerts( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please, don't make functions too big. Refactor if it gets over 70 lines.
| import { getNearestExit, splitIntoSentences, proxyUrl } from './utils' | ||
| import { highlightKeywords } from './render' | ||
|
|
||
| function renderAlerts( |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I feel like the render should be done using a template language, not by using JS.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would content templates be a good fit for this?
User description
Still requires testing, but the foundation is in here.
PR Type
Tests, Enhancement
Description
Add comprehensive CAP v1.2 parser tests
Implement robust CAP feed fetcher
Add fetcher unit tests with caching
Configure project and tooling
Diagram Walkthrough
File Walkthrough
3 files
Comprehensive CAP v1.2 parser test suiteUnit tests for CAPFetcher caching and retriesTest CAP sample file5 files
Implement CAP feed fetcher with cache and backoffApp HTML shell and script includesEdge app bootstrap and integrationCAP XML parsing and app logicCompiled frontend logic bundle7 files
Add TypeScript ESLint configurationTailwind configuration with extended breakpointsTypeScript compiler configuration for appPrettier formatting configurationProject package metadata and scriptsScreenly app manifestScreenly QC configuration7 files
Add demo CAP alert: active shooter scenarioAdd demo CAP alert: hazmat spillAdd demo CAP alert: flood warningAdd demo CAP alert: earthquake advisoryAdd demo CAP alert: tornado warningAdd demo CAP alert: fire emergencyDocumentation for CAP Alerting app2 files
Compiled stylesheet for app UITailwind input styles