Skip to content

Commit f8292f8

Browse files
Copilot0xrinegade
andcommitted
Implement comprehensive code quality improvements and fixes
Co-authored-by: 0xrinegade <[email protected]>
1 parent 928d95b commit f8292f8

File tree

8 files changed

+192
-15
lines changed

8 files changed

+192
-15
lines changed

.github/workflows/build-android.yml

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -45,8 +45,20 @@ jobs:
4545

4646
- name: Initialize Capacitor (if not exists)
4747
run: |
48-
if [ ! -d "android" ] && [ ! -f "capacitor.config.json" ] && [ ! -f "capacitor.config.ts" ]; then
48+
# Check if Capacitor is already configured by looking for config file
49+
CAPACITOR_CONFIG=""
50+
if [ -f "capacitor.config.json" ]; then
51+
CAPACITOR_CONFIG="capacitor.config.json"
52+
elif [ -f "capacitor.config.ts" ]; then
53+
CAPACITOR_CONFIG="capacitor.config.ts"
54+
fi
55+
56+
# Initialize only if no config exists AND no android directory
57+
if [ -z "$CAPACITOR_CONFIG" ] && [ ! -d "android" ]; then
58+
echo "No Capacitor configuration found, initializing..."
4959
npx cap init "SVMSeek Wallet" "com.svmseek.wallet" --web-dir=build
60+
else
61+
echo "Capacitor already configured with: ${CAPACITOR_CONFIG:-android directory}"
5062
fi
5163
5264
- name: Add Android platform (if not exists)
@@ -103,10 +115,16 @@ jobs:
103115
if: github.ref == 'refs/heads/main' && github.event_name == 'push'
104116
working-directory: ./android
105117
run: |
106-
if [ ! -z "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" ]; then
118+
# Use environment variable instead of direct secret interpolation in shell
119+
if [ ! -z "$ANDROID_KEYSTORE_BASE64" ]; then
107120
echo "Setting up production keystore..."
108-
echo "${{ secrets.ANDROID_KEYSTORE_BASE64 }}" | base64 -d > release.keystore
109-
echo "Production keystore created"
121+
# Decode safely with error handling
122+
if echo "$ANDROID_KEYSTORE_BASE64" | base64 -d > release.keystore 2>/dev/null; then
123+
echo "Production keystore created successfully"
124+
else
125+
echo "ERROR: Failed to decode keystore base64 data"
126+
exit 1
127+
fi
110128
else
111129
echo "No production keystore secrets found, skipping production build"
112130
fi
@@ -177,13 +195,11 @@ jobs:
177195
fi
178196
179197
# 5. Check APK size is reasonable (not empty or suspiciously small)
180-
APK_SIZE=$(stat -f%z "$APK_FILE" 2>/dev/null || stat -c%s "$APK_FILE" 2>/dev/null || echo "0")
181-
MIN_SIZE=$((1024 * 1024)) # 1MB minimum
182-
if [ "$APK_SIZE" -lt "$MIN_SIZE" ]; then
183-
echo "ERROR: APK size ($APK_SIZE bytes) is suspiciously small"
198+
echo "Validating APK size..."
199+
if ! ./scripts/check-apk-size.sh "$APK_FILE" 1; then
200+
echo "ERROR: APK size validation failed"
184201
exit 1
185202
fi
186-
echo "✓ APK size validation passed: $(( APK_SIZE / 1024 / 1024 ))MB"
187203
188204
# 6. Verify APK contains expected Android components
189205
REQUIRED_FILES="AndroidManifest.xml classes.dex resources.arsc"

playwright.config.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ export default defineConfig({
7070
command: 'yarn start',
7171
url: 'http://localhost:3000',
7272
reuseExistingServer: !process.env.CI,
73-
timeout: 120 * 1000,
73+
timeout: parseInt(process.env.PLAYWRIGHT_SERVER_TIMEOUT || '240') * 1000, // Default 240 seconds, configurable
74+
retries: process.env.CI ? 3 : 1, // Add retries for flaky network scenarios
7475
},
7576
});

scripts/check-apk-size.sh

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
#!/bin/bash
2+
# Cross-platform APK size validation script
3+
# Supports different stat command formats across various runners
4+
5+
set -e
6+
7+
APK_FILE="$1"
8+
MIN_SIZE_MB="${2:-1}" # Default 1MB minimum
9+
10+
if [ -z "$APK_FILE" ]; then
11+
echo "Usage: $0 <apk-file> [min-size-mb]"
12+
exit 1
13+
fi
14+
15+
if [ ! -f "$APK_FILE" ]; then
16+
echo "ERROR: APK file not found: $APK_FILE"
17+
exit 1
18+
fi
19+
20+
# Function to get file size in bytes - works across different systems
21+
get_file_size() {
22+
local file="$1"
23+
local size=0
24+
25+
# Try different stat command formats
26+
if command -v stat >/dev/null 2>&1; then
27+
# macOS/BSD format
28+
if size=$(stat -f%z "$file" 2>/dev/null); then
29+
echo "$size"
30+
return 0
31+
fi
32+
33+
# Linux/GNU format
34+
if size=$(stat -c%s "$file" 2>/dev/null); then
35+
echo "$size"
36+
return 0
37+
fi
38+
fi
39+
40+
# Fallback: use ls command (less reliable but widely available)
41+
if command -v ls >/dev/null 2>&1; then
42+
size=$(ls -l "$file" | awk '{print $5}')
43+
if [ "$size" -gt 0 ] 2>/dev/null; then
44+
echo "$size"
45+
return 0
46+
fi
47+
fi
48+
49+
# Final fallback: use wc -c
50+
if command -v wc >/dev/null 2>&1; then
51+
size=$(wc -c < "$file" 2>/dev/null || echo "0")
52+
echo "$size"
53+
return 0
54+
fi
55+
56+
echo "0"
57+
return 1
58+
}
59+
60+
# Get APK size
61+
APK_SIZE=$(get_file_size "$APK_FILE")
62+
MIN_SIZE=$((MIN_SIZE_MB * 1024 * 1024))
63+
64+
echo "Checking APK size for: $APK_FILE"
65+
echo "APK size: $APK_SIZE bytes ($(( APK_SIZE / 1024 / 1024 ))MB)"
66+
echo "Minimum required: $MIN_SIZE bytes (${MIN_SIZE_MB}MB)"
67+
68+
if [ "$APK_SIZE" -lt "$MIN_SIZE" ]; then
69+
echo "ERROR: APK size ($APK_SIZE bytes) is below minimum threshold (${MIN_SIZE_MB}MB)"
70+
echo "This may indicate build issues or corrupted APK"
71+
exit 1
72+
fi
73+
74+
if [ "$APK_SIZE" -eq 0 ]; then
75+
echo "ERROR: APK file is empty or could not determine size"
76+
exit 1
77+
fi
78+
79+
echo "✓ APK size validation passed"
80+
exit 0

src/App.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,12 +228,15 @@ const Pages = () => {
228228
/>
229229

230230
{/* popup if connecting from dex UI */}
231+
{/* TODO: Migrate to React Router v6 - Replace Redirect with Navigate component */}
231232
{window.opener && !!wallet && <Redirect from="/" to="/connect_popup" />}
232233

233234
{/* if wallet exists - for case when we'll have unlocked wallet */}
235+
{/* TODO: Migrate to React Router v6 - Replace Redirect with Navigate component */}
234236
{!!wallet && <Redirect from="/" to="/wallet" />}
235237

236238
{/* if have mnemonic in localstorage - login, otherwise - restore/import/create */}
239+
{/* TODO: Migrate to React Router v6 - Replace Redirect with Navigate component */}
237240
{hasLockedMnemonicAndSeed ? (
238241
<Redirect from="/" to="/welcome_back" />
239242
) : (

src/components/OnboardingTutorial.tsx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,28 @@ const StepIcon = styled.div`
5858
margin: 0 auto 1rem;
5959
color: white;
6060
font-size: 24px;
61+
62+
/* Improved accessibility for small devices */
63+
@media (max-width: 480px) {
64+
width: 48px;
65+
height: 48px;
66+
font-size: 20px;
67+
}
68+
69+
/* Ensure sufficient contrast and touch target size */
70+
min-height: 44px;
71+
min-width: 44px;
72+
73+
/* High contrast mode support */
74+
@media (prefers-contrast: high) {
75+
background: #000;
76+
border: 2px solid #fff;
77+
}
78+
79+
/* Reduced motion support */
80+
@media (prefers-reduced-motion: reduce) {
81+
transition: none;
82+
}
6183
`;
6284

6385
const NavigationButtons = styled(Box)`

src/components/WebBrowser/WalletProvider.tsx

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ export const WalletProviderContext: React.FC<WalletProviderContextProps> = ({ ch
3636
const wallet = useWallet();
3737
const [connecting, setConnecting] = useState(false);
3838

39+
// Performance optimization: Memoize callback functions to prevent unnecessary re-renders
40+
// Dependency array [wallet] is intentionally minimal to ensure callbacks update when wallet changes
3941
const connect = useCallback(async () => {
4042
if (!wallet?.publicKey) {
4143
throw new Error('No wallet available');
@@ -47,12 +49,12 @@ export const WalletProviderContext: React.FC<WalletProviderContextProps> = ({ ch
4749
} finally {
4850
setConnecting(false);
4951
}
50-
}, [wallet]);
52+
}, [wallet]); // Dependency: wallet - callback updates when wallet instance changes
5153

5254
const disconnect = useCallback(async () => {
5355
// In a real implementation, this would disconnect the wallet
5456
console.log('Wallet disconnect requested');
55-
}, []);
57+
}, []); // No dependencies - function is static
5658

5759
const signTransaction = useCallback(async (transaction: Transaction) => {
5860
if (!wallet) {
@@ -66,7 +68,7 @@ export const WalletProviderContext: React.FC<WalletProviderContextProps> = ({ ch
6668
// 4. Return the signed transaction
6769

6870
throw new Error('Transaction signing requires user approval - not implemented in iframe context');
69-
}, [wallet]);
71+
}, [wallet]); // Dependency: wallet - callback needs current wallet instance
7072

7173
const signAllTransactions = useCallback(async (transactions: Transaction[]) => {
7274
if (!wallet) {
@@ -75,7 +77,7 @@ export const WalletProviderContext: React.FC<WalletProviderContextProps> = ({ ch
7577

7678
// Similar to signTransaction but for multiple transactions
7779
throw new Error('Batch transaction signing requires user approval - not implemented in iframe context');
78-
}, [wallet]);
80+
}, [wallet]); // Dependency: wallet - callback needs current wallet instance
7981

8082
const signMessage = useCallback(async (message: Uint8Array) => {
8183
if (!wallet) {
@@ -84,7 +86,7 @@ export const WalletProviderContext: React.FC<WalletProviderContextProps> = ({ ch
8486

8587
// In a real implementation, this would sign arbitrary messages
8688
throw new Error('Message signing requires user approval - not implemented in iframe context');
87-
}, [wallet]);
89+
}, [wallet]); // Dependency: wallet - callback needs current wallet instance
8890

8991
const value: WalletContextState = {
9092
isConnected: !!wallet?.publicKey,

src/pages/RestoreWallet/index.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ export const RestorePage = () => {
6767
<title>Restore SVMSeek Wallet by seed phrase</title>
6868
</Helmet>
6969
<FakeInputs />
70+
{/* TODO: Migrate to React Router v6 - Replace Redirect with Navigate component */}
7071
{redirectToWallet && <Redirect to="/wallet" />}
7172
{/* <Logo /> */}
7273
{/* margin={showDerivation ? '0 0 4rem 0' : '0 0 8rem 0'} */}

src/services/WalletInjectionService.ts

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,16 @@ export class WalletInjectionService {
3737
try {
3838
this.iframe = iframe;
3939

40+
// Enhanced security: Check iframe source origin
41+
const iframeSrc = iframe.src;
42+
if (iframeSrc && !this.isAllowedOrigin(iframeSrc)) {
43+
console.warn('Wallet injection blocked for untrusted origin:', iframeSrc);
44+
return {
45+
success: false,
46+
error: 'Wallet injection blocked for security reasons: untrusted origin'
47+
};
48+
}
49+
4050
// Wait for iframe to load
4151
await this.waitForIframeLoad(iframe);
4252

@@ -76,6 +86,48 @@ export class WalletInjectionService {
7686
}
7787
}
7888

89+
/**
90+
* Check if the origin is allowed for wallet injection
91+
* Implements same-origin policy with allowlist for trusted domains
92+
*/
93+
private isAllowedOrigin(url: string): boolean {
94+
try {
95+
const urlObj = new URL(url);
96+
const origin = urlObj.origin;
97+
const hostname = urlObj.hostname;
98+
99+
// Allow same origin (localhost, 127.0.0.1 for development)
100+
if (hostname === 'localhost' || hostname === '127.0.0.1' || hostname === '0.0.0.0') {
101+
return true;
102+
}
103+
104+
// Allow SVMSeek domains
105+
const allowedDomains = [
106+
'svmseek.com',
107+
'www.svmseek.com',
108+
'app.svmseek.com',
109+
'wallet.svmseek.com'
110+
];
111+
112+
// Check exact domain match
113+
if (allowedDomains.includes(hostname)) {
114+
return true;
115+
}
116+
117+
// Check subdomain pattern for svmseek.com
118+
if (hostname.endsWith('.svmseek.com')) {
119+
return true;
120+
}
121+
122+
// Block all other origins for security
123+
console.warn('Blocked wallet injection for untrusted origin:', origin);
124+
return false;
125+
} catch (error) {
126+
console.error('Failed to parse URL for origin check:', url, error);
127+
return false;
128+
}
129+
}
130+
79131
/**
80132
* Handle messages from the iframe
81133
*/

0 commit comments

Comments
 (0)