Skip to content

Commit b8475bc

Browse files
Your Nameclaude
andcommitted
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) <noreply@anthropic.com>
1 parent 3652aa4 commit b8475bc

2 files changed

Lines changed: 76 additions & 7 deletions

File tree

__tests__/withAndroidPushNotifications.test.ts

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -351,6 +351,70 @@ dependencies {
351351

352352
warnSpy.mockRestore();
353353
});
354+
355+
test('detects existing FCM service when action is a single object (not array)', () => {
356+
const config = createMockConfig('com.example.myapp');
357+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation();
358+
359+
config.modResults.manifest.application[0].service.push({
360+
'$': {
361+
'android:name': '.ExistingFcmService',
362+
'android:exported': 'true',
363+
},
364+
'intent-filter': [
365+
{
366+
action: {
367+
$: {
368+
'android:name': 'com.google.firebase.MESSAGING_EVENT',
369+
},
370+
},
371+
},
372+
],
373+
} as any);
374+
375+
withAndroidPushNotifications(config as any, {} as any);
376+
377+
const services = config.modResults.manifest.application[0].service;
378+
expect(services).toHaveLength(1);
379+
expect(services[0].$['android:name']).toBe('.ExistingFcmService');
380+
expect(warnSpy).toHaveBeenCalledWith(
381+
expect.stringContaining('existing FirebaseMessagingService')
382+
);
383+
384+
warnSpy.mockRestore();
385+
});
386+
387+
test('detects existing FCM service when intent-filter is a single object (not array)', () => {
388+
const config = createMockConfig('com.example.myapp');
389+
const warnSpy = jest.spyOn(console, 'warn').mockImplementation();
390+
391+
config.modResults.manifest.application[0].service.push({
392+
'$': {
393+
'android:name': '.ExistingFcmService',
394+
'android:exported': 'true',
395+
},
396+
'intent-filter': {
397+
action: [
398+
{
399+
$: {
400+
'android:name': 'com.google.firebase.MESSAGING_EVENT',
401+
},
402+
},
403+
],
404+
},
405+
} as any);
406+
407+
withAndroidPushNotifications(config as any, {} as any);
408+
409+
const services = config.modResults.manifest.application[0].service;
410+
expect(services).toHaveLength(1);
411+
expect(services[0].$['android:name']).toBe('.ExistingFcmService');
412+
expect(warnSpy).toHaveBeenCalledWith(
413+
expect.stringContaining('existing FirebaseMessagingService')
414+
);
415+
416+
warnSpy.mockRestore();
417+
});
354418
});
355419

356420
describe('error handling', () => {

src/expo-plugins/withAndroidPushNotifications.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -152,13 +152,18 @@ const registerServiceInManifest: ConfigPlugin<IntercomPluginProps> = (
152152
const hasExistingFcmService = mainApplication.service?.some(
153153
(s) =>
154154
s.$?.['android:name'] !== serviceName &&
155-
s['intent-filter']?.some(
156-
(f: any) =>
157-
f.action?.some(
158-
(a: any) =>
159-
a.$?.['android:name'] === 'com.google.firebase.MESSAGING_EVENT'
160-
)
161-
)
155+
([] as any[])
156+
.concat(s['intent-filter'] ?? [])
157+
.some(
158+
(f: any) =>
159+
([] as any[])
160+
.concat(f.action ?? [])
161+
.some(
162+
(a: any) =>
163+
a.$?.['android:name'] ===
164+
'com.google.firebase.MESSAGING_EVENT'
165+
)
166+
)
162167
);
163168

164169
if (hasExistingFcmService) {

0 commit comments

Comments
 (0)