Push Notifications Improvements (#12019)

* start fixing push notitifications

* push notifications: refactor error handling

* remove comment and improve logging

* improve emails errors

* wip: start improving webhooks tests

* add max length to push notifications and tests

* fix typos
This commit is contained in:
Matteo Pagliazzi
2020-04-10 16:41:44 +02:00
committed by GitHub
parent 0a86d04a15
commit 2a8fc7aea2
3 changed files with 144 additions and 30 deletions

View File

@@ -5,25 +5,29 @@ import gcmLib from 'node-gcm'; // works with FCM notifications too
import logger from './logger';
const FCM_API_KEY = nconf.get('PUSH_CONFIGS_FCM_SERVER_API_KEY');
const fcmSender = FCM_API_KEY ? new gcmLib.Sender(FCM_API_KEY) : undefined;
let apnProvider;
// Load APN certificate and key from S3
const APN_ENABLED = nconf.get('PUSH_CONFIGS_APN_ENABLED') === 'true';
const apnProvider = APN_ENABLED ? new apn.Provider({
token: {
key: nconf.get('PUSH_CONFIGS_APN_KEY'),
keyId: nconf.get('PUSH_CONFIGS_APN_KEY_ID'),
teamId: nconf.get('PUSH_CONFIGS_APN_TEAM_ID'),
},
production: true,
}) : undefined;
if (APN_ENABLED) {
apnProvider = APN_ENABLED ? new apn.Provider({
token: {
key: nconf.get('PUSH_CONFIGS_APN_KEY'),
keyId: nconf.get('PUSH_CONFIGS_APN_KEY_ID'),
teamId: nconf.get('PUSH_CONFIGS_APN_TEAM_ID'),
},
production: true,
}) : undefined;
function removePushDevice (user, pushDevice) {
return user.update({
$pull: { pushDevices: { regId: pushDevice.regId } },
}).exec().catch(err => {
logger.error(err, `Error removing pushDevice ${pushDevice.regId} for user ${user._id}`);
});
}
function sendNotification (user, details = {}) {
export const MAX_MESSAGE_LENGTH = 300;
export function sendNotification (user, details = {}) {
if (!user) throw new Error('User is required.');
if (user.preferences.pushNotifications.unsubscribeFromAll === true) return;
const pushDevices = user.pushDevices.toObject ? user.pushDevices.toObject() : user.pushDevices;
@@ -35,6 +39,15 @@ function sendNotification (user, details = {}) {
const payload = details.payload ? details.payload : {};
payload.identifier = details.identifier;
// Cut the message to 300 characters to avoid going over the limit of 4kb per notifications
if (details.message.length > MAX_MESSAGE_LENGTH) {
details.message = _.truncate(details.message, { length: MAX_MESSAGE_LENGTH });
}
if (payload.message && payload.message.length > MAX_MESSAGE_LENGTH) {
payload.message = _.truncate(payload.message, { length: MAX_MESSAGE_LENGTH });
}
_.each(pushDevices, pushDevice => {
switch (pushDevice.type) { // eslint-disable-line default-case
case 'android':
@@ -49,8 +62,33 @@ function sendNotification (user, details = {}) {
fcmSender.send(message, {
registrationTokens: [pushDevice.regId],
}, 10, err => {
if (err) logger.error(err, 'FCM Error');
}, 5, (err, response) => {
if (err) {
logger.error(err, 'Unhandled FCM error.');
return;
}
// Handle failed push notifications deliveries
// Note that we're always sending to one device, not multiple
const failed = response
&& response.results && response.results[0] && response.results[0].error;
if (failed) {
// See https://firebase.google.com/docs/cloud-messaging/http-server-ref#table9
// for the list of errors
// The regId is not valid anymore, remove it
if (failed === 'NotRegistered') {
removePushDevice(user, pushDevice);
logger.error(new Error('FCM error, invalid pushDevice'), {
response, regId: pushDevice.regId, userId: user._id,
});
} else {
logger.error(new Error('FCM error'), {
response, regId: pushDevice.regId, userId: user._id,
});
}
}
});
}
break;
@@ -69,21 +107,32 @@ function sendNotification (user, details = {}) {
});
apnProvider.send(notification, pushDevice.regId)
.then(response => {
// Handle failed push notifications deliveries
response.failed.forEach(failure => {
if (failure.error) {
logger.error(new Error('APN error'), { failure });
} else {
logger.error(new Error('APN transmissionError'), { failure, notification });
if (failure.error) { // generic error
logger.error(new Error('APN error'), {
response, regId: pushDevice.regId, userId: user._id,
});
} else { // rejected
// see https://developer.apple.com/library/archive/documentation/NetworkingInternet/Conceptual/RemoteNotificationsPG/CommunicatingwithAPNs.html#//apple_ref/doc/uid/TP40008194-CH11-SW17
// for a list of rejection reasons
const { reason } = failure.response;
if (reason === 'Unregistered') {
removePushDevice(user, pushDevice);
logger.error(new Error('APN error, invalid pushDevice'), {
response, regId: pushDevice.regId, userId: user._id,
});
} else {
logger.error(new Error('APN error'), {
response, regId: pushDevice.regId, userId: user._id,
});
}
}
});
})
.catch(err => logger.error(err, 'APN error'));
.catch(err => logger.error(err, 'Unhandled APN error.'));
}
break;
}
});
}
export {
sendNotification, // eslint-disable-line import/prefer-default-export
};