Skip to content

Commit c117f9a

Browse files
vpeterssonnicomiguelinoCopilot
authored
feat(cap-alerting): adds Common Alerting Protocol (CAP) 1.2 Edge App (#559)
Co-authored-by: Nico Miguelino <nicomiguelino2014@gmail.com> Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent ead35f9 commit c117f9a

35 files changed

Lines changed: 5047 additions & 1 deletion

.prettierignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
**/*.min.js
2+
edge-apps/cap-alerting/
23
edge-apps/edge-apps-library/
34
edge-apps/grafana/
45
edge-apps/powerbi-legacy/

edge-apps/cap-alerting/.gitignore

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
node_modules/
2+
dist/
3+
*.log
4+
.DS_Store
5+
mock-data.yml
6+
instance.yml
7+
bun.lockb
8+
static/js/*.js
9+
static/js/*.js.map
10+
static/style.css

edge-apps/cap-alerting/.ignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
node_modules/

edge-apps/cap-alerting/.prettierrc

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"semi": false,
3+
"singleQuote": true,
4+
"printWidth": 100,
5+
"trailingComma": "es5"
6+
}

edge-apps/cap-alerting/README.md

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# CAP Alerting Edge App
2+
3+
Display Common Alerting Protocol (CAP) emergency alerts on Screenly digital signage screens. Designed to work with [Override Playlist](https://developer.screenly.io/api-reference/v4/#tag/Playlists/operation/override_playlist) to automatically interrupt regular content when alerts are active.
4+
5+
## Settings
6+
7+
- **CAP Feed URL**: URL or relative path to your CAP XML feed (required)
8+
- **Display Errors**: Show errors on screen for debugging purposes (default: `false`, advanced setting)
9+
- **Default Language**: Preferred language code when multiple languages are available (default: `en`)
10+
- **Maximum Alerts**: Maximum number of alerts to display simultaneously (default: `Infinity`)
11+
- **Mode**: Operation mode - Production, Demo, or Test (default: `production`)
12+
- **Refresh Interval**: Minutes between feed updates (default: `5`)
13+
14+
## Modes
15+
16+
- **Production**: Fetches CAP data from the configured feed URL with offline caching support
17+
- **Demo**: Displays random demo alerts (ignores feed URL if left empty)
18+
- **Test**: Displays a static test alert for development and testing
19+
20+
## Nearest Exit Tags
21+
22+
Add tags to your Screenly screens (e.g., `exit:North Lobby`) to provide location-aware exit directions. The app substitutes `{{closest_exit}}` or `[[closest_exit]]` placeholders in alert instructions.
23+
24+
## NWS Text Product Formatting
25+
26+
The app automatically detects and formats National Weather Service (NWS) CAP alerts that use legacy text formats. This improves readability by converting abbreviated markers into clean, readable text with proper spacing and line breaks.
27+
28+
### Supported Formats
29+
30+
**1. Period-based Forecasts** (marine forecasts, zone forecasts)
31+
32+
Markers: `.TODAY...`, `.TONIGHT...`, `.MON...`, `.SUN NIGHT...`, etc.
33+
34+
Example transformation:
35+
36+
```text
37+
.TODAY...E wind 20 kt. Seas 11 ft. .TONIGHT...E wind 20 kt.
38+
```
39+
40+
becomes:
41+
42+
```text
43+
TODAY: E wind 20 kt. Seas 11 ft.
44+
45+
TONIGHT: E wind 20 kt.
46+
```
47+
48+
**2. Impact Based Warnings (WWWI format)**
49+
50+
Markers: `* WHAT...`, `* WHERE...`, `* WHEN...`, `* IMPACTS...`
51+
52+
Example transformation:
53+
54+
```text
55+
* WHAT...North winds 25 to 30 kt. * WHERE...Coastal waters. * WHEN...Until 3 AM.
56+
```
57+
58+
becomes:
59+
60+
```text
61+
WHAT: North winds 25 to 30 kt.
62+
63+
WHERE: Coastal waters.
64+
65+
WHEN: Until 3 AM.
66+
```
67+
68+
This formatting only applies to CAP alerts from the NWS sender (`w-nws.webmaster@noaa.gov`).
69+
70+
## Override Playlist Integration
71+
72+
This app is designed to use Screenly's [Override Playlist API](https://developer.screenly.io/api-reference/v4/#tag/Playlists/operation/override_playlist) to automatically interrupt regular content when alerts are active. Configure your backend to call the API when new CAP alerts are detected.
73+
74+
## Development
75+
76+
```bash
77+
cd edge-apps/cap-alerting
78+
bun install
79+
bun run dev
80+
bun test
81+
```

edge-apps/cap-alerting/bun.lock

Lines changed: 386 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

edge-apps/cap-alerting/index.html

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
<!doctype html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<title>CAP Alerting - Screenly Edge App</title>
6+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
7+
<link rel="stylesheet" href="dist/css/style.css" />
8+
</head>
9+
<body class="m-0 p-0 overflow-hidden">
10+
<div id="app" class="w-full h-screen flex items-center justify-center">
11+
<div id="alerts" class="w-full h-full flex flex-col"></div>
12+
</div>
13+
14+
<!-- Alert Card Template -->
15+
<template id="alert-card-template">
16+
<div
17+
class="alert-card w-full h-full bg-white flex flex-col overflow-y-auto"
18+
>
19+
<div id="status-banner-container"></div>
20+
<div id="header-row-container"></div>
21+
<h3
22+
id="headline"
23+
class="font-extrabold leading-tight flex-shrink-0 headline-text text-gray-900 mx-[5vw] mb-[1.5vh]"
24+
></h3>
25+
<p
26+
id="description"
27+
class="font-semibold leading-snug body-text text-gray-800 mx-[5vw] mb-[2vh]"
28+
></p>
29+
<div id="instruction-container"></div>
30+
<div id="resources-container"></div>
31+
</div>
32+
</template>
33+
34+
<!-- Status Banner Template -->
35+
<template id="status-banner-template">
36+
<div
37+
class="w-full text-center font-black uppercase tracking-[0.15em] flex-shrink-0 status-stripe-pattern status-banner-text py-[2.5vh] px-[4vw] text-white"
38+
></div>
39+
</template>
40+
41+
<!-- Header Row Template -->
42+
<template id="header-row-template">
43+
<div
44+
class="flex items-center justify-between gap-[2vw] mx-[5vw] mt-[2vh] mb-[1.5vh]"
45+
>
46+
<h2
47+
class="text-red-600 font-black uppercase leading-none event-title-text"
48+
></h2>
49+
<div
50+
class="severity-badge inline-block text-white rounded-xl font-black uppercase tracking-wider flex-shrink-0 severity-badge-text px-[4vw] py-[2vh]"
51+
></div>
52+
</div>
53+
</template>
54+
55+
<!-- Instruction Box Template -->
56+
<template id="instruction-box-template">
57+
<div
58+
class="instruction-box rounded-xl flex-shrink-0 px-[4vw] py-[2.5vh] mx-[5vw] mb-[2vh]"
59+
></div>
60+
</template>
61+
62+
<!-- Instruction List Template -->
63+
<template id="instruction-list-template">
64+
<ul class="instruction-text leading-snug text-gray-900"></ul>
65+
</template>
66+
67+
<!-- Instruction List Item Template -->
68+
<template id="instruction-list-item-template">
69+
<li class="mb-[1vh] flex items-start">
70+
<span class="mr-[2vw] flex-shrink-0 font-black text-orange-600"></span>
71+
<span class="flex-1 font-extrabold"></span>
72+
</li>
73+
</template>
74+
75+
<!-- Instruction Paragraph Template -->
76+
<template id="instruction-paragraph-template">
77+
<p
78+
class="instruction-text font-extrabold leading-snug whitespace-pre-line text-gray-900"
79+
></p>
80+
</template>
81+
82+
<!-- Image Resource Template -->
83+
<template id="image-resource-template">
84+
<div class="mx-[5vw] my-[2vh]">
85+
<img
86+
class="max-w-full max-h-[20vh] rounded-2xl object-contain shadow-lg"
87+
crossorigin="anonymous"
88+
/>
89+
</div>
90+
</template>
91+
92+
<!-- NWS Forecast Content Template (with preamble) -->
93+
<template id="nws-forecast-template">
94+
<div
95+
class="nws-content font-semibold leading-snug body-text text-gray-800 mx-[5vw] mb-[2vh]"
96+
>
97+
<p class="nws-preamble"></p>
98+
<ul class="nws-forecast-list"></ul>
99+
</div>
100+
</template>
101+
102+
<!-- NWS Forecast List Item Template -->
103+
<template id="nws-forecast-item-template">
104+
<li>
105+
<strong class="period-label"></strong>
106+
<span class="period-content"></span>
107+
</li>
108+
</template>
109+
110+
<!-- NWS WWWI Content Template -->
111+
<template id="nws-wwwi-template">
112+
<ul
113+
class="nws-wwwi-list font-semibold leading-snug body-text text-gray-800 mx-[5vw] mb-[2vh]"
114+
></ul>
115+
</template>
116+
117+
<!-- NWS WWWI List Item Template -->
118+
<template id="nws-wwwi-item-template">
119+
<li>
120+
<strong class="wwwi-label"></strong>
121+
<span class="wwwi-content"></span>
122+
</li>
123+
</template>
124+
125+
<script src="screenly.js?version=1"></script>
126+
<script src="dist/js/main.js"></script>
127+
</body>
128+
</html>
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
{
2+
"name": "cap-alerting",
3+
"version": "1.0.0",
4+
"private": true,
5+
"type": "module",
6+
"scripts": {
7+
"prebuild": "bun run type-check",
8+
"generate-mock-data": "screenly edge-app run --generate-mock-data",
9+
"predev": "bun run generate-mock-data && edge-apps-scripts build",
10+
"dev": "run-p build:dev edge-app-server cors-proxy-server",
11+
"cors-proxy-server": "bun ../blueprint/scripts/cors-proxy-server.ts",
12+
"edge-app-server": "screenly edge-app run",
13+
"build": "edge-apps-scripts build",
14+
"build:dev": "edge-apps-scripts build:dev",
15+
"build:prod": "edge-apps-scripts build",
16+
"test": "bun test",
17+
"test:unit": "bun test",
18+
"lint": "edge-apps-scripts lint --fix",
19+
"format": "prettier --write src/ README.md index.html",
20+
"format:check": "prettier --check src/ README.md index.html",
21+
"deploy": "bun run build && screenly edge-app deploy",
22+
"type-check": "edge-apps-scripts type-check",
23+
"prepare": "cd ../edge-apps-library && bun install && bun run build"
24+
},
25+
"dependencies": {
26+
"fast-xml-parser": "^5.3.2"
27+
},
28+
"prettier": "../edge-apps-library/.prettierrc.json",
29+
"devDependencies": {
30+
"@screenly/edge-apps": "workspace:../edge-apps-library",
31+
"@types/bun": "^1.3.5",
32+
"@types/jsdom": "^27.0.0",
33+
"bun-types": "^1.3.5",
34+
"jsdom": "^27.4.0",
35+
"npm-run-all2": "^8.0.4",
36+
"prettier": "^3.7.4",
37+
"typescript": "^5.9.3"
38+
}
39+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
---
2+
syntax: manifest_v1
3+
description: Display CAP emergency alerts on Screenly screens. Supports offline mode and uses screen tags (e.g., exit:North Lobby) to highlight the nearest exit.
4+
icon: https://playground.srly.io/edge-apps/cap-alerting/static/cap-icon.svg
5+
author: Screenly, Inc.
6+
entrypoint:
7+
type: file
8+
ready_signal: true
9+
settings:
10+
cap_feed_url:
11+
type: string
12+
default_value: ''
13+
title: CAP Feed URL
14+
optional: false
15+
help_text:
16+
properties:
17+
help_text: URL or relative path to a CAP XML feed.
18+
type: string
19+
schema_version: 1
20+
display_errors:
21+
type: string
22+
default_value: 'false'
23+
title: Display Errors
24+
optional: true
25+
help_text:
26+
properties:
27+
advanced: true
28+
help_text: For debugging purposes to display errors on the screen.
29+
type: boolean
30+
schema_version: 1
31+
language:
32+
type: string
33+
default_value: en
34+
title: Default Language
35+
optional: true
36+
help_text:
37+
properties:
38+
help_text: Choose the preferred language when multiple info blocks exist (e.g., en, es, fr).
39+
type: string
40+
schema_version: 1
41+
max_alerts:
42+
type: string
43+
default_value: Infinity
44+
title: Maximum Alerts
45+
optional: true
46+
help_text:
47+
properties:
48+
help_text: Maximum number of alerts to display simultaneously.
49+
type: number
50+
schema_version: 1
51+
mode:
52+
type: string
53+
default_value: production
54+
title: Mode
55+
optional: true
56+
help_text:
57+
properties:
58+
help_text: Select the operation mode for the app.
59+
options:
60+
- label: Production
61+
value: production
62+
- label: Demo
63+
value: demo
64+
- label: Test
65+
value: test
66+
type: select
67+
schema_version: 1
68+
refresh_interval:
69+
type: string
70+
default_value: '5'
71+
title: Refresh Interval (minutes)
72+
optional: true
73+
help_text:
74+
properties:
75+
help_text: Time in minutes between feed updates.
76+
type: number
77+
schema_version: 1

0 commit comments

Comments
 (0)