Files
BizGaze_Remote/mobile/scripts/ios-patch.sh
T
Sravan 83b445e32d fix(ios): inject aps-environment entitlement so APNs push registration works
The @capacitor/push-notifications plugin does not add the Push Notifications
capability to the CI-generated Xcode project (that's a manual Xcode step), and
add-share-extension.rb only merged the App Group into App.entitlements, assuming
aps-environment was already there. It never was — so on device PushNotifications.
register() failed with 'no valid aps-environment entitlement', no APNs token was
obtained, and device_tokens stayed empty (server had nothing to push to).

ios-patch.sh now creates App/App.entitlements with aps-environment=production
before the share-extension script merges the App Group in. Still requires the App
ID to have Push Notifications enabled (so the profile carries the entitlement) and
the server APNS_* key set (Step 5) for end-to-end delivery.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-26 21:04:12 +05:30

102 lines
6.4 KiB
Bash

#!/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"
# 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"
# ── 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
# ── 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
# ── 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"
# 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"
# Allow the webview to load our HTTPS origin (we do NOT enable arbitrary cleartext).
"$PB" -c "Delete :NSAppTransportSecurity" "$PLIST" 2>/dev/null || true
# ── 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).
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)"
fi
echo "Info.plist patched:"
"$PB" -c "Print :NSCameraUsageDescription" "$PLIST"
"$PB" -c "Print :NSMicrophoneUsageDescription" "$PLIST"