From d29dd1567f2feaf6eb159c64c5720fede266b776 Mon Sep 17 00:00:00 2001 From: Marcos Rodriguez Velez Date: Mon, 11 Nov 2024 17:45:21 -0400 Subject: [PATCH] wip --- blue_modules/notifications.js | 170 +++++++++++------------ components/CompanionDelegates.tsx | 23 +-- screen/settings/NotificationSettings.tsx | 25 ++-- 3 files changed, 111 insertions(+), 107 deletions(-) diff --git a/blue_modules/notifications.js b/blue_modules/notifications.js index b5419b348..e5d6c899a 100644 --- a/blue_modules/notifications.js +++ b/blue_modules/notifications.js @@ -160,10 +160,6 @@ function Notifications(props) { }); }; - const _sleep = async ms => { - return new Promise(resolve => setTimeout(resolve, ms)); - }; - /** * Submits onchain bitcoin addresses and ln invoice preimage hashes to GroundControl server, so later we could * be notified if they were paid @@ -224,11 +220,6 @@ function Notifications(props) { } }; - Notifications.isNotificationsEnabled = async () => { - const levels = await getLevels(); - return !!(await getPushToken()) && !!levels.level_all; - }; - /** * Validates whether the provided GroundControl URI is valid by pinging it. * @@ -289,55 +280,6 @@ function Notifications(props) { } catch (_) {} }; - /** - * Queries groundcontrol for token configuration, which contains subscriptions to notification levels - * - * @returns {Promise<{}|*>} - */ - const getLevels = async () => { - const pushToken = await getPushToken(); - if (!pushToken || !pushToken.token || !pushToken.os) return; - - let response; - try { - response = await Promise.race([ - fetch(`${baseURI}/getTokenConfiguration`, { - method: 'POST', - headers: _getHeaders(), - body: JSON.stringify({ - token: pushToken.token, - os: pushToken.os, - }), - }), - _sleep(3000), - ]); - } catch (_) {} - - if (!response) return {}; - - return await response.json(); - }; - - Notifications.getStoredNotifications = async () => { - let notifications = []; - try { - const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE); - notifications = JSON.parse(stringified); - if (!Array.isArray(notifications)) notifications = []; - } catch (e) { - if (e instanceof SyntaxError) { - console.error('Invalid notifications format:', e); - notifications = []; - await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, '[]'); - } else { - console.error('Error accessing notifications:', e); - throw e; - } - } - - return notifications; - }; - Notifications.addNotification = async notification => { let notifications = []; try { @@ -379,30 +321,6 @@ function Notifications(props) { } }; - Notifications.clearStoredNotifications = async () => { - try { - await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, JSON.stringify([])); - } catch (_) {} - }; - - Notifications.getDeliveredNotifications = () => { - return new Promise(resolve => { - PushNotification.getDeliveredNotifications(notifications => resolve(notifications)); - }); - }; - - Notifications.removeDeliveredNotifications = (identifiers = []) => { - PushNotification.removeDeliveredNotifications(identifiers); - }; - - Notifications.setApplicationIconBadgeNumber = function (badges) { - PushNotification.setApplicationIconBadgeNumber(badges); - }; - - Notifications.removeAllDeliveredNotifications = () => { - PushNotification.removeAllDeliveredNotifications(); - }; - // on app launch (load module): (async () => { // first, fetching to see if app uses custom GroundControl server, not the default one @@ -420,7 +338,7 @@ function Notifications(props) { } // every launch should clear badges: - Notifications.setApplicationIconBadgeNumber(0); + setApplicationIconBadgeNumber(0); if (!(await getPushToken())) return; // if we previously had token that means we already acquired permission from the user and it is safe to call @@ -431,6 +349,10 @@ function Notifications(props) { return null; } +const _sleep = async ms => { + return new Promise(resolve => setTimeout(resolve, ms)); +}; + export const isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android'; export const getPushToken = async () => { @@ -445,6 +367,35 @@ export const getPushToken = async () => { } }; +/** + * Queries groundcontrol for token configuration, which contains subscriptions to notification levels + * + * @returns {Promise<{}|*>} + */ +const getLevels = async () => { + const pushToken = await getPushToken(); + if (!pushToken || !pushToken.token || !pushToken.os) return; + + let response; + try { + response = await Promise.race([ + fetch(`${baseURI}/getTokenConfiguration`, { + method: 'POST', + headers: _getHeaders(), + body: JSON.stringify({ + token: pushToken.token, + os: pushToken.os, + }), + }), + _sleep(3000), + ]); + } catch (_) {} + + if (!response) return {}; + + return await response.json(); +}; + /** * The opposite of `majorTomToGroundControl` call. * @@ -491,12 +442,36 @@ export const unsubscribe = async (addresses, hashes, txids) => { } }; -function _getHeaders() { +const _getHeaders = () => { return { 'Access-Control-Allow-Origin': '*', 'Content-Type': 'application/json', }; -} +}; + +export const clearStoredNotifications = async () => { + try { + await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, JSON.stringify([])); + } catch (_) {} +}; + +export const getDeliveredNotifications = () => { + return new Promise(resolve => { + PushNotification.getDeliveredNotifications(notifications => resolve(notifications)); + }); +}; + +export const removeDeliveredNotifications = (identifiers = []) => { + PushNotification.removeDeliveredNotifications(identifiers); +}; + +export const setApplicationIconBadgeNumber = function (badges) { + PushNotification.setApplicationIconBadgeNumber(badges); +}; + +export const removeAllDeliveredNotifications = () => { + PushNotification.removeAllDeliveredNotifications(); +}; export const getDefaultUri = () => { return groundControlUri; @@ -529,4 +504,29 @@ export const getSavedUri = async () => { throw e; } }; + +export const isNotificationsEnabled = async () => { + const levels = await getLevels(); + return !!(await getPushToken()) && !!levels.level_all; +}; + +export const getStoredNotifications = async () => { + let notifications = []; + try { + const stringified = await AsyncStorage.getItem(NOTIFICATIONS_STORAGE); + notifications = JSON.parse(stringified); + if (!Array.isArray(notifications)) notifications = []; + } catch (e) { + if (e instanceof SyntaxError) { + console.error('Invalid notifications format:', e); + notifications = []; + await AsyncStorage.setItem(NOTIFICATIONS_STORAGE, '[]'); + } else { + console.error('Error accessing notifications:', e); + throw e; + } + } + + return notifications; +}; export default Notifications; diff --git a/components/CompanionDelegates.tsx b/components/CompanionDelegates.tsx index e9a8079e6..25c063c90 100644 --- a/components/CompanionDelegates.tsx +++ b/components/CompanionDelegates.tsx @@ -7,7 +7,13 @@ import A from '../blue_modules/analytics'; import { getClipboardContent } from '../blue_modules/clipboard'; import { updateExchangeRate } from '../blue_modules/currency'; import triggerHapticFeedback, { HapticFeedbackTypes } from '../blue_modules/hapticFeedback'; -import Notifications from '../blue_modules/notifications'; +import Notifications, { + clearStoredNotifications, + getDeliveredNotifications, + getStoredNotifications, + removeAllDeliveredNotifications, + setApplicationIconBadgeNumber, +} from '../blue_modules/notifications'; import { LightningCustodianWallet } from '../class'; import DeeplinkSchemaMatch from '../class/deeplink-schema-match'; import loc from '../loc'; @@ -43,16 +49,11 @@ const CompanionDelegates = () => { const processPushNotifications = useCallback(async () => { await new Promise(resolve => setTimeout(resolve, 200)); - // @ts-ignore: Notifications type is not defined - const notifications2process = await Notifications.getStoredNotifications(); - // @ts-ignore: Notifications type is not defined - await Notifications.clearStoredNotifications(); - // @ts-ignore: Notifications type is not defined - Notifications.setApplicationIconBadgeNumber(0); - // @ts-ignore: Notifications type is not defined - const deliveredNotifications = await Notifications.getDeliveredNotifications(); - // @ts-ignore: Notifications type is not defined - setTimeout(() => Notifications.removeAllDeliveredNotifications(), 5000); + const notifications2process = await getStoredNotifications(); + await clearStoredNotifications(); + setApplicationIconBadgeNumber(0); + const deliveredNotifications = await getDeliveredNotifications(); + setTimeout(() => removeAllDeliveredNotifications(), 5000); for (const payload of notifications2process) { const wasTapped = payload.foreground === false || (payload.foreground === true && payload.userInteraction); diff --git a/screen/settings/NotificationSettings.tsx b/screen/settings/NotificationSettings.tsx index 90e770e36..5f2da36c4 100644 --- a/screen/settings/NotificationSettings.tsx +++ b/screen/settings/NotificationSettings.tsx @@ -2,7 +2,14 @@ import React, { useCallback, useEffect, useState } from 'react'; import { I18nManager, Linking, ScrollView, StyleSheet, TextInput, View, Pressable } from 'react-native'; import { Button as ButtonRNElements } from '@rneui/themed'; // @ts-ignore: no declaration file -import Notifications, { getDefaultUri, getPushToken, getSavedUri, saveUri } from '../../blue_modules/notifications'; +import Notifications, { + getDefaultUri, + getPushToken, + getSavedUri, + getStoredNotifications, + saveUri, + isNotificationsEnabled, +} from '../../blue_modules/notifications'; import { BlueCard, BlueSpacing20, BlueSpacing40, BlueText } from '../../BlueComponents'; import presentAlert from '../../components/Alert'; import { Button } from '../../components/Button'; @@ -15,7 +22,7 @@ import { openSettings } from 'react-native-permissions'; const NotificationSettings: React.FC = () => { const [isLoading, setIsLoading] = useState(true); - const [isNotificationsEnabled, setNotificationsEnabled] = useState(false); + const [isNotificationsEnabledState, setNotificationsEnabledState] = useState(false); const [tokenInfo, setTokenInfo] = useState(''); const [URI, setURI] = useState(); const [tapCount, setTapCount] = useState(0); @@ -43,7 +50,7 @@ const NotificationSettings: React.FC = () => { const onNotificationsSwitch = async (value: boolean) => { try { - setNotificationsEnabled(value); + setNotificationsEnabledState(value); if (value) { // User is enabling notifications // @ts-ignore: refactor later @@ -63,8 +70,7 @@ const NotificationSettings: React.FC = () => { await Notifications.setLevels(false); } - // @ts-ignore: refactor later - setNotificationsEnabled(await Notifications.isNotificationsEnabled()); + setNotificationsEnabledState(await isNotificationsEnabled()); } catch (error) { console.error(error); presentAlert({ message: (error as Error).message }); @@ -74,20 +80,17 @@ const NotificationSettings: React.FC = () => { useEffect(() => { (async () => { try { - // @ts-ignore: refactor later - setNotificationsEnabled(await Notifications.isNotificationsEnabled()); + setNotificationsEnabledState(await isNotificationsEnabled()); setURI((await getSavedUri()) ?? getDefaultUri()); // @ts-ignore: refactor later setTokenInfo( 'token: ' + - // @ts-ignore: refactor later JSON.stringify(await getPushToken()) + ' permissions: ' + // @ts-ignore: refactor later JSON.stringify(await Notifications.checkPermissions()) + ' stored notifications: ' + - // @ts-ignore: refactor later - JSON.stringify(await Notifications.getStoredNotifications()), + JSON.stringify(await getStoredNotifications()), ); } catch (e) { console.error(e); @@ -131,7 +134,7 @@ const NotificationSettings: React.FC = () => { title={loc.settings.notifications} subtitle={loc.notifications.notifications_subtitle} disabled={isLoading} - switch={{ onValueChange: onNotificationsSwitch, value: isNotificationsEnabled, testID: 'NotificationsSwitch' }} + switch={{ onValueChange: onNotificationsSwitch, value: isNotificationsEnabledState, testID: 'NotificationsSwitch' }} />