Skip to content

blueacorn/Infocal-worker

Repository files navigation

Infocal worker

A serverless application to collect anonymized product usage metrics for Garmin Connect Apps.

This application was written to provide better anonymized product usage metrics than the very limited stats Garmin App store provides to developers.

This application uses Cloudflare "Worker" compute and "D1 SQL" database storage. Cloudflare Worker and D1 are ideal for light telemetry, with up to 100,000 writes a day free, and competitive pricing beyond the free tier ($5/mnth for 50 million writes/month).

Features

  • Heartbeat tracking (/heartbeat) Devices report in with product code, firmware, software version, etc.
  • Active count (/count) Returns number of active devices in the last N seconds.
  • Metrics (/metrics) Query to provide usage metrics grouped by one or more dimension(s). Groups devices by one or more fields (part_num, fw_version, sw_version, ciq_version, country, lang, feat)
  • Device list (/list) Diagnostic to list all active devices.
  • Authentication
    • Client token (X-Auth) → required for client devices to send /heartbeat.
    • Admin token (X-Auth) → required for reading out data /count, /list, /metrics.
  • GDPR-compliant
    • Provides country-level aggregation; does not store any personally-identifiable information,
  • Cache control All responses are served with Cache-Control: no-store, no-cache, must-revalidate to help ensure queries never get stored / cached by any intermediary proxies.

Project structure

item description
- src/worker.ts Worker implementation
- wrangler.jsonc Wrangler config
- schema.sql Database schema
- README.md Documentation

Database schema

See schema.sql


Usage

This worker can be deployed to any Cloudflare account; follow the instructions below to clone/fork and use this serverless application with your Garmin apps.

Prerequisites

Node.js

brew install npm

Cloudflare Wrangler CLI

npm install -g wrangler

Local Deployment (for testing/development)

# Login to your Cloudflare account
wrangler login

# create a local copy of database
wrangler d1 execute infocal-db --file=./schema.sql

# start the worker (api endpoint http://localhost:8787)
wrangler dev --env dev

Note for testing, we use env.dev to pre-configure the test environment. See wrangler.jsonc for details of setup.

Production Deployment

# Login to your Cloudflare account
wrangler login

# Create the remote (production) database
wrangler d1 execute infocal-db --file=./schema.sql --remote

# Set your production secrets in the cloudflare secrets manager
wrangler secret put CLIENT_TOKEN
wrangler secret put ADMIN_TOKEN

# Deploy your code to production
# (note for production we use the default environment)
wrangler deploy --env=""

NOTE: You must save a secure copy of your application secrets (CLIENT_TOKEN, ADMIN_TOKEN) as once written to the Worker Secrets they are encrypted and cannot be retrieved. You can only rotate the secret values.

Development & Testing

Setup Test Environment

Local testing

export URL=http://localhost:8787
export CLIENT_TOKEN=dev-client-token
export ADMIN_TOKEN=dev-admin-token

Production endpoint testing

export URL=https://infocal-worker.<your cloudflare subdomain>.workers.dev
export CLIENT_TOKEN=<your client secret>
export ADMIN_TOKEN=<your admin secret>

Heartbeat endpoint

Simulate a client device sending a heartbeat:

curl -X POST "$URL/heartbeat?uid=test-123&part=F965&fw=15.23" -H "X-Auth: $CLIENT_TOKEN"

# response:
{ "ok": true, "lastSeen": 1725408000 }

Active Devices Count endpoint

Count the number of active devices within the last (window) seconds:

curl "$URL$/count?window=3600" -H "X-Auth: $ADMIN_TOKEN"

# response(JSON):
{ "timestamp": 1725408060, "window": 3600, "active": 123 }
curl "$URL$/count?window=3600&format=csv" -H "X-Auth: $ADMIN_TOKEN"

# response(CSV):
timestamp,window,active
1725408060,3600,123
Metrics endpoint

Provide summary of usage, grouped by any dimension

curl "$URL/metrics?window=86400&groups=country,sw_version&format=csv" -H "X-Auth: $ADMIN_TOKEN"

# response(CSV)
country,sw_version,count
CA,2025.8.16,30
CA,2025.8.21,55
US,2025.8.16,2
US,2025.8.17,5
...

List (debugging) endpoint

List raw table output (for debugging):

curl "$URL$/list?window=600&format=csv" -H "X-Auth: $ADMIN_TOKEN"

# response(CSV):
unique_id,first_seen,last_seen,part_num,fw_version,sw_version,country
d0983fdas,1725408000,1725408060,vivoactive,15.23,Unknown,CA
8moiuasf7,1725312456,1725398909,fenix8,10.5,2025.8.0,CA
...

Development commands

Tail logs for debugging

Used to 'tail' the log file generated by the production worker:

wrangler tail infocal-worker

Print schema for all entities (tables, indexes, triggers, etc.) in the database

Very useful for confirming schema of local/remote database

wrangler d1 execute infocal-db \
  --command "SELECT name, tbl_name, sql FROM sqlite_master;" \
  [--local|--remote]
``

### Inspect table schema
Use to confirm current schema/changes changes

```sh
wrangler d1 execute infocal-db --command "PRAGMA table_info(heartbeats);" [--local|--remote]

Inspect table content

Print the contents of table to console

wrangler d1 execute infocal-db --command "SELECT * FROM heartbeats LIMIT 100;" [--local|--remote]

Get table size

Count the number of rows in table

wrangler d1 execute infocal-db \
  --command "SELECT COUNT(*) AS count FROM heartbeats;" \
  [--local|--remote]

Delete row from table

Useful for deleting test entries from table

wrangler d1 execute infocal-db \
  --command "DELETE FROM heartbeats WHERE unique_id = 'abc123';" \
  [--local|--remote]

Maintenance commands

Export/diff full database schema.sql

Maintain schema.sql in version control, and use this command to confirm/diff to local/remote, to ensure your migrations have been applied properly:

wrangler d1 export infocal-db --no-data --output schema.sql [--local|--remote]

Apply database schema migrations

Prepare a new (non-destructive) migration script under migrations/; run the migrations apply command to apply these migrations in order to the targeted database.

Wrangler will remember which migrations have already been applied, so next time it will only apply any new migrations.

WARNING: Test on local before applying to remote!

wrangler d1 migrations apply infocal-db [--local|--remote]

Re-export the schema.sql to check for correctness

wrangler d1 export infocal-db --no-data --output schema.sql [--local|--remote]

Delete inactive entries from the table

Deletes entries from the heartbeats table that have not been updated in 30 days.

NOTE: Batched as an atomic transaction:

  • either all changes succeed (then COMMIT), or none of them apply (an error forces a rollback).
  • SQLite flushes to disk at end of commit.
  • If you delete 10,000 rows one by one without a transaction - that’s 10,000 commits, very slow!
  • Ensures Consistency for other clients - particularly important for /list, /metrics endpoints

WARNING: you should test this (and any other destructive commands) on a local instance before applying to remote (production).

wrangler d1 execute infocal-db \
  --remote \
  --command "BEGIN; DELETE FROM heartbeats WHERE last_seen < strftime('%s','now') - 30*86400; COMMIT;" \
  [--local|--remote]

Reclaim database space after deletes

wrangler d1 execute infocal-db --command "VACUUM;" [--local|--remote]

Restore (undelete) accidentally deleted heartbeat rows

The following is an sql schema to restore rows accidentally deleted from heartbease, from the archive table.

INSERT INTO heartbeats
SELECT uniqu_id, first_seen, last_seen, part_num, fw_version, sw_version, ... <TODO> add all columns
FROM heartbeats_archive
WHERE deleted_at >= strftime('%s','now') - 3600; -- restore last hour

Apply a schema change

Used for adding/removing tables/columns/triggers etc. NOTE: Use wrangler migrations instead to manage schema changes in local/remote

In this example, the schema is modifying a trigger to the heartbeats table to automatically archive deleted rows to the heartbeats-archive table (for protection against accidental deletes)

wrangler d1 execute infocal-db --file=./schema/tr_heartbeats_delete_to_archive.sql [--local|--remote]

Credentials Management

It is strongly recommended to store your CLIENT and ADMIN tokens in a persistent, safe place (such as a secure key/password manager).

For testing/admin, a good option is to store them in your machine keychain. Below are instructions for managing the token in the macOS keychain.

Store in macOS Keychain

Stores tokens in the system Keychain and fetches them on demand:

# save (one-time)
security add-generic-password -a "$USER" -s infocal-admin  -w '<ADMIN TOKEN HERE>'  >/dev/null
security add-generic-password -a "$USER" -s infocal-client -w '<CLIENT TOKEN HERE>>' >/dev/null

Load from macOS Keychain

# load into env when needed
export ADMIN_TOKEN="$(security find-generic-password -s infocal-admin  -w 2>/dev/null)"
export CLIENT_TOKEN="$(security find-generic-password -s infocal-client -w 2>/dev/null)"

Delete from keychain

WARNING: Make sure you have a copy of your tokens elsewhere!

security delete-generic-password -s infocal-admin  2>/dev/null
security delete-generic-password -s infocal-client 2>/dev/null

Privacy & GDPR Compliant

  • This app collects 'heartbeat' messages, containing anonymized product/version information.
  • The heartbeat message contains: anonymized unique id, device part number, firmware version, software version, connect iq version, language and country code, and recently-used features.
  • No peronallly-identifiable information or IP addresses are ever collected or stored.
  • Data is processed and retained only for the purpose of anonymized product usage, product health, and service availability monitoring.

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published