diff --git a/src/index.d.ts b/src/index.d.ts index 616a5fa204..c506091347 100755 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -3579,6 +3579,7 @@ type BaseMessage = { android?: admin.messaging.AndroidConfig; webpush?: admin.messaging.WebpushConfig; apns?: admin.messaging.ApnsConfig; + fcmOptions?: admin.messaging.FcmOptions; }; interface TokenMessage extends BaseMessage { @@ -3641,6 +3642,11 @@ declare namespace admin.messaging { * Android notification to be included in the message. */ notification?: AndroidNotification; + + /** + * Options for features provided by the FCM SDK for Android. + */ + fcmOptions?: AndroidFcmOptions; } /** @@ -3726,6 +3732,17 @@ declare namespace admin.messaging { channelId?: string; } + /** + * Represents options for features provided by the FCM SDK for Android. + */ + interface AndroidFcmOptions { + + /** + * The label associated with the message's analytics data. + */ + analyticsLabel?: string; + } + /** * Represents the APNs-specific options that can be included in an * {@link admin.messaging.Message}. Refer to @@ -3743,6 +3760,11 @@ declare namespace admin.messaging { * An APNs payload to be included in the message. */ payload?: ApnsPayload; + + /** + * Options for features provided by the FCM SDK for iOS. + */ + fcmOptions?: ApnsFcmOptions; } /** * Represents the payload of an APNs message. Mainly consists of the `aps` @@ -3841,6 +3863,28 @@ declare namespace admin.messaging { volume?: number; } + /** + * Represents options for features provided by the FCM SDK for iOS. + */ + interface ApnsFcmOptions { + + /** + * The label associated with the message's analytics data. + */ + analyticsLabel?: string; + } + + /** + * Represents platform-independent options for features provided by the FCM SDKs. + */ + interface FcmOptions { + + /** + * The label associated with the message's analytics data. + */ + analyticsLabel?: string; + } + /** * A notification that can be included in {@link admin.messaging.Message}. diff --git a/src/messaging/messaging-types.ts b/src/messaging/messaging-types.ts index a8a3cac5b4..c6958cefe9 100644 --- a/src/messaging/messaging-types.ts +++ b/src/messaging/messaging-types.ts @@ -27,6 +27,7 @@ interface BaseMessage { android?: AndroidConfig; webpush?: WebpushConfig; apns?: ApnsConfig; + fcmOptions?: FcmOptions; } interface TokenMessage extends BaseMessage { @@ -60,6 +61,10 @@ export interface Notification { body?: string; } +export interface FcmOptions { + analyticsLabel?: string; +} + export interface WebpushConfig { headers?: {[key: string]: string}; data?: {[key: string]: string}; @@ -97,6 +102,7 @@ export interface WebpushNotification { export interface ApnsConfig { headers?: {[key: string]: string}; payload?: ApnsPayload; + fcmOptions?: ApnsFcmOptions; } export interface ApnsPayload { @@ -135,6 +141,10 @@ export interface ApsAlert { launchImage?: string; } +export interface ApnsFcmOptions { + analyticsLabel?: string; +} + export interface AndroidConfig { collapseKey?: string; priority?: ('high' | 'normal'); @@ -142,6 +152,7 @@ export interface AndroidConfig { restrictedPackageName?: string; data?: {[key: string]: string}; notification?: AndroidNotification; + fcmOptions?: AndroidFcmOptions; } export interface AndroidNotification { @@ -159,6 +170,10 @@ export interface AndroidNotification { channelId?: string; } +export interface AndroidFcmOptions { + analyticsLabel?: string; +} + /* Payload for data messages */ export interface DataMessagePayload { [key: string]: string; @@ -291,6 +306,7 @@ export function validateMessage(message: Message) { validateAndroidConfig(message.android); validateWebpushConfig(message.webpush); validateApnsConfig(message.apns); + validateFcmOptions(message.fcmOptions); } /** @@ -345,8 +361,49 @@ function validateApnsConfig(config: ApnsConfig) { } validateStringMap(config.headers, 'apns.headers'); validateApnsPayload(config.payload); + validateApnsFcmOptions(config.fcmOptions); } +/** + * Checks if the given ApnsFcmOptions object is valid. + * + * @param {ApnsFcmOptions} fcmOptions An object to be validated. + */ +function validateApnsFcmOptions(fcmOptions: ApnsFcmOptions) { + if (typeof fcmOptions === 'undefined') { + return; + } else if (!validator.isNonNullObject(fcmOptions)) { + throw new FirebaseMessagingError( + MessagingClientErrorCode.INVALID_PAYLOAD, 'fcmOptions must be a non-null object'); + } + + if (typeof fcmOptions.analyticsLabel !== 'undefined' && !validator.isString(fcmOptions.analyticsLabel)) { + throw new FirebaseMessagingError( + MessagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value'); + } +} + +/** + * Checks if the given FcmOptions object is valid. + * + * @param {FcmOptions} fcmOptions An object to be validated. + */ +function validateFcmOptions(fcmOptions: FcmOptions) { + if (typeof fcmOptions === 'undefined') { + return; + } else if (!validator.isNonNullObject(fcmOptions)) { + throw new FirebaseMessagingError( + MessagingClientErrorCode.INVALID_PAYLOAD, 'fcmOptions must be a non-null object'); + } + + if (typeof fcmOptions.analyticsLabel !== 'undefined' && !validator.isString(fcmOptions.analyticsLabel)) { + throw new FirebaseMessagingError( + MessagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value'); + } +} + + + /** * Checks if the given ApnsPayload object is valid. The object must have a valid aps value. * @@ -535,6 +592,7 @@ function validateAndroidConfig(config: AndroidConfig) { } validateStringMap(config.data, 'android.data'); validateAndroidNotification(config.notification); + validateAndroidFcmOptions(config.fcmOptions); const propertyMappings = { collapseKey: 'collapse_key', @@ -585,3 +643,22 @@ function validateAndroidNotification(notification: AndroidNotification) { }; renameProperties(notification, propertyMappings); } + +/** + * Checks if the given AndroidFcmOptions object is valid. + * + * @param {AndroidFcmOptions} fcmOptions An object to be validated. + */ +function validateAndroidFcmOptions(fcmOptions: AndroidFcmOptions) { + if (typeof fcmOptions === 'undefined') { + return; + } else if (!validator.isNonNullObject(fcmOptions)) { + throw new FirebaseMessagingError( + MessagingClientErrorCode.INVALID_PAYLOAD, 'fcmOptions must be a non-null object'); + } + + if (typeof fcmOptions.analyticsLabel !== 'undefined' && !validator.isString(fcmOptions.analyticsLabel)) { + throw new FirebaseMessagingError( + MessagingClientErrorCode.INVALID_PAYLOAD, 'analyticsLabel must be a string value'); + } +} diff --git a/test/unit/messaging/messaging.spec.ts b/test/unit/messaging/messaging.spec.ts index 7479dbb583..ef8ffa29b4 100644 --- a/test/unit/messaging/messaging.spec.ts +++ b/test/unit/messaging/messaging.spec.ts @@ -2469,6 +2469,24 @@ describe('Messaging', () => { messaging.send({data: arg, topic: 'test'}); }).to.throw('data must be a non-null object'); }); + + it(`should throw given invalid fcmOptions: ${JSON.stringify(arg)}`, () => { + expect(() => { + messaging.send({fcmOptions: arg, topic: 'test'}); + }).to.throw('fcmOptions must be a non-null object'); + }); + + it(`should throw given invalid AndroidFcmOptions: ${JSON.stringify(arg)}`, () => { + expect(() => { + messaging.send({android: {fcmOptions: arg}, topic: 'test'}); + }).to.throw('fcmOptions must be a non-null object'); + }); + + it(`should throw given invalid ApnsFcmOptions: ${JSON.stringify(arg)}`, () => { + expect(() => { + messaging.send({apns: {fcmOptions: arg}, topic: 'test'}); + }).to.throw('fcmOptions must be a non-null object'); + }); }); const invalidDataMessages: any[] = [ @@ -2750,6 +2768,14 @@ describe('Messaging', () => { }, }, }, + { + label: 'Generic fcmOptions message', + req: { + fcmOptions: { + analyticsLabel: 'test.analytics', + }, + }, + }, { label: 'Android data message', req: { @@ -2852,6 +2878,9 @@ describe('Messaging', () => { bodyLocArgs: ['arg1', 'arg2'], channelId: 'test.channel', }, + fcmOptions: { + analyticsLabel: 'test.analytics', + }, }, }, expectedReq: { @@ -2878,6 +2907,9 @@ describe('Messaging', () => { body_loc_args: ['arg1', 'arg2'], channel_id: 'test.channel', }, + fcmOptions: { + analyticsLabel: 'test.analytics', + }, }, }, }, @@ -2999,6 +3031,9 @@ describe('Messaging', () => { customKey1: 'custom.value', customKey2: {nested: 'value'}, }, + fcmOptions: { + analyticsLabel: 'test.analytics', + }, }, }, expectedReq: { @@ -3032,6 +3067,9 @@ describe('Messaging', () => { customKey1: 'custom.value', customKey2: {nested: 'value'}, }, + fcmOptions: { + analyticsLabel: 'test.analytics', + }, }, }, },