implement recursive connection retry to add more robust internet error handling

This commit is contained in:
Blake Kaufman
2026-01-26 15:53:14 -05:00
parent 4b765124a0
commit a3911a8d59
6 changed files with 102 additions and 64 deletions
@@ -254,7 +254,9 @@ export default function ClaimGiftScreen({
}
if (!initResult) {
initResult = await initializeSparkWallet(giftSeed);
initResult = await initializeSparkWallet(giftSeed, false, {
maxRetries: 4,
});
}
if (!initResult) {
@@ -69,7 +69,9 @@ export default function SelectAltAccountHalfModal(props) {
});
await new Promise(res => setTimeout(res, 800));
await initializeSparkWallet(account.mnemoinc);
await initializeSparkWallet(account.mnemoinc, false, {
maxRetries: 4,
});
let balance = 0;
if (transferType === 'from') {
const balanceResponse = await getSparkBalance(account.mnemoinc);
+1 -19
View File
@@ -25,25 +25,7 @@ export async function initWallet({
}) {
try {
crashlyticsLogReport('Trying to connect to nodes');
const [
didConnectToSpark,
// balance
] = await Promise.all([
initializeSparkWallet(mnemonic),
// handleBalanceCache({
// isCheck: false,
// mnemonic: mnemonic,
// returnBalanceOnly: true,
// }),
]);
// if (balance) {
// setSparkInformation(prev => ({
// ...prev,
// didConnect: true,
// balance: balance,
// }));
// }
const didConnectToSpark = await initializeSparkWallet(mnemonic);
if (didConnectToSpark.isConnected) {
crashlyticsLogReport('Loading node balances for session');
+82 -36
View File
@@ -118,47 +118,93 @@ export const clearMnemonicCache = () => {
Object.keys(sparkWallet).forEach(key => delete sparkWallet[key]);
};
export const initializeSparkWallet = async (mnemonic, isInitialLoad = true) => {
try {
const runtime = await selectSparkRuntime(mnemonic, isInitialLoad);
export const initializeSparkWallet = async (
mnemonic,
isInitialLoad = true,
options = {},
) => {
const {
maxRetries = 8,
retryDelay = 15000, // 15 seconds between retries
enableRetry = true,
} = options;
if (runtime === 'webview') {
// Use WebView to initialize wallet
const response = await sendWebViewRequestGlobal(
OPERATION_TYPES.initWallet,
{
mnemonic,
},
const attemptInitialization = async (attemptNumber = 0) => {
try {
const runtime = await selectSparkRuntime(mnemonic, isInitialLoad);
if (runtime === 'webview') {
// Use WebView to initialize wallet
const response = await sendWebViewRequestGlobal(
OPERATION_TYPES.initWallet,
{
mnemonic,
},
);
if (response?.isConnected) return response;
}
const hash = getMnemonicHash(mnemonic);
// Early return if already initialized
if (sparkWallet[hash]) {
return { isConnected: true };
}
if (initializingWallets[hash]) {
await initializingWallets[hash];
return { isConnected: true };
}
initializingWallets[hash] = (async () => {
try {
const wallet = await initializeWallet(mnemonic);
sparkWallet[hash] = wallet;
return wallet;
} catch (err) {
delete initializingWallets[hash]; // cleanup after done
delete sparkWallet[hash];
throw err;
}
})();
await initializingWallets[hash];
delete initializingWallets[hash];
setForceReactNative(true);
return { isConnected: true };
} catch (err) {
console.log(
`Initialize spark wallet error (attempt ${attemptNumber + 1}/${
maxRetries + 1
}):`,
err,
);
if (response?.isConnected) return response;
const hash = getMnemonicHash(mnemonic);
delete initializingWallets[hash];
delete sparkWallet[hash];
// If retry is disabled or max retries reached, return error
if (!enableRetry || attemptNumber >= maxRetries) {
return { isConnected: false, error: err.message };
}
// Log retry attempt
console.log(
`Wallet failed to connect. Retrying in ${
retryDelay / 1000
} seconds... (${attemptNumber + 1}/${maxRetries} retries)`,
);
// Wait before retry
await new Promise(res => setTimeout(res, retryDelay));
// Recursive retry
return attemptInitialization(attemptNumber + 1);
}
};
const hash = getMnemonicHash(mnemonic);
// Early return if already initialized
if (sparkWallet[hash]) {
return { isConnected: true };
}
if (initializingWallets[hash]) {
await initializingWallets[hash];
return { isConnected: true };
}
initializingWallets[hash] = (async () => {
const wallet = await initializeWallet(mnemonic);
sparkWallet[hash] = wallet;
delete initializingWallets[hash]; // cleanup after done
})();
await initializingWallets[hash];
setForceReactNative(true);
return { isConnected: true };
} catch (err) {
console.log('Initialize spark wallet error:', err);
return { isConnected: false, error: err.message };
}
return attemptInitialization(0);
};
const initializeWallet = async mnemonic => {
+2
View File
@@ -58,6 +58,7 @@ import {
incomingSparkTransaction,
OPERATION_TYPES,
sendWebViewRequestGlobal,
setForceReactNative,
useWebView,
} from './webViewContext';
import { useGlobalContextProvider } from './context';
@@ -1624,6 +1625,7 @@ const SparkWalletProvider = ({ children }) => {
setDidRunNormalConnection(true);
// lastConnectedTimeRef.current = Date.now();
if (!didWork) {
setForceReactNative(true);
setSparkInformation(prev => ({ ...prev, didConnect: false }));
setSparkConnectionError(error);
console.log('Error connecting to spark wallet:', error);
+11 -7
View File
@@ -704,7 +704,11 @@ export const WebViewProvider = ({ children }) => {
}
// Reject importent messages if app is not connected to the internet
if (!internetConnectionRef.current && action !== 'handshake:init') {
if (
!internetConnectionRef.current &&
action !== 'handshake:init' &&
action !== 'initializeSparkWallet'
) {
console.log(
'App is not connected to the internet, queueing message:',
action,
@@ -866,11 +870,11 @@ export const WebViewProvider = ({ children }) => {
result,
);
forceReactNativeUse = true;
setChangeSparkConnectionState(prev => ({
state: true,
count: prev.count + 1,
}));
// forceReactNativeUse = true;
// setChangeSparkConnectionState(prev => ({
// state: true,
// count: prev.count + 1,
// }));
queuedRequests.current.forEach(({ reject }) => {
reject({
@@ -1091,7 +1095,7 @@ export const WebViewProvider = ({ children }) => {
if (!response?.isConnected) throw new Error('Wallet init failed');
} catch (err) {
console.log('Error re-initializing wallet:', err);
forceReactNativeUse = true;
// forceReactNativeUse = true;
// Reject all queued requests since WebView is now unusable
queuedRequests.current.forEach(({ reject }) => {
reject({