Skip to content

Add anonymous telemetry#626

Merged
marionbarker merged 6 commits intodevfrom
add-telemetry
May 2, 2026
Merged

Add anonymous telemetry#626
marionbarker merged 6 commits intodevfrom
add-telemetry

Conversation

@bjorkert
Copy link
Copy Markdown
Member

@bjorkert bjorkert commented Apr 29, 2026

Summary

  • Opt-out anonymous telemetry check-in (with a one-time consent prompt on first foreground), sent at most once every 24 hours plus once after a new build is installed.
  • Triggered from AppDelegate.application(_:didFinishLaunchingWithOptions:): cold-launch is recorded for the rolling 7-day count, an immediate send fires only when the build SHA has changed since the last successful send, and TaskScheduler carries the steady-state 24h cadence from there.
  • 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

{
  "appVersion":              "6.0.7",
  "buildDate":               "2026-04-15",
  "isTestFlight":            true,
  "instance":                "LoopFollow",
  "idfv":                    "<UIDevice.identifierForVendor UUID>",
  "device":                  "iPhone15,2",
  "platform":                "iOS",
  "osVersion":               "17.5",
  "usesDexcom":              false,
  "usesNightscout":          true,
  "followingApp":            "Trio",
  "backgroundRefreshMethod": "Silent Tune",
  "units":                   "mg/dL",
  "remoteType":              "none",
  "appearanceMode":          "dark",
  "contactEnabled":          false,
  "calendarEnabled":         false,
  "coldLaunches7d":          12
}

followingApp is omitted until the followed app has been detected. The server adds receivedAt and dayBucket 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 time zone. 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

  • 24-hour scheduled cadence; a build SHA change at startup forces an immediate re-send.
  • Failure-to-send does not advance telemetryLastSentAt, so the next launch / next tick retries naturally.

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, last-sent state, and the cold-launch window.
LoopFollow/Helpers/BuildDetails.swift New commitSha accessor mirroring the existing branch accessor.
LoopFollow/Application/AppDelegate.swift Records cold launches and triggers the SHA-change send + recurring schedule.
LoopFollow/Application/SceneDelegate.swift 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/Task/TaskScheduler.swift Adds the .telemetry task ID.
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

Browser-build vs TestFlight is already covered by isTestFlight; the
hashed team ID added a stable per-builder fingerprint without enough
analytic value to justify the privacy cost.
@dnzxy
Copy link
Copy Markdown
Collaborator

dnzxy commented May 1, 2026

Approving based on code review after continued discussions in private chat .

@marionbarker
Copy link
Copy Markdown
Collaborator

Test

✅ Acceptable list of items to share.

Test yet again with one final privacy issues addresses (discussed via PM).

This is what is sent after the update with commit 3c82b3d:

{
  "appearanceMode" : "dark",
  "appVersion" : "6.0.8",
  "backgroundRefreshMethod" : "RileyLink",
  "buildDate" : "2026-05-02",
  "calendarEnabled" : false,
  "coldLaunches7d" : 7,
  "contactEnabled" : false,
  "device" : "iPhone14,6",
  "followingApp" : "Trio",
  "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
}

Copy link
Copy Markdown
Collaborator

@marionbarker marionbarker left a comment

Choose a reason for hiding this comment

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

approve by test and review of what is included.

@marionbarker marionbarker merged commit 8b5ce73 into dev May 2, 2026
1 check passed
@marionbarker marionbarker deleted the add-telemetry branch May 2, 2026 04:05
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