Skip to content

Add anonymous telemetry#626

Open
bjorkert wants to merge 4 commits intodevfrom
add-telemetry
Open

Add anonymous telemetry#626
bjorkert wants to merge 4 commits intodevfrom
add-telemetry

Conversation

@bjorkert
Copy link
Copy Markdown
Member

@bjorkert bjorkert commented Apr 29, 2026

Summary

  • Adds an opt-out (with first-foreground prompt) anonymous telemetry check-in that ships once a week or after a build SHA change.
  • Triggers fire from both AppDelegate.application(_:didFinishLaunchingWithOptions:) and SceneDelegate.sceneDidBecomeActive, so cadence stays honest for follower-style usage where the app is rarely opened.
  • POSTs to https://lf.bjorkert.se/api/telemetry/checkin with a Bearer token committed in source. The token is intentionally not a secret — the backend's TLS, NGINX rate limit, schema validation, MongoDB dedup index, and an insert+find-only role are what bound abuse.

User-facing documentation

loopandlearn/loopfollowdocs#30 — adds a new Privacy → Anonymous Telemetry page covering what is and isn't sent, opt-out, frequency, and where reports go.

Payload

{
  "clientId":              "<permanent UUID, generated locally on first launch>",
  "appVersion":            "6.0.7",
  "buildNumber":           "100",
  "buildBranch":           "dev",
  "buildSha":              "d691b34",
  "buildDate":             "2026-04-15",
  "isTestFlight":          true,
  "hashedTeamId":          "<salted SHA-256 of dev team, 16 hex chars>",
  "instance":              "LoopFollow",
  "hashedIDFV":            "<salted SHA-256 of identifierForVendor>",
  "device":                "iPhone15,2",
  "platform":              "iOS",
  "osVersion":             "17.5",
  "timeZone":              "Europe/Stockholm",
  "hashedDexcomAccount":   "<sent only when Dexcom Share is configured>",
  "hashedNightscoutHost":  "<sent only when Nightscout is configured>",
  "backgroundRefreshMethod": "Silent Tune",
  "units":                 "mg/dL",
  "remoteType":            "none",
  "appearanceMode":        "dark",
  "contactEnabled":        false,
  "calendarEnabled":       false,
  "coldLaunches7d":        12
}

The server adds receivedAt and weekBucket before storing.

What is NOT sent

No glucose, insulin, carbs, treatments, or any other health data. No Nightscout URL or API token. No Dexcom credentials. No remote-command secrets or APNS keys. No location data. No logs — logs are only ever shared if the user explicitly exports them via the existing flow.

User-facing surfaces

  • First-foreground consent sheet ("Help us help you!"). Cannot be swipe-dismissed; user picks Yes / No / See exactly what's sent. Decision is remembered; never re-prompted.
  • Settings → Diagnostics with a toggle, a privacy summary, and a "What's sent" inspector that renders the exact JSON about to be sent.

Cadence and resilience

  • Sliding 7-day timer; buildSha change forces an immediate re-send.
  • Failure-to-send does not advance the timer, so the next launch retries naturally.
  • A re-entrancy lock collapses concurrent triggers (cold-launch hook + foreground hook firing at the same time) into a single POST.

Files

File Why
LoopFollow/Helpers/Telemetry.swift New — TelemetryClient plus three SwiftUI views (consent, preview, privacy).
LoopFollow/Storage/Storage.swift New storage entries for consent, enabled flag, clientId, last-sent state, and cold-launch window.
LoopFollow/Helpers/BuildDetails.swift New commitSha accessor mirroring the existing branch accessor.
LoopFollow/Application/AppDelegate.swift Records cold launches and triggers maybeSend() on every cold launch.
LoopFollow/Application/SceneDelegate.swift Triggers maybeSend() and presents the consent prompt on first foreground.
LoopFollow/Settings/GeneralSettingsView.swift Adds the Diagnostics section.
LoopFollow/Log/LogManager.swift Adds a .telemetry log category.
LoopFollow.xcodeproj/project.pbxproj Registers Telemetry.swift.

Opt-out (with first-foreground prompt) weekly check-in to
https://lf.bjorkert.se/api/telemetry/checkin. Trigger fires from both
AppDelegate.didFinishLaunchingWithOptions (covers background launches)
and SceneDelegate.sceneDidBecomeActive (covers foregrounds), keeping
cadence honest for follower-style usage where the app is rarely opened.

Payload covers app/iOS/device version, build origin (TestFlight or
not), time zone, and a few selected settings (units, remoteType,
appearanceMode, contactEnabled, calendarEnabled, backgroundRefreshMethod).
Salted-and-truncated SHA-256 hashes of Dexcom username and Nightscout
host are sent only when those backends are configured. A sliding 7-day
cold-launch counter rides along as a stability signal.

No glucose, insulin, carbs, Nightscout URL/token, Dexcom credentials,
or logs leave the device.

Settings -> Diagnostics has the toggle, a privacy summary, and a
"What's sent" inspector that renders the exact JSON about to be posted.
@marionbarker
Copy link
Copy Markdown
Collaborator

I tested this - so you should be getting data from my mope site.
I'd like a developer to review the code and give approval before I merge.
Also - is this telemetry site one we should set up under the Nightscout Foundation to have a succession plan and not be under bjorkert.

- Drop hashed Nightscout host and hashed Dexcom username; replace with
  usesNightscout / usesDexcom booleans. The salt is committed in source,
  so a hashed NS URL is reverse-lookupable from a forum post, and the
  hashed host is effectively a patient identifier the patient never
  consented to.
- Drop timeZone from the payload.
- Send IDFV raw rather than hashed — it's already an opaque per-vendor
  UUID, hashing it with a public salt is purely cosmetic.
- Add followingApp (Loop / Trio / ...), omitted when device isn't yet
  known.
- Switch cadence from "weekly + every cold launch + on build change" to
  once per 24h via TaskScheduler. Startup fires one immediate ping when
  the build SHA changed since the last successful send. The settings
  toggle re-arms the scheduler when flipped from off to on.
IDFV (sent raw) plus the existing instance field already gives a stable
per-install identifier, so the separate generated UUID is redundant.
One fewer stable identifier in the wire.
AppDelegate handles the launch-time SHA-change shortcut and
TaskScheduler handles the 24h cadence; the foreground hook also
calling maybeSend produced two pings per cold launch (and two
first-seen Telegram alerts).

The hook keeps its other job — presenting the consent sheet on first
foreground when the user hasn't decided yet.
Copy link
Copy Markdown
Collaborator

@dnzxy dnzxy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving based on code review.

Some of the comments made privately have already been implemented. Thanks to @bjorkert for the lightning-fast response.

@marionbarker
Copy link
Copy Markdown
Collaborator

Test

Test again with some privacy issues updated.

I saved the json of what was sent in prior instance and then saved again the one now that some security issues were updated.

This is what is sent after the update:

{
  "appearanceMode" : "dark",
  "appVersion" : "6.0.8",
  "backgroundRefreshMethod" : "RileyLink",
  "buildBranch" : "add-telemetry",
  "buildDate" : "2026-04-29T20:43:28Z",
  "buildNumber" : "1",
  "buildSha" : "3758a4b",
  "calendarEnabled" : false,
  "coldLaunches7d" : 5,
  "contactEnabled" : false,
  "device" : "iPhone14,6",
  "followingApp" : "Trio",
  "hashedTeamId" : "f16ba590fc7b6a22",
  "idfv" : "2D95A671-0A29-48AA-93FE-7A085F55E656",
  "instance" : "LoopFollow",
  "isTestFlight" : false,
  "osVersion" : "26.4.2",
  "platform" : "iOS",
  "remoteType" : "Trio Remote Control",
  "units" : "mg\/dL",
  "usesDexcom" : false,
  "usesNightscout" : true
}

These items are no longer included:

clientId
hashedIDFV (replaced by idfv)
hashedNightscoutHost

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants