push-receiver
Version:
A module to subscribe to GCM/FCM and receive notifications within a node process.
129 lines (117 loc) • 3.55 kB
JavaScript
const path = require('path');
const request = require('../utils/request');
const protobuf = require('protobufjs');
const Long = require('long');
const { waitFor } = require('../utils/timeout');
const fcmKey = require('../fcm/server-key');
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()
const serverKey = toBase64(Buffer.from(fcmKey));
const REGISTER_URL = 'https://android.clients.google.com/c2dm/register3';
const CHECKIN_URL = 'https://android.clients.google.com/checkin';
let root;
let AndroidCheckinResponse;
module.exports = {
register,
checkIn,
};
async function register(appId) {
const options = await checkIn();
const credentials = await doRegister(options, appId);
return credentials;
}
async function checkIn(androidId, securityToken) {
await loadProtoFile();
const buffer = getCheckinRequest(androidId, securityToken);
const body = await request({
url : CHECKIN_URL,
method : 'POST',
headers : {
'Content-Type' : 'application/x-protobuf',
},
body : buffer,
encoding : null,
});
const message = AndroidCheckinResponse.decode(body);
const object = AndroidCheckinResponse.toObject(message, {
longs : String,
enums : String,
bytes : String,
});
return object;
}
async function doRegister({ androidId, securityToken }, appId) {
const body = {
app : 'org.chromium.linux',
'X-subtype' : appId,
device : androidId,
sender : serverKey,
};
const response = await postRegister({ androidId, securityToken, body });
const token = response.split('=')[1];
return {
token,
androidId,
securityToken,
appId,
};
}
async function postRegister({ androidId, securityToken, body, retry = 0 }) {
const response = await request({
url : REGISTER_URL,
method : 'POST',
headers : {
Authorization : `AidLogin ${androidId}:${securityToken}`,
'Content-Type' : 'application/x-www-form-urlencoded',
},
form : body,
});
if (response.includes('Error')) {
console.warn(`Register request has failed with ${response}`);
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;
}
async function loadProtoFile() {
if (root) {
return;
}
root = await protobuf.load(path.join(__dirname, 'checkin.proto'));
return root;
}
function getCheckinRequest(androidId, securityToken) {
const AndroidCheckinRequest = root.lookupType(
'checkin_proto.AndroidCheckinRequest'
);
AndroidCheckinResponse = root.lookupType(
'checkin_proto.AndroidCheckinResponse'
);
const payload = {
userSerialNumber : 0,
checkin : {
type : 3,
chromeBuild : {
platform : 2,
chromeVersion : '63.0.3234.0',
channel : 1,
},
},
version : 3,
id : androidId ? Long.fromString(androidId) : undefined,
securityToken : securityToken
? Long.fromString(securityToken, true)
: undefined,
};
const errMsg = AndroidCheckinRequest.verify(payload);
if (errMsg) throw Error(errMsg);
const message = AndroidCheckinRequest.create(payload);
return AndroidCheckinRequest.encode(message).finish();
}