@@ -24,6 +24,7 @@ import { ErrorInfo } from '../utils/error';
2424import * as utils from '../utils' ;
2525import * as validator from '../utils/validator' ;
2626import { validateMessage } from './messaging-internal' ;
27+ import { getErrorCode , createFirebaseError } from './messaging-errors-internal' ;
2728import { FirebaseMessagingRequestHandler } from './messaging-api-request-internal' ;
2829
2930import {
@@ -36,53 +37,14 @@ import {
3637 // Legacy API types
3738 SendResponse ,
3839} from './messaging-api' ;
39- import { Http2SessionHandler } from '../utils/api-request' ;
40+ import { Http2SessionHandler , RequestResponseError } from '../utils/api-request' ;
4041
4142// FCM endpoints
4243const FCM_SEND_HOST = 'fcm.googleapis.com' ;
43- const FCM_TOPIC_MANAGEMENT_HOST = 'iid.googleapis.com' ;
44- const FCM_TOPIC_MANAGEMENT_ADD_PATH = '/iid/v1:batchAdd' ;
45- const FCM_TOPIC_MANAGEMENT_REMOVE_PATH = '/iid/v1:batchRemove' ;
4644
4745// Maximum messages that can be included in a batch request.
4846const FCM_MAX_BATCH_SIZE = 500 ;
4947
50- /**
51- * Maps a raw FCM server response to a `MessagingTopicManagementResponse` object.
52- *
53- * @param {object } response The raw FCM server response to map.
54- *
55- * @returns {MessagingTopicManagementResponse } The mapped `MessagingTopicManagementResponse` object.
56- */
57- function mapRawResponseToTopicManagementResponse ( response : object ) : MessagingTopicManagementResponse {
58- // Add the success and failure counts.
59- const result : MessagingTopicManagementResponse = {
60- successCount : 0 ,
61- failureCount : 0 ,
62- errors : [ ] ,
63- } ;
64-
65- if ( 'results' in response ) {
66- ( response as any ) . results . forEach ( ( tokenManagementResult : any , index : number ) => {
67- // Map the FCM server's error strings to actual error objects.
68- if ( 'error' in tokenManagementResult ) {
69- result . failureCount += 1 ;
70- const newError = FirebaseMessagingError . fromTopicManagementServerError (
71- tokenManagementResult . error , /* message */ undefined , tokenManagementResult . error ,
72- ) ;
73-
74- result . errors . push ( {
75- index,
76- error : newError ,
77- } ) ;
78- } else {
79- result . successCount += 1 ;
80- }
81- } ) ;
82- }
83- return result ;
84- }
85-
8648
8749/**
8850 * Messaging service bound to the provided app.
@@ -379,7 +341,7 @@ export class Messaging {
379341 registrationTokenOrTokens ,
380342 topic ,
381343 'subscribeToTopic' ,
382- FCM_TOPIC_MANAGEMENT_ADD_PATH ,
344+ '' ,
383345 ) ;
384346 }
385347
@@ -406,7 +368,7 @@ export class Messaging {
406368 registrationTokenOrTokens ,
407369 topic ,
408370 'unsubscribeFromTopic' ,
409- FCM_TOPIC_MANAGEMENT_REMOVE_PATH ,
371+ '' ,
410372 ) ;
411373 }
412374
@@ -448,7 +410,7 @@ export class Messaging {
448410 registrationTokenOrTokens : string | string [ ] ,
449411 topic : string ,
450412 methodName : string ,
451- path : string ,
413+ _path : string ,
452414 ) : Promise < MessagingTopicManagementResponse > {
453415 this . validateRegistrationTokensType ( registrationTokenOrTokens , methodName ) ;
454416 this . validateTopicType ( topic , methodName ) ;
@@ -469,17 +431,88 @@ export class Messaging {
469431 registrationTokensArray = [ registrationTokenOrTokens as string ] ;
470432 }
471433
472- const request = {
473- to : topic ,
474- registration_tokens : registrationTokensArray ,
475- } ;
434+ return utils . findProjectId ( this . app ) . then ( ( projectId ) => {
435+ if ( ! validator . isNonEmptyString ( projectId ) ) {
436+ throw new FirebaseMessagingError (
437+ MessagingClientErrorCode . INVALID_ARGUMENT ,
438+ 'Failed to determine project ID for Messaging. Initialize the '
439+ + 'SDK with service account credentials or set project ID as an app option. '
440+ + 'Alternatively set the GOOGLE_CLOUD_PROJECT environment variable.' ,
441+ ) ;
442+ }
476443
477- return this . messagingRequestHandler . invokeRequestHandler (
478- FCM_TOPIC_MANAGEMENT_HOST , path , request ,
479- ) ;
480- } )
481- . then ( ( response ) => {
482- return mapRawResponseToTopicManagementResponse ( response ) ;
444+ const topicName = topic . replace ( / ^ \/ t o p i c s \/ / , '' ) ;
445+ const isSubscribe = methodName === 'subscribeToTopic' ;
446+ const httpMethod = isSubscribe ? 'POST' : 'DELETE' ;
447+
448+ const http2SessionHandler = new Http2SessionHandler ( 'https://fcm.googleapis.com' ) ;
449+
450+ let settledPromise : Promise < PromiseSettledResult < any > [ ] > ;
451+ return new Promise < MessagingTopicManagementResponse > ( ( resolve , reject ) => {
452+ http2SessionHandler . invoke ( ) . catch ( ( error ) => {
453+ reject ( new FirebaseMessagingSessionError ( error , undefined , undefined ) ) ;
454+ } ) ;
455+
456+ const requests = registrationTokensArray . map ( async ( registrationId ) => {
457+ let requestPath = `/v1/projects/${ projectId } /registrations/${ registrationId } /topicSubscriptions` ;
458+ if ( isSubscribe ) {
459+ requestPath += `?topic_name=${ topicName } ` ;
460+ } else {
461+ requestPath += `/${ topicName } ?allow_missing=true` ;
462+ }
463+ return this . messagingRequestHandler . invokeHttp2RequestHandler (
464+ 'fcm.googleapis.com' ,
465+ requestPath ,
466+ httpMethod ,
467+ isSubscribe ? { } : undefined ,
468+ http2SessionHandler
469+ ) ;
470+ } ) ;
471+
472+ settledPromise = Promise . allSettled ( requests ) ;
473+ settledPromise . then ( ( results ) => {
474+ if ( results . length > 0 && results . every ( ( r ) => r . status === 'rejected' ) ) {
475+ const firstReason = ( results [ 0 ] as PromiseRejectedResult ) . reason ;
476+ if ( firstReason instanceof RequestResponseError ) {
477+ reject ( createFirebaseError ( firstReason ) ) ;
478+ } else {
479+ reject ( firstReason ) ;
480+ }
481+ return ;
482+ }
483+
484+ const response : MessagingTopicManagementResponse = {
485+ successCount : 0 ,
486+ failureCount : 0 ,
487+ errors : [ ] ,
488+ } ;
489+
490+ results . forEach ( ( result , index ) => {
491+ if ( result . status === 'fulfilled' ) {
492+ response . successCount += 1 ;
493+ } else {
494+ response . failureCount += 1 ;
495+ const err = result . reason ;
496+ const errorCode = err . response ?. isJson ( ) ? getErrorCode ( err . response . data ) : null ;
497+ const errorMessage = err . response ?. isJson ( ) ? err . response . data ?. error ?. message : err . message ;
498+ const newError = FirebaseMessagingError . fromTopicManagementServerError (
499+ errorCode || 'UNKNOWN' ,
500+ errorMessage ,
501+ err . response ?. isJson ( ) ? err . response . data : undefined
502+ ) ;
503+ response . errors . push ( {
504+ index,
505+ error : newError ,
506+ } ) ;
507+ }
508+ } ) ;
509+
510+ resolve ( response ) ;
511+ } ) ;
512+ } ) . finally ( ( ) => {
513+ http2SessionHandler . close ( ) ;
514+ } ) ;
515+ } ) ;
483516 } ) ;
484517 }
485518
0 commit comments