Lets a native-call user share their iPhone screen (whole device, works backgrounded). LiveKit 2.15.3 ships the broadcast stack (BroadcastManager + LKSampleHandler + IPC), so: - New Broadcast Upload Extension target "BroadcastExtension" (com.bizgaze.connect.broadcast): SampleHandler.swift subclasses LKSampleHandler; injected by mobile/scripts/add-broadcast-extension.rb which also LINKS the LiveKit SPM product into the extension + sets the App Group. Sources in mobile/ios-broadcast/. - Plugin: Room created with ScreenShareCaptureOptions(useBroadcastExtension: true); startScreenShare -> BroadcastManager.requestActivation() (system picker); stopScreenShare -> requestStop(); BroadcastManagerDelegate -> fires screenShareState to the web. LiveKit auto-publishes the track. - ios-patch.sh: RTCScreenSharingExtension + RTCAppGroupIdentifier keys. - codemagic.yaml: run the injector + sign the 3rd bundle id (.broadcast). - home.html: toggleScreen native -> start/stop; screenShareState listener reflects state + broadcasts meeting-screen so peers' stage shows it. REQUIRES a one-time manual Apple portal step: enable the App Group on the com.bizgaze.connect.broadcast App ID (see mobile/IOS_SETUP.md) or the archive fails code-signing. SPM-linked extension is new on our CI — expect build iteration. Web deployed (no-ops on builds without startScreenShare). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
9.5 KiB
Biz Connect — iOS App Store setup (Codemagic, no Mac needed)
The iOS app is a Capacitor shell that loads the live Connect web UI (https://remote.bizgaze.com).
Building/signing/uploading happens on Codemagic's macOS cloud — you never need a Mac.
Bundle id: com.bizgaze.connect · CI config: codemagic.yaml (repo root).
Step 0 — Register the App ID (Identifiers → + → App IDs → App)
On the Register an App ID page, only three fields matter — leave everything else default:
- Platform: leave as-is (the default
iOS, iPadOS, macOS…combined App ID is fine). - Description:
Biz Connect(label only; no@ & * "). - Bundle ID: keep Explicit →
com.bizgaze.connect. - Capabilities: tick Push Notifications only. Leave all others unchecked. (Camera/mic are NOT here — they're Info.plist runtime strings, added by the build pipeline.)
- Continue → Register. (The App ID Prefix shown is your Team ID — note it for Step 5's APNs.)
Step 1 — App Store Connect: create the app record
- appstoreconnect.apple.com → Apps → + → New App.
- Platform iOS, Name Biz Connect, primary language, Bundle ID =
com.bizgaze.connect(the App ID you registered in Step 0 now appears in the dropdown). - SKU: anything unique (e.g.
bizconnect-ios). Create.
Step 2 — App Store Connect API key (for Codemagic to sign + upload)
- App Store Connect → Users and Access → Integrations → App Store Connect API → +.
- Access App Manager. Generate. Note the Issuer ID (top of the page) and the key's Key ID,
and download the
.p8(you can only download it once).
Step 3 — Codemagic: connect + add the key
- codemagic.io → sign in with the git provider → add this repository.
- Teams → Integrations → App Store Connect → Connect, upload the
.p8, paste the Issuer ID and Key ID. Name it exactlyBizGaze App Store Connect(thecodemagic.yamlreferences that name). - Codemagic detects
codemagic.yaml. That's all the signing setup — automatic signing creates the distribution certificate + provisioning profile from this key on the first build.
Step 4 — Run the build
- Codemagic → the app → Start new build → workflow "Biz Connect iOS → TestFlight".
- ~10–15 min. On success the build appears in App Store Connect → TestFlight.
- Add yourself under TestFlight → Internal Testing to install via the TestFlight app on your iPhone.
Step 5 — Push notifications (APNs) — do this once, then tell me
So the app gets calls/messages while it's closed. Two independent pieces must BOTH be in place:
A. App ID capability (client side). The App ID
com.bizgaze.connectmust have Push Notifications enabled (Step 0 ticks it). The build now injects theaps-environmententitlement automatically (ios-patch.sh), so the app can obtain an APNs token — but if the App ID lacks the Push capability, the archive fails code-signing onaps-environment. If a build errors on that after you enable the capability, delete the app's provisioning profile in App Store Connect so the next Codemagic run regenerates one that includes push.B. APNs key (server side). Do the two steps below so the server can actually SEND to that token.
- developer.apple.com → Keys → + → enable Apple Push Notifications service (APNs) → download the
.p8. Note its Key ID and your Team ID (top-right of the developer portal). - Send me: the
.p8contents, the Key ID, and the Team ID. I set these in the server.env(server-side only, like the LiveKit/Giphy keys):The APNs sender is already built into the server — it's a no-op until these are set.APNS_KEY=<contents of the .p8> APNS_KEY_ID=<key id> APNS_TEAM_ID=<team id> APNS_BUNDLE_ID=com.bizgaze.connect APNS_PRODUCTION=1
Step 6 — Public App Store submission (when you're ready to leave TestFlight)
In App Store Connect, fill the listing: screenshots (6.7" + 6.1" iPhone), description, keywords,
support URL, and a Privacy Policy URL (required). Complete the App Privacy questionnaire (we
collect account info + usage for chat/calls). Then submit for review (or flip submit_to_app_store in
codemagic.yaml).
App Review note (Guideline 4.2 — "Minimum Functionality")
Apple scrutinises apps that look like "just a website". Ours passes because it ships real native capabilities — push notifications, camera/microphone for calls, photo sharing. Make sure push (Step 5) is live before the public submission, and in the reviewer notes mention the native video/voice calling + push notifications. Do not advertise "share your screen" as an iOS feature in the store listing yet — see the follow-up below (you can still view a screen someone else shares).
Known iOS limitations & follow-ups (phase 2 — after TestFlight)
1. Sharing YOUR iOS screen into a meeting → needs a ReplayKit Broadcast Upload Extension
- Why: the app's screen share uses the web
getDisplayMediaAPI, which iOS WebViews and Safari do not support. Apple only allows capturing the device screen via ReplayKit. - What works today on iOS: viewing a screen another participant shares (it's just incoming video), chat, voice/video calls, camera, photo sharing.
- What's needed to broadcast the iOS screen: a native Broadcast Upload Extension target that captures frames via ReplayKit and feeds them into the LiveKit/WebRTC session, plus the App Groups capability (to pass data between the app and the extension). This is native Swift work — NOT part of the Capacitor wrapper — so it's tracked as a separate task, done after the app is on TestFlight.
- Store impact: don't claim iOS screen-sharing in the listing until this ships, or a reviewer may test it and it will fail.
2. Native mobile audio routing (speaker / earpiece / Bluetooth) — needs a Capacitor audio plugin
- Mobile web can't switch the audio output route (
setSinkIdis unimplemented on iOS/Android), so the in-meeting speaker/earpiece/Bluetooth control is web-only where it works and hidden where it doesn't. - True routing on iOS needs a small native plugin driving
AVAudioSession. Phase-2 native task.
Share Extension ("Biz Connect" in the iOS share sheet) — one-time Apple portal setup
The app now has a Share Extension target (com.bizgaze.connect.share) so users can share a photo /
video / file FROM the Photos or Files app INTO a Biz Connect conversation. The Codemagic build injects the
target and fetches a profile for it automatically, but two things can ONLY be done once, by hand, in the
Apple Developer portal — CI cannot toggle App capabilities:
- Create the App Group (developer.apple.com → Identifiers → App Groups → +):
identifier
group.com.bizgaze.connect. - Enable the App Groups capability on BOTH App IDs and assign them to that group:
com.bizgaze.connect(the app)com.bizgaze.connect.share(the extension — create this App ID if the first build hasn't yet;fetch-signing-files --createwill register it, then edit it to add App Groups) After enabling the capability, the provisioning profiles must be regenerated — the next Codemagic build does that viafetch-signing-files, so just re-run it once the capability is on.
If the App Group isn't set up, the app and the extension can't see each other's files: sharing will appear to do nothing (the extension stages the file, but the app finds an empty inbox). Everything else — download to the Files folder, the Photos "Connect" album, Manage storage — works without it.
Also enabled by this change (main app Info.plist, done automatically by ios-patch.sh):
UIFileSharingEnabled+LSSupportsOpeningDocumentsInPlace→ the Biz Connect folder in Files.CFBundleURLTypesschemebizconnect→ lets the extension bounce back into the app after staging.
Broadcast Extension (share your iPhone SCREEN in a call) — one-time Apple portal setup
Screen sharing from iOS uses a Broadcast Upload Extension (com.bizgaze.connect.broadcast) — the same
pattern as the Share Extension. The Codemagic build injects the target, links LiveKit into it, and fetches a
profile automatically, but the App Group capability can only be toggled by hand in the Apple portal:
- Reuse the SAME App Group as the Share Extension:
group.com.bizgaze.connect(no new group needed). - Enable the App Groups capability on the broadcast App ID and assign it to that group:
com.bizgaze.connect.broadcast(create this App ID if the first build hasn't yet —fetch-signing-files --createregisters it, then edit it to add App Groups). The app (com.bizgaze.connect) already has the group from the Share Extension setup above. After enabling it, re-run the Codemagic build sofetch-signing-filesregenerates the profile.
Until the App Group is on the broadcast App ID, the archive fails code-signing (entitlement mismatch) —
that's the expected first-build failure. The shared App Group is how the extension (ReplayKit capture) hands
screen frames to the app over LiveKit's IPC socket. Receiving OTHERS' shared screens needs none of this — it
already works. ios-patch.sh sets RTCScreenSharingExtension + RTCAppGroupIdentifier in the app Info.plist
so LiveKit finds the extension + group.