Updating lnvpn (#160)

* upating vpn countries list data to new endpoint

* update durations text

* updating vpn api endpoints + new response

* fixing vpn duration picker sizing

* standardize file hash name by hashing config data

* updated purchase style

* using flag insted of name
This commit is contained in:
Blake Kaufman
2025-09-18 08:40:42 -04:00
committed by GitHub
parent ddcac310c9
commit 7d518b6a27
10 changed files with 406 additions and 337 deletions
@@ -6,16 +6,13 @@ import VPNDurationSlider from './components/durationSlider';
import CustomButton from '../../../../../functions/CustomElements/button';
import FullLoadingScreen from '../../../../../functions/CustomElements/loadingScreen';
import {useNavigation} from '@react-navigation/native';
import {SATSPERBITCOIN} from '../../../../../constants/math';
import GeneratedFile from './pages/generatedFile';
import {encriptMessage} from '../../../../../functions/messaging/encodingAndDecodingMessages';
import {useGlobalAppData} from '../../../../../../context-store/appData';
import GetThemeColors from '../../../../../hooks/themeColors';
import {useNodeContext} from '../../../../../../context-store/nodeContext';
import {useKeysContext} from '../../../../../../context-store/keys';
import sendStorePayment from '../../../../../functions/apps/payments';
import {useGlobalContextProvider} from '../../../../../../context-store/context';
import {sparkPaymenWrapper} from '../../../../../functions/spark/payments';
import {useSparkWallet} from '../../../../../../context-store/sparkContext';
import {useGlobalInsets} from '../../../../../../context-store/insetsProvider';
import {useActiveCustodyAccount} from '../../../../../../context-store/activeAccount';
@@ -24,13 +21,13 @@ import CountryFlag from 'react-native-country-flag';
import {useGlobalThemeContext} from '../../../../../../context-store/theme';
import {decode} from 'bolt11';
export default function VPNPlanPage({countryList}) {
export default function VPNPlanPage({vpnInformation}) {
const countryList = vpnInformation.countries;
const {theme, darkModeType} = useGlobalThemeContext();
const [searchInput, setSearchInput] = useState('');
const {currentWalletMnemoinc} = useActiveCustodyAccount();
const {sparkInformation} = useSparkWallet();
const {contactsPrivateKey, publicKey} = useKeysContext();
const {fiatStats} = useNodeContext();
const {decodedVPNS, toggleGlobalAppDataInformation} = useGlobalAppData();
const {masterInfoObject} = useGlobalContextProvider();
const [selectedDuration, setSelectedDuration] = useState('week');
@@ -47,25 +44,25 @@ export default function VPNPlanPage({countryList}) {
return (
<TouchableOpacity
onPress={() => {
if (item.country === searchInput) {
if (item.name === searchInput) {
setSearchInput('');
return;
}
setSearchInput(item.country);
setSearchInput(item.name);
}}
style={[
styles.countryItem,
{
borderWidth: 2,
borderColor:
searchInput === item.country
searchInput === item.name
? theme && darkModeType
? COLORS.darkModeText
: COLORS.primary
: 'transparent',
},
]}
key={item.country}>
key={item.name}>
<CountryFlag
style={{marginBottom: 5, borderRadius: 8}}
size={50}
@@ -74,7 +71,7 @@ export default function VPNPlanPage({countryList}) {
<ThemeText
CustomNumberOfLines={1}
styles={styles.countryText}
content={item.country
content={item.name
.replace(/[\u{1F1E6}-\u{1F1FF}]{2}\s*/gu, '')
.replace(/-/g, ' ')}
/>
@@ -86,7 +83,7 @@ export default function VPNPlanPage({countryList}) {
const handleSubmit = useCallback(() => {
const didAddLocation = countryList.filter(item => {
return item.country === searchInput;
return item.name === searchInput;
});
if (didAddLocation.length === 0) {
@@ -97,27 +94,13 @@ export default function VPNPlanPage({countryList}) {
return;
}
const [{cc, country}] = didAddLocation;
const cost = Math.round(
(SATSPERBITCOIN / fiatStats.value) *
(selectedDuration === 'hour'
? 0.1
: selectedDuration === 'day'
? 0.5
: selectedDuration === 'week'
? 1.5
: selectedDuration === 'month'
? 4
: 9),
);
const [{name}] = didAddLocation;
navigate.navigate('CustomHalfModal', {
wantedContent: 'confirmVPN',
country: country,
country: name,
duration: selectedDuration,
createVPN: createVPN,
price: cost,
sliderHight: 0.5,
});
}, [countryList, navigate, selectedDuration, createVPN]);
@@ -148,6 +131,7 @@ export default function VPNPlanPage({countryList}) {
<VPNDurationSlider
setSelectedDuration={setSelectedDuration}
selectedDuration={selectedDuration}
vpnInformation={vpnInformation}
/>
<FlatList
@@ -161,6 +145,7 @@ export default function VPNPlanPage({countryList}) {
data={countryList}
renderItem={flatListElement}
keyExtractor={item => item.isoCode}
showsVerticalScrollIndicator={false}
/>
<CustomButton
@@ -177,77 +162,31 @@ export default function VPNPlanPage({countryList}) {
setIsPaying(true);
let savedVPNConfigs = JSON.parse(JSON.stringify(decodedVPNS));
const [{cc, country}] = countryList.filter(item => {
return item.country === searchInput;
const [{code, name, isoCode}] = countryList.filter(item => {
return item.name === searchInput;
});
try {
let invoice = '';
let invoice = invoiceInformation;
if (
invoiceInformation.payment_request &&
invoiceInformation.payment_hash
invoice.payment_hash &&
invoice.payment_request &&
invoice.paymentIdentifier
) {
invoice = invoiceInformation;
} else {
const response = await fetch('https://lnvpn.net/api/v1/getInvoice', {
method: 'POST',
body: new URLSearchParams({
duration:
selectedDuration === 'hour'
? 0.1
: selectedDuration === 'day'
? 0.5
: selectedDuration === 'week'
? 1.5
: selectedDuration === 'month'
? 4
: 9,
}).toString(),
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
},
});
const responseData = await response.json();
const cost = Math.round(
(SATSPERBITCOIN / fiatStats.value) *
(selectedDuration === 'week'
? 1.5
: selectedDuration === 'month'
? 4
: 9),
);
const fee = await sparkPaymenWrapper({
getFee: true,
address: responseData.payment_request,
paymentType: 'lightning',
amountSats: cost,
masterInfoObject,
sparkInformation,
userBalance: sparkInformation.balance,
mnemonic: currentWalletMnemoinc,
});
if (!fee.didWork) throw new Error(fee.error);
invoice = {
...responseData,
supportFee: fee.supportFee,
fee: fee.fee,
};
}
if (invoice.payment_hash && invoice.payment_request) {
savedVPNConfigs.push({
payment_hash: invoice.payment_hash,
payment_request: invoice.payment_request,
createdTime: new Date(),
duration: selectedDuration,
country: country,
});
setLoadingMessage(
t('apps.VPN.VPNPlanPage.payingInvoiceLoadingMessage'),
);
savedVPNConfigs.push({
payment_hash: invoice.payment_hash,
payment_request: invoice.payment_request,
paymentIdentifier: invoice.paymentIdentifier,
createdTime: new Date(),
duration: selectedDuration,
country: name,
countryCode: code,
isoCode: isoCode,
});
saveVPNConfigsToDB(savedVPNConfigs);
const parsedInvoice = decode(invoice.payment_request);
@@ -276,7 +215,8 @@ export default function VPNPlanPage({countryList}) {
}
getVPNConfig({
paymentHash: invoice.payment_hash,
location: cc,
paymentIdentifier: invoice.paymentIdentifier,
location: code,
savedVPNConfigs,
});
} else {
@@ -293,9 +233,14 @@ export default function VPNPlanPage({countryList}) {
}
}
async function getVPNConfig({paymentHash, location, savedVPNConfigs}) {
async function getVPNConfig({
paymentHash,
paymentIdentifier,
location,
savedVPNConfigs,
}) {
let didSettleInvoice = false;
let runCount = 0;
let runCount = 1;
while (!didSettleInvoice && runCount < 10) {
try {
@@ -307,38 +252,45 @@ export default function VPNPlanPage({countryList}) {
);
runCount += 1;
const response = await fetch(
'https://lnvpn.net/api/v1/getTunnelConfig',
{
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
},
body: new URLSearchParams({
paymentHash,
location: `${location}`,
partnerCode: 'BlitzWallet',
}).toString(),
const apiResponse = await fetch(process.env.LNVPN_CONFIG_DOWNLOAD, {
method: 'POST',
headers: {
Accept: '*/*',
'Content-Type': 'application/json',
},
);
body: JSON.stringify({
paymentIdentifier: paymentIdentifier,
paymentMethod: 'lightning',
country: location,
partnerCode: 'BlitzWallet',
}),
});
const data = await response.json();
const contentType = apiResponse.headers.get('content-type');
if (data.WireguardConfig) {
didSettleInvoice = true;
setGeneratedFile(data.WireguardConfig);
const updatedList = savedVPNConfigs.map(item => {
if (item.payment_hash === paymentHash) {
return {...item, config: data.WireguardConfig};
} else return item;
});
saveVPNConfigsToDB(updatedList);
let dataResponse;
if (contentType && contentType.includes('application/json')) {
dataResponse = await apiResponse.json();
} else {
console.log('Wating for confirmation...');
await new Promise(resolve => setTimeout(resolve, 12000));
dataResponse = await apiResponse.text();
}
if (dataResponse?.error || apiResponse.status !== 200)
throw new Error('Error with backend', dataResponse?.error);
didSettleInvoice = true;
const configFile =
typeof dataResponse === 'string'
? dataResponse
: dataResponse.data.config; // make sure to match with actual api
const updatedList = savedVPNConfigs.map(item => {
if (item.payment_hash === paymentHash) {
return {...item, config: configFile};
} else return item;
});
await saveVPNConfigsToDB(updatedList);
setGeneratedFile(configFile);
} catch (err) {
console.log(err);
console.log('Wating for confirmation...');
@@ -350,7 +302,6 @@ export default function VPNPlanPage({countryList}) {
errorMessage: t('apps.VPN.VPNPlanPage.configError'),
});
setIsPaying(false);
return;
}
}
@@ -31,34 +31,31 @@ export default function ConfirmVPNPage(props) {
useEffect(() => {
async function fetchInvoice() {
try {
const response = await fetch('https://lnvpn.net/api/v1/getInvoice', {
const response = await fetch(process.env.LNVPN_PURCHASE_REQUEST, {
method: 'POST',
body: new URLSearchParams({
duration:
duration === 'hour'
? 0.1
: duration === 'day'
? 0.5
: duration === 'week'
? 1.5
: duration === 'month'
? 4
: 9,
}).toString(),
body: JSON.stringify({
duration: duration,
paymentMethod: 'lightning',
refCode: 'BlitzWallet',
}),
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
Accept: '*/*',
'Content-Type': 'application/json',
},
});
const invoice = await response.json();
if (!invoice || !invoice.payment_hash || !invoice.payment_request)
if (!invoice.success)
throw new Error(t('apps.VPN.confirmationSlideUp.invoiceInfoError'));
const {data} = invoice;
if (!data.payment_hash || !data.payment_request)
throw new Error(t('apps.VPN.confirmationSlideUp.invoiceInfoError'));
const parsedInvoice = decode(invoice.payment_request);
const parsedInvoice = decode(data.payment_request);
const fee = await sparkPaymenWrapper({
getFee: true,
address: invoice.payment_request,
address: data.payment_request,
paymentType: 'lightning',
amountSats: parsedInvoice.satoshis,
masterInfoObject,
@@ -74,8 +71,9 @@ export default function ConfirmVPNPage(props) {
}
setInvoiceInformation({
payment_hash: invoice.payment_hash,
payment_request: invoice.payment_request,
payment_hash: data.payment_hash,
payment_request: data.payment_request,
paymentIdentifier: data.paymentIdentifier,
supportFee: fee.supportFee,
fee: fee.fee,
price: parsedInvoice.satoshis,
@@ -121,7 +119,7 @@ export default function ConfirmVPNPage(props) {
/>
<ThemeText
styles={{marginTop: 5}}
content={'1 ' + t(`constants.${duration.toLowerCase()}`)}
content={'1 ' + t(`apps.VPN.durationSlider.${duration}`)}
/>
<FormattedSatText
neverHideBalance={true}
@@ -9,76 +9,49 @@ import {useTranslation} from 'react-i18next';
export default function VPNDurationSlider({
setSelectedDuration,
selectedDuration,
vpnInformation,
}) {
const {theme, darkModeType} = useGlobalThemeContext();
const {textColor} = GetThemeColors();
const {t} = useTranslation();
const durations = vpnInformation.durations || [];
const durationOption = useMemo(() => {
return [
[
t('apps.VPN.durationSlider.durationOption', {
duration: t('constants.hour'),
}),
'hour',
],
[
t('apps.VPN.durationSlider.durationOption', {
duration: t('constants.day'),
}),
'day',
],
[
t('apps.VPN.durationSlider.durationOption', {
duration: t('constants.week'),
}),
'week',
],
[
t('apps.VPN.durationSlider.durationOption', {
duration: t('constants.month'),
}),
'month',
],
[
t('apps.VPN.durationSlider.durationOption', {
duration: t('constants.quarter'),
}),
'quarter',
],
].map(item => {
const [name, itemSelector] = item;
return durations.map(item => {
const {duration} = item;
return (
<TouchableOpacity
onPress={() => setSelectedDuration(itemSelector)}
onPress={() => setSelectedDuration(duration)}
style={{
...styles.durationButton,
borderColor: theme ? COLORS.darkModeText : COLORS.primary,
backgroundColor:
selectedDuration === itemSelector
selectedDuration === duration
? theme
? COLORS.darkModeText
: COLORS.primary
: 'transparent',
}}
key={name}>
key={duration}>
<ThemeText
styles={{
padding: 10,
color:
selectedDuration === itemSelector
selectedDuration === duration
? theme
? COLORS.lightModeText
: COLORS.darkModeText
: textColor,
includeFontPadding: false,
}}
content={name}
content={t('apps.VPN.durationSlider.durationOption', {
duration: t(`apps.VPN.durationSlider.${duration}`),
})}
/>
</TouchableOpacity>
);
});
}, [selectedDuration, theme]);
}, [selectedDuration, theme, durations]);
return (
<View style={styles.durationContainer}>
@@ -86,7 +59,13 @@ export default function VPNDurationSlider({
styles={{...styles.infoHeaders}}
content={t('apps.VPN.durationSlider.duration')}
/>
<View style={styles.durationInnerContianer}>{durationOption}</View>
<View style={styles.durationInnerContianer}>
{durationOption.length ? (
durationOption
) : (
<ThemeText content={t('apps.VPN.durationSlider.noDurations')} />
)}
</View>
</View>
);
}
@@ -97,14 +76,14 @@ const styles = StyleSheet.create({
alignItems: 'center',
},
durationInnerContianer: {
columnGap: 10,
rowGap: 10,
gap: 10,
flexDirection: 'row',
flexWrap: 'wrap',
},
durationButton: {
borderWidth: 2,
flexGrow: 1,
minWidth: '45%',
alignItems: 'center',
justifyContent: 'center',
borderRadius: 8,
@@ -3,8 +3,8 @@ import {
GlobalThemeView,
ThemeText,
} from '../../../../../functions/CustomElements';
import {WINDOWWIDTH} from '../../../../../constants/theme';
import {CENTER} from '../../../../../constants';
import {COLORS, SIZES} from '../../../../../constants/theme';
import {CENTER, CONTENT_KEYBOARD_OFFSET} from '../../../../../constants';
import {useNavigation} from '@react-navigation/native';
import {useEffect, useState} from 'react';
import {copyToClipboard, getLocalStorageItem} from '../../../../../functions';
@@ -17,6 +17,9 @@ import FullLoadingScreen from '../../../../../functions/CustomElements/loadingSc
import openWebBrowser from '../../../../../functions/openWebBrowser';
import {useToast} from '../../../../../../context-store/toastManager';
import {useTranslation} from 'react-i18next';
import {useGlobalThemeContext} from '../../../../../../context-store/theme';
import GetThemeColors from '../../../../../hooks/themeColors';
import CountryFlag from 'react-native-country-flag';
export default function HistoricalVPNPurchases() {
const {showToast} = useToast();
@@ -26,6 +29,8 @@ export default function HistoricalVPNPurchases() {
const {contactsPrivateKey, publicKey} = useKeysContext();
const [isRetryingConfig, setIsRetryingConfig] = useState(false);
const {t} = useTranslation();
const {theme} = useGlobalThemeContext();
const {backgroundOffset, backgroundColor} = GetThemeColors();
useEffect(() => {
async function getSavedPurchases() {
@@ -43,7 +48,10 @@ export default function HistoricalVPNPurchases() {
return (
<TouchableOpacity
key={item.createdTime}
style={styles.container}
style={[
styles.purchaseCard,
{backgroundColor: theme ? backgroundOffset : COLORS.darkModeText},
]}
onPress={() => handleConfigClick(item)}
onLongPress={() => {
navigate.navigate('ConfirmActionPage', {
@@ -53,86 +61,95 @@ export default function HistoricalVPNPurchases() {
confirmFunction: () => removeVPNFromList(item.payment_hash),
});
}}>
<View style={styles.infoContainer}>
<View style={styles.cardHeader}>
<View style={[styles.countryBadge, {backgroundColor}]}>
{item.isoCode ? (
<CountryFlag size={15} isoCode={item.isoCode} />
) : (
<ThemeText
styles={styles.dateText}
content={
item.country
?.replace(/[\u{1F1E6}-\u{1F1FF}]{2}\s*/gu, '')
?.replace(/-/g, ' ') || ''
}
/>
)}
</View>
<ThemeText
styles={{...styles.label}}
content={t('apps.VPN.historicalPurchasesPage.country')}
/>
<ThemeText styles={{...styles.value}} content={item.country} />
</View>
<View style={styles.infoContainer}>
<ThemeText
styles={{...styles.label}}
content={t('apps.VPN.historicalPurchasesPage.createdAt')}
/>
<ThemeText
styles={{...styles.value}}
content={new Date(item.createdTime).toLocaleString()}
styles={styles.dateText}
content={new Date(item.createdTime).toLocaleDateString()}
/>
</View>
<View style={styles.infoContainer}>
<View style={styles.durationContainer}>
<ThemeText
styles={{...styles.label}}
content={t('apps.VPN.historicalPurchasesPage.duration')}
/>
<ThemeText
styles={{...styles.value}}
content={t(t(`constants.${item.duration?.toLowerCase()}`))}
styles={styles.durationText}
content={
typeof item.duration === 'string'
? t(`constants.${item.duration?.toLowerCase()}`)
: t(`apps.VPN.durationSlider.${item.duration}`)
}
/>
</View>
<TouchableOpacity
onPress={() => {
copyToClipboard(item.payment_hash, showToast);
}}
style={styles.infoContainer}>
style={styles.hashContainer}>
<ThemeText
styles={{...styles.label}}
styles={styles.hashLabel}
content={t('apps.VPN.historicalPurchasesPage.paymentHash')}
/>
<ThemeText
CustomNumberOfLines={2}
styles={{...styles.value}}
content={`${item.payment_hash}`}
/>
<View style={[styles.hashBox, {backgroundColor}]}>
<ThemeText
CustomNumberOfLines={1}
styles={styles.hashText}
content={`${item.payment_hash}`}
/>
</View>
</TouchableOpacity>
</TouchableOpacity>
);
})
.filter(Boolean);
return (
<GlobalThemeView>
<View style={styles.globalContainer}>
<CustomSettingsTopBar
containerStyles={{
marginBottom: 0,
}}
label={t('apps.VPN.historicalPurchasesPage.title')}
<GlobalThemeView useStandardWidth={true}>
<CustomSettingsTopBar
containerStyles={styles.topBarStyle}
label={t('apps.VPN.historicalPurchasesPage.title')}
/>
{isRetryingConfig ? (
<FullLoadingScreen
text={t('apps.VPN.historicalPurchasesPage.retryClaim')}
/>
{isRetryingConfig ? (
<FullLoadingScreen
text={t('apps.VPN.historicalPurchasesPage.retryClaim')}
) : purchaseElements.length === 0 ? (
<View style={styles.emptyState}>
<ThemeText
styles={styles.emptyText}
content={t('apps.VPN.historicalPurchasesPage.noPurchases')}
/>
) : purchaseElements.length === 0 ? (
<View
style={{flex: 1, alignItems: 'center', justifyContent: 'center'}}>
</View>
) : (
<>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={styles.scrollContainer}
style={styles.scrollView}>
{purchaseElements}
</ScrollView>
<View style={styles.bottomSection}>
<ThemeText
content={t('apps.VPN.historicalPurchasesPage.noPurchases')}
/>
</View>
) : (
<>
<ScrollView
showsVerticalScrollIndicator={false}
contentContainerStyle={{paddingVertical: 30}}
style={{width: '90%', ...CENTER}}>
{purchaseElements}
</ScrollView>
<ThemeText
styles={{textAlign: 'center', paddingTop: 5}}
styles={styles.assistanceText}
content={t('apps.VPN.historicalPurchasesPage.assistanceText')}
/>
<CustomButton
buttonStyles={{...CENTER, marginTop: 10}}
buttonStyles={styles.contactButton}
textContent={t('apps.VPN.historicalPurchasesPage.contact')}
actionFunction={async () => {
await openWebBrowser({
@@ -141,9 +158,9 @@ export default function HistoricalVPNPurchases() {
});
}}
/>
</>
)}
</View>
</View>
</>
)}
</GlobalThemeView>
);
@@ -154,96 +171,123 @@ export default function HistoricalVPNPurchases() {
});
else {
setIsRetryingConfig(true);
(async () => {
const response = await getConfig(item.payment_hash, item.country);
if (response.didWork) {
const newCardsList = decodedVPNS
?.map(vpn => {
if (vpn.payment_hash === item.payment_hash) {
return {...vpn, config: response.config};
} else return vpn;
})
.filter(Boolean);
const em = encriptMessage(
contactsPrivateKey,
publicKey,
JSON.stringify(newCardsList),
);
toggleGlobalAppDataInformation({VPNplans: em}, true);
const response = await getConfig(
item.paymentIdentifier || item.payment_hash,
item.country,
item.countryCode,
);
if (response.didWork) {
const newCardsList = decodedVPNS
?.map(vpn => {
if (vpn.payment_hash === item.payment_hash) {
return {...vpn, config: response.config};
} else return vpn;
})
.filter(Boolean);
const em = encriptMessage(
contactsPrivateKey,
publicKey,
JSON.stringify(newCardsList),
);
toggleGlobalAppDataInformation({VPNplans: em}, true);
requestAnimationFrame(() => {
requestAnimationFrame(() => {
requestAnimationFrame(() => {
navigate.navigate('GeneratedVPNFile', {
generatedFile: response.config,
});
navigate.navigate('GeneratedVPNFile', {
generatedFile: response.config,
});
});
setIsRetryingConfig(false);
return;
}
});
setIsRetryingConfig(false);
} else {
setIsRetryingConfig(false);
navigate.navigate('ErrorScreen', {
errorMessage: response.error,
});
})();
}
}
}
async function getConfig(paymentHash, location) {
async function getConfig(payment_hash, location, countryCode) {
try {
const countriesListResponse = await fetch(
'https://lnvpn.net/api/v1/countryList',
{
method: 'GET',
},
);
const countriesList = await countriesListResponse.json();
console.log(countriesList);
const [{cc}] = countriesList.filter(item => {
console.log(item.country, location);
return isCountryMatch(item.country, location);
});
console.log(cc);
if (!cc) {
return {
didWork: false,
error: t('apps.VPN.historicalPurchasesPage.noValidCountryCodeError'),
};
let countryCodeIdentifier = '';
if (!countryCode) {
const countriesListResponse = await fetch(
process.env.LNVPN_COUNTRY_LIST,
{
method: 'GET',
},
);
const countriesList = await countriesListResponse.json();
if (
countriesListResponse.status !== 200 ||
!countriesList?.data?.countries
) {
return {
didWork: false,
error: t(
'apps.VPN.historicalPurchasesPage.noValidCountryCodeError',
),
};
}
const [{code}] = countriesList.data.countries.filter(item => {
console.log(item, location);
return isCountryMatch(item.name, location);
});
if (!code) {
return {
didWork: false,
error: t(
'apps.VPN.historicalPurchasesPage.noValidCountryCodeError',
),
};
}
countryCodeIdentifier = code;
} else {
countryCodeIdentifier = countryCode;
}
const response = await fetch('https://lnvpn.net/api/v1/getTunnelConfig', {
const response = await fetch(process.env.LNVPN_CONFIG_DOWNLOAD, {
method: 'POST',
headers: {
Accept: 'application/json',
'Content-Type': 'application/x-www-form-urlencoded',
Accept: '*/*',
'Content-Type': 'application/json',
},
body: new URLSearchParams({
paymentHash,
location: `${cc}`,
body: JSON.stringify({
paymentIdentifier: payment_hash,
paymentMethod: 'lightning',
country: countryCodeIdentifier,
partnerCode: 'BlitzWallet',
}).toString(),
}),
});
const data = await response.json();
if (!data.WireguardConfig) {
const contentType = response.headers.get('content-type');
let dataResponse;
if (contentType && contentType.includes('application/json')) {
dataResponse = await response.json();
} else {
dataResponse = await response.text();
}
if (dataResponse?.error || response.status !== 200) {
return {
didWork: false,
error:
data?.error ||
dataResponse?.error ||
t('apps.VPN.historicalPurchasesPage.claimConfigError'),
};
}
return {didWork: true, config: data.WireguardConfig};
const configFile =
typeof dataResponse === 'string'
? dataResponse
: dataResponse.data.config;
return {didWork: true, config: configFile};
} catch (err) {
console.log(err);
return {didWork: false, error: String(err)};
}
}
function removeVPNFromList(selctedVPN) {
const newCardsList = decodedVPNS?.filter(
vpn => vpn.payment_hash !== selctedVPN,
);
const em = encriptMessage(
contactsPrivateKey,
publicKey,
@@ -252,6 +296,7 @@ export default function HistoricalVPNPurchases() {
toggleGlobalAppDataInformation({VPNplans: em}, true);
}
}
function isCountryMatch(selected, text) {
// Normalize by removing flags, making lowercase, and replacing hyphens with spaces
const normalize = str =>
@@ -259,29 +304,89 @@ function isCountryMatch(selected, text) {
.replace(/[\uD800-\uDBFF][\uDC00-\uDFFF]/g, '') // Remove emoji flags
.toLowerCase()
.replace(/[-\s]+/g, ' '); // Normalize spaces and hyphens
return normalize(text).includes(normalize(selected));
}
const styles = StyleSheet.create({
globalContainer: {
topBarStyle: {
marginBottom: 0,
},
scrollView: {
width: '100%',
flex: 1,
width: WINDOWWIDTH,
...CENTER,
},
container: {
marginVertical: 10,
scrollContainer: {
paddingHorizontal: 20,
paddingTop: 24,
paddingBottom: 100,
},
infoContainer: {
// Purchase Card Styles
purchaseCard: {
borderRadius: 16,
marginBottom: 16,
padding: 20,
},
cardHeader: {
flexDirection: 'row',
marginBottom: 10,
justifyContent: 'space-between',
alignItems: 'center',
marginBottom: 16,
},
label: {
fontWeight: 'bold',
countryBadge: {
flexShrink: 1,
paddingHorizontal: 12,
paddingVertical: 6,
borderRadius: 20,
marginRight: 10,
},
value: {
countryText: {},
dateText: {
fontSize: SIZES.small,
},
durationContainer: {
marginBottom: 16,
},
durationText: {
fontSize: 14,
},
hashContainer: {
marginTop: 4,
},
hashLabel: {
fontSize: 12,
marginBottom: 6,
},
hashBox: {
borderRadius: 8,
paddingHorizontal: 12,
paddingVertical: 8,
},
hashText: {
fontSize: SIZES.small,
opacity: 0.7,
},
// Empty State Styles
emptyState: {
flex: 1,
flexWrap: 'wrap',
alignItems: 'center',
justifyContent: 'center',
},
emptyText: {
textAlign: 'center',
},
// Bottom Section Styles
bottomSection: {marginTop: CONTENT_KEYBOARD_OFFSET},
assistanceText: {
textAlign: 'center',
opacity: 0.7,
marginBottom: 16,
lineHeight: 20,
},
contactButton: {
...CENTER,
},
});
@@ -29,23 +29,24 @@ export default function VPNHome() {
const navigate = useNavigation();
const {theme, darkModeType} = useGlobalThemeContext();
const [selectedPage, setSelectedPage] = useState(null);
const [countryList, setCountriesList] = useState([]);
const [vpnInformation, setVpnInformation] = useState({
countries: [],
durations: [],
});
const {t} = useTranslation();
useEffect(() => {
async function getAvailableCountries() {
try {
const response = await fetch('https://lnvpn.net/api/v1/countryList', {
const response = await fetch(process.env.LNVPN_COUNTRY_LIST, {
method: 'GET',
});
const data = await response.json();
setCountriesList(data);
if (data.success) {
setVpnInformation(data.data);
} else throw new Error('Unable to fetch vpn information');
} catch (err) {
navigate.navigate('ErrorScreen', {
errorMessage: t('apps.VPN.home.apiConnectionError'),
customNavigator: () => {
navigate.popTo('HomeAdmin');
},
});
console.log(err);
}
@@ -112,15 +113,22 @@ export default function VPNHome() {
<CustomButton
buttonStyles={{width: '80%', marginTop: 50}}
actionFunction={() => {
if (!countryList.length) return;
if (
!vpnInformation.countries.length ||
!vpnInformation.durations.length
)
return;
setSelectedPage('Select Plan');
}}
textContent={t('constants.continue')}
useLoading={!countryList.length}
useLoading={
!vpnInformation.countries.length ||
!vpnInformation.durations.length
}
/>
</View>
) : (
<VPNPlanPage countryList={countryList} />
<VPNPlanPage vpnInformation={vpnInformation} />
)}
</View>
</CustomKeyboardAvoidingView>
@@ -17,13 +17,14 @@ import {
INSET_WINDOW_WIDTH,
WINDOWWIDTH,
} from '../../../../../../constants/theme';
import {backArrow} from '../../../../../../constants/styles';
import GetThemeColors from '../../../../../../hooks/themeColors';
import QrCodeWrapper from '../../../../../../functions/CustomElements/QrWrapper';
import writeAndShareFileToFilesystem from '../../../../../../functions/writeFileToFilesystem';
import {useToast} from '../../../../../../../context-store/toastManager';
import {useTranslation} from 'react-i18next';
import CustomSettingsTopBar from '../../../../../../functions/CustomElements/settingsTopBar';
import customUUID from '../../../../../../functions/customUUID';
import sha256Hash from '../../../../../../functions/hash';
export default function GeneratedVPNFile(props) {
const generatedFile =
@@ -52,7 +53,12 @@ function VPNFileDisplay({generatedFile}) {
const navigate = useNavigation();
const {backgroundOffset} = GetThemeColors();
const {t} = useTranslation();
console.log(generatedFile);
console.log(generatedFile, typeof generatedFile);
const configData =
typeof generatedFile === 'string'
? generatedFile
: generatedFile.join('\n');
return (
<>
@@ -63,9 +69,9 @@ function VPNFileDisplay({generatedFile}) {
<TouchableOpacity
onPress={() => {
copyToClipboard(generatedFile.join('\n'), showToast);
copyToClipboard(configData, showToast);
}}>
<QrCodeWrapper QRData={generatedFile.join('\n')} />
<QrCodeWrapper QRData={configData} />
</TouchableOpacity>
<View style={styles.copyButtonsContainer}>
@@ -73,14 +79,14 @@ function VPNFileDisplay({generatedFile}) {
buttonStyles={styles.buttonContainer}
textContent={t('constants.download')}
actionFunction={() => {
downloadVPNFile({generatedFile, navigate});
downloadVPNFile({generatedFile: configData, navigate});
}}
/>
<CustomButton
buttonStyles={styles.buttonContainer}
textContent={t('constants.copy')}
actionFunction={() => {
copyToClipboard(generatedFile.join('\n'), showToast);
copyToClipboard(configData, showToast);
}}
/>
</View>
@@ -97,8 +103,9 @@ function VPNFileDisplay({generatedFile}) {
}
async function downloadVPNFile({generatedFile, navigate}) {
const content = generatedFile.join('\n');
const fileName = `blitzVPN.conf`;
const content = generatedFile;
const fileHash = sha256Hash(content);
const fileName = `blitzVPN-${fileHash?.slice(0, 8) || customUUID()}.conf`;
const response = await writeAndShareFileToFilesystem(
content,
+1 -1
View File
@@ -316,7 +316,7 @@ async function handleSupportPayment(masterInfoObject, supportFee, mnemonic) {
});
await Promise.race([
txPromise,
new Promise(res => setTimeout(res, 10000)),
new Promise(res => setTimeout(res, 30000)),
]);
txPromise.catch(err =>
console.log('Error sending support payment (late)', err),
+8 -1
View File
@@ -290,7 +290,14 @@
"durationSlider": {
"durationOption": "1 {{duration}}",
"price": "Price: ",
"duration": "Duration"
"noDurations": "No available durations",
"duration": "Duration",
"0.1": "hour",
"0.5": "day",
"1.5": "week",
"3": "month",
"8": "quarter",
"30": "year"
},
"historicalPurchasesPage": {
"deleteVPNConfirmMessage": "Are you sure you want to remove this VPN.",
+8 -1
View File
@@ -290,7 +290,14 @@
"durationSlider": {
"durationOption": "1 {{duration}}",
"price": "Prezzo: ",
"duration": "Durata"
"noDurations": "Nessuna durata disponibile",
"duration": "Durata",
"0.1": "hora",
"0.5": "día",
"1.5": "semana",
"3": "mes",
"8": "trimestre",
"30": "año"
},
"historicalPurchasesPage": {
"deleteVPNConfirmMessage": "Sei sicuro di voler rimuovere questa VPN?",
+8 -1
View File
@@ -284,7 +284,14 @@
"durationSlider": {
"durationOption": "1 {{duration}}",
"price": "Precio: ",
"duration": "Duración"
"noDurations": "No hay duraciones disponibles",
"duration": "Duración",
"0.1": "ora",
"0.5": "giorno",
"1.5": "settimana",
"3": "mese",
"8": "trimestre",
"30": "anno"
},
"historicalPurchasesPage": {
"deleteVPNConfirmMessage": "¿Estás seguro de que quieres eliminar este VPN?",