@@ -23,6 +23,7 @@ import {
2323import * as utils from '../utils' ;
2424import * as validator from '../utils/validator' ;
2525import { validateMessage } from './messaging-internal' ;
26+ import { getErrorCode , createFirebaseError } from './messaging-errors-internal' ;
2627import { FirebaseMessagingRequestHandler } from './messaging-api-request-internal' ;
2728
2829import {
@@ -34,53 +35,14 @@ import {
3435 // Legacy API types
3536 SendResponse ,
3637} from './messaging-api' ;
37- import { Http2SessionHandler } from '../utils/api-request' ;
38+ import { Http2SessionHandler , RequestResponseError } from '../utils/api-request' ;
3839
3940// FCM endpoints
4041const FCM_SEND_HOST = 'fcm.googleapis.com' ;
41- const FCM_TOPIC_MANAGEMENT_HOST = 'iid.googleapis.com' ;
42- const FCM_TOPIC_MANAGEMENT_ADD_PATH = '/iid/v1:batchAdd' ;
43- const FCM_TOPIC_MANAGEMENT_REMOVE_PATH = '/iid/v1:batchRemove' ;
4442
4543// Maximum messages that can be included in a batch request.
4644const FCM_MAX_BATCH_SIZE = 500 ;
4745
48- /**
49- * Maps a raw FCM server response to a `MessagingTopicManagementResponse` object.
50- *
51- * @param {object } response The raw FCM server response to map.
52- *
53- * @returns {MessagingTopicManagementResponse } The mapped `MessagingTopicManagementResponse` object.
54- */
55- function mapRawResponseToTopicManagementResponse ( response : object ) : MessagingTopicManagementResponse {
56- // Add the success and failure counts.
57- const result : MessagingTopicManagementResponse = {
58- successCount : 0 ,
59- failureCount : 0 ,
60- errors : [ ] ,
61- } ;
62-
63- if ( 'results' in response ) {
64- ( response as any ) . results . forEach ( ( tokenManagementResult : any , index : number ) => {
65- // Map the FCM server's error strings to actual error objects.
66- if ( 'error' in tokenManagementResult ) {
67- result . failureCount += 1 ;
68- const newError = FirebaseMessagingError . fromTopicManagementServerError (
69- tokenManagementResult . error , /* message */ undefined , tokenManagementResult . error ,
70- ) ;
71-
72- result . errors . push ( {
73- index,
74- error : newError ,
75- } ) ;
76- } else {
77- result . successCount += 1 ;
78- }
79- } ) ;
80- }
81- return result ;
82- }
83-
8446
8547/**
8648 * Messaging service bound to the provided app.
@@ -344,7 +306,7 @@ export class Messaging {
344306 registrationTokenOrTokens ,
345307 topic ,
346308 'subscribeToTopic' ,
347- FCM_TOPIC_MANAGEMENT_ADD_PATH ,
309+ '' ,
348310 ) ;
349311 }
350312
@@ -371,7 +333,7 @@ export class Messaging {
371333 registrationTokenOrTokens ,
372334 topic ,
373335 'unsubscribeFromTopic' ,
374- FCM_TOPIC_MANAGEMENT_REMOVE_PATH ,
336+ '' ,
375337 ) ;
376338 }
377339
@@ -413,7 +375,7 @@ export class Messaging {
413375 registrationTokenOrTokens : string | string [ ] ,
414376 topic : string ,
415377 methodName : string ,
416- path : string ,
378+ _path : string ,
417379 ) : Promise < MessagingTopicManagementResponse > {
418380 this . validateRegistrationTokensType ( registrationTokenOrTokens , methodName ) ;
419381 this . validateTopicType ( topic , methodName ) ;
@@ -434,17 +396,88 @@ export class Messaging {
434396 registrationTokensArray = [ registrationTokenOrTokens as string ] ;
435397 }
436398
437- const request = {
438- to : topic ,
439- registration_tokens : registrationTokensArray ,
440- } ;
399+ return utils . findProjectId ( this . app ) . then ( ( projectId ) => {
400+ if ( ! validator . isNonEmptyString ( projectId ) ) {
401+ throw new FirebaseMessagingError (
402+ MessagingClientErrorCode . INVALID_ARGUMENT ,
403+ 'Failed to determine project ID for Messaging. Initialize the '
404+ + 'SDK with service account credentials or set project ID as an app option. '
405+ + 'Alternatively set the GOOGLE_CLOUD_PROJECT environment variable.' ,
406+ ) ;
407+ }
441408
442- return this . messagingRequestHandler . invokeRequestHandler (
443- FCM_TOPIC_MANAGEMENT_HOST , path , request ,
444- ) ;
445- } )
446- . then ( ( response ) => {
447- return mapRawResponseToTopicManagementResponse ( response ) ;
409+ const topicName = topic . replace ( / ^ \/ t o p i c s \/ / , '' ) ;
410+ const isSubscribe = methodName === 'subscribeToTopic' ;
411+ const httpMethod = isSubscribe ? 'POST' : 'DELETE' ;
412+
413+ const http2SessionHandler = new Http2SessionHandler ( 'https://fcm.googleapis.com' ) ;
414+
415+ let settledPromise : Promise < PromiseSettledResult < any > [ ] > ;
416+ return new Promise < MessagingTopicManagementResponse > ( ( resolve , reject ) => {
417+ http2SessionHandler . invoke ( ) . catch ( ( error ) => {
418+ reject ( new FirebaseMessagingSessionError ( error , undefined , undefined ) ) ;
419+ } ) ;
420+
421+ const requests = registrationTokensArray . map ( async ( registrationId ) => {
422+ let requestPath = `/v1/projects/${ projectId } /registrations/${ registrationId } /topicSubscriptions` ;
423+ if ( isSubscribe ) {
424+ requestPath += `?topic_name=${ topicName } ` ;
425+ } else {
426+ requestPath += `/${ topicName } ?allow_missing=true` ;
427+ }
428+ return this . messagingRequestHandler . invokeHttp2RequestHandler (
429+ 'fcm.googleapis.com' ,
430+ requestPath ,
431+ httpMethod ,
432+ isSubscribe ? { } : undefined ,
433+ http2SessionHandler
434+ ) ;
435+ } ) ;
436+
437+ settledPromise = Promise . allSettled ( requests ) ;
438+ settledPromise . then ( ( results ) => {
439+ if ( results . length > 0 && results . every ( ( r ) => r . status === 'rejected' ) ) {
440+ const firstReason = ( results [ 0 ] as PromiseRejectedResult ) . reason ;
441+ if ( firstReason instanceof RequestResponseError ) {
442+ reject ( createFirebaseError ( firstReason ) ) ;
443+ } else {
444+ reject ( firstReason ) ;
445+ }
446+ return ;
447+ }
448+
449+ const response : MessagingTopicManagementResponse = {
450+ successCount : 0 ,
451+ failureCount : 0 ,
452+ errors : [ ] ,
453+ } ;
454+
455+ results . forEach ( ( result , index ) => {
456+ if ( result . status === 'fulfilled' ) {
457+ response . successCount += 1 ;
458+ } else {
459+ response . failureCount += 1 ;
460+ const err = result . reason ;
461+ const errorCode = err . response ?. isJson ( ) ? getErrorCode ( err . response . data ) : null ;
462+ const errorMessage = err . response ?. isJson ( ) ? err . response . data ?. error ?. message : err . message ;
463+ const newError = FirebaseMessagingError . fromTopicManagementServerError (
464+ errorCode || 'UNKNOWN' ,
465+ errorMessage ,
466+ err . response ?. isJson ( ) ? err . response . data : undefined
467+ ) ;
468+ response . errors . push ( {
469+ index,
470+ error : newError ,
471+ } ) ;
472+ }
473+ } ) ;
474+
475+ resolve ( response ) ;
476+ } ) ;
477+ } ) . finally ( ( ) => {
478+ http2SessionHandler . close ( ) ;
479+ } ) ;
480+ } ) ;
448481 } ) ;
449482 }
450483
0 commit comments