Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
92 changes: 51 additions & 41 deletions docs/privacy/lf-telemetry.md
Original file line number Diff line number Diff line change
@@ -1,49 +1,59 @@
---
## *LoopFollow* Anonymous Telemetry

*LoopFollow* can send a small anonymous report once a day so the
maintainers can see which app and *iOS* versions are still in active use.
The data helps make decisions like when it's safe to drop support for
older *iOS* releases. **No glucose data, no credentials, no logs leave
your device.**

This is opt-out: the first time the app comes to the foreground after
installing or updating to a version that supports telemetry, you'll see
a one-time prompt to choose Yes or No. You can change your mind any
time in *Settings* > *General Settings* > *Diagnostics*.
*LoopFollow* sends a small anonymous report to the maintainers, once a
day and after you install a new version. It tells them which app and
*iOS* versions are in active use and how quickly people update. That
helps decide, for example, when it's safe to drop support for an older
*iOS* release, and shows early whether a new version is working well.
**No glucose data, no credentials, no logs leave your device.**

Usage stats are **on by default**. You can turn them off at any time in
*Settings* > *General Settings* > *Diagnostics* > *Send anonymous usage
stats*. If you already chose **No thanks** or turned the toggle off in
an earlier version, that choice is kept. When usage stats are off,
nothing is sent.

- - -

## What is sent

A typical check-in looks like this — you can see the exact JSON your
own install would send under *Settings* > *General Settings* >
*Diagnostics* > *What's sent*.
A typical report looks like this. You can see the exact report your own
install would send under *Settings* > *General Settings* > *Diagnostics*
> *What's sent*.

| Field | Example | Notes |
|:--|:--|:--|
| App version | `6.0.7` | |
| Build date | `2026-04-15` | |
| App version | `8.0.1` | |
| Build date | `2026-11-03` | The day this copy of the app was built. |
| Build commit | `46a9ed3` | A short code that identifies the exact source code the app was built from. |
| TestFlight or not | `true` / `false` | Whether the install came from TestFlight or a local Xcode build. |
| Instance | `LoopFollow` / `LoopFollow_2` / ... | If you have multiple *LoopFollow* installs side by side, this distinguishes them. |
| IDFV | Apple per-vendor UUID | Apple's `identifierForVendor` — opaque, scoped to *LoopFollow*'s bundle prefix, and resets when all of this developer's apps are removed from the device. Combined with the *Instance* field above, identifies a specific install. |
| Instance | `LoopFollow` / `LoopFollow_.Second` / ... | If you have more than one *LoopFollow* installed side by side, this tells them apart. |
| IDFV | Apple per-vendor UUID | Apple's `identifierForVendor`: an opaque code that is the same for all apps from this developer on your device, and resets when all of them are removed. Together with *Instance*, it identifies one install. |
| Device | `iPhone15,2` | Apple's hardware identifier (not the marketing name). |
| Platform | `iOS` / `iPadOS` / `macCatalyst` | |
| iOS version | `17.5` | |
| Following app | `Loop` / `Trio` / ... | Which closed-loop app *LoopFollow* is following, if known. Field is omitted when not yet detected. |
| Following app | `Loop` / `Trio` / ... | Which automated insulin delivery app *LoopFollow* is following, if known. Left out when not yet detected. |
| Uses *Dexcom* | `true` / `false` | Whether *Dexcom Share* is configured. **No username or password.** |
| Uses *Nightscout* | `true` / `false` | Whether a *Nightscout* site is configured. **No URL or API token.** |
| Background-refresh method | `Silent Tune` | Which background-refresh strategy is selected. |
| Background-refresh method | `Silent Tune` | Which background-refresh method is selected. |
| Display units | `mg/dL` / `mmol/L` | |
| Remote-command type | `none` / `Loop APNS` / ... | Which remote-command path is configured. |
| Remote-command type | `None` / `Loop APNS` / ... | Which remote-command method is configured. |
| Appearance | `dark` / `light` / `system` | |
| Calendar / Contact integrations | `true` / `false` | Whether those features are turned on. |
| Cold launches in past 7 days | `12` | A count of process restarts; high values can flag stability issues. |
| Cold launches in past 7 days | `12` | How many times the app was started from scratch in the last 7 days. High values can point to stability problems. |

Each report also says what triggered it, for example `cold_launch` (the
app started), `foreground` (the app was opened), `build_change` (first
start of a new version) or `timer` (the daily check). If the previous
attempt to send failed, it includes a short error category such as
`checkin_request_failed`.

The server adds two fields when it stores each report:

* `receivedAt` — the time the report was received.
* `dayBucket` — a date label (e.g. `2026-04-29`) used to deduplicate.
* `dayBucket` — the date (in UTC), used to keep at most one report per
install, day and version.

- - -

Expand All @@ -57,47 +67,47 @@ Specifically and explicitly:
* No time zone.
* No remote-command secrets, no APNS keys.
* No GPS or location data.
* **No logs.** Logs are never sent automatically. The existing *Settings* > *Logs* sharing flow is unchanged and only triggered by you.
* **No logs.** Logs are never sent automatically. The *Settings* > *Logs* sharing flow is unchanged and only triggered by you.

The receiving server also does not log your IP address — the *NGINX*
edge zeroes the last octet of *IPv4* addresses and only retains the
The receiving server also does not log your IP address: the *NGINX*
edge zeroes the last octet of *IPv4* addresses and keeps only the
`/64` prefix of *IPv6* addresses before anything is written to disk.

- - -

## Where it goes

Reports are sent over *HTTPS* to **`https://lf.bjorkert.se/api/telemetry/checkin`**,
which is self-hosted by the maintainer. There is no third-party
analytics service involved.
Reports are sent over *HTTPS* to **`https://lf.bjorkert.se/api/telemetry/`**,
which is self-hosted by the maintainer. No third-party analytics
service is involved. Stored reports are kept on that server and are
not shared.

- - -

## How to opt out

The toggle lives at *Settings* > *General Settings* > *Diagnostics* >
*Send anonymous usage stats*. Turning it off is immediate and persistent.
The toggle is at *Settings* > *General Settings* > *Diagnostics* >
*Send anonymous usage stats*. Turning it off takes effect immediately
and stays off. From then on nothing is sent.

The *Diagnostics* section also has:

* *What's sent* — renders the exact *JSON* your install would send right now,
with a *Copy* button if you want to inspect it more closely.
* *What's sent* — shows exactly what your install would send right now,
with a *Copy* button if you want to look at it more closely.
* *Privacy* — a shorter in-app version of this page.

- - -

## How often

A check-in is sent at most once every 24 hours, or once after a new
build is installed (whichever fires first). It runs in the background
while the app is active or refreshing in the background — *LoopFollow*
schedules the next check-in based on when the last one was actually
sent, so a relaunch a few hours after the previous send simply waits
out the remainder of the 24 hours.
At most once every 24 hours, plus once on the first start of a new
version. *LoopFollow* checks whether a report is due when the app
starts, when you open it, and while it refreshes in the background, so
a report goes out even if the app is rarely opened.

If a send fails (network error, the server is unreachable, etc.) the
last-sent timestamp is not updated, so the next scheduled check tries
again.
If sending fails (no network, server unreachable, and so on), the app
waits an hour before it tries again, and the next report includes the
error category so the maintainers can see why reports go missing.

- - -

Expand Down
3 changes: 1 addition & 2 deletions docs/setup/lf-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ The guide steps through:
7. **Recommended alarms** — a set of useful alarms is offered with sensible defaults and a single control each: **Low glucose**, **Urgent low**, **High glucose**, **Fast drop**, and **Missed readings**. When following *Nightscout*, alarms that rely on loop/uploader data are also offered: **Not looping**, **Looping phone battery**, **IOB**, **COB**, **Sensor change**, **Pump change**, and **Pump insulin**. Alarm types you already have are not offered again.
8. **Tabs** — arrange which features sit in the tab bar. You can always reach everything through the [Menu](#menu-screen).
9. **Notifications** — *LoopFollow* explains why it needs notifications for alarms, then asks iOS for permission. Choose **Enable Notifications** or **Not now**.
10. **Privacy** — optionally share anonymous diagnostics to help improve *LoopFollow*. Choose **Yes, send anonymous stats** or **No thanks**; you can change this any time in Settings → General → Diagnostics. See [Telemetry](../privacy/lf-telemetry.md){: target="_blank" } for exactly what is and isn't shared.
11. **You're all set** — a summary with a **Finish** button, plus a few features worth exploring next.
10. **You're all set** — a summary with a **Finish** button, plus a few features worth exploring next.

![Choose a data source step](img/onboarding-data-source.png){width="280"}
![Nightscout connect step](img/onboarding-nightscout.png){width="280"}
Expand Down