# A heartbeat that goes quiet when the route is dead: signed presence records with a short TTL

> Source: <https://dev.to/ashwar_sadh_f7abe6f82c8e9/a-heartbeat-that-goes-quiet-when-the-route-is-dead-signed-presence-records-with-a-short-ttl-26e8>
> Published: 2026-11-04 06:19:13+00:00

*Disclosure: I'm the developer of the app described here, and this post was drafted and published by an AI agent from the project's source code.*

[Bolo](https://play.google.com/store/apps/details?id=com.ashwar.bolo&referrer=utm_source%3Ddevto%26utm_medium%3Darticle) (search Bolo AI on Google Play) turns an Android phone into a microphone for a computer. On the same Wi-Fi the phone finds the PC directly. Away from it, the PC's Node.js helper publishes a small "I'm here, reach me at this URL" record that the phone looks up. This post covers `src/presence.js`, the 146-line module behind that record, and four decisions in it.

The helper generates an EC key pair (`prime256v1`) once and keeps it in its settings file. The server ID is the SHA-256 of the public key, so identity is the key, not a name anyone can claim.

Each record is signed over a canonical, newline-joined payload of fixed fields in a fixed order (server ID, URL, display name, version, boot ID, reported-at, expires-at, lookup-token hash):

```
record.signature = crypto.sign(
  'sha256',
  Buffer.from(canonicalPresencePayload(record), 'utf8'),
  identity.privateKey
).toString('base64');
```

Signing a canonical string instead of `JSON.stringify(record)` matters: key order and whitespace in JSON are not guaranteed to survive a round trip, and a signature check that depends on them fails for no good reason.

The phone looks the PC up by a random lookup token, but the record stores only its hash, so the lookup service holds nothing that lets it replay the lookup.

Records expire after 150 s and the helper re-sends every 45 s: three chances to refresh before a record lapses. A PC that is switched off just stops sending and falls out of the lookup on its own, with no "goodbye" message to lose. The interval timer is `unref()` ed so the heartbeat never keeps the process alive by itself.

This is the fix that taught me the most. A record for a tunnel URL that no longer reached the PC made the phone show the PC as online, and then every tap failed. The refresh now asks a health check first:

```
async function publishPresence() {
  if (!currentTunnelUrl || !currentVersion) return false;
  if (!routeHealthy()) return false;
  ...
}
```

`routeHealthy` is injected (`setRouteHealthCheck`) and defaults to "yes", which keeps the module testable. When the probe says the URL does not reach this machine, the helper stays silent, the old record expires within the TTL, and the phone sees "offline", which is true. Absence of a record is more honest than a stale one.

A rejected refresh now logs *who* rejected it. The service answers with JSON; an edge bot-challenge answers with HTML. So the log line is either `proxy: {...}` or `edge (cf-mitigated=..., ray=...)`. On one CI runner on a datacenter network, the edge had answered a bare `https.request` with a bot challenge while `fetch` from the same machine got a 200. Home networks passed both. The module now uses `fetch` with a 10 s `AbortSignal.timeout`, and the log makes the next such difference diagnosable in one line instead of a guessing game.

Bolo is on Google Play (search Bolo AI): [https://play.google.com/store/apps/details?id=com.ashwar.bolo](https://play.google.com/store/apps/details?id=com.ashwar.bolo). The presence record carries only a server ID, a URL, a display name and version info, signed by the PC's own key; it does not carry dictated text.
