Created
March 20, 2025 16:39
-
-
Save HugoGresse/4479d44f373e121a82dc436fa8958c90 to your computer and use it in GitHub Desktop.
react-native-altcha
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import React, { useState } from 'react' | |
import { View, Text, TouchableOpacity, ActivityIndicator, StyleSheet } from 'react-native' | |
import { Svg, Path } from 'react-native-svg' | |
import { sha256 } from 'react-native-sha256' | |
import { Buffer } from 'buffer' | |
const AltchaLogo = () => ( | |
<Svg width={24} height={24} viewBox="0 0 20 20" fill="none"> | |
<Path | |
d="M2.33955 16.4279C5.88954 20.6586 12.1971 21.2105 16.4279 17.6604C18.4699 15.947 19.6548 13.5911 19.9352 11.1365L17.9886 10.4279C17.8738 12.5624 16.909 14.6459 15.1423 16.1284C11.7577 18.9684 6.71167 18.5269 3.87164 15.1423C1.03163 11.7577 1.4731 6.71166 4.8577 3.87164C8.24231 1.03162 13.2883 1.4731 16.1284 4.8577C16.9767 5.86872 17.5322 7.02798 17.804 8.2324L19.9522 9.01429C19.7622 7.07737 19.0059 5.17558 17.6604 3.57212C14.1104 -0.658624 7.80283 -1.21043 3.57212 2.33956C-0.658625 5.88958 -1.21046 12.1971 2.33955 16.4279Z" | |
fill="#666666" | |
/> | |
<Path | |
d="M3.57212 2.33956C1.65755 3.94607 0.496389 6.11731 0.12782 8.40523L2.04639 9.13961C2.26047 7.15832 3.21057 5.25375 4.8577 3.87164C8.24231 1.03162 13.2883 1.4731 16.1284 4.8577L13.8302 6.78606L19.9633 9.13364C19.7929 7.15555 19.0335 5.20847 17.6604 3.57212C14.1104 -0.658624 7.80283 -1.21043 3.57212 2.33956Z" | |
fill="#666666" | |
/> | |
<Path | |
d="M7 10H5C5 12.7614 7.23858 15 10 15C12.7614 15 15 12.7614 15 10H13C13 11.6569 11.6569 13 10 13C8.3431 13 7 11.6569 7 10Z" | |
fill="#666666" | |
/> | |
</Svg> | |
) | |
export const Altcha = ({ getVerification, onChallengeSolved }) => { | |
const [isLoading, setIsLoading] = useState(false) | |
const [errorMessage, setErrorMessage] = useState('') | |
const [isSolved, setIsSolved] = useState(false) | |
const hashChallenge = async (salt, num, algorithm) => { | |
const input = salt + num.toString() | |
if (algorithm.toUpperCase() === 'SHA-256') { | |
return await sha256(input) | |
} else { | |
throw new Error(`Unsupported hashing algorithm: ${algorithm}`) | |
} | |
} | |
const solveChallenge = async (challenge, salt, algorithm, max) => { | |
const startTime = Date.now() | |
for (let n = 0; n <= max; n++) { | |
const hashedValue = await hashChallenge(salt, n, algorithm) | |
if (hashedValue === challenge) { | |
const took = Date.now() - startTime | |
return { number: n, took } | |
} | |
} | |
return null | |
} | |
const fetchChallenge = async () => { | |
setIsLoading(true) | |
setErrorMessage('') | |
try { | |
const response = await getVerification() | |
if (response.status === 200) { | |
const responseData = response.data ? response.data : await response.json() | |
const { challenge, salt, algorithm, maxnumber: maxNumber, signature } = responseData | |
const resolvedValue = await solveChallenge(challenge, salt, algorithm, maxNumber) | |
if (resolvedValue) { | |
const payload = { | |
algorithm, | |
challenge, | |
number: resolvedValue.number, | |
salt, | |
signature, | |
took: resolvedValue.took, | |
} | |
const encodedPayload = Buffer.from(JSON.stringify(payload)).toString('base64') | |
onChallengeSolved(encodedPayload) | |
setIsSolved(true) | |
} else { | |
setErrorMessage('Failed to solve the challenge. Please try again.') | |
} | |
} else { | |
setErrorMessage('Failed to fetch the challenge. Please try again.') | |
} | |
} catch (e) { | |
setErrorMessage(`An error occurred: ${e.message}`) | |
} finally { | |
setIsLoading(false) | |
} | |
} | |
return ( | |
<View style={styles.container}> | |
<View style={styles.rowBetween}> | |
{isLoading ? ( | |
<View style={styles.row}> | |
<ActivityIndicator size="small" color="#4CAF50" style={styles.icon} /> | |
<Text style={styles.text}>Verifying...</Text> | |
</View> | |
) : isSolved ? ( | |
<View style={styles.row}> | |
<View style={[styles.icon, styles.checkbox, styles.solvedCheckbox]}> | |
<Text style={styles.checkmark}>✓</Text> | |
</View> | |
<Text style={styles.text}>Verified</Text> | |
</View> | |
) : ( | |
<TouchableOpacity style={styles.row} onPress={fetchChallenge}> | |
<View style={styles.checkbox}>{isSolved && <Text style={styles.checkmark}>✓</Text>}</View> | |
<Text style={styles.text}>I'm not a robot</Text> | |
</TouchableOpacity> | |
)} | |
<AltchaLogo /> | |
</View> | |
{errorMessage ? <Text style={styles.errorText}>{errorMessage}</Text> : null} | |
<Text style={styles.footerText}>Protected by Altcha</Text> | |
</View> | |
) | |
} | |
const styles = StyleSheet.create({ | |
container: { | |
borderWidth: 1, | |
borderColor: '#DDDDDD', | |
borderRadius: 8, | |
backgroundColor: '#FFFFFF', | |
padding: 16, | |
marginHorizontal: 16, | |
}, | |
row: { | |
flexDirection: 'row', | |
alignItems: 'center', | |
}, | |
rowBetween: { | |
flexDirection: 'row', | |
alignItems: 'center', | |
justifyContent: 'space-between', | |
}, | |
checkbox: { | |
width: 24, | |
height: 24, | |
borderWidth: 1, | |
borderColor: '#DDDDDD', | |
borderRadius: 4, | |
alignItems: 'center', | |
justifyContent: 'center', | |
marginRight: 8, | |
}, | |
solvedCheckbox: { | |
backgroundColor: '#E8F5E9', | |
borderColor: '#4CAF50', | |
}, | |
icon: { | |
width: 24, | |
height: 24, | |
alignItems: 'center', | |
justifyContent: 'center', | |
marginRight: 8, | |
}, | |
checkmark: { | |
color: '#4CAF50', | |
fontSize: 16, | |
fontWeight: 'bold', | |
}, | |
text: { | |
fontSize: 16, | |
color: '#666666', | |
}, | |
errorText: { | |
color: '#F44336', | |
marginTop: 8, | |
fontSize: 14, | |
}, | |
footerText: { | |
color: '#444444', | |
textAlign: 'right', | |
marginTop: 8, | |
fontSize: 12, | |
}, | |
}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment