This commit is contained in:
Marcos Rodriguez Velez 2024-11-11 17:45:21 -04:00
parent 0eb6b4f192
commit d29dd1567f
3 changed files with 111 additions and 107 deletions

View File

@ -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;

View File

@ -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);

View File

@ -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('<empty>');
const [URI, setURI] = useState<string | undefined>();
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' }}
/>
<Pressable onPress={handleTap}>