fix(ios): forward APNs device token from AppDelegate to Capacitor

ROOT CAUSE of iOS push not working: Capacitor 7's default AppDelegate.swift
template does NOT implement application(_:didRegisterForRemoteNotificationsWithDeviceToken:)
or ...didFailToRegisterForRemoteNotificationsWithError:. So when @capacitor/push-
notifications calls registerForRemoteNotifications(), iOS fetches the APNs token
and calls the AppDelegate, but nothing posts .capacitorDidRegisterForRemoteNotifications,
so the plugin never delivers the token to JS. register() 'succeeds' yet neither the
registration nor registrationError event fires — proven by server push telemetry
(register-called logged; no token, no error; device_tokens stayed empty).

inject-push.js adds the two forwarding methods to the CI-generated AppDelegate
(idempotent, tolerant — never fails the build), wired into ios-patch.sh after the
audio patch. Verified against the real Capacitor 7 template: methods land inside
the class, braces balance, both listeners present.

This is the missing piece alongside the earlier aps-environment entitlement fix and
the server APNs config. Needs a fresh Codemagic build to take effect.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
2026-07-27 21:27:25 +05:30
parent c6523a8461
commit 1920258fd6
2 changed files with 54 additions and 0 deletions
+50
View File
@@ -0,0 +1,50 @@
// Inject the APNs registration-forwarding methods into the Capacitor iOS AppDelegate.
// Run on Codemagic (macOS) from ios-patch.sh:
// node mobile/scripts/inject-push.js mobile/ios/App/App/AppDelegate.swift
//
// WHY: Capacitor 7's default AppDelegate.swift template does NOT implement
// application(_:didRegisterForRemoteNotificationsWithDeviceToken:) / ...didFailToRegister...
// so when @capacitor/push-notifications calls registerForRemoteNotifications(), iOS DOES obtain the
// APNs token but the AppDelegate never posts .capacitorDidRegisterForRemoteNotifications — so the plugin
// never delivers the token to JS. register() "succeeds", yet NEITHER the `registration` nor the
// `registrationError` event ever fires (proven via server-side push telemetry: register-called logged,
// no token, no error). These two methods forward the token / error to Capacitor. The plugin listens for
// the notifications defined in @capacitor/ios CAPNotifications.swift; `import Capacitor` (already in the
// AppDelegate) exposes the Notification.Name values.
//
// TOLERANT: exits 0 and no-ops if anything is off, so it can NEVER fail the build. Idempotent (keyed on
// the bzcPushForward marker), so re-runs never duplicate the methods.
const fs = require('fs');
const p = process.argv[2];
if (!p || !fs.existsSync(p)) { console.log(' (AppDelegate.swift not found — push forwarding patch skipped)'); process.exit(0); }
try {
let s = fs.readFileSync(p, 'utf8');
if (s.includes('bzcPushForward') || s.includes('capacitorDidRegisterForRemoteNotifications')) {
console.log(' push forwarding already patched'); process.exit(0);
}
const methods = [
'',
' // bzcPushForward: forward APNs device-token registration to Capacitor. The Capacitor 7 AppDelegate',
' // template omits these, so @capacitor/push-notifications never receives the token without them.',
' func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken: Data) {',
' NotificationCenter.default.post(name: .capacitorDidRegisterForRemoteNotifications, object: deviceToken)',
' }',
'',
' func application(_ application: UIApplication, didFailToRegisterForRemoteNotificationsWithError error: Error) {',
' NotificationCenter.default.post(name: .capacitorDidFailToRegisterForRemoteNotifications, object: error)',
' }',
'',
].join('\n');
// Insert the methods just before the final closing brace of the file (which closes the AppDelegate class).
const orig = s;
s = s.replace(/\}\s*$/, methods + '}\n');
if (s !== orig && s.includes('bzcPushForward')) {
fs.writeFileSync(p, s);
console.log(' APNs registration forwarding methods injected into AppDelegate');
} else {
console.log(' (AppDelegate closing brace not matched — push forwarding patch skipped)');
}
} catch (e) {
console.log(' (push forwarding patch error, skipped: ' + (e && e.message) + ')');
}
process.exit(0);
+4
View File
@@ -94,6 +94,10 @@ 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)"
# 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)"
fi
echo "Info.plist patched:"