firebase-fcm-v1-http2
Version:
A Node.js library for sending push notifications using Firebase Cloud Messaging HTTP v1 APIs.
111 lines (92 loc) • 3.7 kB
text/typescript
import * as http2 from 'http2';
import * as async from 'async';
import { Message, CustomClientHttp2Session } from './types';
const fcmv1Api = 'https://fcm.googleapis.com';
export function processBatch(
message: Message,
devices: string[],
projectId: string,
accessToken: string,
maxConcurrentStreamsAllowed: number,
maxConcurrentConnections: number
): Promise<string[]> {
return new Promise((resolve, reject) => {
const client = http2.connect(fcmv1Api, {
peerMaxConcurrentStreams: maxConcurrentConnections
}) as CustomClientHttp2Session;
client.on('error', (err) => reject(err));
client.on('socketError', (err) => reject(err));
client.unregisteredTokens = [];
async.eachLimit(devices, maxConcurrentStreamsAllowed, (device : any, doneCallback : any) => {
sendRequest(client, device, message, projectId, accessToken, doneCallback, 0, maxConcurrentStreamsAllowed, maxConcurrentConnections);
}, (err: any) => {
client.close();
if (err) return reject(err);
resolve(client.unregisteredTokens);
});
});
}
function sendRequest(
client: CustomClientHttp2Session,
device: string,
message: Message,
projectId: string,
accessToken: string,
doneCallback: (err?: Error) => void,
tries: number,
maxConcurrentStreamsAllowed: number,
maxConcurrentConnections: number
) {
const request = client.request({
':method': 'POST',
':scheme': 'https',
':path': `/v1/projects/${projectId}/messages:send`,
Authorization: `Bearer ${accessToken}`,
});
request.setEncoding('utf8');
const clonedMessage = { ...message, token: device };
request.write(JSON.stringify({ message: clonedMessage }));
let data = '';
request.on('data', (chunk) => {
data += chunk;
});
let retrying = false;
const errorHandler = (err: any) => {
if (tries <= 3) {
if (retrying) return;
retrying = true;
if (client.destroyed) {
return processBatch(message, [device], projectId, accessToken, maxConcurrentStreamsAllowed, maxConcurrentConnections)
.finally(doneCallback);
}
return setTimeout(() => sendRequest(client, device, message, projectId, accessToken, doneCallback, tries + 1, maxConcurrentStreamsAllowed, maxConcurrentConnections), 10000);
}
err.data = data;
doneCallback(err);
};
request.on('end', () => {
try {
if (data.toLowerCase().includes('server error')) {
return errorHandler(new Error('Internal Server Error'));
}
const response = JSON.parse(data);
const statusCode = response.statusCode ?? response.status;
if (statusCode && statusCode >= 500) {
return errorHandler(new Error(`${statusCode} Internal Server Error`));
}
if (response.error) {
if ((response.error.details && response.error.details[0].errorCode === 'UNREGISTERED') ||
(response.error.code === 400 && response.error.status === 'INVALID_ARGUMENT')) {
client.unregisteredTokens.push(device);
} else {
return doneCallback(response.error);
}
}
doneCallback();
} catch (err) {
errorHandler(err);
}
});
request.on('error', errorHandler);
request.end();
}