Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
190ade4
CAP WIP
vpetersson Dec 6, 2025
0b1c557
More tweaks
vpetersson Dec 6, 2025
d3ea3c4
Merge branch 'master' into cap
nicomiguelino Dec 26, 2025
47880db
chore(cap-alerting): add a `configVersion` field in `bun.lock`
nicomiguelino Dec 26, 2025
b3669f0
chore(cap-alerting): remove app IDs from manifest files
nicomiguelino Dec 26, 2025
3d51c1f
chore(cap-alerting): ignore generated JS and CSS files in Git
nicomiguelino Dec 26, 2025
3f0057f
chore(cap-alerting): change formatting rules
nicomiguelino Dec 26, 2025
b733519
chore(cap-alerting): add a `package.json` script for only checking fo…
nicomiguelino Dec 26, 2025
28f31e4
chore(cap-alerting): fix failures in CI checks
nicomiguelino Dec 26, 2025
e8893c1
refactor: add watch mode for build scripts in cap-alerting
nicomiguelino Dec 26, 2025
3c80359
chore(cap-alerting): reorganize fields in manifest files
nicomiguelino Dec 26, 2025
09d1a67
fix: improve Anywhere screen detection
nicomiguelino Dec 26, 2025
b6ab84b
chore(cap-alerting): refactor interface declarations into a separate …
nicomiguelino Dec 26, 2025
0d0f179
refactor: organize cap-alerting and improve code quality
nicomiguelino Dec 27, 2025
a573831
refactor(cap-alerting): consolidate demo_mode and test_mode into mode…
nicomiguelino Dec 27, 2025
437a3bd
refactor(cap-alerting): remove unused legacy code and orphaned fetcher
nicomiguelino Dec 27, 2025
04ad763
refactor(cap-alerting): extract fetching logic into new CAPFetcher class
nicomiguelino Dec 27, 2025
b3f186a
chore(cap-alerting): remove `.js` extension from imports
nicomiguelino Dec 27, 2025
efc6251
refactor(cap-alerting): extract parsing logic into dedicated parser m…
nicomiguelino Dec 27, 2025
86fb6b6
chore(cap-alerting): use `getSettingWithDefault` in `main.ts`
nicomiguelino Dec 27, 2025
ac14706
refactor: extract utility functions and improve settings handling
nicomiguelino Dec 27, 2025
9f4a659
refactor: rename audio_alert setting to mute_sound with inverted logic
nicomiguelino Dec 27, 2025
fa3f271
refactor: standardize manifest help_text structure
nicomiguelino Dec 28, 2025
fb72816
Merge branch 'cap' of github.com:Screenly/Playground into cap
nicomiguelino Dec 28, 2025
7011718
Merge branch 'master' into cap
nicomiguelino Dec 28, 2025
721f048
chore: start a local CORS proxy server when `bun run dev` runs
nicomiguelino Dec 28, 2025
2d4da43
chore(cap-alerting): set the default max alters to infinity
nicomiguelino Dec 28, 2025
cb021c7
Merge branch 'master' into cap
nicomiguelino Dec 29, 2025
4418cd8
Merge branch 'master' into cap
nicomiguelino Dec 31, 2025
a4da454
Merge branch 'master' into cap
nicomiguelino Jan 2, 2026
5e1f830
chore(cap-alerting): add a `prepare` hook in `package.json`
nicomiguelino Jan 2, 2026
303c30c
chore(cap-alerting): use `eslint` for linting code instead of `tsc`
nicomiguelino Jan 3, 2026
0581783
Merge branch 'master' into cap
nicomiguelino Jan 13, 2026
cb00fab
refactor(cap-alerting): migrate to edge-apps-library pattern
nicomiguelino Jan 13, 2026
d6d1c46
refactor(cap-alerting): break down functions to meet 70-line limit
nicomiguelino Jan 13, 2026
229de4e
refactor(cap-alerting): replace isAnywhereScreen with getHardware
nicomiguelino Jan 13, 2026
002ee5f
Merge branch 'master' into cap
nicomiguelino Jan 19, 2026
f1119b1
Merge branch 'master' into cap
nicomiguelino Jan 19, 2026
053255f
chore(cap-alerting): remove unused dependencies
nicomiguelino Jan 19, 2026
6804999
refactor(cap-alerting): use HTML templates instead of DOM APIs
nicomiguelino Jan 19, 2026
8f333b0
chore(cap-alerting): remove unused caching code
nicomiguelino Jan 19, 2026
ad0fad6
refactor(cap-alerting): remove offline_mode setting and always use ca…
nicomiguelino Jan 20, 2026
d0c93f0
Merge branch 'master' into cap
nicomiguelino Jan 20, 2026
d9d0650
refactor: remove audio playback from CAP alerting app
nicomiguelino Jan 20, 2026
bf87151
docs: update CAP alerting README settings documentation
nicomiguelino Jan 20, 2026
ab22033
Merge branch 'master' into cap
nicomiguelino Jan 22, 2026
32e4a80
chore(cap-alerting): add run-time type-checking and assignment of def…
nicomiguelino Jan 22, 2026
14d4137
fix(cap-alerting): fix DOMTokenList error with multiple status classes
nicomiguelino Jan 22, 2026
8d0dab7
test(cap-alerting): fix failing tests by importing the required library
nicomiguelino Jan 23, 2026
5817fb1
feat(cap-alerting): add display_errors setting for debugging
nicomiguelino Jan 23, 2026
678f113
Merge branch 'master' into cap
nicomiguelino Jan 23, 2026
56a0d98
Merge branch 'master' into cap
nicomiguelino Jan 23, 2026
5fbb268
fix(cap-alerting): throw error when no CAP data available
nicomiguelino Jan 23, 2026
4129a46
feat(cap-alerting): update mode default and fix test data fetching
nicomiguelino Jan 23, 2026
5ba0147
feat(cap-alerting): update settings and documentation
nicomiguelino Jan 23, 2026
b2ee35c
feat(cap-alerting): add NWS text product parsing for improved readabi…
nicomiguelino Jan 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
**/*.min.js
edge-apps/cap-alerting/
edge-apps/edge-apps-library/
edge-apps/grafana/
edge-apps/powerbi-legacy/
Expand Down
10 changes: 10 additions & 0 deletions edge-apps/cap-alerting/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules/
dist/
*.log
.DS_Store
mock-data.yml
instance.yml
bun.lockb
static/js/*.js
static/js/*.js.map
static/style.css
1 change: 1 addition & 0 deletions edge-apps/cap-alerting/.ignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules/
6 changes: 6 additions & 0 deletions edge-apps/cap-alerting/.prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 100,
"trailingComma": "es5"
}
81 changes: 81 additions & 0 deletions edge-apps/cap-alerting/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# CAP Alerting Edge App

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.

## Settings

- **CAP Feed URL**: URL or relative path to your CAP XML feed (required)
- **Display Errors**: Show errors on screen for debugging purposes (default: `false`, advanced setting)
- **Default Language**: Preferred language code when multiple languages are available (default: `en`)
- **Maximum Alerts**: Maximum number of alerts to display simultaneously (default: `Infinity`)
- **Mode**: Operation mode - Production, Demo, or Test (default: `production`)
- **Refresh Interval**: Minutes between feed updates (default: `5`)

## Modes

- **Production**: Fetches CAP data from the configured feed URL with offline caching support
- **Demo**: Displays random demo alerts (ignores feed URL if left empty)
- **Test**: Displays a static test alert for development and testing

## Nearest Exit Tags

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.

## NWS Text Product Formatting

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.

### Supported Formats

**1. Period-based Forecasts** (marine forecasts, zone forecasts)

Markers: `.TODAY...`, `.TONIGHT...`, `.MON...`, `.SUN NIGHT...`, etc.

Example transformation:

```text
.TODAY...E wind 20 kt. Seas 11 ft. .TONIGHT...E wind 20 kt.
```

becomes:

```text
TODAY: E wind 20 kt. Seas 11 ft.

TONIGHT: E wind 20 kt.
```

**2. Impact Based Warnings (WWWI format)**

Markers: `* WHAT...`, `* WHERE...`, `* WHEN...`, `* IMPACTS...`

Example transformation:

```text
* WHAT...North winds 25 to 30 kt. * WHERE...Coastal waters. * WHEN...Until 3 AM.
```

becomes:

```text
WHAT: North winds 25 to 30 kt.

WHERE: Coastal waters.

WHEN: Until 3 AM.
```

This formatting only applies to CAP alerts from the NWS sender (`w-nws.webmaster@noaa.gov`).

## Override Playlist Integration

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.

## Development

```bash
cd edge-apps/cap-alerting
bun install
bun run dev
bun test
```
386 changes: 386 additions & 0 deletions edge-apps/cap-alerting/bun.lock

Large diffs are not rendered by default.

128 changes: 128 additions & 0 deletions edge-apps/cap-alerting/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>CAP Alerting - Screenly Edge App</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<link rel="stylesheet" href="dist/css/style.css" />
</head>
<body class="m-0 p-0 overflow-hidden">
<div id="app" class="w-full h-screen flex items-center justify-center">
<div id="alerts" class="w-full h-full flex flex-col"></div>
</div>

<!-- Alert Card Template -->
<template id="alert-card-template">
<div
class="alert-card w-full h-full bg-white flex flex-col overflow-y-auto"
>
<div id="status-banner-container"></div>
<div id="header-row-container"></div>
<h3
id="headline"
class="font-extrabold leading-tight flex-shrink-0 headline-text text-gray-900 mx-[5vw] mb-[1.5vh]"
></h3>
<p
id="description"
class="font-semibold leading-snug body-text text-gray-800 mx-[5vw] mb-[2vh]"
></p>
<div id="instruction-container"></div>
<div id="resources-container"></div>
</div>
</template>

<!-- Status Banner Template -->
<template id="status-banner-template">
<div
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"
></div>
</template>

<!-- Header Row Template -->
<template id="header-row-template">
<div
class="flex items-center justify-between gap-[2vw] mx-[5vw] mt-[2vh] mb-[1.5vh]"
>
<h2
class="text-red-600 font-black uppercase leading-none event-title-text"
></h2>
<div
class="severity-badge inline-block text-white rounded-xl font-black uppercase tracking-wider flex-shrink-0 severity-badge-text px-[4vw] py-[2vh]"
></div>
</div>
</template>

<!-- Instruction Box Template -->
<template id="instruction-box-template">
<div
class="instruction-box rounded-xl flex-shrink-0 px-[4vw] py-[2.5vh] mx-[5vw] mb-[2vh]"
></div>
</template>

<!-- Instruction List Template -->
<template id="instruction-list-template">
<ul class="instruction-text leading-snug text-gray-900"></ul>
</template>

<!-- Instruction List Item Template -->
<template id="instruction-list-item-template">
<li class="mb-[1vh] flex items-start">
<span class="mr-[2vw] flex-shrink-0 font-black text-orange-600">•</span>
<span class="flex-1 font-extrabold"></span>
</li>
</template>

<!-- Instruction Paragraph Template -->
<template id="instruction-paragraph-template">
<p
class="instruction-text font-extrabold leading-snug whitespace-pre-line text-gray-900"
></p>
</template>

<!-- Image Resource Template -->
<template id="image-resource-template">
<div class="mx-[5vw] my-[2vh]">
<img
class="max-w-full max-h-[20vh] rounded-2xl object-contain shadow-lg"
crossorigin="anonymous"
/>
</div>
</template>

<!-- NWS Forecast Content Template (with preamble) -->
<template id="nws-forecast-template">
<div
class="nws-content font-semibold leading-snug body-text text-gray-800 mx-[5vw] mb-[2vh]"
>
<p class="nws-preamble"></p>
<ul class="nws-forecast-list"></ul>
</div>
</template>

<!-- NWS Forecast List Item Template -->
<template id="nws-forecast-item-template">
<li>
<strong class="period-label"></strong>
<span class="period-content"></span>
</li>
</template>

<!-- NWS WWWI Content Template -->
<template id="nws-wwwi-template">
<ul
class="nws-wwwi-list font-semibold leading-snug body-text text-gray-800 mx-[5vw] mb-[2vh]"
></ul>
</template>

<!-- NWS WWWI List Item Template -->
<template id="nws-wwwi-item-template">
<li>
<strong class="wwwi-label"></strong>
<span class="wwwi-content"></span>
</li>
</template>

<script src="screenly.js?version=1"></script>
<script src="dist/js/main.js"></script>
</body>
</html>
39 changes: 39 additions & 0 deletions edge-apps/cap-alerting/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "cap-alerting",
"version": "1.0.0",
"private": true,
"type": "module",
"scripts": {
"prebuild": "bun run type-check",
"generate-mock-data": "screenly edge-app run --generate-mock-data",
"predev": "bun run generate-mock-data && edge-apps-scripts build",
"dev": "run-p build:dev edge-app-server cors-proxy-server",
"cors-proxy-server": "bun ../blueprint/scripts/cors-proxy-server.ts",
"edge-app-server": "screenly edge-app run",
"build": "edge-apps-scripts build",
"build:dev": "edge-apps-scripts build:dev",
"build:prod": "edge-apps-scripts build",
"test": "bun test",
"test:unit": "bun test",
"lint": "edge-apps-scripts lint --fix",
"format": "prettier --write src/ README.md index.html",
"format:check": "prettier --check src/ README.md index.html",
"deploy": "bun run build && screenly edge-app deploy",
"type-check": "edge-apps-scripts type-check",
"prepare": "cd ../edge-apps-library && bun install && bun run build"
},
"dependencies": {
"fast-xml-parser": "^5.3.2"
},
"prettier": "../edge-apps-library/.prettierrc.json",
"devDependencies": {
"@screenly/edge-apps": "workspace:../edge-apps-library",
"@types/bun": "^1.3.5",
"@types/jsdom": "^27.0.0",
"bun-types": "^1.3.5",
"jsdom": "^27.4.0",
"npm-run-all2": "^8.0.4",
"prettier": "^3.7.4",
"typescript": "^5.9.3"
}
}
77 changes: 77 additions & 0 deletions edge-apps/cap-alerting/screenly.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
---
syntax: manifest_v1
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.
icon: https://playground.srly.io/edge-apps/cap-alerting/static/cap-icon.svg
author: Screenly, Inc.
entrypoint:
type: file
ready_signal: true
settings:
cap_feed_url:
type: string
default_value: ''
title: CAP Feed URL
optional: false
help_text:
properties:
help_text: URL or relative path to a CAP XML feed.
type: string
schema_version: 1
display_errors:
type: string
default_value: 'false'
title: Display Errors
optional: true
help_text:
properties:
advanced: true
help_text: For debugging purposes to display errors on the screen.
type: boolean
schema_version: 1
language:
type: string
default_value: en
title: Default Language
optional: true
help_text:
properties:
help_text: Choose the preferred language when multiple info blocks exist (e.g., en, es, fr).
type: string
schema_version: 1
max_alerts:
type: string
default_value: Infinity
title: Maximum Alerts
optional: true
help_text:
properties:
help_text: Maximum number of alerts to display simultaneously.
type: number
schema_version: 1
mode:
type: string
default_value: production
title: Mode
optional: true
help_text:
properties:
help_text: Select the operation mode for the app.
options:
- label: Production
value: production
- label: Demo
value: demo
- label: Test
value: test
type: select
schema_version: 1
refresh_interval:
type: string
default_value: '5'
title: Refresh Interval (minutes)
optional: true
help_text:
properties:
help_text: Time in minutes between feed updates.
type: number
schema_version: 1
Loading