diff --git a/mobile/scripts/inject-push.js b/mobile/scripts/inject-push.js new file mode 100644 index 0000000..948ba5e --- /dev/null +++ b/mobile/scripts/inject-push.js @@ -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); diff --git a/mobile/scripts/ios-patch.sh b/mobile/scripts/ios-patch.sh index c1c9de9..20dd909 100644 --- a/mobile/scripts/ios-patch.sh +++ b/mobile/scripts/ios-patch.sh @@ -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:"