diff --git a/etc/firebase-admin.messaging.api.md b/etc/firebase-admin.messaging.api.md index 1ad50f85f6..3c1c73c5b7 100644 --- a/etc/firebase-admin.messaging.api.md +++ b/etc/firebase-admin.messaging.api.md @@ -309,6 +309,7 @@ export interface MessagingOptions { priority?: string; restrictedPackageName?: string; timeToLive?: number; + contentChanged?: boolean } // @public diff --git a/src/messaging/messaging-api.ts b/src/messaging/messaging-api.ts index d0c5d5af7e..5992518b04 100644 --- a/src/messaging/messaging-api.ts +++ b/src/messaging/messaging-api.ts @@ -308,6 +308,12 @@ export interface Aps { */ mutableContent?: boolean; + /** + * Specifies whether to set the `content-changed` property on the message + * so the clients can modify the notification via app extensions. + */ + contentChanged?: boolean; + /** * Type of the notification. */ @@ -948,6 +954,18 @@ export interface MessagingOptions { */ contentAvailable?: boolean; + /** + * On iOS, use this field to represent `content-changed` in the APNs payload. + * + * See {@link https://developer.apple.com/documentation/widgetkit/updating-widgets-with-widgetkit-push-notifications | + * Updating Widgets with WidgetKit Push Notifications} for more information. + * + * On Android and web, this field is ignored. + * + * **Default value:** `false` + */ + contentChanged?: boolean; + /** * The package name of the application which the registration tokens must match * in order to receive the message. diff --git a/src/messaging/messaging-internal.ts b/src/messaging/messaging-internal.ts index c41f93a923..f32bf7c280 100644 --- a/src/messaging/messaging-internal.ts +++ b/src/messaging/messaging-internal.ts @@ -274,6 +274,7 @@ function validateAps(aps: Aps): void { contentAvailable: 'content-available', mutableContent: 'mutable-content', threadId: 'thread-id', + contentChanged: 'content-changed', }; Object.keys(propertyMappings).forEach((key) => { if (key in aps && propertyMappings[key] in aps) { @@ -300,6 +301,15 @@ function validateAps(aps: Aps): void { delete aps['mutable-content']; } } + + const contentChanged = aps['content-changed']; + if (typeof contentChanged !== 'undefined' && contentChanged !== 1) { + if (contentChanged === true) { + aps['content-changed'] = 1; + } else { + delete aps['content-changed']; + } + } } function validateApsSound(sound: string | CriticalSound | undefined): void { diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index 9e8d1ea814..58c34b1c85 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -2192,6 +2192,7 @@ describe('Messaging', () => { category: 'test.category', contentAvailable: true, mutableContent: true, + contentChanged: true, threadId: 'thread.id', }, customKey1: 'custom.value', @@ -2229,6 +2230,7 @@ describe('Messaging', () => { 'category': 'test.category', 'content-available': 1, 'mutable-content': 1, + 'content-changed': 1, 'thread-id': 'thread.id', }, customKey1: 'custom.value', @@ -2380,6 +2382,44 @@ describe('Messaging', () => { }, }, }, + { + label: 'APNS contentChanged explicitly false', + req: { + apns: { + payload: { + aps: { + contentChanged: false, + }, + }, + }, + }, + expectedReq: { + apns: { + payload: { + aps: {}, + }, + }, + }, + }, + { + label: 'APNS contentChanged set explicitly', + req: { + apns: { + payload: { + aps: { + 'content-changed': 1, + }, + }, + }, + }, + expectedReq: { + apns: { + payload: { + aps: { 'content-changed': 1 }, + }, + }, + }, + }, { label: 'APNS custom fields', req: {