REF: processing push notifications

This commit is contained in:
Overtorment 2021-01-01 19:15:40 +00:00
parent fe39c957fc
commit 81e8fcc85f
3 changed files with 58 additions and 66 deletions

78
App.js
View File

@ -66,26 +66,16 @@ const App = () => {
},
});
const fetchWalletTransactionsInReceivedNotification = notification => {
console.log('Received notification that has NOT been acted upon, fetch TXs...');
const onNotificationReceived = async notification => {
const payload = Object.assign({}, notification, notification.data);
if (notification.data && notification.data.data) Object.assign(payload, notification.data.data);
delete payload.data;
let wallet;
switch (+payload.type) {
case 2:
case 3:
wallet = wallets.find(w => w.weOwnAddress(payload.address));
break;
case 1:
case 4:
wallet = wallets.find(w => w.weOwnTransaction(payload.txid || payload.hash));
break;
}
if (wallet) {
const walletID = wallet.getID();
fetchAndSaveWalletTransactions(walletID);
}
payload.foreground = true;
payload.userInteraction = false;
await Notifications.addNotification(payload);
// if user is staring at the app when he receives the notification we process it instantly
// so app refetches related wallet
if (payload.foreground) await processPushNotifications();
};
const openSettings = () => {
@ -130,7 +120,11 @@ const App = () => {
DeviceEventEmitter.addListener('quickActionShortcut', walletQuickActions);
QuickActions.popInitialAction().then(popInitialAction);
handleAppStateChange(undefined);
eventEmitter.addListener('onNotificationReceived', fetchWalletTransactionsInReceivedNotification);
/*
When a notification on iOS is shown while the app is on foreground;
On willPresent on AppDelegate.m
*/
eventEmitter.addListener('onNotificationReceived', onNotificationReceived);
eventEmitter.addListener('openSettings', openSettings);
};
@ -199,15 +193,13 @@ const App = () => {
await new Promise(resolve => setTimeout(resolve, 200));
// sleep needed as sometimes unsuspend is faster than notification module actually saves notifications to async storage
const notifications2process = await Notifications.getStoredNotifications();
/* notifications2process uses AsyncStorage and it might be empty. deliveredNotifications will
return notifications that are still in the Notification Center and have not processed.
Useful for when app comes back from background.
*/
await Notifications.clearStoredNotifications();
Notifications.setApplicationIconBadgeNumber(0);
const deliveredNotifications = await Notifications.getDeliveredNotifications();
// Set will remove duplicates.
const notificationsSet = Array.from(new Set(notifications2process.concat(deliveredNotifications)));
console.log(notificationsSet);
for (const payload of notificationsSet) {
setTimeout(() => Notifications.removeAllDeliveredNotifications(), 5000); // so notification bubble wont disappear too fast
for (const payload of notifications2process) {
const wasTapped = payload.foreground === false || (payload.foreground === true && payload.userInteraction === true);
console.log('processing push notification:', payload);
@ -237,35 +229,21 @@ const App = () => {
},
}),
);
try {
Notifications.removeDeliveredNotifications([payload.identifier]);
} catch (e) {
Notifications.removeAllDeliveredNotifications();
}
Notifications.setApplicationIconBadgeNumber(0);
return true;
}
// no delay (1ms) as we don't need to wait for transaction propagation. 500ms is a delay to wait for the navigation
await Notifications.clearStoredNotifications();
return true;
} else {
console.log('could not find wallet while processing push notification tap, NOP');
try {
Notifications.removeDeliveredNotifications([payload.identifier]);
} catch (e) {
Notifications.removeAllDeliveredNotifications();
Notifications.setApplicationIconBadgeNumber(0);
}
console.log('could not find wallet while processing push notification, NOP');
}
}
} // end foreach notifications loop
// TODO: if we are here - we did not act upon any push, so we need to iterate over _not tapped_ pushes
// and refetch appropriate wallet and redraw screen
if (notificationsSet.length > 0) {
// notification object is missing userInfo. We know we received a notification but don't have sufficient data to refresh 1 wallet. let's refresh all.
if (deliveredNotifications.length > 0) {
// notification object is missing userInfo. We know we received a notification but don't have sufficient
// data to refresh 1 wallet. let's refresh all.
refreshAllWalletTransactions();
}
await Notifications.clearStoredNotifications();
// if we are here - we did not act upon any push
return false;
};

View File

@ -54,25 +54,19 @@ function Notifications(props) {
//
// ...we save notification in internal notifications queue thats gona be processed later (on unsuspend with decrypted storage)
setTimeout(() => props.onProcessNotifications(), 500);
const payload = Object.assign({}, notification, notification.data);
if (notification.data && notification.data.data) Object.assign(payload, notification.data.data);
delete payload.data;
// ^^^ weird, but sometimes payload data is not in `data` but in root level
let notifications = [];
try {
const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE);
notifications = JSON.parse(stringified);
if (!Array.isArray(notifications)) notifications = [];
const payload = Object.assign({}, notification, notification.data);
if (notification.data && notification.data.data) Object.assign(payload, notification.data.data);
delete payload.data;
// ^^^ weird, but sometimes payload data is not in `data` but in root level
notifications.push(payload);
await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, JSON.stringify(notifications));
} catch (_) {}
await Notifications.addNotification(payload);
// (required) Called when a remote is received or opened, or local notification is opened
notification.finish(PushNotificationIOS.FetchResult.NoData);
// if user is staring at the app when he receives the notification we process it instantly
// so app refetches related wallet
if (payload.foreground) props.onProcessNotifications();
},
// (optional) Called when Registered Action is pressed and invokeApp is false, if true onNotification will be called (Android)
@ -357,6 +351,18 @@ function Notifications(props) {
return notifications;
};
Notifications.addNotification = async function (notification) {
let notifications = [];
try {
const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE);
notifications = JSON.parse(stringified);
if (!Array.isArray(notifications)) notifications = [];
} catch (_) {}
notifications.push(notification);
await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, JSON.stringify(notifications));
};
const postTokenConfig = async function () {
const pushToken = await Notifications.getPushToken();
if (!pushToken || !pushToken.token || !pushToken.os) return;

View File

@ -5,6 +5,8 @@ import { AppStorage } from '../class';
const BlueApp = require('../BlueApp');
const BlueElectrum = require('./BlueElectrum');
const _lastTimeTriedToRefetchWallet = {}; // hashmap of timestamps we _started_ refetching some wallet
export const BlueStorageContext = createContext();
export const BlueStorageProvider = ({ children }) => {
const [wallets, setWallets] = useState([]);
@ -83,7 +85,13 @@ export const BlueStorageProvider = ({ children }) => {
const index = wallets.findIndex(wallet => wallet.getID() === walletID);
let noErr = true;
try {
// await BlueElectrum.ping();
// 5sec debounce:
if (+new Date() - _lastTimeTriedToRefetchWallet[walletID] < 5000) {
console.log('re-fetch wallet happens too fast; NOP');
return;
}
_lastTimeTriedToRefetchWallet[walletID] = +new Date();
await BlueElectrum.waitTillConnected();
const balanceStart = +new Date();
await fetchWalletBalances(index);