From b8475bcb7f882344191ababcf489273d62f2034f Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 9 Apr 2026 13:09:31 +0100 Subject: [PATCH 1/2] fix: handle non-array action and intent-filter in AndroidManifest parsing XML parsers like xml2js (used by @expo/config-plugins) may return a single object instead of an array when there is only one child element. The hasExistingFcmService check assumed action and intent-filter were always arrays, causing a TypeError on Expo SDK 55 for versions > 9.6.3. Normalize both fields with [].concat() before calling .some() so the check works regardless of whether the XML parser returns an object or an array. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../withAndroidPushNotifications.test.ts | 64 +++++++++++++++++++ .../withAndroidPushNotifications.ts | 19 ++++-- 2 files changed, 76 insertions(+), 7 deletions(-) diff --git a/__tests__/withAndroidPushNotifications.test.ts b/__tests__/withAndroidPushNotifications.test.ts index f57b808a..4f3cdd83 100644 --- a/__tests__/withAndroidPushNotifications.test.ts +++ b/__tests__/withAndroidPushNotifications.test.ts @@ -351,6 +351,70 @@ dependencies { warnSpy.mockRestore(); }); + + test('detects existing FCM service when action is a single object (not array)', () => { + const config = createMockConfig('com.example.myapp'); + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(); + + config.modResults.manifest.application[0].service.push({ + '$': { + 'android:name': '.ExistingFcmService', + 'android:exported': 'true', + }, + 'intent-filter': [ + { + action: { + $: { + 'android:name': 'com.google.firebase.MESSAGING_EVENT', + }, + }, + }, + ], + } as any); + + withAndroidPushNotifications(config as any, {} as any); + + const services = config.modResults.manifest.application[0].service; + expect(services).toHaveLength(1); + expect(services[0].$['android:name']).toBe('.ExistingFcmService'); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('existing FirebaseMessagingService') + ); + + warnSpy.mockRestore(); + }); + + test('detects existing FCM service when intent-filter is a single object (not array)', () => { + const config = createMockConfig('com.example.myapp'); + const warnSpy = jest.spyOn(console, 'warn').mockImplementation(); + + config.modResults.manifest.application[0].service.push({ + '$': { + 'android:name': '.ExistingFcmService', + 'android:exported': 'true', + }, + 'intent-filter': { + action: [ + { + $: { + 'android:name': 'com.google.firebase.MESSAGING_EVENT', + }, + }, + ], + }, + } as any); + + withAndroidPushNotifications(config as any, {} as any); + + const services = config.modResults.manifest.application[0].service; + expect(services).toHaveLength(1); + expect(services[0].$['android:name']).toBe('.ExistingFcmService'); + expect(warnSpy).toHaveBeenCalledWith( + expect.stringContaining('existing FirebaseMessagingService') + ); + + warnSpy.mockRestore(); + }); }); describe('error handling', () => { diff --git a/src/expo-plugins/withAndroidPushNotifications.ts b/src/expo-plugins/withAndroidPushNotifications.ts index 118a371c..c1e94083 100644 --- a/src/expo-plugins/withAndroidPushNotifications.ts +++ b/src/expo-plugins/withAndroidPushNotifications.ts @@ -152,13 +152,18 @@ const registerServiceInManifest: ConfigPlugin = ( const hasExistingFcmService = mainApplication.service?.some( (s) => s.$?.['android:name'] !== serviceName && - s['intent-filter']?.some( - (f: any) => - f.action?.some( - (a: any) => - a.$?.['android:name'] === 'com.google.firebase.MESSAGING_EVENT' - ) - ) + ([] as any[]) + .concat(s['intent-filter'] ?? []) + .some( + (f: any) => + ([] as any[]) + .concat(f.action ?? []) + .some( + (a: any) => + a.$?.['android:name'] === + 'com.google.firebase.MESSAGING_EVENT' + ) + ) ); if (hasExistingFcmService) { From dd5a5ab603dd79b59e54558766ee2ed9a3c08ebf Mon Sep 17 00:00:00 2001 From: Your Name Date: Thu, 9 Apr 2026 13:16:29 +0100 Subject: [PATCH 2/2] style: fix prettier formatting Co-Authored-By: Claude Opus 4.6 (1M context) --- .../withAndroidPushNotifications.ts | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/src/expo-plugins/withAndroidPushNotifications.ts b/src/expo-plugins/withAndroidPushNotifications.ts index c1e94083..8bf765bb 100644 --- a/src/expo-plugins/withAndroidPushNotifications.ts +++ b/src/expo-plugins/withAndroidPushNotifications.ts @@ -154,15 +154,14 @@ const registerServiceInManifest: ConfigPlugin = ( s.$?.['android:name'] !== serviceName && ([] as any[]) .concat(s['intent-filter'] ?? []) - .some( - (f: any) => - ([] as any[]) - .concat(f.action ?? []) - .some( - (a: any) => - a.$?.['android:name'] === - 'com.google.firebase.MESSAGING_EVENT' - ) + .some((f: any) => + ([] as any[]) + .concat(f.action ?? []) + .some( + (a: any) => + a.$?.['android:name'] === + 'com.google.firebase.MESSAGING_EVENT' + ) ) );