Last updated: 2026-05-04 ·
Effective date: 2026-05-04 ·
Contact: David Kaiser, mail@davidkaiser.dk Applications: FruitConnect for macOS (companion application) and FruitConnect for Meta Quest (Quest 2 / Pro / 3 / 3S)
Plain-language summary. FruitConnect streams your Mac's screen to a 2D panel inside Meta Quest. We do not collect analytics, telemetry, or behavioural data. We do not show ads. We do not sell, share, or rent your data. Your screen content, keystrokes, and files never reach any server we control.
What the app actually does:
Stores a per-installation device identifier and your paired Macs locally on the headset.
Relays encrypted pairing handshakes through a public mailbox endpoint when you pair over the internet (Remote Access).
Uses public STUN servers to negotiate a peer-to-peer WebRTC connection between Quest and Mac.
Communicates with Meta's In-App Purchase service if you upgrade to Pro.
Uploads a debug log only when you explicitly tap "Send debug log" in the macOS menu — never automatically and never on crash.
1. Data stored on your device
All data below is stored exclusively on your Quest headset. None of it leaves the device unless you take an action that requires it.
1.1 Device identifier
What: A randomly generated UUID created on first launch.
Where: Encrypted preferences on the headset.
Why: Identifies this Quest installation to a Mac it has paired with, so the Mac knows which device is reconnecting.
Retention: Until you uninstall the app. The app declares android:allowBackup="false", which means uninstalling permanently deletes this identifier.
1.2 Paired Macs
What: For each Mac you have paired: pairing ID, device name (your Mac's mDNS hostname), public key material, last-known transport (USB / Wi-Fi / internet), and capability flags.
Where:EncryptedSharedPreferences using AES-256-GCM for values and AES-256-SIV for keys, with a master key in the Android Keystore (hardware-backed where the headset supports it).
Why: Lets the app reconnect to a previously paired Mac without re-entering a code, and prove pairing identity using a zero-knowledge protocol.
Retention: Until you tap "Forget" on a Mac, "Forget All" in Settings, or uninstall the app.
Why: Restore your preferred settings between sessions.
Retention: Until you reset to defaults or uninstall.
1.4 Meta account identifier (v3.35+)
What: Your Meta user identifier and Meta display name as returned by the Meta Horizon Platform SDK on Quest.
Where: On Quest, in app-private storage. On Mac, alongside each paired-Quest record in macOS UserDefaults.
Why: Two reasons. (1) Security: each paired Mac record is bound to a specific Meta user so a different account logging into the same Quest hardware cannot inherit a previous user's pairings. On Quest, switching Meta accounts wipes the local paired-Macs list at next app launch. On Mac, every connect is gated on the incoming Quest's Meta user matching the stored value — mismatch automatically removes the pair. (2) UI: the Mac shows the Meta display name in its "Paired Quests" list so the Mac user can see who is connected, not just what device.
Wire-path: peer-to-peer ONLY. The Meta identifier never reaches any server controlled by the Licensor. It travels:
Over the encrypted LAN socket (ChaCha20-Poly1305-AEAD) directly between Quest and Mac, OR
Over the encrypted WebRTC DataChannel inside the rendezvous mailbox payload — which is itself ChaCha20-Poly1305-AEAD encrypted client-side BEFORE upload, so the rendezvous server only ever sees opaque ciphertext (zero-knowledge — see section 2.2.1).
Debug log redaction. The Meta identifier is explicitly excluded from every debug-log entry on both Quest and Mac. Diagnostic uploads (which only happen when you tap "Send debug log" — see section 2.4) therefore cannot leak the identifier even via the debug-log payload. Log entries say metaUser=(present) / metaUser=(absent) and (ids redacted from log) instead of the raw value.
Retention: On Quest, until you uninstall or switch Meta account (whichever comes first). On Mac, until you "Forget" the pair from the menubar, factory-reset the org, or uninstall the Mac app.
Permissions: This feature uses the OVR Platform SDK's logged-in-user API. On Meta Quest Store builds, the SDK returns the user info silently (entitlement-based access). On sideloaded / dev-mode builds without entitlement, the SDK returns no user info — the app continues to work with the legacy v3.34 device-only behavior (Mac shows "Quest 3" in the UI as before; no per-user security gate is applied to that pair).
2. Data sent over the network
2.1 Local network (LAN) pairing and streaming
When your Mac is on the same Wi-Fi or USB-tethered, all communication stays inside your local network.
Pairing: Bonjour / mDNS discovery; the pairing handshake (6-digit code or QR code) exchanges public keys directly between Quest and Mac.
Streaming: Encoded video frames sent from Mac to Quest over a direct TCP socket.
Input: Mouse and keyboard events sent from Quest to Mac over a separate TCP socket.
No server in this path. No traffic leaves your local network.
2.2 Internet pairing and streaming (Remote Access)
If your Mac is not on the same network, FruitConnect can connect over the internet.
Purpose: A zero-knowledge message-relay that lets Quest and Mac exchange short signaling payloads while neither side knows the other's IP yet.
What is sent: ChaCha20-Poly1305-encrypted signaling envelopes (WebRTC SDP offers/answers, ICE candidates), addressed by opaque mailbox keys derived locally on each device from your shared pairing token via HMAC-SHA-256. The pairing token itself never leaves your devices.
What the server stores: Up to 64 short encrypted messages per opaque mailbox key, automatically deleted after 120 seconds (whichever comes first). On disk, only a SHA-256 hash of the mailbox key is used as the filename — the raw key is never written to disk.
What the server cannot do: Cannot read the conversation, cannot impersonate either side, cannot link mailbox keys back to a specific user or device because keys rotate every 5 minutes.
Rate-limiting: Scoped per opaque mailbox key, not per IP — no IP address is hashed into rate-limit state.
Application-level logs: The application code does not write IP addresses or user identifiers to disk; rate-limiting state stores only SHA-256 hashes of opaque keys. Standard webserver access logs (containing IP, timestamp, and request path) are maintained automatically by the hosting provider's default rotation policy and used solely for abuse prevention; FruitConnect does not access, analyse, or export them.
2.2.2 STUN servers
Purpose: Help WebRTC determine each peer's public IP and negotiate a direct connection.
STUN endpoints used:
stun:stun.l.google.com:19302 (Google)
stun:stun1.l.google.com:19302 (Google)
stun:stun.cloudflare.com:3478 (Cloudflare)
stun:stun.1und1.de:3478 (1&1 IONOS)
stun:stun.t-online.de:3478 (Deutsche Telekom)
What STUN servers see: Your public IP and the source port of the STUN request. They cannot see media content. No third-party analytics are loaded from these endpoints.
2.2.3 TURN relay (optional, build-time)
If a TURN relay is enabled in this build (some networks behind symmetric NAT require it), encrypted media bytes may be relayed through the configured TURN provider. The TURN provider sees encrypted bytes only — they cannot decrypt video, audio, or input events.
2.2.4 Encrypted media stream
Once Quest and Mac have established a WebRTC connection (via the rendezvous mailbox and STUN), all video frames, input events, and control messages are end-to-end encrypted using DTLS-SRTP (the WebRTC standard). Neither we nor any third party can read the contents of this stream.
2.3 Meta Horizon Platform (In-App Purchase)
If you tap the Pro upgrade button:
Endpoint: Meta's official IAP service via the Meta Horizon Platform SDK.
What is sent: A purchase request for the Pro SKU and your Meta Horizon entitlement.
What we receive: A boolean entitlement (Pro yes/no) and a purchase receipt validated by Meta.
What we send to our servers: Nothing. Your Meta account ID, billing details, and purchase history are not shared with us.
2.4 Diagnostic and feedback uploads (opt-in, never automatic)
FruitConnect contains a "Send debug log" button (macOS menu) and a "Send Feedback" form (both platforms). These are the only ways the application transmits anything to a server controlled by the Licensor outside of the rendezvous mailbox described in section 2.2.
Trigger: A button tap by you. The application does not have a crash handler, an uncaught-exception handler, a scheduled background uploader, or any analytics/crash-reporting SDK that could collect or transmit data without an explicit tap. The presence or absence of crashes has no effect on what is sent.
Cross-device button: When sending a diagnostic from one device, you may opt in to also collecting the log from the other paired device. The other device receives the request over the established WebRTC DataChannel and uploads only after you confirmed the action that triggered the request — there is no out-of-band channel that lets the Licensor request a log from your device.
Trigger: Mac menu → "Send debug log" → fill in form → tap "Send".
What is sent:
Free-text fields you typed: name (≤100 chars), email (≤200 chars), message (≤8 000 chars), occurrence time/frequency.
Lifecycle counters: total app launches, number of unclean exits in the last 24 hours, a short description of how the previous session ended.
System info: macOS version, Mac model, locale, app version, screen resolution, and similar non-personal environment metadata (capped at 40 keys / 200 chars per value).
The contents of the rolling debug log file (plain text, capped at 5 MB on the client and 8 MB on the server). The log records what FruitConnect itself wrote — connection events, error messages, performance counters. It does not record screen content, keystrokes, mouse coordinates, clipboard data, file paths from outside the app sandbox, or anything you typed into other applications.
What the server stores: The metadata fields and the log file are written to a folder inside signal/diagnostic_inbox/, which is not exposed on the public web (HTTP access is denied by .htaccess); only the Licensor can retrieve submissions over an authenticated FTP channel. The application does not store your IP address with the submission — your IP is only used in-memory by the server to enforce rate limits, where it is hashed (SHA-256) and never written to disk in raw form. (The hosting provider keeps standard webserver access logs containing IP, timestamp, and request path on its own short rotation, used solely for abuse prevention; see section 2.2.1.)
Rate limiting: A maximum of 3 submissions per IP per 10 minutes is enforced to prevent abuse.
Retention: Each submission is automatically deleted 90 days after it is received. The exact deletion date is recorded in the metadata file alongside the submission as an expiresAt timestamp, and is also returned to the application in the upload response. Cleanup runs opportunistically on each new submission, so no separate scheduler is required. The Licensor does not aggregate, profile, or share these reports.
Trigger: "Send Feedback" form (Mac menu or Quest Settings) → fill in → tap "Send".
What is sent: Optional name (≤100 chars), optional email (≤200 chars), required message (≤8 000 chars), and a short string identifying which device sent it ("mac" or "quest"). No log file is attached.
What the server stores: A plain-text file with the four fields, inside signal/feedback_inbox/ (HTTP access denied; FTP-only retrieval). Your IP address is not stored with the submission (same handling as for diagnostic submissions: in-memory rate-limit hashing only).
Rate limiting: Max 5 submissions per IP per minute.
Retention: Each submission is automatically deleted 90 days after it is received. The deletion date is written into the file header as an Expires: line. Cleanup runs opportunistically on each new submission.
2.4.3 Your control
The buttons are clearly labelled. You can use FruitConnect indefinitely without ever tapping them, and nothing diagnostic-related leaves your devices.
Email and name fields in both forms are optional. If you leave them blank, the resulting submission contains only the technical content (log + system info for diagnostic; message text for feedback) — no information that ties it back to you personally.
All submissions auto-delete after 90 days regardless. To request earlier deletion of a previously sent submission (for example, under the GDPR right to erasure), email mail@davidkaiser.dk with the approximate time and a snippet of the message and it will be removed within 30 days.
3. Data we do not collect
To make this concrete, FruitConnect explicitly does not:
Track your usage, sessions, or session duration on a server we control.
Record, store, or transmit the contents of your Mac screen except as encrypted real-time frames in the active streaming connection.
Record, store, or transmit your keystrokes, mouse movements, or clipboard content except as encrypted real-time input events in the active streaming connection.
Read or transmit files, emails, or other documents from your Mac.
Use third-party analytics or crash-reporting SDKs (no Firebase, Crashlytics, Sentry, Bugsnag, Mixpanel, Amplitude, AppsFlyer, or Adjust). The "Send debug log" and "Send Feedback" buttons described in section 2.4 are the only mechanisms by which the application can transmit data outside the streaming session, and neither is invoked unless you explicitly tap it.
Show ads or use ad networks.
Sell, rent, license, or share any data with advertisers, data brokers, or third parties for marketing.
4. Permissions the app requests
Android permission
Why we request it
INTERNET
Required for Remote Access (internet pairing/streaming). Not used during LAN-only sessions.
ACCESS_NETWORK_STATE
Detect whether you are online before attempting Remote Access.
ACCESS_WIFI_STATE
Detect Wi-Fi changes to auto-upgrade from Remote Access to LAN when your Mac becomes reachable.
CAMERA (runtime prompt)
Scan the QR code shown on your Mac when pairing. The camera feed is processed locally on the headset by ML Kit's barcode scanner; no frames leave the device. Granted only when you open the QR-scan screen, and you can deny it and use the 6-digit code instead.
horizonos.permission.HEADSET_CAMERA
Quest-specific equivalent of the Android camera permission, required by the QR scanner.
com.oculus.permission.HAND_TRACKING
Allows the UI to respond to hand-tracking input as an alternative to controllers.
The app does not request microphone, location, contacts, calendar, storage, SMS, telephony, or any other permission.
5. Children's privacy
FruitConnect is not directed at children under 13. The Meta Horizon Store age rating for this app reflects this. We do not knowingly collect personal data from children. If you believe a child has used the app on your headset, uninstalling the app will permanently delete all locally stored data.
6. Your rights
You can at any time:
Delete a paired Mac — Settings → My Macs → "…" → Forget.
Delete all paired Macs — Settings → My Macs → "Forget All".
Reset all preferences — Settings → "Reset to defaults".
Permanently erase all locally stored data — Uninstall the app from Quest Home. Because allowBackup is disabled, no backup of FruitConnect data exists in your Meta account or any cloud.
For privacy questions or to exercise other rights, email mail@davidkaiser.dk and a response will be sent within 30 days.
If you are in the EU/EEA or UK, you have rights under GDPR including access, rectification, erasure, restriction, portability, and objection. Use the same email above to exercise these rights.
7. Data security
Paired Mac records are encrypted at rest using AES-256-GCM (values) and AES-256-SIV (keys), with a master key in the Android Keystore (hardware-backed where supported).
Pairing handshakes use X25519 (Curve25519) ECDH and HKDF-SHA-256 to derive session keys, with rendezvous-key addressing rotated every 5 minutes to prevent correlation attacks.
Signaling payloads exchanged through the rendezvous mailbox are encrypted client-side with ChaCha20-Poly1305 AEAD before upload.
Streaming media is end-to-end encrypted via DTLS-SRTP (the WebRTC standard).
The rendezvous mailbox enforces a 32 KB payload cap, a 64-message limit per opaque key, automatic deletion after 120 seconds, and per-key rate-limiting.
8. Open source attribution
FruitConnect is built on top of several open-source libraries (most notably WebRTC and its sub-components, plus Jetpack Compose, Kotlin, AndroidX, CameraX, and Google ML Kit on the Quest side). The complete list of components and their licenses is shipped with the application:
macOS: open the FruitConnect menu in the menu bar and click "Licenses" in the footer.
Quest: open Settings inside FruitConnect on the headset and tap "Open Source Licenses".
We may update this policy as the app evolves. The "Last updated" date at the top changes when we do. Material changes that affect what data is collected or how it is used will be highlighted in-app the next time you launch FruitConnect after the update is published.