cd /news/developer-tools/a-heartbeat-that-goes-quiet-when-the… · home › topics › developer-tools › article
[ARTICLE · art-163647] src=dev.to ↗ pub= topic=developer-tools verified=true sentiment=↑ positive

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

A developer of Bolo, an Android app that turns a phone into a microphone for a PC, published a 146-line Node.js presence module that signs short-lived lookup records with an EC (prime256v1) key, hashing the public key into the server ID and storing only a hash of the lookup token. Records expire after 150 seconds and are re-sent every 45 seconds, and a refresh now runs an injected route health check first so a dead tunnel URL goes silent and expires rather than leaving the phone showing a stale online status. The module switched to fetch with a 10-second AbortSignal timeout after a CI runner on a datacenter network received a bot challenge to a bare https.request while fetch from the same machine got a 200.

by read3 min views1 publishedNov 4, 2026

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 (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. 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.

── more in #developer-tools 4 stories · sorted by recency
── more on @bolo 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
→ Live at https://your-agent.zahid.host ✓
Get free account → Pricing
from €0/mo · no card required
LIVE [news/a-heartbeat-that-goe…] indexed:0 read:3min 2026-11-04 · —