Skip to content

Fcm GAPIC type conversion #1354

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
8fe5e05
Import generated types & convert Message -> generated Message
maceonthompson Jun 28, 2021
a005e7d
Add JSDoc to `convertToClientMessage` method
maceonthompson Jun 28, 2021
f7e0b1b
Fix error where private Message type was used as object
maceonthompson Jun 28, 2021
96a5675
Add support for different public Message types
maceonthompson Jun 28, 2021
de08189
Add `getAppKey()` as a precursor to using the generated client
maceonthompson Jul 2, 2021
b53df09
Switch to using generated client in `send()`
maceonthompson Jul 2, 2021
b9f6913
Remove TODOS
maceonthompson Jul 2, 2021
5c86e2c
Temporary adjustment to generated client to avoid errors at runtime.
maceonthompson Jul 2, 2021
a81cdc2
Import generated types & convert Message -> generated Message
maceonthompson Jun 28, 2021
3bc7f20
Add JSDoc to `convertToClientMessage` method
maceonthompson Jun 28, 2021
91f00f2
Fix error where private Message type was used as object
maceonthompson Jun 28, 2021
66b6a18
Add support for different public Message types
maceonthompson Jun 28, 2021
69173a4
Add `getAppKey()` as a precursor to using the generated client
maceonthompson Jul 2, 2021
01ddff8
Switch to using generated client in `send()`
maceonthompson Jul 2, 2021
f76848b
Remove TODOS
maceonthompson Jul 2, 2021
4acfe87
Temporary adjustment to generated client to avoid errors at runtime.
maceonthompson Jul 2, 2021
f0225a9
Merge branch 'maceo-fcm-gapic-type-conversion' of github.com:firebase…
maceonthompson Jul 7, 2021
7d1dfa4
Convert send non error expects to use sinon stub
maceonthompson Jul 14, 2021
73da589
refactor Sinon behavior to be more in line with original code
maceonthompson Jul 14, 2021
43b9cde
Add `createFirebaseErrorFromGapicError`
maceonthompson Jul 16, 2021
4cdb520
Add error handling for `send()` with the generated client
maceonthompson Jul 16, 2021
146bfa1
Simplify `createFirebaseErrorFromGapicError`
maceonthompson Jul 19, 2021
134c621
Address casting comment on PR in messaging.ts
maceonthompson Jul 20, 2021
cc435ab
Fix unit tests to accomodate new generated client.
maceonthompson Jul 20, 2021
4bd6059
Use the same `client` instance for multiple calls
maceonthompson Jul 21, 2021
b798f9d
Remove commented out/redundant tests
maceonthompson Jul 26, 2021
aac6778
Remove redundancy in `send` test `afterEach` logic
maceonthompson Jul 26, 2021
d5347ea
Add test for send error properties in addition to rejection message
maceonthompson Jul 26, 2021
393760d
Address comments on message serialization
maceonthompson Jul 26, 2021
f766736
Add missing return statement
maceonthompson Jul 26, 2021
60a2bef
Move gapic imports closer to other imports
maceonthompson Jul 29, 2021
453cae6
Use original type names from generated client
maceonthompson Aug 3, 2021
b7f0b9b
Instantiate client with projectId instead of having client infer it
maceonthompson Aug 3, 2021
c2e7dc9
Fix typo on 462, examle -> example
maceonthompson Aug 3, 2021
4fcd759
Address redudant gapic return constant by making it module-level
maceonthompson Aug 3, 2021
25da011
Remove commented out code + unnecessary function call
maceonthompson Aug 3, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/generated/messaging/src/v1/fcm_service_client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,9 @@ import jsonProtos = require('../../protos/protos.json');
*/
import * as gapicConfig from './fcm_service_client_config.json';

const version = require('../../../package.json').version;
//! Temporary change, this modification should be avoidable with the suggested
// directory refactoring.
const version = require('../../../../../package.json').version;

/**
* Firebase Cloud Messaging service (FCM) to target cross-platform messaging.
Expand Down
16 changes: 16 additions & 0 deletions src/messaging/messaging-errors-internal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,22 @@ import { HttpError } from '../utils/api-request';
import { FirebaseMessagingError, MessagingClientErrorCode } from '../utils/error';
import * as validator from '../utils/validator';

/**
* Creates a new FirebaseMessagingError by extracting the error code, message, and
* other relevant details from an error thrown by the generated GAPIC client.
*
* @param err
* @returns
*/
export function createFirebaseErrorFromGapicError(err: Error): FirebaseMessagingError {
const json = { error: err };

const errorCode = getErrorCode(json);
const errorMessage = getErrorMessage(json);

return FirebaseMessagingError.fromServerError(errorCode, errorMessage, json);
}

/**
* Creates a new FirebaseMessagingError by extracting the error code, message and other relevant
* details from an HTTP error response.
Expand Down
111 changes: 101 additions & 10 deletions src/messaging/messaging.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,12 @@ import { validateMessage, BLACKLISTED_DATA_PAYLOAD_KEYS, BLACKLISTED_OPTIONS_KEY
import { messaging } from './index';
import { FirebaseMessagingRequestHandler } from './messaging-api-request-internal';
import { ErrorInfo, MessagingClientErrorCode, FirebaseMessagingError } from '../utils/error';
import { createFirebaseErrorFromGapicError } from './messaging-errors-internal';
import { ServiceAccountCredential } from '../credential/credential-internal';
import * as utils from '../utils';
import * as validator from '../utils/validator';
import * as protos from '../generated/messaging/protos/protos';
import { FcmServiceClient } from '../generated/messaging/src/v1/fcm_service_client'

import MessagingInterface = messaging.Messaging;
import Message = messaging.Message;
Expand All @@ -41,6 +45,10 @@ import MessagingConditionResponse = messaging.MessagingConditionResponse;
import DataMessagePayload = messaging.DataMessagePayload;
import NotificationMessagePayload = messaging.NotificationMessagePayload;

// GAPIC Generated client types
import IMessage = protos.google.firebase.fcm.v1.IMessage;
import ISendRequest = protos.google.firebase.fcm.v1.ISendMessageRequest;

/* eslint-disable @typescript-eslint/camelcase */

// FCM endpoints
Expand Down Expand Up @@ -193,6 +201,7 @@ export class Messaging implements MessagingInterface {
private urlPath: string;
private readonly appInternal: FirebaseApp;
private readonly messagingRequestHandler: FirebaseMessagingRequestHandler;
private fcmServiceClient: FcmServiceClient;

/**
* Gets the {@link messaging.Messaging `Messaging`} service for the
Expand Down Expand Up @@ -228,6 +237,77 @@ export class Messaging implements MessagingInterface {
return this.appInternal;
}


/**
* Converts from the public {@link messaging.Message `Message`} type to the
* internal/generated {@link protos.google.firebase.fcm.v1.Message `clientMessage`}
* type.
*
* @param message The {@link messaging.Message `Message`} to be converted
* @returns A {@link protos.google.firebase.fcm.v1.Message `clientMessage`},
* where like fields are the same as the inptuted message.
*/
private convertToIMessage(message: Message): IMessage {
//TODO: Add conversion for android, webpush, apns

const convertedMessage: IMessage = {
data: message.data,
notification: message.notification,
fcmOptions: message.fcmOptions
}

if ('token' in message) {
convertedMessage.token = message.token;
} else if ('topic' in message) {
convertedMessage.topic = message.topic;
} else if ('condition' in message) {
convertedMessage.condition = message.condition;
}

return convertedMessage;
}

/**
* Method that returns a fully instantiated service client.
*
* @returns an instantiated service client with either the application's
* service account credentials or the default credentials
*/
private getFcmServiceClient(): Promise<FcmServiceClient> {
if (this.fcmServiceClient) {
return Promise.resolve(this.fcmServiceClient);
}

const credential = this.app.options.credential;
const options: { credentials?: object; fallback: 'rest'; projectId?: string } = {
fallback: 'rest'
};

if (credential instanceof ServiceAccountCredential) {
options.credentials = {
private_key: credential.privateKey,
client_email: credential.clientEmail
};
}

return utils.findProjectId(this.appInternal).then(projectId => {
if (!validator.isNonEmptyString(projectId)) {
// Assert for an explicit project ID (either via AppOptions or the cert itself).
throw new FirebaseMessagingError(
MessagingClientErrorCode.INVALID_ARGUMENT,
'Failed to determine project ID for Messaging. Initialize the '
+ 'SDK with service account credentials or set project ID as an app option. '
+ 'Alternatively set the GOOGLE_CLOUD_PROJECT environment variable.',
);
}

options.projectId = projectId;
}).then(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You can drop this line safely, and it would still work.

this.fcmServiceClient = new FcmServiceClient(options);
return this.fcmServiceClient;
})
}

/**
* Sends the given message via FCM.
*
Expand All @@ -245,16 +325,27 @@ export class Messaging implements MessagingInterface {
throw new FirebaseMessagingError(
MessagingClientErrorCode.INVALID_ARGUMENT, 'dryRun must be a boolean');
}
return this.getUrlPath()
.then((urlPath) => {
const request: { message: Message; validate_only?: boolean } = { message: copy };
if (dryRun) {
request.validate_only = true;
}
return this.messagingRequestHandler.invokeRequestHandler(FCM_SEND_HOST, urlPath, request);
})
.then((response) => {
return (response as any).name;

const IMessage = this.convertToIMessage(copy);

return this.getFcmServiceClient()
.then(client => {
return client.getProjectId()
.then((projectId) => {
const parent = `projects/${projectId}`;

const request: ISendRequest = {
parent: parent,
message: IMessage,
validateOnly: dryRun
};
return client.sendMessage(request);
})
.then(([response]) => {
return response.name!;
}).catch(err => {
throw createFirebaseErrorFromGapicError(err);
});
});
}

Expand Down
Loading