Native iOS device client for the Splintercom video splintercom system — plays the same device role as
ClientPython (OAuth2 device flow + WebRTC camera streaming), so an iPhone/iPad can be repurposed
as a CCTV or doorbell camera instead of needing a Raspberry Pi.
- Swift 6 (strict concurrency), SwiftUI (iOS 15+)
- stasel/WebRTC — prebuilt Google libwebrtc via Swift Package Manager
- Built via xtool, not Xcode — works from Linux/Windows, not just macOS
- Keychain (OAuth tokens), UserDefaults (server settings + device preferences)
# One-time setup (installs the Darwin Swift SDK, handles Apple auth)
xtool setup
# Build
xtool dev build
# Pure-logic tests (no WebRTC/UIKit dependency) — runs on plain Linux Swift
swift test
# Install + launch on a connected device
xtool install xtool/ClientiOS.app
xtool launch com.intercom.clientios # or launch manually from the home screenIf you're on a Mac with Xcode, xtool dev generate-xcode-project can produce an .xcodeproj for
a more familiar editing experience — Package.swift/xtool.yml remain the source of truth either way.
- Build and install the app on a device (see above).
- Tap "Scan QR Code" in Settings and scan the QR code on the Frontend's "Add Device" page
(
/devices/add→ iOS section) — fills in the server URLs + OAuth client ID in one go (manual entry is available as a fallback). - Tap "Start Pairing", then approve the device from the Splintercom web dashboard using the displayed user code.
- Once paired, the app streams its camera to any viewer who opens that device in the Frontend.
- Foreground-only by design — iOS suspends camera capture within seconds of backgrounding, so there's no attempt to fight that with VoIP/PiP background modes. The app disables the idle timer while active and shows a "streaming paused" banner if backgrounded. Mount the device somewhere it can stay plugged in and awake.
- Front/back camera choice (Settings → Camera) — useful for a doorbell-style mount using the front/selfie camera, screen facing the visitor.
- On-screen display message — set locally (Settings) or remotely from the Frontend's device view page, e.g. "We're not home right now" — shown on the device's own screen without affecting the video feed to viewers. Anonymous share-link viewers can't set it, only signed-in owners.
- Full-screen "kiosk" mode — hides connection diagnostics/viewer count, showing just the camera preview or message full-screen; available once the camera is set to front.
- Two-way audio — the device streams mic audio alongside video and plays incoming viewer
audio through the speaker (not the earpiece), so it works as a real splintercom once the viewer
enables audio from the Frontend. No mute control on the device side by design — mute, if
wanted, is a viewer-side concern (see Frontend's
ViewDevice.tsx).
See CLAUDE.md for architecture details (DeviceClient orchestration, WebRTC/signaling internals,
xtool-specific gotchas).
Most of this project was built and verified from a Linux dev machine with no physical iPhone or
macOS host available, using xtool dev build/swift test plus live-backend wire-protocol checks.
Two things couldn't be verified from Linux and needed a real device:
URLSessionWebSocketTaskat runtime. It compiles fine on Linux, but crashes there at runtime ("WebSockets not supported by libcurl") — a gap in swift-corelibs-foundation's Linux networking backend, not a bug in this code. Confirmed working on a real iPhone (iOS 15.8.3, installed viaxtool install) — Apple's native, non-libcurlURLSessionWebSocket implementation handles the signaling connection correctly.- A single
RTCVideoTrackadded to multiple concurrentRTCPeerConnections. Standard behavior in libwebrtc's C++ core, and the reasonCameraControllerdoesn't need to port ClientPython's manualSharedCameraSourceframe-queue fan-out. Confirmed working: paired the physical device, connected as a viewer from the Frontend dashboard, and saw live camera video.
Deployment note: the app was installed via xtool install under a free/personal Apple Developer
account, which required revoking an existing "iOS Development" certificate first (Apple limits
personal accounts to one active dev cert) — do this deliberately, since it invalidates any other
app currently signed with that cert until rebuilt. xtool launch's automatic launch (which
attaches a debugserver) failed with DebugserverClient.Error.unknown on iOS 15.8.3, likely a
Developer Disk Image mismatch for this older OS version — launching the app manually from the
home screen works fine and is unaffected.
Still untested: multiple simultaneous viewers on the same device (only one viewer has been tried so far).