Skip to content

Commit fadfd3d

Browse files
Copilot0xrinegade
andcommitted
Fix external wallet extension script conflicts and console noise - Enhanced error handling system
Co-authored-by: 0xrinegade <[email protected]>
1 parent 7b91e7b commit fadfd3d

File tree

5 files changed

+507
-53
lines changed

5 files changed

+507
-53
lines changed
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
/**
2+
* Enhanced Error Boundary Component
3+
*
4+
* Provides comprehensive error handling for React components
5+
* with special handling for external script conflicts
6+
*/
7+
8+
import React from 'react';
9+
import { classifyError, shouldSuppressError, logError, ERROR_CATEGORIES } from '../utils/errorHandling';
10+
11+
class EnhancedErrorBoundary extends React.Component {
12+
constructor(props) {
13+
super(props);
14+
this.state = {
15+
hasError: false,
16+
error: null,
17+
errorInfo: null,
18+
errorCategory: null,
19+
retryCount: 0
20+
};
21+
}
22+
23+
static getDerivedStateFromError(error) {
24+
const category = classifyError(error);
25+
26+
// Don't show error boundary for external errors
27+
if (shouldSuppressError(error)) {
28+
return null;
29+
}
30+
31+
return {
32+
hasError: true,
33+
error,
34+
errorCategory: category
35+
};
36+
}
37+
38+
componentDidCatch(error, errorInfo) {
39+
const category = classifyError(error);
40+
41+
// Log error with appropriate level based on category
42+
if (category === ERROR_CATEGORIES.APPLICATION) {
43+
logError(error, 'ErrorBoundary caught application error');
44+
this.setState({
45+
error,
46+
errorInfo,
47+
errorCategory: category
48+
});
49+
} else {
50+
// External errors - just log for debugging
51+
logError(error, 'ErrorBoundary caught external error', 'debug');
52+
}
53+
}
54+
55+
handleRetry = () => {
56+
this.setState({
57+
hasError: false,
58+
error: null,
59+
errorInfo: null,
60+
errorCategory: null,
61+
retryCount: this.state.retryCount + 1
62+
});
63+
}
64+
65+
handleReload = () => {
66+
window.location.reload();
67+
}
68+
69+
render() {
70+
if (this.state.hasError) {
71+
const { error, errorCategory, retryCount } = this.state;
72+
const { fallback: FallbackComponent } = this.props;
73+
74+
// If a custom fallback is provided, use it
75+
if (FallbackComponent) {
76+
return React.isValidElement(FallbackComponent) ?
77+
FallbackComponent :
78+
<FallbackComponent
79+
error={error}
80+
errorCategory={errorCategory}
81+
onRetry={this.handleRetry}
82+
onReload={this.handleReload}
83+
retryCount={retryCount}
84+
/>;
85+
}
86+
87+
// Default error UI based on error category
88+
return (
89+
<div className="error-boundary min-h-screen flex items-center justify-center bg-gray-50 dark:bg-gray-900">
90+
<div className="max-w-md w-full bg-white dark:bg-gray-800 rounded-lg shadow-lg p-6 mx-4">
91+
<div className="flex items-center mb-4">
92+
<div className="flex-shrink-0">
93+
<svg className="h-8 w-8 text-red-500" fill="none" stroke="currentColor" viewBox="0 0 24 24">
94+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth="2" d="M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-2.5L13.732 4c-.77-.833-1.732-.833-2.5 0L4.268 15.5c-.77.833.192 2.5 1.732 2.5z" />
95+
</svg>
96+
</div>
97+
<div className="ml-3">
98+
<h3 className="text-lg font-medium text-gray-900 dark:text-white">
99+
Application Error
100+
</h3>
101+
<p className="text-sm text-gray-500 dark:text-gray-400">
102+
{errorCategory === ERROR_CATEGORIES.NETWORK_CONNECTION ?
103+
'Connection error occurred' :
104+
'Something went wrong'
105+
}
106+
</p>
107+
</div>
108+
</div>
109+
110+
{process.env.NODE_ENV === 'development' && (
111+
<div className="mb-4 p-3 bg-gray-100 dark:bg-gray-700 rounded text-xs">
112+
<div className="font-mono text-red-600 dark:text-red-400">
113+
{error?.message || 'Unknown error'}
114+
</div>
115+
{error?.stack && (
116+
<details className="mt-2">
117+
<summary className="cursor-pointer text-gray-600 dark:text-gray-300">
118+
Stack trace
119+
</summary>
120+
<pre className="mt-1 text-gray-500 dark:text-gray-400 overflow-auto">
121+
{error.stack}
122+
</pre>
123+
</details>
124+
)}
125+
</div>
126+
)}
127+
128+
<div className="flex space-x-3">
129+
{retryCount < 3 && (
130+
<button
131+
onClick={this.handleRetry}
132+
className="flex-1 bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2"
133+
>
134+
Try Again {retryCount > 0 && `(${retryCount + 1})`}
135+
</button>
136+
)}
137+
138+
<button
139+
onClick={this.handleReload}
140+
className="flex-1 bg-gray-600 text-white px-4 py-2 rounded hover:bg-gray-700 focus:outline-none focus:ring-2 focus:ring-gray-500 focus:ring-offset-2"
141+
>
142+
Reload Page
143+
</button>
144+
</div>
145+
146+
{errorCategory === ERROR_CATEGORIES.NETWORK_CONNECTION && (
147+
<div className="mt-4 p-3 bg-blue-50 dark:bg-blue-900/20 rounded">
148+
<p className="text-sm text-blue-700 dark:text-blue-300">
149+
This appears to be a network connectivity issue. Please check your internet connection and try again.
150+
</p>
151+
</div>
152+
)}
153+
</div>
154+
</div>
155+
);
156+
}
157+
158+
return this.props.children;
159+
}
160+
}
161+
162+
export default EnhancedErrorBoundary;

src/contexts/PhantomWalletProvider.js

Lines changed: 65 additions & 49 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import { ToastContainer } from '../components/Toast';
1212
import { ReconnectionModal } from '../components/ReconnectionModal';
1313
import { SVM_NETWORKS, getNetworkConfig, getDefaultNetworkConfig } from '../config/networks';
1414
import { ERROR_CATEGORIES } from '../hooks/useToast';
15+
import { safeExecute, logError } from '../utils/errorHandling';
1516

1617
// Maximum number of reconnection attempts
1718
const MAX_RECONNECT_ATTEMPTS = 3;
@@ -151,40 +152,46 @@ const PhantomWalletProvider = ({ children }) => {
151152
return baseDelay + (baseDelay * jitter);
152153
}, []);
153154

154-
// Detect Phantom wallet
155+
// Detect Phantom wallet with error protection
155156
const detectPhantomWallet = useCallback(() => {
156-
if (typeof window === 'undefined') return null;
157-
158-
// Check for Phantom's Solana provider
159-
if (window.phantom?.solana) {
160-
return window.phantom.solana;
161-
}
162-
163-
// Check for legacy solana provider
164-
if (window.solana?.isPhantom) {
165-
return window.solana;
166-
}
167-
168-
return null;
157+
return safeExecute(() => {
158+
if (typeof window === 'undefined') return null;
159+
160+
// Check for Phantom's Solana provider
161+
if (window.phantom?.solana) {
162+
return window.phantom.solana;
163+
}
164+
165+
// Check for legacy solana provider
166+
if (window.solana?.isPhantom) {
167+
return window.solana;
168+
}
169+
170+
return null;
171+
}, 'detectPhantomWallet', null);
169172
}, []);
170173

171-
// Connect to Phantom wallet
174+
// Connect to Phantom wallet with enhanced error handling
172175
const connect = useCallback(async () => {
173176
try {
174177
setConnecting(true);
175178
setError(null);
176179
setConnectionState('connecting');
177180

178-
const phantomWallet = detectPhantomWallet();
181+
const phantomWallet = await detectPhantomWallet();
179182

180183
if (!phantomWallet) {
181184
throw new Error('Phantom wallet not found. Please install Phantom browser extension.');
182185
}
183186

184-
// Connect to Phantom
185-
const response = await phantomWallet.connect();
187+
// Connect to Phantom with safe execution
188+
const response = await safeExecute(
189+
() => phantomWallet.connect(),
190+
'phantom-wallet-connect',
191+
null
192+
);
186193

187-
if (response.publicKey) {
194+
if (response?.publicKey) {
188195
const pubKey = new PublicKey(response.publicKey.toString());
189196
const address = pubKey.toString();
190197

@@ -207,7 +214,7 @@ const PhantomWalletProvider = ({ children }) => {
207214
throw new Error('Failed to get public key from Phantom wallet');
208215
}
209216
} catch (err) {
210-
console.error('[PhantomWalletProvider] Connection failed:', err);
217+
logError(err, 'PhantomWalletProvider connection');
211218
const errorMsg = err.message || 'Failed to connect to Phantom wallet';
212219
setError(errorMsg);
213220
setConnectionState('error');
@@ -245,9 +252,9 @@ const PhantomWalletProvider = ({ children }) => {
245252
}
246253
}, [detectPhantomWallet, toast]);
247254

248-
// Disconnect from Phantom wallet
255+
// Disconnect from Phantom wallet with error protection
249256
const disconnect = useCallback(async () => {
250-
try {
257+
return safeExecute(async () => {
251258
if (wallet && wallet.disconnect) {
252259
await wallet.disconnect();
253260
}
@@ -268,12 +275,12 @@ const PhantomWalletProvider = ({ children }) => {
268275
});
269276

270277
console.log('[PhantomWalletProvider] Disconnected from Phantom wallet');
271-
} catch (err) {
272-
console.error('[PhantomWalletProvider] Disconnect failed:', err);
278+
}, 'phantom-wallet-disconnect').catch((err) => {
279+
logError(err, 'PhantomWalletProvider disconnect');
273280
const errorMsg = err.message || 'Disconnect failed';
274281
setError(errorMsg);
275282
toast.criticalError(`Disconnect failed: ${errorMsg}`);
276-
}
283+
});
277284
}, [wallet, toast]);
278285

279286
// Sign transaction
@@ -483,11 +490,11 @@ const PhantomWalletProvider = ({ children }) => {
483490
console.log('[PhantomWalletProvider] Reconnection cancelled by user');
484491
}, []);
485492

486-
// Check for existing connection on mount
493+
// Check for existing connection on mount with error protection
487494
useEffect(() => {
488495
const checkExistingConnection = async () => {
489-
try {
490-
const phantomWallet = detectPhantomWallet();
496+
return safeExecute(async () => {
497+
const phantomWallet = await detectPhantomWallet();
491498
const wasConnected = localStorage.getItem('phantomConnected') === 'true';
492499

493500
if (phantomWallet && wasConnected) {
@@ -509,41 +516,50 @@ const PhantomWalletProvider = ({ children }) => {
509516
localStorage.removeItem('phantomAddress');
510517
}
511518
}
512-
} catch (error) {
513-
console.warn('[PhantomWalletProvider] Failed to restore connection:', error);
519+
}, 'phantom-wallet-restore-connection').catch((error) => {
520+
logError(error, 'PhantomWalletProvider restore connection');
514521
localStorage.removeItem('phantomConnected');
515522
localStorage.removeItem('phantomAddress');
516-
}
523+
});
517524
};
518525

519526
checkExistingConnection();
520527
}, [detectPhantomWallet]);
521528

522-
// Listen for account changes
529+
// Listen for account changes with error protection
523530
useEffect(() => {
524531
if (wallet && connected) {
525532
const handleAccountChanged = (publicKey) => {
526-
if (publicKey) {
527-
const pubKey = new PublicKey(publicKey.toString());
528-
const address = pubKey.toString();
529-
530-
setPublicKey(pubKey);
531-
setWalletAddress(address);
532-
localStorage.setItem('phantomAddress', address);
533-
534-
toast.info('Phantom wallet account changed', {
535-
category: ERROR_CATEGORIES.SUCCESS
536-
});
537-
} else {
538-
// Account disconnected
539-
disconnect();
540-
}
533+
safeExecute(() => {
534+
if (publicKey) {
535+
const pubKey = new PublicKey(publicKey.toString());
536+
const address = pubKey.toString();
537+
538+
setPublicKey(pubKey);
539+
setWalletAddress(address);
540+
localStorage.setItem('phantomAddress', address);
541+
542+
toast.info('Phantom wallet account changed', {
543+
category: ERROR_CATEGORIES.SUCCESS
544+
});
545+
} else {
546+
// Account disconnected
547+
disconnect();
548+
}
549+
}, 'phantom-wallet-account-change').catch((error) => {
550+
logError(error, 'PhantomWalletProvider account change handler');
551+
});
541552
};
542553

543-
wallet.on('accountChanged', handleAccountChanged);
554+
// Safe event listener attachment
555+
safeExecute(() => {
556+
wallet.on('accountChanged', handleAccountChanged);
557+
}, 'phantom-wallet-event-listener');
544558

545559
return () => {
546-
wallet.off('accountChanged', handleAccountChanged);
560+
safeExecute(() => {
561+
wallet.off('accountChanged', handleAccountChanged);
562+
}, 'phantom-wallet-event-cleanup');
547563
};
548564
}
549565
}, [wallet, connected, disconnect, toast]);

src/pages/_app.js

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@ import dynamic from 'next/dynamic';
55
import { initWebVitals } from '@/utils/webVitals';
66
import { analyzeBundleSize } from '@/utils/lazyLoading';
77

8+
// Import error handling for external script conflicts
9+
import { setupGlobalErrorHandling } from '@/utils/errorHandling';
10+
811
// Import styles - order matters for CSS
912
// IMPORTANT: In CSS files, all @import statements must be at the top of the file
1013
// with proper formatting and line breaks between them and the first CSS rule.
@@ -33,13 +36,16 @@ import { PhantomWalletProvider } from '@/contexts/PhantomWalletProvider';
3336
// Import Layout
3437
import Layout from '@/components/Layout';
3538

36-
// Dynamically import ErrorBoundary to prevent SSR issues
37-
const ErrorBoundary = dynamic(() => import('@/components/ErrorBoundary'), { ssr: false });
39+
// Dynamically import EnhancedErrorBoundary to prevent SSR issues
40+
const ErrorBoundary = dynamic(() => import('@/components/EnhancedErrorBoundary'), { ssr: false });
3841

3942
export default function App({ Component, pageProps }) {
4043

4144
// Initialize performance monitoring
4245
useEffect(() => {
46+
// Setup global error handling first to catch external script errors
47+
setupGlobalErrorHandling();
48+
4349
// Initialize Web Vitals monitoring
4450
initWebVitals();
4551

0 commit comments

Comments
 (0)