oficina / meet Public

Clone
git clone https://amadan.net/oficina/meet

Plain git — no account needed to clone.

Download

Download this file

1# Calls for iPhone
2
3A SwiftUI iOS 17+ app using the same Go authentication/signaling core and native
4WebRTC engine as Calls for Mac. No embedded webview is used. The Xcode project is
5generated from `project.yml`; edit that specification and the Swift sources.
6
7## Build
8
9Requires Xcode, Go, and XcodeGen (`brew install xcodegen`). Dependencies are pinned
10in the shared Swift package and the isolated native Go module.
11
12```sh
13make iphone
14# For a signed physical-device build:
15CALLS_DEVELOPMENT_TEAM=YOUR_TEAM_ID make iphone-device
16```
17
18Build products are `native/build/ios-simulator/Build/Products/Debug-iphonesimulator/CallsPhone.app`
19and `native/build/ios-device/Build/Products/Debug-iphoneos/CallsPhone.app`.
20The device build uses automatic development signing with your selected team; it
21does not upload an app to App Store Connect or TestFlight.
22
23Generate/open the Xcode project for normal device development:
24
25```sh
26xcodegen generate --spec native/ios/project.yml
27open native/ios/CallsPhone.xcodeproj
28```
29
30Select your development team and connected iPhone, then Run. Alternatively,
31install a signed app using `xcrun devicectl device install app --device DEVICE_ID`
32followed by the device build's `.app` path. The iPhone must be connected and
33available to Xcode, with Developer Mode enabled.
34
35## First phone milestone
36
37- **Sign in** to the Calls server (calls.oficina.works unless changed with the
38 home screen's gear), or **Join a call as a guest** with a pasted invite link;
39 sign in in Safari/the default browser.
40- Open the email link normally, compare the browser's code with Calls's code,
41 approve, then return to Calls. A member's session is kept in the Keychain
42 (this device only) and checked with the server at the next launch; see
43 [../README.md](../README.md). **Sign out** forgets it.
44- Join using the front camera and microphone; the local video display is mirrored.
45 Sent video and remote video retain their original orientation.
46- Native remote video, microphone/camera controls, guest admission, chat, and leave.
47- Chat is always available beside the call controls. Its button shows new-message
48 counts; opening it shows the latest messages and a composer without scrolling
49 past the video tiles. Opening chat keeps the call active.
50- Members have a Share button beside Chat, with Copy link and the system share menu.
51- Camera/microphone permission refusals appear in the app. Retry after changing
52 permission in Settings. The simulator has no physical camera.
53
54Calls are foreground-only in this milestone. Switching away ends the call,
55except while you are sharing your screen; sign-in survives switching to the
56browser. Camera switching, CallKit/background calling, and distribution are
57follow-up work. Physical camera, audible audio, Bluetooth routing, rotation while
58capturing, and interruption recovery need a real-device acceptance pass.
59
60## Sharing the screen
61
62**Share your screen** in the call is the system's broadcast button
63(`RPSystemBroadcastPickerView`, offering only Calls' own extension). The whole
64screen is shared, which is the only thing iOS lets any app share. The frames
65reach a **broadcast upload extension** (`Broadcast/SampleHandler.swift`, target
66`CallsBroadcast`), a separate process with a memory ceiling of about fifty
67megabytes. It links only `CallsBroadcastKit` and no WebRTC, which keeps it near
68600 KB. It shrinks each frame to 1280 on the long side, at up to fifteen a second,
69and passes it to the app over a Unix socket in the **App Group** container
70(`calls.sock`). The app is already in the call and sends the frames on the same
71`sendonly` screen transceiver the Mac and the browser use. Only one frame is in
72flight at a time; any frame that arrives while one is being written is dropped,
73never queued.
74
75Stopping works from either end. **Stop sharing** in the app closes the socket;
76the extension's next write fails and it ends the broadcast with "You stopped
77sharing your screen in Calls." Stopping from Control Center or the red status
78bar closes the socket from the other side, and the app stops the share. A
79broadcast started with no call to take it ends with "Join a call in Calls, then
80share your screen from there."
81
82A call that is sharing stays up when you leave the app, because showing other
83apps is the point. That needs the `audio` background mode. Every other call
84still ends when the app goes to the background. If the broadcast is stopped
85from outside while Calls is in the background, the call ends too.
86
87The App Group is one build setting, `CALLS_APP_GROUP` in `project.yml`
88(`group.net.amadan.oficina.calls`). It feeds both targets' entitlements and the
89`CallsAppGroup` key in both Info.plists. The extension's bundle identifier is the
90app's plus `.broadcast`. The app looks for its extension by that rule, so the
91two must stay in step.
92
93**Signing.** The simulator build is unsigned, so it has no App Group and cannot
94broadcast; ReplayKit broadcasts need a device anyway. Before the first signed
95device build, in the Apple Developer portal (Certificates, Identifiers &
96Profiles):
97
981. Identifiers → App Groups → **+** → `group.net.amadan.oficina.calls`.
992. Identifiers → `net.amadan.oficina.calls.ios` → enable **App Groups** →
100 choose that group. (Push stays as it is.)
1013. Identifiers → **+** → App IDs → App → `net.amadan.oficina.calls.ios.broadcast`,
102 description "Calls broadcast", with **App Groups** enabled and the same group.
1034. `CALLS_DEVELOPMENT_TEAM=<team> make iphone-device`. Automatic signing makes
104 both development profiles once the identifiers exist, and
105 `-allowProvisioningUpdates` can also create steps 1–3 for you if you would
106 rather let Xcode do it.
107
108A TestFlight or App Store build needs distribution profiles for **both** bundle
109identifiers, and the extension goes up inside the app.
110
111**The TestFlight release branch differs.** `release/calls-testflight` ships
112under the bundle identifier `works.oficina.meet`, not
113`net.amadan.oficina.calls.ios`. When screen sharing reaches that branch it
114needs, in its `project.yml`:
115
116- `CALLS_APP_GROUP: group.works.oficina.meet`
117- the `CallsBroadcast` target's `PRODUCT_BUNDLE_IDENTIFIER:
118 works.oficina.meet.broadcast` (the app finds its extension as its own
119 identifier plus `.broadcast`, so this one is not a free choice)
120
121and, in the Apple Developer portal, the App Group `group.works.oficina.meet`
122enabled on both `works.oficina.meet` and `works.oficina.meet.broadcast`, with
123distribution profiles for both. The development values above stay as they are
124on main.
125
126## Verification
127
128```sh
129xcrun simctl list devices available
130# Choose an iPhone simulator UUID:
131CALLS_IOS_SIMULATOR=YOUR_SIMULATOR_UUID make iphone-browser
132```
133
134The Go harness creates isolated databases, meetings, and captured magic-link
135emails. iPhone UI tests use the real Safari handoff for members and guests and
136compare the authorization codes before returning to the app. No production
137credentials are used and no real email is sent. The UI tests launch with
138`-CallsForgetSignIn YES`, which signs the app out before anything else: the
139simulator's Keychain outlives reinstalls, and a session kept by an earlier run
140against a harness server long gone would otherwise decide what the next run sees.
141
142The native media test creates two iOS WebRTC peers with a synthetic camera,
143checks decoded video in both directions, continuity through three-second stream
144renewals, and removal of the departed peer. Hardware audio is disabled in this
145simulator-only test: it neither records the host microphone nor claims to prove
146physical audio quality. Synthetic capture is compiled only into test targets.
147Xcode logs and result bundles are retained under `native/build/`.
148