Skip to content

Commit 83f1db7

Browse files
committed
added legacy methods
1 parent 27361a7 commit 83f1db7

3 files changed

Lines changed: 1208 additions & 878 deletions

File tree

etc/firebase-admin.messaging.api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,7 +206,11 @@ export class Messaging {
206206
sendEachForMulticast(message: MulticastMessage, dryRun?: boolean): Promise<BatchResponse>;
207207
sendEachForMulticast(message: FidMulticastMessage, dryRun?: boolean): Promise<BatchResponse>;
208208
subscribeToTopic(registrationTokenOrTokens: string | string[], topic: string): Promise<MessagingTopicManagementResponse>;
209+
// @deprecated
210+
subscribeToTopicLegacy(registrationTokenOrTokens: string | string[], topic: string): Promise<MessagingTopicManagementResponse>;
209211
unsubscribeFromTopic(registrationTokenOrTokens: string | string[], topic: string): Promise<MessagingTopicManagementResponse>;
212+
// @deprecated
213+
unsubscribeFromTopicLegacy(registrationTokenOrTokens: string | string[], topic: string): Promise<MessagingTopicManagementResponse>;
210214
}
211215

212216
// @public

src/messaging/messaging.ts

Lines changed: 187 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -41,10 +41,49 @@ import { Http2SessionHandler, RequestResponseError } from '../utils/api-request'
4141

4242
// FCM endpoints
4343
const FCM_SEND_HOST = 'fcm.googleapis.com';
44+
const FCM_TOPIC_MANAGEMENT_HOST = 'iid.googleapis.com';
45+
const FCM_TOPIC_MANAGEMENT_ADD_PATH = '/iid/v1:batchAdd';
46+
const FCM_TOPIC_MANAGEMENT_REMOVE_PATH = '/iid/v1:batchRemove';
4447

4548
// Maximum messages that can be included in a batch request.
4649
const FCM_MAX_BATCH_SIZE = 500;
4750

51+
/**
52+
* Maps a raw FCM server response to a `MessagingTopicManagementResponse` object.
53+
*
54+
* @param response - The raw FCM server response to map.
55+
*
56+
* @returns The mapped `MessagingTopicManagementResponse` object.
57+
*/
58+
function mapRawResponseToTopicManagementResponse(response: object): MessagingTopicManagementResponse {
59+
// Add the success and failure counts.
60+
const result: MessagingTopicManagementResponse = {
61+
successCount: 0,
62+
failureCount: 0,
63+
errors: [],
64+
};
65+
66+
if ('results' in response) {
67+
(response as any).results.forEach((tokenManagementResult: any, index: number) => {
68+
// Map the FCM server's error strings to actual error objects.
69+
if ('error' in tokenManagementResult) {
70+
result.failureCount += 1;
71+
const newError = FirebaseMessagingError.fromTopicManagementServerError(
72+
tokenManagementResult.error, /* message */ undefined, tokenManagementResult.error,
73+
);
74+
75+
result.errors.push({
76+
index,
77+
error: newError,
78+
});
79+
} else {
80+
result.successCount += 1;
81+
}
82+
});
83+
}
84+
return result;
85+
}
86+
4887

4988
/**
5089
* Messaging service bound to the provided app.
@@ -341,7 +380,6 @@ export class Messaging {
341380
registrationTokenOrTokens,
342381
topic,
343382
'subscribeToTopic',
344-
'',
345383
);
346384
}
347385

@@ -368,7 +406,56 @@ export class Messaging {
368406
registrationTokenOrTokens,
369407
topic,
370408
'unsubscribeFromTopic',
371-
'',
409+
);
410+
}
411+
412+
/**
413+
* Subscribes a device or list of devices to an FCM topic using the legacy Instance ID API.
414+
* Served as a backup/legacy endpoint.
415+
*
416+
* @param registrationTokenOrTokens - A token or array of registration tokens
417+
* for the devices to subscribe to the topic.
418+
* @param topic - The topic to which to subscribe.
419+
*
420+
* @returns A promise fulfilled with the server's response after the device has been
421+
* subscribed to the topic.
422+
*
423+
* @deprecated Use {@link Messaging.subscribeToTopic} instead.
424+
*/
425+
public subscribeToTopicLegacy(
426+
registrationTokenOrTokens: string | string[],
427+
topic: string,
428+
): Promise<MessagingTopicManagementResponse> {
429+
return this.sendTopicManagementRequestLegacy(
430+
registrationTokenOrTokens,
431+
topic,
432+
'subscribeToTopicLegacy',
433+
FCM_TOPIC_MANAGEMENT_ADD_PATH,
434+
);
435+
}
436+
437+
/**
438+
* Unsubscribes a device or list of devices from an FCM topic using the legacy Instance ID API.
439+
* Served as a backup/legacy endpoint.
440+
*
441+
* @param registrationTokenOrTokens - A device registration token or an array of
442+
* device registration tokens to unsubscribe from the topic.
443+
* @param topic - The topic from which to unsubscribe.
444+
*
445+
* @returns A promise fulfilled with the server's response after the device has been
446+
* unsubscribed from the topic.
447+
*
448+
* @deprecated Use {@link Messaging.unsubscribeFromTopic} instead.
449+
*/
450+
public unsubscribeFromTopicLegacy(
451+
registrationTokenOrTokens: string | string[],
452+
topic: string,
453+
): Promise<MessagingTopicManagementResponse> {
454+
return this.sendTopicManagementRequestLegacy(
455+
registrationTokenOrTokens,
456+
topic,
457+
'unsubscribeFromTopicLegacy',
458+
FCM_TOPIC_MANAGEMENT_REMOVE_PATH,
372459
);
373460
}
374461

@@ -401,7 +488,6 @@ export class Messaging {
401488
* registration tokens to unsubscribe from the topic.
402489
* @param topic - The topic to which to subscribe.
403490
* @param methodName - The name of the original method called.
404-
* @param path - The endpoint path to use for the request.
405491
*
406492
* @returns A Promise fulfilled with the parsed server
407493
* response.
@@ -410,7 +496,6 @@ export class Messaging {
410496
registrationTokenOrTokens: string | string[],
411497
topic: string,
412498
methodName: string,
413-
_path: string,
414499
): Promise<MessagingTopicManagementResponse> {
415500
this.validateRegistrationTokensType(registrationTokenOrTokens, methodName);
416501
this.validateTopicType(topic, methodName);
@@ -434,7 +519,7 @@ export class Messaging {
434519
return utils.findProjectId(this.app).then((projectId) => {
435520
if (!validator.isNonEmptyString(projectId)) {
436521
throw new FirebaseMessagingError(
437-
MessagingClientErrorCode.INVALID_ARGUMENT,
522+
messagingClientErrorCode.INVALID_ARGUMENT,
438523
'Failed to determine project ID for Messaging. Initialize the '
439524
+ 'SDK with service account credentials or set project ID as an app option. '
440525
+ 'Alternatively set the GOOGLE_CLOUD_PROJECT environment variable.',
@@ -447,75 +532,116 @@ export class Messaging {
447532

448533
const http2SessionHandler = new Http2SessionHandler('https://fcm.googleapis.com');
449534

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-
});
535+
const requests = registrationTokensArray.map((registrationId) => {
536+
let requestPath = `/v1/projects/${projectId}/registrations/${registrationId}/topicSubscriptions`;
537+
if (isSubscribe) {
538+
requestPath += `?topic_name=${topicName}`;
539+
} else {
540+
requestPath += `/${topicName}?allow_missing=true`;
541+
}
542+
return this.messagingRequestHandler.invokeHttp2RequestHandler(
543+
'fcm.googleapis.com',
544+
requestPath,
545+
httpMethod,
546+
isSubscribe ? {} : undefined,
547+
http2SessionHandler
548+
);
549+
});
455550

456-
const requests = registrationTokensArray.map(async (registrationId) => {
457-
let requestPath = `/v1/projects/${projectId}/registrations/${registrationId}/topicSubscriptions`;
458-
if (isSubscribe) {
459-
requestPath += `?topic_name=${topicName}`;
551+
return Promise.allSettled(requests).then((results) => {
552+
if (results.length > 0 && results.every((r) => r.status === 'rejected')) {
553+
const firstReason = (results[0] as PromiseRejectedResult).reason;
554+
if (firstReason instanceof RequestResponseError) {
555+
throw createFirebaseError(firstReason);
460556
} else {
461-
requestPath += `/${topicName}?allow_missing=true`;
557+
throw firstReason;
462558
}
463-
return this.messagingRequestHandler.invokeHttp2RequestHandler(
464-
'fcm.googleapis.com',
465-
requestPath,
466-
httpMethod,
467-
isSubscribe ? {} : undefined,
468-
http2SessionHandler
469-
);
470-
});
559+
}
471560

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-
}
561+
const response: MessagingTopicManagementResponse = {
562+
successCount: 0,
563+
failureCount: 0,
564+
errors: [],
565+
};
483566

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);
567+
results.forEach((result, index) => {
568+
if (result.status === 'fulfilled') {
569+
response.successCount += 1;
570+
} else {
571+
response.failureCount += 1;
572+
const err = (result as PromiseRejectedResult).reason;
573+
const errorCode = err.response?.isJson() ? getErrorCode(err.response.data) : null;
574+
const errorMessage = err.response?.isJson() ? err.response.data?.error?.message : err.message;
575+
const newError = FirebaseMessagingError.fromTopicManagementServerError(
576+
errorCode || 'UNKNOWN',
577+
errorMessage,
578+
err.response?.isJson() ? err.response.data : undefined
579+
);
580+
response.errors.push({
581+
index,
582+
error: newError,
583+
});
584+
}
511585
});
586+
587+
return response;
512588
}).finally(() => {
513589
http2SessionHandler.close();
514590
});
515591
});
516592
});
517593
}
518594

595+
/**
596+
* Helper method which sends and handles topic subscription management requests using the legacy Instance ID API.
597+
*
598+
* @param registrationTokenOrTokens - The registration token or an array of
599+
* registration tokens to unsubscribe from the topic.
600+
* @param topic - The topic to which to subscribe.
601+
* @param methodName - The name of the original method called.
602+
* @param path - The endpoint path to use for the request.
603+
*
604+
* @returns A Promise fulfilled with the parsed server response.
605+
*/
606+
private sendTopicManagementRequestLegacy(
607+
registrationTokenOrTokens: string | string[],
608+
topic: string,
609+
methodName: string,
610+
path: string,
611+
): Promise<MessagingTopicManagementResponse> {
612+
this.validateRegistrationTokensType(registrationTokenOrTokens, methodName);
613+
this.validateTopicType(topic, methodName);
614+
615+
// Prepend the topic with /topics/ if necessary.
616+
topic = this.normalizeTopic(topic);
617+
618+
return Promise.resolve()
619+
.then(() => {
620+
// Validate the contents of the input arguments. Because we are now in a promise, any thrown
621+
// error will cause this method to return a rejected promise.
622+
this.validateRegistrationTokens(registrationTokenOrTokens, methodName);
623+
this.validateTopic(topic, methodName);
624+
625+
// Ensure the registration token(s) input argument is an array.
626+
let registrationTokensArray: string[] = registrationTokenOrTokens as string[];
627+
if (validator.isString(registrationTokenOrTokens)) {
628+
registrationTokensArray = [registrationTokenOrTokens as string];
629+
}
630+
631+
const request = {
632+
to: topic,
633+
registration_tokens: registrationTokensArray,
634+
};
635+
636+
return this.messagingRequestHandler.invokeRequestHandler(
637+
FCM_TOPIC_MANAGEMENT_HOST, path, request,
638+
);
639+
})
640+
.then((response) => {
641+
return mapRawResponseToTopicManagementResponse(response);
642+
});
643+
}
644+
519645
/**
520646
* Validates the type of the provided registration token(s). If invalid, an error will be thrown.
521647
*

0 commit comments

Comments
 (0)