1- const { ConnectionError , ERROR , SendError, PushError , FCM_SDK_ERRORS } = require ( '../data/error' ) ,
1+ const { ERROR , SendError, FCM_SDK_ERRORS } = require ( '../data/error' ) ,
22 logger = require ( '../../../../../api/utils/log' ) ,
33 { Splitter } = require ( './utils/splitter' ) ,
44 { util } = require ( '../std' ) ,
@@ -56,7 +56,7 @@ class FCM extends Splitter {
5656 * Standard constructor
5757 * @param {string } log logger name
5858 * @param {string } type type of connection: ap, at, id, ia, ip, ht, hp
59- * @param {Credentials } creds FCM server key
59+ * @param {Credentials } creds FCM credentials
6060 * @param {Object[] } messages initial array of messages to send
6161 * @param {Object } options standard stream options
6262 * @param {number } options.pool.pushes number of notifications which can be processed concurrently, this parameter is strictly set to 500
@@ -71,34 +71,20 @@ class FCM extends Splitter {
7171 this . legacyApi = ! creds . _data . serviceAccountFile ;
7272
7373 this . log = logger ( log ) . sub ( `${ threadId } -a` ) ;
74- if ( this . legacyApi ) {
75- this . opts = {
76- agent : this . agent ,
77- hostname : 'fcm.googleapis.com' ,
78- port : 443 ,
79- path : '/fcm/send' ,
80- method : 'POST' ,
81- headers : {
82- 'Accept' : 'application/json' ,
83- 'Content-Type' : 'application/json' ,
84- 'Authorization' : `key=${ creds . _data . key } ` ,
85- } ,
86- } ;
87- }
88- else {
89- const serviceAccountJSON = FORGE . util . decode64 (
90- creds . _data . serviceAccountFile . substring ( creds . _data . serviceAccountFile . indexOf ( ',' ) + 1 )
91- ) ;
92- const serviceAccountObject = JSON . parse ( serviceAccountJSON ) ;
93- const appName = creds . _data . hash ; // using hash as the app name
94- const firebaseApp = firebaseAdmin . apps . find ( app => app . name === appName )
95- ? firebaseAdmin . app ( appName )
96- : firebaseAdmin . initializeApp ( {
97- credential : firebaseAdmin . credential . cert ( serviceAccountObject , this . agent ) ,
98- httpAgent : this . agent
99- } , appName ) ;
100- this . firebaseMessaging = firebaseApp . messaging ( ) ;
101- }
74+
75+ const serviceAccountJSON = FORGE . util . decode64 (
76+ creds . _data . serviceAccountFile . substring ( creds . _data . serviceAccountFile . indexOf ( ',' ) + 1 )
77+ ) ;
78+ const serviceAccountObject = JSON . parse ( serviceAccountJSON ) ;
79+ const appName = creds . _data . hash ; // using hash as the app name
80+ const firebaseApp = firebaseAdmin . apps . find ( app => app . name === appName )
81+ ? firebaseAdmin . app ( appName )
82+ : firebaseAdmin . initializeApp ( {
83+ credential : firebaseAdmin . credential . cert ( serviceAccountObject , this . agent ) ,
84+ httpAgent : this . agent
85+ } , appName ) ;
86+ this . firebaseMessaging = firebaseApp . messaging ( ) ;
87+
10288
10389 this . log . i ( 'Initialized' ) ;
10490 }
@@ -139,8 +125,6 @@ class FCM extends Splitter {
139125 const one = Math . ceil ( bytes / pushes . length ) ;
140126 let content = this . template ( pushes [ 0 ] . m ) . compile ( pushes [ 0 ] ) ;
141127
142- let printBody = false ;
143- const oks = [ ] ;
144128 const errors = { } ;
145129 /**
146130 * Get an error for given code & message, create it if it doesn't exist yet
@@ -156,221 +140,66 @@ class FCM extends Splitter {
156140 }
157141 return errors [ err ] ;
158142 } ;
159- if ( ! this . legacyApi ) {
160- const tokens = pushes . map ( p => p . t ) ;
161-
162- // new fcm api doesn't allow objects or arrays inside "data" property
163- if ( content . data && typeof content . data === "object" ) {
164- for ( let prop in content . data ) {
165- if ( content . data [ prop ] && typeof content . data [ prop ] === "object" ) {
166- content . data [ prop ] = JSON . stringify ( content . data [ prop ] ) ;
167- }
168- }
169- }
170143
171- const messages = tokens . map ( token => ( {
172- token,
173- ...content ,
174- } ) ) ;
175-
176- return this . firebaseMessaging
177- // EXAMPLE RESPONSE of sendEach
178- // {
179- // "responses": [
180- // {
181- // "success": false,
182- // "error": {
183- // "code": "messaging/invalid-argument",
184- // "message": "The registration token is not a valid FCM registration token"
185- // }
186- // }
187- // ],
188- // "successCount": 0,
189- // "failureCount": 1
190- // }
191- . sendEach ( messages )
192- . then ( async result => {
193- const allPushIds = pushes . map ( p => p . _id ) ;
194-
195- if ( ! result . failureCount ) {
196- this . send_results ( allPushIds , bytes ) ;
197- return ;
198- }
144+ const tokens = pushes . map ( p => p . t ) ;
145+ const messages = tokens . map ( token => ( {
146+ token,
147+ ...content ,
148+ } ) ) ;
199149
200- // array of successfully sent push._id:
201- const sentSuccessfully = [ ] ;
202-
203- // check for each message
204- for ( let i = 0 ; i < result . responses . length ; i ++ ) {
205- const { success, error } = result . responses [ i ] ;
206- if ( success ) {
207- sentSuccessfully . push ( allPushIds [ i ] ) ;
208- }
209- else {
210- const sdkError = FCM_SDK_ERRORS [ error . code ] ;
211- // check if the sdk error is mapped to an internal error.
212- // set to default if its not.
213- let internalErrorCode = sdkError ?. mapTo ?? ERROR . DATA_PROVIDER ;
214- let internalErrorMessage = sdkError ?. message ?? "Invalid error message" ;
215- errorObject ( internalErrorCode , internalErrorMessage )
216- . addAffected ( pushes [ i ] . _id , one ) ;
217- }
218- }
219- // send results back:
220- for ( let errorKey in errors ) {
221- this . send_push_error ( errors [ errorKey ] ) ;
222- }
223- if ( sentSuccessfully . length ) {
224- this . send_results ( sentSuccessfully , one * sentSuccessfully . length ) ;
225- }
226- } ) ;
227- }
228-
229- content . registration_ids = pushes . map ( p => p . t ) ;
230-
231- // CONNECTION TEST PAYLOAD (invalid registration token)
150+ return this . firebaseMessaging
151+ // EXAMPLE RESPONSE of sendEach
232152 // {
233- // "data": {
234- // "c.i": "663389aab53ebbf71a115edb",
235- // "message": "test"
236- // },
237- // "registration_ids": [
238- // "0.2124088209996502"
239- // ]
153+ // "responses": [
154+ // {
155+ // "success": false,
156+ // "error": {
157+ // "code": "messaging/invalid-argument",
158+ // "message": "The registration token is not a valid FCM registration token"
159+ // }
160+ // }
161+ // ],
162+ // "successCount": 0,
163+ // "failureCount": 1
240164 // }
241- // NORMAL PAYLOAD
242- // {
243- // "data": {
244- // "c.i": "663389a949c58657a8e625b3",
245- // "title": "qwer",
246- // "message": "qwer",
247- // "sound": "default"
248- // },
249- // "registration_ids": [
250- // "dw_CueiXThqYI9owrQC0Pb:APA91bHanJn9RM-ZYnC-3wCMld5Nk3QaVJppS4HOKrpdV8kCXq7pjQlJjcd8_1xq9G6XaceZfrFPxbfehJ4YCEfMsfQVhZW1WKhnY3TbtO7HIQfYfbj35-sx_-BHAhQ5eSDuiCOZWUDP"
251- // ]
252- // }
253- return this . sendRequest ( JSON . stringify ( content ) ) . then ( resp => {
254- // CONNECTION TEST RESPONSE (with error)
255- // {
256- // "multicast_id": 2829871343601014000,
257- // "success": 0,
258- // "failure": 1,
259- // "canonical_ids": 0,
260- // "results": [
261- // {
262- // "error": "InvalidRegistration"
263- // }
264- // ]
265- // }
266- // NORMAL SUCCESSFUL RESPONSE
267- // {
268- // "multicast_id": 5676989510572196000,
269- // "success": 1,
270- // "failure": 0,
271- // "canonical_ids": 0,
272- // "results": [
273- // {
274- // "message_id": "0:1714653611139550%68dc6e82f9fd7ecd"
275- // }
276- // ]
277- // }
278- try {
279- resp = JSON . parse ( resp ) ;
280- }
281- catch ( error ) {
282- this . log . e ( 'Bad FCM response format: %j' , resp , error ) ;
283- throw PushError . deserialize ( error , SendError ) ;
284- }
165+ . sendEach ( messages )
166+ . then ( async result => {
167+ const allPushIds = pushes . map ( p => p . _id ) ;
285168
286- if ( resp . failure === 0 && resp . canonical_ids === 0 ) {
287- this . send_results ( pushes . map ( p => p . _id ) , bytes ) ;
288- return ;
289- }
169+ if ( ! result . failureCount ) {
170+ this . send_results ( allPushIds , bytes ) ;
171+ return ;
172+ }
290173
291- if ( resp . results ) {
292- resp . results . forEach ( ( r , i ) => {
293- if ( r . message_id ) {
294- if ( r . registration_id ) {
295- if ( r . registration_id === 'BLACKLISTED' ) {
296- errorObject ( ERROR . DATA_TOKEN_INVALID , 'Blacklisted' ) . addAffected ( pushes [ i ] . _id , one ) ;
297- printBody = true ;
298- }
299- else {
300- oks . push ( [ pushes [ i ] . _id , r . registration_id ] ) ;
301- }
302- // oks.push([pushes[i]._id, r.registration_id], one); ???
303- }
304- else {
305- oks . push ( pushes [ i ] . _id ) ;
306- }
307- }
308- else if ( r . error === 'NotRegistered' ) {
309- this . log . d ( 'Token %s expired (%s)' , pushes [ i ] . t , r . error ) ;
310- errorObject ( ERROR . DATA_TOKEN_EXPIRED , r . error ) . addAffected ( pushes [ i ] . _id , one ) ;
311- }
312- else if ( r . error === 'InvalidRegistration' || r . error === 'MismatchSenderId' || r . error === 'InvalidPackageName' ) {
313- this . log . d ( 'Token %s is invalid (%s)' , pushes [ i ] . t , r . error ) ;
314- errorObject ( ERROR . DATA_TOKEN_INVALID , r . error ) . addAffected ( pushes [ i ] . _id , one ) ;
174+ // array of successfully sent push._id:
175+ const sentSuccessfully = [ ] ;
176+
177+ // check for each message
178+ for ( let i = 0 ; i < result . responses . length ; i ++ ) {
179+ const { success, error } = result . responses [ i ] ;
180+ if ( success ) {
181+ sentSuccessfully . push ( allPushIds [ i ] ) ;
315182 }
316- // these are identical to "else" block:
317- // else if (r.error === 'InvalidParameters') { // still hasn't figured out why this error is thrown, therefore not critical yet
318- // printBody = true;
319- // errorObject(ERROR.DATA_PROVIDER, r.error).addAffected(pushes[i]._id, one);
320- // }
321- // else if (r.error === 'MessageTooBig' || r.error === 'InvalidDataKey' || r.error === 'InvalidTtl') {
322- // printBody = true;
323- // errorObject(ERROR.DATA_PROVIDER, r.error).addAffected(pushes[i]._id, one);
324- // }
325183 else {
326- printBody = true ;
327- errorObject ( ERROR . DATA_PROVIDER , r . error ) . addAffected ( pushes [ i ] . _id , one ) ;
184+ const sdkError = FCM_SDK_ERRORS [ error . code ] ;
185+ // check if the sdk error is mapped to an internal error.
186+ // set to default if its not.
187+ let internalErrorCode = sdkError ?. mapTo ?? ERROR . DATA_PROVIDER ;
188+ let internalErrorMessage = sdkError ?. message ?? "Invalid error message" ;
189+ errorObject ( internalErrorCode , internalErrorMessage )
190+ . addAffected ( pushes [ i ] . _id , one ) ;
328191 }
329- } ) ;
330- let errored = 0 ;
331- for ( let k in errors ) {
332- errored += errors [ k ] . affectedBytes ;
333- this . send_push_error ( errors [ k ] ) ;
334192 }
335- if ( oks . length ) {
336- this . send_results ( oks , bytes - errored ) ;
193+ // send results back:
194+ for ( let errorKey in errors ) {
195+ this . send_push_error ( errors [ errorKey ] ) ;
337196 }
338- if ( printBody ) {
339- this . log . e ( 'Provider returned error %j for %j' , resp , content ) ;
340- }
341- }
342- } , ( [ code , error ] ) => {
343- this . log . w ( 'FCM error %d / %j' , code , error ) ;
344- console . log ( "========== MAIN PROMISE ERROR" ) ;
345- if ( code === 0 ) {
346- if ( error . message === 'ECONNRESET' || error . code === 'ENOTFOUND' || error . code === 'ETIMEDOUT' ||
347- error . code === 'ECONNREFUSED' || error . code === 'ECONNABORTED' || error . code === 'EHOSTUNREACH' ||
348- error . code === 'EAI_AGAIN' ) {
349- this . log . w ( 'FCM error %d / %j' , bytes , pushes . map ( p => p . _id ) ) ;
350- throw new ConnectionError ( `FCM ${ error . code } ` , ERROR . CONNECTION_PROVIDER )
351- . setConnectionError ( error . code , `${ error . errno } ${ error . code } ${ error . syscall } ` )
352- . addAffected ( pushes . map ( p => p . _id ) , bytes ) ;
197+ if ( sentSuccessfully . length ) {
198+ this . send_results ( sentSuccessfully , one * sentSuccessfully . length ) ;
353199 }
354- let pe = PushError . deserialize ( error , SendError ) ;
355- pe . addAffected ( pushes . map ( p => p . _id ) , bytes ) ;
356- throw pe ;
357- }
358- else if ( code >= 500 ) {
359- throw new ConnectionError ( `FCM Unavailable: ${ code } ` , ERROR . CONNECTION_PROVIDER ) . addAffected ( pushes . map ( p => p . _id ) , bytes ) ;
360- }
361- else if ( code === 401 ) {
362- throw new ConnectionError ( `FCM Unauthorized: ${ code } ` , ERROR . INVALID_CREDENTIALS ) . addAffected ( pushes . map ( p => p . _id ) , bytes ) ;
363- }
364- else if ( code === 400 ) {
365- throw new ConnectionError ( `FCM Bad message: ${ code } ` , ERROR . DATA_PROVIDER ) . addAffected ( pushes . map ( p => p . _id ) , bytes ) ;
366- }
367- else {
368- throw new ConnectionError ( `FCM Bad response code: ${ code } ` , ERROR . EXCEPTION ) . addAffected ( pushes . map ( p => p . _id ) , bytes ) ;
369- }
370- } ) ;
200+ } ) ;
371201 } ) ;
372202 }
373-
374203}
375204
376205/**
@@ -581,7 +410,6 @@ const CREDS = {
581410 static get scheme ( ) {
582411 return Object . assign ( super . scheme , {
583412 serviceAccountFile : { required : false , type : "String" } ,
584- key : { required : false , type : 'String' , 'min-length' : 100 } ,
585413 hash : { required : false , type : 'String' } ,
586414 } ) ;
587415 }
@@ -621,9 +449,6 @@ const CREDS = {
621449 }
622450 this . _data . hash = FORGE . md . sha256 . create ( ) . update ( serviceAccountJSON ) . digest ( ) . toHex ( ) ;
623451 }
624- else if ( this . _data . key ) {
625- this . _data . hash = FORGE . md . sha256 . create ( ) . update ( this . _data . key ) . digest ( ) . toHex ( ) ;
626- }
627452 else {
628453 return [ "Updating FCM credentials requires a service-account.json file" ] ;
629454 }
@@ -635,16 +460,12 @@ const CREDS = {
635460 * @returns {object } json without sensitive information
636461 */
637462 get view ( ) {
638- const fcmKey = this . _data ?. key
639- ? `FCM server key "${ this . _data . key . substr ( 0 , 10 ) } ... ${ this . _data . key . substr ( this . _data . key . length - 10 ) } "`
640- : "" ;
641463 const serviceAccountFile = this . _data ?. serviceAccountFile
642464 ? "service-account.json"
643465 : "" ;
644466 return {
645467 _id : this . _id ,
646468 type : this . _data ?. type ,
647- key : fcmKey ,
648469 serviceAccountFile,
649470 hash : this . _data ?. hash ,
650471 } ;
0 commit comments