The Problem

Messaging apps like WhatsApp and Signal leak device state through delivery receipts. A recipient's phone acknowledges message delivery at different speeds depending on whether the device is actively used, in standby, or offline. This repo turns that timing side-channel into a working surveillance tool, demonstrating that a phone number alone is enough to track someone's activity patterns.

What This Does

device-activity-tracker implements the "Careless Whisper" research: it sends probe messages and measures Round-Trip Time (RTT) to classify a device as Online, Standby, or Offline. The project ships as a full-stack app with a React/Tailwind frontend (client/) and a Node/TypeScript backend (src/).

Two probe methods exist—sendReactionProbe and a second method in src/tracker.ts—both measuring how fast the recipient's device acknowledges delivery. The backend maintains per-device RTT history, computes medians and thresholds, and streams state changes to the web dashboard via WebSocket.

How It Is Wired

Execution starts at src/index.ts (CLI) or src/server.ts (web backend). src/server.ts:119 calls checkSignalConnection, which chains through checkSignalApiAvailable, getSignalAccounts, and startSignalLinking—the only path that makes an outbound network call. The server then hands off to the tracking engine.

The core logic lives in src/tracker.ts (24 functions) and src/signal-tracker.ts (25 functions). The signal tracker is the hub: 3 files import it, and its constructor and setDebugMode are called from multiple entry points. The internal call graph shows sendProbe -> sendReactionProbe, addMeasurementForDevice -> determineDeviceState, and sendUpdate -> calculateGlobalMedian as the critical path. markDeviceOffline triggers sendUpdate, which routes through calculateGlobalMedian—so a single offline event cascades through the entire state machine.

The frontend (client/src/App.tsx) is the second hub with 4 modules importing it, but it sits inside a circular dependency with Login.tsx and Dashboard.tsx. That cycle costs you: changing shared state or props in any of those three files risks runtime import errors, and the graph shows App at instability 0.33—meaning it's both depended-upon and dependent, the hardest kind of module to refactor.

The wiring for the CLI path (src/index.ts) has not been fully mapped—the analysis shows connectToWhatsApp and askForTarget as entry points, but their downstream effects aren't traced beyond shouldSuppressOutput handling console output.

How To Use It

git clone https://github.com/moses-y/device-activity-tracker
cd device-activity-tracker
cp .env.example .env
docker compose up --build

Frontend: http://localhost:3000, backend: http://localhost:3001. Scan the WhatsApp QR code, enter a target phone number, and watch the dashboard update with live RTT measurements and state classifications. For CLI-only tracking: npm start.

Real-World Use

A security researcher auditing WhatsApp's privacy posture runs this against a test account to quantify the RTT gap between active and standby states. The dashboard's per-contact cards (client/src/components/ContactCard.tsx) show live state, RTT, and threshold—enough to build a daily activity timeline from the addMeasurementForDevice history.

Code Health & Issues

Static analysis found 12 issues (9 high, 3 medium) across 4 categories:

  • High – Deep nesting (7 instances): client/src/App.tsx, Login.tsx, src/tracker.ts hit indentation depth 8. Flatten with guard clauses.
  • High – Import cycles (3 instances): App.tsx, Login.tsx, Dashboard.tsx form a circular dependency. Extract shared types to break it.
  • High – Duplicated code (19 repeated 6-line blocks across 4 files): extract shared helpers.
  • Medium – High branching density in src/index.ts: 50 branch points over 143 lines.

SDLC gaps: no CI pipeline, no lockfile committed (non-reproducible builds), wildcard CORS in src/server.ts, unpinned Docker base image, no Dependabot config, and a postinstall script in package.json that warrants review.

The Bottom Line

This is a functional proof-of-concept that demonstrates a real privacy vulnerability with working code, not a slide deck. The architecture is sound for a PoC, but the circular imports, deep nesting, and missing CI make it fragile to extend. Use it for security research and education—not production surveillance.