Skip to content

Commit

Permalink
Migrate from request to fetch
Browse files Browse the repository at this point in the history
  • Loading branch information
guiconti committed Jul 17, 2024
1 parent 05702eb commit 99c687e
Show file tree
Hide file tree
Showing 8 changed files with 10,742 additions and 960 deletions.
9,486 changes: 9,486 additions & 0 deletions package-lock.json

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "push-receiver-v2",
"version": "2.2.0",
"version": "2.2.1",
"description":
"A module to subscribe to GCM/FCM and receive notifications within a node process. v2 is compatible with the latest FCM registration endpoints.",
"main": "src/index.js",
Expand Down Expand Up @@ -60,9 +60,8 @@
"dependencies": {
"http_ece": "^1.0.5",
"long": "^3.2.0",
"node-fetch": "^3.3.2",
"protobufjs": "^6.8.0",
"request": "^2.81.0",
"request-promise": "^4.2.1",
"uuid": "^3.1.0"
}
}
17 changes: 8 additions & 9 deletions scripts/send/index.js
Original file line number Diff line number Diff line change
@@ -1,36 +1,35 @@
// Note: The send endpoint is deprecated and removed as of June 20, 2024: https://firebase.google.com/support/faq#fcm-depr-features

const request = require('request-promise');
const argv = require('yargs').argv;
const { fetch } = require('../../src/utils/fetch');

const serverKey = argv.serverKey;
const token = argv.token;

if (!serverKey) {
console.error('Missing serverKey argument');
return;
process.exit(171);
}

if (!token) {
console.error('Missing token argument');
return;
process.exit(171);
}

(async () => {
try {
const response = await request({
const response = await fetch('https://fcm.googleapis.com/fcm/send', {
method : 'POST',
url : 'https://fcm.googleapis.com/fcm/send',
json : true,
body : {
body : JSON.stringify({
to : token,
notification : {
title : 'Hello world',
body : 'Test',
},
},
}),
headers : {
Authorization : `key=${serverKey}`,
Authorization : `key=${serverKey}`,
'Content-Type' : 'application/json',
},
});
console.log(response);
Expand Down
93 changes: 51 additions & 42 deletions src/fcm/index.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
const crypto = require('crypto');
const request = require('request-promise');
const { fetch } = require('../utils/fetch');
const { escape } = require('../utils/base64');

const FIREBASE_INSTALLATION = 'https://firebaseinstallations.googleapis.com/v1/';
const FIREBASE_INSTALLATION =
'https://firebaseinstallations.googleapis.com/v1/';
const FCM_REGISTRATION = 'https://fcmregistrations.googleapis.com/v1/';
const FCM_ENDPOINT = 'https://fcm.googleapis.com/fcm/send';

Expand All @@ -11,58 +12,66 @@ module.exports = { installFCM, registerFCM };
function generateFirebaseFID() {
// A valid FID has exactly 22 base64 characters, which is 132 bits, or 16.5
// bytes. our implementation generates a 17 byte array instead.
let fid = crypto.randomBytes(17);
const fid = crypto.randomBytes(17);

// Replace the first 4 random bits with the constant FID header of 0b0111.
fid[0] = 0b01110000 + (fid[0] % 0b00010000);
fid[0] = 0b01110000 + fid[0] % 0b00010000;

return fid.toString('base64');
}

async function installFCM(config) {
const response = await request({
url : `${FIREBASE_INSTALLATION}projects/${config.firebase.projectID}/installations`,
method : 'POST',
headers : {
'x-firebase-client': btoa(JSON.stringify({ heartbeats: [], version: 2 })).toString('base64'),
'x-goog-api-key': config.firebase.apiKey
},
body : {
appId: config.firebase.appID,
authVersion: 'FIS_v2',
fid: generateFirebaseFID(),
sdkVersion: 'w:0.6.4'
},
json: true
});
const response = await fetch(
`${FIREBASE_INSTALLATION}projects/${
config.firebase.projectID
}/installations`,
{
method : 'POST',
headers : {
'x-firebase-client' : btoa(
JSON.stringify({ heartbeats : [], version : 2 })
).toString('base64'),
'x-goog-api-key' : config.firebase.apiKey,
'Content-Type' : 'application/json',
},
body : JSON.stringify({
appId : config.firebase.appID,
authVersion : 'FIS_v2',
fid : generateFirebaseFID(),
sdkVersion : 'w:0.6.4',
}),
}
);
return response;
}

async function registerFCM(config) {
const keys = await createKeys();
const response = await request({
url : `${FCM_REGISTRATION}projects/${config.firebase.projectID}/registrations`,
method : 'POST',
headers : {
'x-goog-api-key' : config.firebase.apiKey,
'x-goog-firebase-installations-auth' : config.authToken
},
body : {
web: {
applicationPubKey: config.vapidKey,
auth: keys.authSecret
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_'),
endpoint: `${FCM_ENDPOINT}/${config.token}`,
p256dh: keys.publicKey
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_')
}
},
json: true
});
const response = await fetch(
`${FCM_REGISTRATION}projects/${config.firebase.projectID}/registrations`,
{
method : 'POST',
headers : {
'x-goog-api-key' : config.firebase.apiKey,
'x-goog-firebase-installations-auth' : config.authToken,
'Content-Type' : 'application/json',
},
body : JSON.stringify({
web : {
applicationPubKey : config.vapidKey,
auth : keys.authSecret
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_'),
endpoint : `${FCM_ENDPOINT}/${config.token}`,
p256dh : keys.publicKey
.replace(/=/g, '')
.replace(/\+/g, '-')
.replace(/\//g, '_'),
},
}),
}
);
return {
keys,
fcm : response,
Expand Down
32 changes: 15 additions & 17 deletions src/gcm/index.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const path = require('path');
const request = require('../utils/request');
const { fetchWithRetry } = require('../utils/fetch');
const protobuf = require('protobufjs');
const Long = require('long');
const { waitFor } = require('../utils/timeout');
Expand All @@ -8,8 +8,8 @@ const { toBase64 } = require('../utils/base64');

// Hack to fix PHONE_REGISTRATION_ERROR #17 when bundled with webpack
// https://github.com/dcodeIO/protobuf.js#browserify-integration
protobuf.util.Long = Long
protobuf.configure()
protobuf.util.Long = Long;
protobuf.configure();

const serverKey = toBase64(Buffer.from(fcmKey));

Expand All @@ -33,16 +33,15 @@ async function register(appId) {
async function checkIn(androidId, securityToken) {
await loadProtoFile();
const buffer = getCheckinRequest(androidId, securityToken);
const body = await request({
url : CHECKIN_URL,
const responseBuffer = await fetchWithRetry(CHECKIN_URL, {
method : 'POST',
headers : {
'Content-Type' : 'application/x-protobuf',
},
body : buffer,
encoding : null,
});
const message = AndroidCheckinResponse.decode(body);
body : buffer,
}).then(response => response.arrayBuffer());
const typedArrayBuffer = new Uint8Array(responseBuffer);
const message = AndroidCheckinResponse.decode(typedArrayBuffer);
const object = AndroidCheckinResponse.toObject(message, {
longs : String,
enums : String,
Expand All @@ -69,25 +68,24 @@ async function doRegister({ androidId, securityToken }, appId) {
}

async function postRegister({ androidId, securityToken, body, retry = 0 }) {
const response = await request({
url : REGISTER_URL,
const response = await fetchWithRetry(REGISTER_URL, {
method : 'POST',
headers : {
Authorization : `AidLogin ${androidId}:${securityToken}`,
'Content-Type' : 'application/x-www-form-urlencoded',
Authorization : `AidLogin ${androidId}:${securityToken}`,
},
form : body,
body : new URLSearchParams(body),
});
if (response.includes('Error')) {
console.warn(`Register request has failed with ${response}`);
const responseText = await response.text();
if (responseText.includes('Error')) {
console.warn(`Register request has failed with ${responseText}`);
if (retry >= 5) {
throw new Error('GCM register has failed');
}
console.warn(`Retry... ${retry + 1}`);
await waitFor(1000);
return postRegister({ androidId, securityToken, body, retry : retry + 1 });
}
return response;
return responseText;
}

async function loadProtoFile() {
Expand Down
19 changes: 15 additions & 4 deletions src/utils/request/index.js → src/utils/fetch/index.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,31 @@
const request = require('request-promise');
const fetchAsync = import('node-fetch');
const { waitFor } = require('../timeout');

// In seconds
const MAX_RETRY_TIMEOUT = 15;
// Step in seconds
const RETRY_STEP = 5;

module.exports = requestWithRety;
module.exports = { fetchWithRetry, fetch };

function requestWithRety(...args) {
/**
* Fetch with retry
* @param url URL | RequestInfo
* @param init Optional<RequestInit>
* @returns {Promise<Response>}
*/
async function fetch(url, init) {
const loadedFetch = (await fetchAsync).default;
return loadedFetch(url, init);
}

function fetchWithRetry(...args) {
return retry(0, ...args);
}

async function retry(retryCount = 0, ...args) {
try {
const result = await request(...args);
const result = await fetch(...args);
return result;
} catch (e) {
const timeout = Math.min(retryCount * RETRY_STEP, MAX_RETRY_TIMEOUT);
Expand Down
13 changes: 7 additions & 6 deletions test/notification.test.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const request = require('request-promise');
const { fetch } = require('../src/utils/fetch');
const { SENDER_ID, SERVER_KEY } = require('./keys');
const { register, listen } = require('../src/index');

Expand Down Expand Up @@ -57,15 +57,16 @@ describe('Parser', function() {
});

async function send(notification) {
const response = await request({
const response = await fetch('https://fcm.googleapis.com/fcm/send', {
method : 'POST',
url : 'https://fcm.googleapis.com/fcm/send',
json : true,
body : {
body : JSON.stringify({
to : credentials.fcm.token,
notification : notification,
}),
headers : {
Authorization : `key=${SERVER_KEY}`,
'content-type' : 'application/json',
},
headers : { Authorization : `key=${SERVER_KEY}` },
});
try {
expect(response.success).toEqual(1);
Expand Down
Loading

0 comments on commit 99c687e

Please sign in to comment.