From 7a3c1ce5b6de1369bcfbd9e441ec6c0858994b76 Mon Sep 17 00:00:00 2001 From: david Date: Mon, 21 Oct 2019 20:23:06 +0200 Subject: [PATCH] Push --- .../notification/notification.controller.js | 95 ++++++++---- modules/notification/notification.service.js | 139 +++++++----------- .../notification/notification_detail.model.js | 2 +- .../notification_detail.service.js | 17 +++ modules/notification/user_device.service.js | 21 ++- 5 files changed, 157 insertions(+), 117 deletions(-) diff --git a/modules/notification/notification.controller.js b/modules/notification/notification.controller.js index 4e5646e..b676aaf 100644 --- a/modules/notification/notification.controller.js +++ b/modules/notification/notification.controller.js @@ -38,37 +38,70 @@ const extraControllers = { } try { - let getUserDevicesPromise = (userId) => userDeviceService.fetchAll({ params: { userId: userId }}, context); + let getUserDevicesPromise = (userId) => userDeviceService.fetchAll({ params: { userId: userId }}, context).then(function(result) { + return new Promise(function(resolve) { resolve(result.rows) }); + }); let saveNotificationPromise = (notification) => notificationService.create(notification, context); let sendNotificationsPromise = (messages) => notificationService.sendNotification(messages); - let disableUserDevicePromise = (userDevice) => userDeviceService.update({ - userId: userDevice.userId, - token: userDevice.token, - }, { valid: false }, context); + let disableUserDevicePromise = (token) => userDeviceService.update({ params: { + token: token, + }}, { valid: false }, context); + + let disableInvalidUserDevicesPromise = (userDevices) => { + return new Promise(function (resolve) { + let _userDevices = []; + userDevices.forEach(async function (userDevice) { + if (!userDeviceService.isValidPushToken(userDevice.token)) { + await disableUserDevicePromise(userDevice.token); + } else { + _userDevices.push(userDevice); + } + }); + + resolve(_userDevices) + }); + }; + + let disableUserDevicesWithErrorStatus = (messages, tickets) => { + return new Promise(function (resolve) { + tickets.forEach(async function (ticket, index) { + if ((ticket.status === 'error') && (ticket.details.error === 'DeviceNotRegistered')) { + await disableUserDevicePromise(messages[index].to) + } + }); + resolve(true); + }); + } + + let saveResponseStatusPromise = (messages, tickets) => notificationDetailService.saveNotificationDetails(messages, tickets); const notificationRecord = { title: req.body.title, body: req.body.message, - ttl: req.body.ttl, - priority: req.body.priority, - data: req.body.data || req.body.userIds, + ttl: req.body.ttl || undefined, + priority: req.body.priority || 'default', + data: req.body.data || { userIds: req.body.userIds }, }; - let buildMessagePromise = async (userDevices) => { - let message = undefined; - userDevices.rows.forEach(async function (userDevice) { - if (notificationService.isValidPushToken(userDevice.token)) { - message = { - ...notificationRecord, + let buildMessagesPromise = (userDevices) => { + return new Promise(function (resolve) { + let messages = []; + + userDevices.forEach(async function (userDevice) { + messages.push({ + title: notificationRecord.title, + body: notificationRecord.body, + ttl: notificationRecord.ttl, + priority: notificationRecord.priority, userId: userDevice.userId, to: userDevice.token, sound: 'default', - }; - } else { - await disableUserDevicePromise(userDevice); - } + notificationId: notificationRecord.id, + }); + }); + + resolve(messages) }); - return new Promise(function(resolve) { resolve(message) }); }; let getUserDevicesList = []; @@ -77,15 +110,27 @@ const extraControllers = { getUserDevicesList.push(getUserDevicesPromise(userId)); }); - receipt = await saveNotificationPromise(notificationRecord) - .then(function() { + saveNotificationPromise(notificationRecord) + .then(function(notification) { + notificationRecord.id = notification.id; return Promise.all(getUserDevicesList) - }).then(function(userDeviceList) { - return Promise.all(userDeviceList.map(buildMessagePromise)) - }).then(sendNotificationsPromise) - + }).then(function (userDeviceList) { + return new Promise(function(resolve) { resolve(userDeviceList[0]); }); + }) + .then(disableInvalidUserDevicesPromise) + .then(buildMessagesPromise) + .then(sendNotificationsPromise) + .then(function({ messages, tickets }) { + let _saveStatus = saveResponseStatusPromise(messages, tickets); + let _disableDevices = disableUserDevicesWithErrorStatus(messages, tickets); + return Promise.all([_saveStatus, _disableDevices]); + }) + .then(function (details) { + console.log(details); + }); } catch (error) { + console.error(error); return handleErrorResponse(controllerOptions.MODULE_NAME, 'sendNotification', error, res) } finally { return handleResultResponse(receipt, null, null, res, httpStatus.OK); diff --git a/modules/notification/notification.service.js b/modules/notification/notification.service.js index 06c3a14..b3ec254 100644 --- a/modules/notification/notification.service.js +++ b/modules/notification/notification.service.js @@ -8,10 +8,6 @@ const expo = new Expo(); const extraMethods = { - isValidPushToken: (token) => { - return Expo.isExpoPushToken(token); - }, - sendNotification: async (messages) => { // The Expo push notification service accepts batches of notifications so @@ -43,33 +39,10 @@ const extraMethods = { // Apple and Google so you can handle it appropriately. let chunks = expo.chunkPushNotifications(messages); - let tickets = await _sendPushNotificationsAsync(chunks); - - console.log(tickets); - let receiptIds = []; - let invalidTokens = []; - for (let [key, ticket] of tickets.entries()) { - // NOTE: Not all tickets have IDs; for example, tickets for notifications - // that could not be enqueued will have error information and no receipt ID. - if (ticket.id) { - receiptIds.push(ticket); - } else { - if ((ticket.status === 'error') && (ticket.details.error === 'DeviceNotRegistered')) { - invalidTokens.push({ - ...messages[key], - valid: false, - invalidated: moment(), - }); - } - } - } - - console.log(receiptIds); - console.log(invalidTokens); - - let notifications = await _saveNotifications(messages, tickets); - return new Promise(function (resolve) { resolve(notifications) }); - + return { + messages, + tickets: await _sendPushNotificationsAsync(chunks) + }; }, getNotificationsWithoutReceipt: async() => { @@ -88,76 +61,66 @@ module.exports = generateService(models.Notification, extraMethods); const _sendPushNotificationsAsync = async function (chunks) { - let tickets = []; - // Send the chunks to the Expo push notification service. There are - // different strategies you could use. A simple one is to send one chunk at a - // time, which nicely spreads the load out over time: - for (let chunk of chunks) { - try { - let ticketChunk = await expo.sendPushNotificationsAsync(chunk); - tickets.push(...ticketChunk); + return new Promise(async function (resolve) { + let tickets = []; + // Send the chunks to the Expo push notification service. There are + // different strategies you could use. A simple one is to send one chunk at a + // time, which nicely spreads the load out over time: + for (let chunk of chunks) { + try { + let ticketChunk = await expo.sendPushNotificationsAsync(chunk); + tickets.push(...ticketChunk); - // NOTE: If a ticket contains an error code in ticket.details.error, you - // must handle it appropriately. The error codes are listed in the Expo - // documentation: - // https://docs.expo.io/versions/latest/guides/push-notifications#response-format + // NOTE: If a ticket contains an error code in ticket.details.error, you + // must handle it appropriately. The error codes are listed in the Expo + // documentation: + // https://docs.expo.io/versions/latest/guides/push-notifications#response-format - } catch (error) { - console.error(error); + } catch (error) { + console.error(error); + } } - } - return new Promise(function (resolve) { resolve(tickets) }); + resolve(tickets); + }); }; const _getPushNotificationsResultAsync = async function (receiptIdChunks) { - // Like sending notifications, there are different strategies you could use - // to retrieve batches of receipts from the Expo service. + return new Promise(async function (resolve) { + // Like sending notifications, there are different strategies you could use + // to retrieve batches of receipts from the Expo service. - let result = []; + let result = []; - console.log(receiptIdChunks); + console.log(receiptIdChunks); - for (let chunk of receiptIdChunks) { - try { - let receipts = await expo.getPushNotificationReceiptsAsync(chunk); - console.log('hola', receipts); + for (let chunk of receiptIdChunks) { + try { + let receipts = await expo.getPushNotificationReceiptsAsync(chunk); + console.log('hola', receipts); - // The receipts specify whether Apple or Google successfully received the - // notification and information about an error, if one occurred. - for (let key in receipts) { - if (receipts[key].status === 'ok') { - result.push[receipts[key]]; - continue; - } else if (receipts[key].status === 'error') { - console.error(`There was an error sending a notification: ${receipts[key].message}`); - if (receipts[key].details && receipts[key].details.error) { - // The error codes are listed in the Expo documentation: - // https://docs.expo.io/versions/latest/guides/push-notifications#response-format - // You must handle the errors appropriately. - console.error(`The error code is ${receipts[key].details.error}`); + // The receipts specify whether Apple or Google successfully received the + // notification and information about an error, if one occurred. + for (let key in receipts) { + if (receipts[key].status === 'ok') { + result.push[receipts[key]]; + continue; + } else if (receipts[key].status === 'error') { + console.error(`There was an error sending a notification: ${receipts[key].message}`); + if (receipts[key].details && receipts[key].details.error) { + // The error codes are listed in the Expo documentation: + // https://docs.expo.io/versions/latest/guides/push-notifications#response-format + // You must handle the errors appropriately. + console.error(`The error code is ${receipts[key].details.error}`); + } } - } - } - } catch (error) { - console.error(error); + } + } catch (error) { + console.error(error); + } } - } - return new Promise(function (resolve) { resolve(result) }); + resolve(result); + }); } -const _saveNotifications = async function (messages, tickets) { - let notifications = []; - messages.forEach(function (message, index) { - let notification = models.NotificationDetail.build({ - ...message, - ticket: tickets[index].id, - status: tickets[index].status, - error: (tickets[index].status === 'error') ? tickets[index].details.error : undefined, - }); - notifications.push(notification); - }); - - return new Promise(function (resolve) { resolve(notifications) }); -} \ No newline at end of file diff --git a/modules/notification/notification_detail.model.js b/modules/notification/notification_detail.model.js index cb577a9..63a45f7 100644 --- a/modules/notification/notification_detail.model.js +++ b/modules/notification/notification_detail.model.js @@ -32,7 +32,7 @@ module.exports = function (sequelize, DataTypes) { }, ticket: { type: DataTypes.STRING, - allowNull: false, + allowNull: true, }, deliveryDate: { type: DataTypes.DATE, diff --git a/modules/notification/notification_detail.service.js b/modules/notification/notification_detail.service.js index 137e8e7..3606f99 100644 --- a/modules/notification/notification_detail.service.js +++ b/modules/notification/notification_detail.service.js @@ -4,6 +4,23 @@ const { generateService, parseParamsToFindOptions } = require('../../helpers/ser const models = require('../../core/models'); const extraMethods = { + saveNotificationDetails: async function (messages, tickets) { + return new Promise(function (resolve) { + messages.forEach(async function (message, index) { + let notification = models.NotificationDetail.build({ + ...message, + token: message.to, + ticket: tickets[index].id ? tickets[index].id : undefined, + deliveryDate: moment(), + deliveryStatus: (tickets[index].status === 'error') ? tickets[index].details.error : tickets[index].status, + notificationId: message.notificationId, + }); + await notification.save(); + }); + + resolve(true); + }); + } }; diff --git a/modules/notification/user_device.service.js b/modules/notification/user_device.service.js index 1a903e8..3f5efd1 100644 --- a/modules/notification/user_device.service.js +++ b/modules/notification/user_device.service.js @@ -3,10 +3,25 @@ const { Expo } = require('expo-server-sdk'); const { generateService, parseParamsToFindOptions } = require('../../helpers/service.helper'); const models = require('../../core/models'); -// Create a new Expo SDK client -const expo = new Expo(); - const extraMethods = { + isValidPushToken: (token) => { + return Expo.isExpoPushToken(token); + }, + + afterFetchAll: (result, params, context) => { + + if (!result.count) { + return result; + } + + let rows = result.rows.map(row => row.toJSON()); + + return { + count: result.count, + rows: rows + } + }, + getPushToken: (params) => { return models.UserDevice.findOne({