A lightweight Express service that schedules and sends Firebase Cloud Messaging (FCM) push notifications to wake up registered devices.

Device registrations are stored in a local SQLite database (not JSON).

Dev

cp .env.example .env

Edit .env — set FIREBASE_SERVICE_ACCOUNT_JSON. Here is one way to generate the contents: cat your-downloaded-key.json | jq -c .

Optionally set ENDORSER_URL if you are not using the default production Endorser API (https://api.endorser.ch).

Optionally set FCM_TOKEN_DATA_DIR if you want the SQLite database somewhere other than ./data.

pnpm install
pnpm run dev

The server starts on http://localhost:3003 (or the port in PORT). Hot-reloads on file changes.

On first use, the service creates FCM_TOKEN_DATA_DIR (default ./data) and the SQLite file notify.sqlite with the required schema.

Authentication

POST /notifications/register and POST /notifications/refresh require a Bearer JWT. After local JWT verification, the service checks the token with Endorser (GET /api/report/rateLimits on ENDORSER_URL). Registration and refresh continue only if Endorser accepts the JWT.

Local notification test bypass: send testMode: true in the JSON body and omit the Authorization header. The request skips JWT and Endorser checks and uses a synthetic local test user, same as before.

Set NODE_ENV=test-local in .env to bypass ethr JWT expiry verification during local development (this is separate from the testMode bypass above).

Storage

Database location

Path Description
{FCM_TOKEN_DATA_DIR}/notify.sqlite Primary SQLite database (default dir: ./data)
{FCM_TOKEN_DATA_DIR}/notify.sqlite-wal WAL journal (present while the process is running)
{FCM_TOKEN_DATA_DIR}/notify.sqlite-shm Shared-memory file used with WAL mode

FCM_TOKEN_DATA_DIR defaults to ./data (relative to the process working directory). The data/ directory is gitignored.

Schema (high level)

Table fcm_registrations holds one row per registered device:

  • Identity: id, user_id, device_id, fcm_token, platform
  • Flags: test_mode
  • Timestamps: created_at, updated_at, last_notified_at

Unique on (user_id, device_id). Indexes also exist on user_id, device_id, fcm_token, and (user_id, fcm_token).

The schema is created automatically on startup if the database or tables do not already exist.

JSON → SQLite

There is no automatic migration from the old JSON file (fcm-tokens.json). That format is no longer used. If you still have a local fcm-tokens.json from earlier development, it is ignored; re-register devices or import data manually if you need it.

Backup

Persist or back up the SQLite files under FCM_TOKEN_DATA_DIR:

  1. Prefer stopping the service, then copy notify.sqlite (and any -wal / -shm sidecars if present).
  2. Or, while the service is running, copy all three files (notify.sqlite, -wal, -shm) together so the backup stays consistent under WAL mode.
  3. For Docker, mount a volume at the data directory (or set FCM_TOKEN_DATA_DIR to a mounted path) so registrations survive container recreation.

Production

Runs TypeScript directly via tsx (no compile step).

pnpm install --prod
pnpm start

Ensure FCM_TOKEN_DATA_DIR points at a durable location (or accept the default ./data next to the process cwd).

Or with Docker:

docker build --no-cache -t notify-wakeup-api:amd-$NOTIFY_WAKEUP_API_VERSION --platform linux/amd64 .
docker run --env-file notify-wakeup-api.env -p 3003:3003 \
  -v notify-wakeup-data:/app/data \
  notify-wakeup-api

Mount a volume over /app/data (or whatever path you set with FCM_TOKEN_DATA_DIR) so the SQLite database is not lost when the container is replaced.

Required environment variables:

Variable Description
FIREBASE_SERVICE_ACCOUNT_JSON Inline service account JSON (one line). If unset, falls back to Application Default Credentials.
PORT HTTP port (default: 3003).
ENDORSER_URL Endorser API base URL used for auth checks on register/refresh (default: https://api.endorser.ch).
FCM_TOKEN_DATA_DIR Directory for the SQLite database file notify.sqlite (default: ./data).
Description
No description provided
Readme 521 KiB
Languages
TypeScript 98.1%
Dockerfile 1.9%