Compare commits

...

2 Commits

Author SHA1 Message Date
Cursor Agent
be07656f3a
REF: remove GroundControl server URI saving from notifications module
Co-authored-by: Overtorment <Overtorment@users.noreply.github.com>
2026-06-09 20:24:56 +00:00
Cursor Agent
f77e566ed9
REF: remove GroundControl server URL option from notification settings
Co-authored-by: Overtorment <Overtorment@users.noreply.github.com>
2026-06-09 18:36:25 +00:00
4 changed files with 3 additions and 156 deletions

View File

@ -13,11 +13,10 @@ import { groundControlUri } from './constants';
import { fetch } from '../util/fetch';
const PUSH_TOKEN = 'PUSH_TOKEN';
const GROUNDCONTROL_BASE_URI = 'GROUNDCONTROL_BASE_URI';
const NOTIFICATIONS_STORAGE = 'NOTIFICATIONS_STORAGE';
const ANDROID_NOTIFICATION_CHANNEL_ID = 'channel_01';
export const NOTIFICATIONS_NO_AND_DONT_ASK_FLAG = 'NOTIFICATIONS_NO_AND_DONT_ASK_FLAG';
let baseURI = groundControlUri;
const baseURI = groundControlUri;
let notificationSubscriptions: EmitterSubscription[] = [];
let onProcessNotificationsHandler: undefined | (() => void | Promise<void>);
const handledNotificationKeys = new Set<string>();
@ -529,22 +528,6 @@ const configureNotifications = async (onProcessNotifications?: () => void): Prom
}
};
/**
* Validates whether the provided GroundControl URI is valid by pinging it.
*
* @param uri {string}
* @returns {Promise<boolean>} TRUE if valid, FALSE otherwise
*/
export const isGroundControlUriValid = async (uri: string) => {
try {
const response = await fetch(`${uri}/ping`, { headers: _getHeaders() });
const json = await response.json();
return !!json.description;
} catch (_) {
return false;
}
};
export const isNotificationsCapable = hasGmsSync() || hasHmsSync() || Platform.OS !== 'android';
export const getPushToken = async (): Promise<TPushToken> => {
@ -676,38 +659,6 @@ export const removeAllDeliveredNotifications = () => {
Notifications.removeAllDeliveredNotifications();
};
export const getDefaultUri = () => {
return groundControlUri;
};
export const saveUri = async (uri: string) => {
try {
baseURI = uri || groundControlUri;
await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, baseURI);
} catch (error) {
console.error('Error saving URI:', error);
throw error;
}
};
export const getSavedUri = async () => {
try {
const baseUriStored = await AsyncStorage.getItem(GROUNDCONTROL_BASE_URI);
if (baseUriStored) {
baseURI = baseUriStored;
}
return baseUriStored;
} catch (e) {
console.error(e);
try {
await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, groundControlUri);
} catch (storageError) {
console.error('Failed to reset URI:', storageError);
}
throw e;
}
};
export const isNotificationsEnabled = async () => {
try {
const levels = await getLevels();
@ -757,10 +708,6 @@ export const initializeNotifications = async (onProcessNotifications?: () => voi
return;
}
const baseUriStored = await AsyncStorage.getItem(GROUNDCONTROL_BASE_URI);
baseURI = baseUriStored || groundControlUri;
console.log('Base URI set to:', baseURI);
setApplicationIconBadgeNumber(0);
// Only check permissions, never request
@ -781,7 +728,5 @@ export const initializeNotifications = async (onProcessNotifications?: () => voi
}
} catch (error) {
console.error('Failed to initialize notifications:', error);
baseURI = groundControlUri;
await AsyncStorage.setItem(GROUNDCONTROL_BASE_URI, groundControlUri).catch(err => console.error('Failed to reset URI:', err));
}
};

View File

@ -288,7 +288,6 @@
"general": "General",
"general_continuity": "Continuity",
"general_continuity_e": "When enabled, you will be able to view selected wallets, and transactions, using your other Apple iCloud connected devices.",
"groundcontrol_explanation": "GroundControl is a free, open-source push notifications server for Bitcoin wallets. You can install your own GroundControl server and put its URL here to not rely on BlueWallets infrastructure. Leave blank to use GroundControls default server.",
"header": "Settings",
"language": "Language",
"last_updated": "Last Updated",
@ -304,7 +303,6 @@
"network_broadcast": "Broadcast Transaction",
"network_electrum": "Electrum Server",
"electrum_suggested_description": "When a preferred server is not set, a suggested server will be selected for use at random.",
"not_a_valid_uri": "Invalid URI",
"notifications": "Notifications",
"open_link_in_explorer": "Open link in explorer",
"password": "Password",
@ -322,7 +320,6 @@
"push_notifications_explanation": "By enabling notifications, your device token will be sent to the server, along with wallet addresses and transaction IDs for all wallets and transactions made after enabling notifications. The device token is used to send notifications, and the wallet information allows us to notify you about incoming Bitcoin or transaction confirmations.\n\nOnly information from after you enable notifications is transmitted—nothing from before is collected.\n\nDisabling notifications will remove all of this information from the server. Additionally, deleting a wallet from the app will also remove its associated information from the server.",
"selfTest": "Self-Test",
"save": "Save",
"saved": "Saved",
"success_transaction_broadcasted": "Your transaction has been successfully broadcasted!",
"total_balance": "Total Balance",
"total_balance_explanation": "Display the total balance of all your wallets on your home screen widgets.",

View File

@ -1,23 +1,17 @@
import React, { useCallback, useEffect, useState } from 'react';
import { Linking, StyleSheet, TextInput, View, Pressable, AppState, Text } from 'react-native';
import { Linking, StyleSheet, View, Pressable, AppState, Text } from 'react-native';
import {
getDefaultUri,
getPushToken,
getSavedUri,
getStoredNotifications,
saveUri,
isNotificationsEnabled,
setLevels,
tryToObtainPermissions,
cleanUserOptOutFlag,
isGroundControlUriValid,
checkPermissions,
checkNotificationPermissionStatus,
NOTIFICATIONS_NO_AND_DONT_ASK_FLAG,
} from '../../blue_modules/notifications';
import { BlueSpacing20 } from '../../components/BlueSpacing';
import presentAlert from '../../components/Alert';
import { Button } from '../../components/Button';
import CopyToClipboardButton from '../../components/CopyToClipboardButton';
import { useTheme } from '../../components/themes';
import loc from '../../loc';
@ -43,7 +37,6 @@ const NotificationSettings: React.FC = () => {
const [isNotificationsEnabledState, setNotificationsEnabledState] = useState<boolean | undefined>(undefined);
const [tokenInfo, setTokenInfo] = useState('<empty>');
const [URI, setURI] = useState<string | undefined>();
const [tapCount, setTapCount] = useState(0);
const { colors } = useTheme();
@ -139,7 +132,6 @@ const NotificationSettings: React.FC = () => {
await updateNotificationStatus();
}
setURI((await getSavedUri()) ?? getDefaultUri());
setTokenInfo(
'token: ' +
JSON.stringify(await getPushToken()) +
@ -172,37 +164,12 @@ const NotificationSettings: React.FC = () => {
};
}, []);
const save = useCallback(async () => {
setIsLoading(true);
try {
if (URI) {
if (await isGroundControlUriValid(URI)) {
await saveUri(URI);
presentAlert({ message: loc.settings.saved });
} else {
presentAlert({ message: loc.settings.not_a_valid_uri });
}
} else {
await saveUri('');
presentAlert({ message: loc.settings.saved });
}
} catch (error) {
console.error('Error saving URI:', error);
}
setIsLoading(false);
}, [URI]);
const renderDeveloperSettings = useCallback(() => {
if (tapCount < 10) return null;
return (
<View>
<View style={[styles.divider, { backgroundColor: colors.lightBorder ?? colors.borderTopColor }]} />
<SettingsCard style={styles.card}>
<View style={styles.cardContent}>
<Text style={[styles.multilineText, { color: colors.foregroundColor }]}>{loc.settings.groundcontrol_explanation}</Text>
</View>
</SettingsCard>
<SettingsListItem
title="github.com/BlueWallet/GroundControl"
@ -215,27 +182,6 @@ const NotificationSettings: React.FC = () => {
<SettingsCard style={styles.card}>
<View style={styles.cardContent}>
<View
style={[
styles.uri,
{ borderColor: colors.formBorder, borderBottomColor: colors.formBorder, backgroundColor: colors.inputBackgroundColor },
]}
>
<TextInput
placeholder={getDefaultUri()}
value={URI}
onChangeText={setURI}
numberOfLines={1}
style={[styles.uriText, { color: colors.alternativeTextColor }]}
placeholderTextColor="#81868e"
editable={!isLoading}
textContentType="URL"
autoCapitalize="none"
underlineColorAndroid="transparent"
/>
</View>
<BlueSpacing20 />
<Text style={[styles.centered, { color: colors.foregroundColor }]} onPress={() => setTapCount(tapCount + 1)}>
Ground Control to Major Tom
</Text>
@ -246,14 +192,11 @@ const NotificationSettings: React.FC = () => {
<View>
<CopyToClipboardButton stringToCopy={tokenInfo} displayText={tokenInfo} />
</View>
<BlueSpacing20 />
<Button onPress={save} title={loc.settings.save} />
</View>
</SettingsCard>
</View>
);
}, [tapCount, colors, isLoading, URI, tokenInfo, save]);
}, [tapCount, colors, tokenInfo]);
const renderPushNotificationsExplanation = useCallback(() => {
return (
@ -375,28 +318,10 @@ const styles = StyleSheet.create({
paddingHorizontal: horizontalPadding,
paddingVertical: isAndroid ? 12 : 10,
},
multilineText: {
lineHeight: 20,
paddingBottom: 10,
},
centered: {
textAlign: 'center',
marginVertical: 4,
},
uri: {
flexDirection: 'row',
borderWidth: 1,
borderBottomWidth: 0.5,
minHeight: 44,
height: 44,
alignItems: 'center',
},
uriText: {
flex: 1,
marginHorizontal: 8,
minHeight: 36,
height: 36,
},
divider: {
marginVertical: isAndroid ? 16 : 12,
height: 0.5,

View File

@ -1,20 +0,0 @@
import assert from 'assert';
import { isGroundControlUriValid } from '../../blue_modules/notifications';
// Notifications.default = new Notifications();
describe('notifications', () => {
// yeah, lets rely less on external services...
// eslint-disable-next-line jest/no-disabled-tests
it.skip('can check groundcontrol server uri validity', async () => {
assert.ok(await isGroundControlUriValid('https://groundcontrol.bluewallet.io/'));
assert.ok(!(await isGroundControlUriValid('https://www.google.com')));
await new Promise(resolve => setTimeout(resolve, 2000));
});
// muted because it causes jest to hang waiting indefinitely
// eslint-disable-next-line jest/no-disabled-tests
it.skip('can check non-responding url', async () => {
assert.ok(!(await isGroundControlUriValid('https://localhost.com')));
});
});