2026-07-15 16:45:10 +05:30
|
|
|
#!/usr/bin/env bash
|
|
|
|
|
# Patch the freshly-generated Capacitor iOS project (mobile/ios/App) for App Store submission.
|
|
|
|
|
# Runs on the Codemagic macOS instance after `npx cap add ios`. Idempotent — safe to re-run.
|
|
|
|
|
set -euo pipefail
|
|
|
|
|
|
|
|
|
|
PLIST="mobile/ios/App/App/Info.plist"
|
|
|
|
|
PB=/usr/libexec/PlistBuddy
|
|
|
|
|
|
|
|
|
|
set_str() { # set_str <key> <value> — add the key if missing, else overwrite
|
|
|
|
|
"$PB" -c "Add :$1 string $2" "$PLIST" 2>/dev/null || "$PB" -c "Set :$1 $2" "$PLIST"
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
echo "Patching $PLIST"
|
|
|
|
|
|
|
|
|
|
# ── Privacy usage strings (Apple REJECTS the build if a used capability has no purpose string) ──
|
|
|
|
|
set_str NSCameraUsageDescription "Biz Connect uses the camera for video calls and to share photos and your screen."
|
|
|
|
|
set_str NSMicrophoneUsageDescription "Biz Connect uses the microphone for voice and video calls."
|
|
|
|
|
set_str NSPhotoLibraryUsageDescription "Biz Connect needs photo access so you can send images in chat."
|
|
|
|
|
set_str NSPhotoLibraryAddUsageDescription "Biz Connect saves images and recordings you download to your photos."
|
|
|
|
|
|
|
|
|
|
# Human-readable display name on the home screen.
|
|
|
|
|
set_str CFBundleDisplayName "Biz Connect"
|
|
|
|
|
|
2026-07-17 20:09:51 +05:30
|
|
|
# CFBundleVersion (build number) MUST be unique AND higher than every previous App Store Connect upload,
|
|
|
|
|
# or publishing fails with "The bundle version must be higher than the previously uploaded version".
|
|
|
|
|
# Capacitor ships "1" by default, so every build collided. Use Codemagic's monotonically-increasing
|
|
|
|
|
# BUILD_NUMBER (this is build index 6+, already > the "1" that's on TestFlight). Fall back to an epoch
|
|
|
|
|
# timestamp if it's somehow unset, which is also strictly increasing.
|
|
|
|
|
BUILD_NO="${BUILD_NUMBER:-$(date +%s)}"
|
|
|
|
|
echo "Setting CFBundleVersion = $BUILD_NO"
|
|
|
|
|
set_str CFBundleVersion "$BUILD_NO"
|
|
|
|
|
|
2026-07-23 15:29:29 +05:30
|
|
|
# ── Make the app's Documents folder visible in the Files app ───────────────────────────────────────
|
|
|
|
|
# Downloads are saved to Documents/{Images,Videos,Files}. Without these two keys that folder is private
|
|
|
|
|
# and the user has no way to reach what they saved. With them, Files shows
|
|
|
|
|
# Files → Browse → On My iPhone → Biz Connect → Images / Videos / Files
|
|
|
|
|
# UIFileSharingEnabled exposes the folder; LSSupportsOpeningDocumentsInPlace lets other apps open those
|
|
|
|
|
# files in place rather than silently working on a copy.
|
|
|
|
|
set_bool() { "$PB" -c "Add :$1 bool $2" "$PLIST" 2>/dev/null || "$PB" -c "Set :$1 $2" "$PLIST"; }
|
|
|
|
|
set_bool UIFileSharingEnabled true
|
|
|
|
|
set_bool LSSupportsOpeningDocumentsInPlace true
|
|
|
|
|
|
2026-07-27 22:24:39 +05:30
|
|
|
# ── Keep CALL AUDIO alive when the app is BACKGROUNDED (minimised / screen locked) ──────────────────
|
|
|
|
|
# Without the 'audio' background mode, iOS suspends the WebView a few seconds after it backgrounds, which
|
|
|
|
|
# freezes the WebRTC mic + audio pipeline — so participants can't hear each other once the app is minimised
|
|
|
|
|
# or the phone locks. Declaring background audio keeps the audio session (and the app) running so a voice
|
|
|
|
|
# call continues in the background. (Video RENDERING still pauses while backgrounded — unavoidable in a
|
|
|
|
|
# WebView — but audio keeps flowing, which is what matters for a call.) Idempotent: rebuild the array each run.
|
2026-07-28 22:34:27 +05:30
|
|
|
# 'audio' keeps the call audio session alive when backgrounded; 'voip' is REQUIRED for PushKit to deliver
|
|
|
|
|
# VoIP pushes (CallKit incoming-call wake). Both are legitimate for a calling app and accepted by review
|
|
|
|
|
# because the app uses CallKit.
|
2026-07-27 22:24:39 +05:30
|
|
|
"$PB" -c "Delete :UIBackgroundModes" "$PLIST" 2>/dev/null || true
|
|
|
|
|
"$PB" -c "Add :UIBackgroundModes array" "$PLIST"
|
|
|
|
|
"$PB" -c "Add :UIBackgroundModes:0 string audio" "$PLIST"
|
2026-07-28 22:34:27 +05:30
|
|
|
"$PB" -c "Add :UIBackgroundModes:1 string voip" "$PLIST"
|
|
|
|
|
echo "UIBackgroundModes: audio, voip (call audio + CallKit VoIP wake)"
|
2026-07-27 22:24:39 +05:30
|
|
|
|
2026-07-23 22:58:08 +05:30
|
|
|
# ── Custom URL scheme so the Share Extension can bounce the user back into the app ──────────────────
|
|
|
|
|
# The extension stages the shared files into the App Group, then opens bizconnect://share; the app reads
|
|
|
|
|
# the staged files and shows "Send to…". Registering the scheme is what makes that openURL succeed.
|
|
|
|
|
if ! "$PB" -c "Print :CFBundleURLTypes" "$PLIST" >/dev/null 2>&1; then
|
|
|
|
|
"$PB" -c "Add :CFBundleURLTypes array" "$PLIST"
|
|
|
|
|
"$PB" -c "Add :CFBundleURLTypes:0 dict" "$PLIST"
|
|
|
|
|
"$PB" -c "Add :CFBundleURLTypes:0:CFBundleURLName string com.bizgaze.connect" "$PLIST"
|
|
|
|
|
"$PB" -c "Add :CFBundleURLTypes:0:CFBundleURLSchemes array" "$PLIST"
|
|
|
|
|
"$PB" -c "Add :CFBundleURLTypes:0:CFBundleURLSchemes:0 string bizconnect" "$PLIST"
|
|
|
|
|
fi
|
|
|
|
|
|
2026-07-26 21:04:12 +05:30
|
|
|
# ── Push Notifications entitlement (aps-environment) ────────────────────────────────────────────────
|
|
|
|
|
# The @capacitor/push-notifications plugin does NOT add the Push Notifications capability to the generated
|
|
|
|
|
# Xcode project — in Xcode that's a manual "Signing & Capabilities → + Push Notifications" click, which
|
|
|
|
|
# never happens on a fresh CI checkout. Without the aps-environment entitlement, PushNotifications.register()
|
|
|
|
|
# fails on device ("no valid 'aps-environment' entitlement string found") and NO APNs token is ever
|
|
|
|
|
# obtained, so device_tokens stays empty and the server has nothing to push to. Create the entitlements
|
|
|
|
|
# file with aps-environment HERE; add-share-extension.rb (runs after this) MERGES the App Group into the
|
|
|
|
|
# same file, preserving this key. REQUIRES: the App ID com.bizgaze.connect must have the Push Notifications
|
|
|
|
|
# capability enabled in the Apple Developer portal, so the fetched provisioning profile carries
|
|
|
|
|
# aps-environment — otherwise the archive fails code-signing. "production" is correct for App Store +
|
|
|
|
|
# TestFlight (pair it with APNS_PRODUCTION=1 on the server).
|
|
|
|
|
ENT="mobile/ios/App/App/App.entitlements"
|
|
|
|
|
if [ ! -f "$ENT" ]; then
|
|
|
|
|
cat > "$ENT" <<'PLIST'
|
|
|
|
|
<?xml version="1.0" encoding="UTF-8"?>
|
|
|
|
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
|
|
|
<plist version="1.0">
|
|
|
|
|
<dict>
|
|
|
|
|
</dict>
|
|
|
|
|
</plist>
|
|
|
|
|
PLIST
|
|
|
|
|
fi
|
|
|
|
|
"$PB" -c "Add :aps-environment string production" "$ENT" 2>/dev/null || "$PB" -c "Set :aps-environment production" "$ENT"
|
|
|
|
|
echo "Entitlements: aps-environment=production ensured in $ENT"
|
|
|
|
|
|
2026-07-17 16:02:28 +05:30
|
|
|
# We use only standard encryption (HTTPS/TLS), which is exempt — declaring this up front stops App Store
|
|
|
|
|
# Connect from asking the "export compliance" question on every single build/TestFlight upload.
|
|
|
|
|
"$PB" -c "Add :ITSAppUsesNonExemptEncryption bool false" "$PLIST" 2>/dev/null \
|
|
|
|
|
|| "$PB" -c "Set :ITSAppUsesNonExemptEncryption false" "$PLIST"
|
|
|
|
|
|
2026-07-15 16:45:10 +05:30
|
|
|
# Allow the webview to load our HTTPS origin (we do NOT enable arbitrary cleartext).
|
|
|
|
|
"$PB" -c "Delete :NSAppTransportSecurity" "$PLIST" 2>/dev/null || true
|
|
|
|
|
|
2026-07-19 16:18:52 +05:30
|
|
|
# ── Default in-call audio to the LOUDSPEAKER (AVAudioSession) ──────────────────────────────────────
|
|
|
|
|
# Without this, iOS routes WebRTC call audio to the quiet EARPIECE. A Node helper (Node 20 is already set
|
|
|
|
|
# up for this build) injects an AVAudioSession category into the generated AppDelegate so calls default to
|
|
|
|
|
# the speaker (headphones/Bluetooth still win when connected). The helper is tolerant and exits 0 even if
|
|
|
|
|
# the template differs, so it NEVER fails the build. First-pass fix — if WebRTC re-grabs the session
|
|
|
|
|
# mid-call on device, we follow up with a plugin that re-asserts .overrideOutputAudioPort(.speaker).
|
2026-07-27 23:03:45 +05:30
|
|
|
# ── Bundle the custom notification sound so chat/call pushes have an explicit tone ──────────────────
|
|
|
|
|
# APNs plays the sound named in the push payload (the server sends sound:'notif.wav'). The file must be a
|
|
|
|
|
# resource in the app bundle ROOT; copy it in here — add-share-extension.rb then adds it to the App
|
|
|
|
|
# target's "Copy Bundle Resources". iOS accepts a PCM .wav (<30s) for a notification sound.
|
|
|
|
|
SND_SRC="mobile/ios-assets/notif.wav"
|
|
|
|
|
SND_DST="mobile/ios/App/App/notif.wav"
|
|
|
|
|
if [ -f "$SND_SRC" ]; then cp "$SND_SRC" "$SND_DST" && echo "Copied notification sound -> $SND_DST"; else echo " (notif.wav source missing — sound not bundled)"; fi
|
|
|
|
|
|
2026-07-19 16:18:52 +05:30
|
|
|
AD="mobile/ios/App/App/AppDelegate.swift"
|
|
|
|
|
if [ -f "$AD" ]; then
|
|
|
|
|
echo "Patching AppDelegate audio session"
|
|
|
|
|
node "$(dirname "$0")/inject-audio.js" "$AD" || echo " (AVAudioSession patch skipped — non-fatal)"
|
2026-07-27 21:27:25 +05:30
|
|
|
# Capacitor 7's AppDelegate template omits the APNs registration callbacks, so the push-notifications
|
|
|
|
|
# plugin never receives the device token (register() fires no event at all). Inject the forwarding methods.
|
|
|
|
|
echo "Patching AppDelegate APNs registration forwarding"
|
|
|
|
|
node "$(dirname "$0")/inject-push.js" "$AD" || echo " (push forwarding patch skipped — non-fatal)"
|
2026-07-19 16:18:52 +05:30
|
|
|
fi
|
|
|
|
|
|
2026-07-31 19:46:48 +05:30
|
|
|
# ── Pin LiveKit to 2.15.3 via its Git tag (stay on CocoaPods; no SPM migration) ─────────────────────
|
|
|
|
|
# The CallKit audio-session coordination API (AudioManager.audioSession / setEngineAvailability) exists only
|
|
|
|
|
# in LiveKit 2.1+, but the LiveKitClient CocoaPod PUBLISHED to trunk caps at 2.0.18 (2.1+ is SPM-only). The
|
|
|
|
|
# repo still ships a VALID podspec at tag 2.15.3, and its deps (LiveKitWebRTC 144.7559.11 / LiveKitUniFFI
|
|
|
|
|
# 0.0.6 / SwiftProtobuf) ARE on trunk — so we point the Podfile straight at the git tag. The NativeCall
|
|
|
|
|
# podspec's `LiveKitClient ~> 2.0` is satisfied by 2.15.3 (2.15.3 ∈ [2.0, 3.0)). Idempotent (grep guard).
|
|
|
|
|
PODFILE="mobile/ios/App/Podfile"
|
|
|
|
|
if [ -f "$PODFILE" ]; then
|
|
|
|
|
if grep -q "client-sdk-swift.git" "$PODFILE"; then
|
|
|
|
|
echo "Podfile: LiveKitClient git pin already present"
|
|
|
|
|
else
|
|
|
|
|
ruby -e '
|
|
|
|
|
p = "mobile/ios/App/Podfile"
|
|
|
|
|
s = File.read(p)
|
2026-07-31 20:25:35 +05:30
|
|
|
# LiveKitClient 2.15.3 depends on LiveKitUniFFI (= 0.0.6), which is NOT on the CocoaPods CDN (SPM-only),
|
|
|
|
|
# so we ALSO pin it to its git tag (its podspec at 0.0.6 has no further deps — just downloads its
|
|
|
|
|
# prebuilt XCFramework). LiveKitWebRTC 144.x + SwiftProtobuf resolve from the CDN normally.
|
|
|
|
|
pin = " pod \x27LiveKitClient\x27, :git => \x27https://github.com/livekit/client-sdk-swift.git\x27, :tag => \x272.15.3\x27\n" +
|
|
|
|
|
" pod \x27LiveKitUniFFI\x27, :git => \x27https://github.com/livekit/livekit-uniffi-xcframework.git\x27, :tag => \x270.0.6\x27\n"
|
2026-07-31 19:46:48 +05:30
|
|
|
if s =~ /target ["\x27]App["\x27] do\n/
|
|
|
|
|
s = s.sub(/target ["\x27]App["\x27] do\n/) { |m| m + pin }
|
|
|
|
|
File.write(p, s)
|
2026-07-31 20:25:35 +05:30
|
|
|
puts "Podfile: pinned LiveKitClient 2.15.3 + LiveKitUniFFI 0.0.6 (git tags)"
|
2026-07-31 19:46:48 +05:30
|
|
|
else
|
|
|
|
|
STDERR.puts "WARN: could not find \"target App do\" in Podfile — LiveKit pin NOT applied"
|
|
|
|
|
exit 1
|
|
|
|
|
end
|
|
|
|
|
'
|
|
|
|
|
fi
|
|
|
|
|
grep -n "LiveKitClient" "$PODFILE" || true
|
2026-07-31 19:55:10 +05:30
|
|
|
# `npx cap sync` already ran `pod install` with the PRE-pin Podfile, leaving a Podfile.lock that pins
|
|
|
|
|
# LiveKitClient = 2.0.18 — which conflicts with the git-tag source we just injected ("could not find
|
|
|
|
|
# compatible versions … In snapshot (Podfile.lock): LiveKitClient (= 2.0.18)"). Drop the lock so the
|
|
|
|
|
# later "Install CocoaPods" step re-resolves cleanly against tag 2.15.3.
|
|
|
|
|
rm -f "mobile/ios/App/Podfile.lock" && echo "Removed stale Podfile.lock (cap-sync pinned 2.0.18)"
|
2026-07-31 19:46:48 +05:30
|
|
|
fi
|
|
|
|
|
|
2026-07-15 16:45:10 +05:30
|
|
|
echo "Info.plist patched:"
|
|
|
|
|
"$PB" -c "Print :NSCameraUsageDescription" "$PLIST"
|
|
|
|
|
"$PB" -c "Print :NSMicrophoneUsageDescription" "$PLIST"
|