Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion .babelrc
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
"@babel/preset-env",
{
"targets": {
"node": "6"
"node": "14"
}
}
]
Expand Down
7 changes: 6 additions & 1 deletion .eslintrc
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
{
"extends": ["airbnb-base", "plugin:prettier/recommended"],
"env": {
"node": true
"node": true,
"es2020": true
},
"parserOptions": {
"ecmaVersion": 2020,
"sourceType": "module"
}
}
16 changes: 0 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 0 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@
"firebase-admin": "12.1.1",
"node-adm": "0.9.1",
"node-gcm": "1.1.4",
"ramda": "0.32.0",
"web-push": "3.6.7",
"wns": "0.5.4"
},
Expand Down
4 changes: 2 additions & 2 deletions src/push-notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -37,11 +37,11 @@ class PN {
sendWith(method, regIds, data, cb) {
return method(regIds, data, this.settings)
.then((results) => {
(cb || ((noop) => noop))(null, results);
cb?.(null, results);
return results;
})
.catch((error) => {
(cb || ((noop) => noop))(error);
cb?.(error);
return Promise.reject(error);
});
}
Expand Down
8 changes: 1 addition & 7 deletions src/sendADM.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,7 @@ const sendADM = (regIds, _data, settings) => {
};
const promises = [];
const admSender = new adm.Sender(settings.adm);
const data = { ..._data };
const { consolidationKey, expiry, timeToLive, custom } = data;

delete data.consolidationKey;
delete data.expiry;
delete data.timeToLive;
delete data.custom;
const { consolidationKey, expiry, timeToLive, custom, ...data } = _data;

const message = {
expiresAfter:
Expand Down
7 changes: 1 addition & 6 deletions src/sendAPN.js
Original file line number Diff line number Diff line change
@@ -1,13 +1,8 @@
const apn = require('@parse/node-apn');
const R = require('ramda');
const { APN_METHOD } = require('./constants');
const { buildApnsMessage } = require('./utils/tools');

const getDeviceTokenOrSelf = R.ifElse(
R.has('device'),
R.prop('device'),
R.identity
);
const getDeviceTokenOrSelf = (token) => token?.device ?? token;

class APN {
constructor(settings) {
Expand Down
22 changes: 6 additions & 16 deletions src/sendFCM.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,8 @@ const { FCM_METHOD } = require('./constants');
const FcmMessage = require('./utils/fcmMessage');
const { containsValidRecipients } = require('./utils/tools');

const getRecipientList = (obj) => {
if (obj.tokens) {
return obj.tokens;
}
if (obj.token) {
return [obj.token];
}
if (obj.condition) {
return [obj.condition];
}
if (obj.topic) {
return [obj.topic];
}
return [];
};
const getRecipientList = (obj) =>
obj.tokens ?? [obj.token, obj.condition, obj.topic].filter(Boolean);

const sendChunk = (firebaseApp, recipients, message) => {
const firebaseMessage = message.buildWithRecipients(recipients);
Expand Down Expand Up @@ -50,12 +37,15 @@ const sendChunk = (firebaseApp, recipients, message) => {
message: response.responses.map((value) => {
const regToken = recipientList[regIndex];
regIndex += 1;
const errorMsg = value.error
? value.error.message || value.error
: null;
return {
messageId: value.message_id,
originalRegId: regToken,
regId: value.registration_id || regToken,
error: value.error ? new Error(value.error) : null,
errorMsg: value.error ? value.error.message || value.error : null,
errorMsg,
};
}),
};
Expand Down
19 changes: 6 additions & 13 deletions src/sendGCM.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,8 @@ const gcm = require('node-gcm');
const { GCM_METHOD } = require('./constants');
const { containsValidRecipients, buildGcmMessage } = require('./utils/tools');

const getRecipientList = (obj) => {
if (obj.registrationTokens) {
return obj.registrationTokens;
}
if (obj.to) {
return [obj.to];
}
if (obj.condition) {
return [obj.condition];
}
return [];
};
const getRecipientList = (obj) =>
obj.registrationTokens ?? [obj.to, obj.condition].filter(Boolean);

const sendChunk = (GCMSender, recipients, message, retries) =>
new Promise((resolve) => {
Expand Down Expand Up @@ -43,12 +33,15 @@ const sendChunk = (GCMSender, recipients, message, retries) =>
message: response.results.map((value) => {
const regToken = recipientList[regIndex];
regIndex += 1;
const errorMsg = value.error
? value.error.message || value.error
: null;
return {
messageId: value.message_id,
originalRegId: regToken,
regId: value.registration_id || regToken,
error: value.error ? new Error(value.error) : null,
errorMsg: value.error ? value.error.message || value.error : null,
errorMsg,
};
}),
});
Expand Down
8 changes: 2 additions & 6 deletions src/sendWNS.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,8 @@ const { WNS_METHOD } = require('./constants');

const parseErrorMessage = (err) => (err instanceof Error ? err.message : err);
const parseError = (err) => {
if (err instanceof Error) {
return err;
}
if (err) {
return new Error(err);
}
if (err instanceof Error) return err;
if (err) return new Error(err);
return null;
};

Expand Down
6 changes: 3 additions & 3 deletions src/sendWeb.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
const webPush = require('web-push');
const { is, unless, assoc } = require('ramda');
const { WEB_METHOD } = require('./constants');

const stringify = unless(is(String), JSON.stringify);
const stringify = (data) =>
typeof data === 'string' ? data : JSON.stringify(data);

const sendWebPush = async (regIds, data, settings) => {
const payload = stringify(data);
Expand Down Expand Up @@ -39,7 +39,7 @@ const sendWebPush = async (regIds, data, settings) => {
failure: acc.failure + current.failure,
message: [...acc.message, ...current.message],
}));
return assoc('method', WEB_METHOD, reduced);
return { ...reduced, method: WEB_METHOD };
};

module.exports = sendWebPush;
10 changes: 2 additions & 8 deletions src/utils/fcmMessage.js
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,10 @@ class FcmMessage {
static normalizeDataParams(data) {
if (!data) return {};
return Object.entries(data).reduce((normalized, [key, value]) => {
if (value === undefined || value === null) {
return normalized;
}

if (value == null) return normalized;
const stringifyValue =
typeof value === 'string' ? value : JSON.stringify(value);

Object.assign(normalized, { [key]: stringifyValue });

return normalized;
return { ...normalized, [key]: stringifyValue };
}, {});
}

Expand Down
105 changes: 59 additions & 46 deletions src/utils/tools.js
Original file line number Diff line number Diff line change
@@ -1,50 +1,53 @@
const R = require('ramda');
const { Notification: ApnsMessage } = require('@parse/node-apn');
const { Message: GcmMessage } = require('node-gcm');

const { DEFAULT_TTL, GCM_MAX_TTL } = require('../constants');

const ttlFromExpiry = R.compose(
R.min(GCM_MAX_TTL),
R.max(0),
(expiry) => expiry - Math.floor(Date.now() / 1000)
);
const ttlFromExpiry = (expiry) => {
const ttl = expiry - Math.floor(Date.now() / 1000);
return Math.min(Math.max(ttl, 0), GCM_MAX_TTL);
};

const extractTimeToLive = R.cond([
[R.propIs(Number, 'expiry'), ({ expiry }) => ttlFromExpiry(expiry)],
[R.propIs(Number, 'timeToLive'), R.prop('timeToLive')],
[R.T, R.always(DEFAULT_TTL)],
]);
const extractTimeToLive = (data) => {
if (typeof data?.expiry === 'number') return ttlFromExpiry(data.expiry);
if (typeof data?.timeToLive === 'number') return data.timeToLive;
return DEFAULT_TTL;
};

const expiryFromTtl = (ttl) => ttl + Math.floor(Date.now() / 1000);

const extractExpiry = R.cond([
[R.propIs(Number, 'expiry'), R.prop('expiry')],
[
R.propIs(Number, 'timeToLive'),
({ timeToLive }) => expiryFromTtl(timeToLive),
],
[R.T, () => expiryFromTtl(DEFAULT_TTL)],
]);

const getPropValueOrUndefinedIfIsSilent = (propName, data) => {
if (data.silent) {
const extractExpiry = (data) => {
if (typeof data?.expiry === 'number') return data.expiry;
if (typeof data?.timeToLive === 'number')
return expiryFromTtl(data.timeToLive);
return expiryFromTtl(DEFAULT_TTL);
};

const getPropValueOrUndefinedIfIsSilent = (propName, data) =>
data.silent ? undefined : data[propName];

const toJSONorUndefined = (value) => {
if (typeof value !== 'string') {
return value;
}
try {
return JSON.parse(value);
} catch (e) {
return undefined;
}
return data[propName];
};

const toJSONorUndefined = R.when(
R.is(String),
R.tryCatch(JSON.parse, R.always(undefined))
);

const alertLocArgsToJSON = R.evolve({
alert: {
'title-loc-args': toJSONorUndefined,
'loc-args': toJSONorUndefined,
},
});
const alertLocArgsToJSON = (data) => {
const alert = data.alert ?? {};
return {
...data,
alert: {
...alert,
'title-loc-args': toJSONorUndefined(alert['title-loc-args']),
'loc-args': toJSONorUndefined(alert['loc-args']),
},
};
};

const getDefaultAlert = (data) => ({
title: data.title,
Expand All @@ -57,21 +60,31 @@ const getDefaultAlert = (data) => ({
action: data.action,
});

const alertOrDefault = (data) =>
R.when(
R.propSatisfies(R.isNil, 'alert'),
R.assoc('alert', getDefaultAlert(data))
);
const alertOrDefault = (data) => ({
...data,
alert: data.alert ?? getDefaultAlert(data),
});

const getParsedAlertOrDefault = (data) =>
R.pipe(alertOrDefault(data), alertLocArgsToJSON)(data);
const getParsedAlertOrDefault = (data) => {
const withAlert = alertOrDefault(data);
return alertLocArgsToJSON(withAlert);
};

const pathIsString = R.pathSatisfies(R.is(String));
const pathIsString = (path) => (val) => {
const current = path.reduce((acc, key) => {
if (acc && typeof acc === 'object') {
return acc[key];
}
return null;
}, val);
return typeof current === 'string';
};

const containsValidRecipients = R.either(
pathIsString(['recipients', 'to']),
pathIsString(['recipients', 'condition'])
);
const containsValidRecipients = (obj) => {
const checkTo = pathIsString(['recipients', 'to'])(obj);
const checkCondition = pathIsString(['recipients', 'condition'])(obj);
return checkTo || checkCondition;
};

const buildGcmNotification = (data) => {
const notification = data.fcm_notification || {
Expand Down