feat: drop rn-qr-generator depenedency

This commit is contained in:
Ivan Vershigora 2026-04-26 11:28:58 +01:00
parent f82de26f59
commit 15319ed2e6
No known key found for this signature in database
GPG Key ID: DCCF7FB5ED2CEBD7
7 changed files with 56 additions and 126 deletions

View File

@ -2,7 +2,7 @@ import { Platform } from 'react-native';
import { pick, types, keepLocalCopy, errorCodes } from '@react-native-documents/picker';
import RNFS from 'react-native-fs';
import { launchImageLibrary, ImagePickerResponse } from 'react-native-image-picker';
import RNQRGenerator from 'rn-qr-generator';
import { detectQRCodeInImage } from 'react-native-camera-kit-no-google';
import Share from 'react-native-share';
import presentAlert from '../components/Alert';
@ -113,6 +113,7 @@ export const showImagePickerAndReadImage = async (): Promise<string | undefined>
maxHeight: 800,
maxWidth: 600,
selectionLimit: 1,
includeBase64: true,
});
if (response.didCancel) {
@ -120,19 +121,12 @@ export const showImagePickerAndReadImage = async (): Promise<string | undefined>
} else if (response.errorCode) {
throw new Error(response.errorMessage);
} else if (response.assets) {
try {
const uri = response.assets[0].uri;
if (uri) {
const result = await RNQRGenerator.detect({ uri: decodeURI(uri.toString()) });
if (result?.values.length > 0) {
return result?.values[0];
}
}
throw new Error(loc.send.qr_error_no_qrcode);
} catch (error) {
console.error(error);
presentAlert({ message: loc.send.qr_error_no_qrcode });
const base64 = response.assets[0].base64;
if (base64) {
const result = await detectQRCodeInImage(base64);
if (result) return result;
}
throw new Error(loc.send.qr_error_no_qrcode);
}
return undefined;
@ -186,33 +180,23 @@ export const showFilePickerAndReadFile = async function (): Promise<{ data: stri
}
};
const handleImageFile = async (fileCopyUri: string): Promise<{ data: string | false; uri: string | false }> => {
const readFileAsBase64 = async (uri: string): Promise<string> => {
try {
const exists = await RNFS.exists(fileCopyUri);
if (!exists) {
presentAlert({ message: 'File does not exist' });
return { data: false, uri: false };
}
// First attempt: use original URI
let result = await RNQRGenerator.detect({ uri: decodeURI(fileCopyUri) });
if (result?.values && result.values.length > 0) {
return { data: result.values[0], uri: fileCopyUri };
}
// Second attempt: remove file:// prefix and try again
const altUri = fileCopyUri.replace(/^file:\/\//, '');
result = await RNQRGenerator.detect({ uri: decodeURI(altUri) });
if (result?.values && result.values.length > 0) {
return { data: result.values[0], uri: fileCopyUri };
}
presentAlert({ message: loc.send.qr_error_no_qrcode });
return { data: false, uri: false };
} catch (error: any) {
console.error(error);
presentAlert({ message: loc.send.qr_error_no_qrcode });
return { data: false, uri: false };
return await RNFS.readFile(uri, 'base64');
} catch {
return await RNFS.readFile(uri.replace(/^file:\/\//, ''), 'base64');
}
};
const handleImageFile = async (fileCopyUri: string): Promise<{ data: string | false; uri: string | false }> => {
const base64 = await readFileAsBase64(fileCopyUri);
const result = await detectQRCodeInImage(base64);
if (result) {
return { data: result, uri: fileCopyUri };
}
throw new Error(loc.send.qr_error_no_qrcode);
};
export const readFileOutsideSandbox = (filePath: string) => {
if (Platform.OS === 'ios') {
return readFile(filePath);

View File

@ -6,7 +6,7 @@ import loc from '../loc';
import { showFilePickerAndReadFile, showImagePickerAndReadImage } from '../blue_modules/fs';
import presentAlert from './Alert';
import { useTheme } from './themes';
import RNQRGenerator from 'rn-qr-generator';
import { detectQRCodeInImage } from 'react-native-camera-kit-no-google';
import { CommonToolTipActions } from '../typings/CommonToolTipActions';
import { useSettings } from '../hooks/context/useSettings';
import { scanQrHelper } from '../helpers/scan-qr.ts';
@ -79,12 +79,9 @@ export const AddressInputScanButton = ({
if (getImage) {
try {
const base64Data = getImage.replace(/^data:image\/(png|jpeg|jpg);base64,/, '');
const values = await RNQRGenerator.detect({
base64: base64Data,
});
if (values && values.values.length > 0) {
onChangeText(values.values[0]);
const result = await detectQRCodeInImage(base64Data);
if (result) {
onChangeText(result);
} else {
presentAlert({ message: loc.send.qr_error_no_qrcode });
}

View File

@ -19,7 +19,8 @@ import { Chain } from '../models/bitcoinUnits';
import { navigationRef } from '../NavigationService';
import ActionSheet from '../screen/ActionSheet';
import { useStorage } from './context/useStorage';
import RNQRGenerator from 'rn-qr-generator';
import { detectQRCodeInImage } from 'react-native-camera-kit-no-google';
import RNFS from 'react-native-fs';
import presentAlert from '../components/Alert';
import useWidgetCommunication from './useWidgetCommunication';
import useWatchConnectivity from './useWatchConnectivity';
@ -202,35 +203,27 @@ const useCompanionListeners = (skipIfNotInitialized = true) => {
}
const fileName = decodedUrl.split('/').pop()?.toLowerCase() || '';
if (/\.(jpe?g|png)$/i.test(fileName)) {
let qrResult;
let base64: string;
try {
qrResult = await RNQRGenerator.detect({ uri: decodedUrl });
} catch (e) {
console.error('QR detection first attempt failed:', e);
base64 = await RNFS.readFile(decodedUrl, 'base64');
} catch {
base64 = await RNFS.readFile(decodedUrl.replace(/^file:\/\//, ''), 'base64');
}
if (!qrResult || !qrResult.values || qrResult.values.length === 0) {
const altUrl = decodedUrl.replace(/^file:\/\//, '');
try {
qrResult = await RNQRGenerator.detect({ uri: altUrl });
} catch (e) {
console.error('QR detection second attempt failed:', e);
}
}
if (qrResult?.values?.length) {
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
DeeplinkSchemaMatch.navigationRouteFor(
{ url: qrResult.values[0] },
(value: [string, any]) => navigationRef.navigate(...value),
{
wallets,
addWallet,
saveToDisk,
setSharedCosigner,
},
);
} else {
const qrValue = await detectQRCodeInImage(base64);
if (!qrValue) {
throw new Error(loc.send.qr_error_no_qrcode);
}
triggerHapticFeedback(HapticFeedbackTypes.NotificationSuccess);
DeeplinkSchemaMatch.navigationRouteFor(
{ url: qrValue },
(value: [string, any]) => navigationRef.navigate(...value),
{
wallets,
addWallet,
saveToDisk,
setSharedCosigner,
},
);
} else {
DeeplinkSchemaMatch.navigationRouteFor(event, (value: [string, any]) => navigationRef.navigate(...value), {
wallets,

View File

@ -2156,29 +2156,6 @@ PODS:
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- RNQrGenerator (2.0.0):
- hermes-engine
- RCTRequired
- RCTTypeSafety
- React-Core
- React-Core-prebuilt
- React-debug
- React-Fabric
- React-featureflags
- React-graphics
- React-ImageManager
- React-jsi
- React-NativeModulesApple
- React-RCTFabric
- React-renderercss
- React-rendererdebug
- React-utils
- ReactCodegen
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- ReactNativeDependencies
- Yoga
- ZXingObjC
- RNQuickAction (0.3.13):
- React
- RNReactNativeHapticFeedback (2.3.4):
@ -2465,9 +2442,6 @@ PODS:
- ReactNativeDependencies
- Yoga
- Yoga (0.0.0)
- ZXingObjC (3.6.9):
- ZXingObjC/All (= 3.6.9)
- ZXingObjC/All (3.6.9)
DEPENDENCIES:
- "BugsnagReactNative (from `../node_modules/@bugsnag/react-native`)"
@ -2576,7 +2550,6 @@ DEPENDENCIES:
- RNKeychain (from `../node_modules/react-native-keychain`)
- RNLocalize (from `../node_modules/react-native-localize`)
- RNPermissions (from `../node_modules/react-native-permissions`)
- RNQrGenerator (from `../node_modules/rn-qr-generator`)
- RNQuickAction (from `../node_modules/react-native-quick-actions`)
- RNReactNativeHapticFeedback (from `../node_modules/react-native-haptic-feedback`)
- RNReanimated (from `../node_modules/react-native-reanimated`)
@ -2591,7 +2564,6 @@ SPEC REPOS:
trunk:
- CocoaAsyncSocket
- lottie-ios
- ZXingObjC
EXTERNAL SOURCES:
BugsnagReactNative:
@ -2805,8 +2777,6 @@ EXTERNAL SOURCES:
:path: "../node_modules/react-native-localize"
RNPermissions:
:path: "../node_modules/react-native-permissions"
RNQrGenerator:
:path: "../node_modules/rn-qr-generator"
RNQuickAction:
:path: "../node_modules/react-native-quick-actions"
RNReactNativeHapticFeedback:
@ -2831,7 +2801,7 @@ SPEC CHECKSUMS:
BVLinearGradient: cb006ba232a1f3e4f341bb62c42d1098c284da70
CocoaAsyncSocket: 065fd1e645c7abab64f7a6a2007a48038fdc6a99
FBLazyVector: e97c19a5a442429d1988f182a1940fb08df514da
hermes-engine: 26a52ac276936854ac2797880e2c181aa1fb0025
hermes-engine: 5b6b255f9e4ba0f6f699433393c9ca57c402fc96
lottie-ios: 8f959969761e9c45d70353667d00af0e5b9cadb3
lottie-react-native: 6a080b2f109ef611c75c503a33ebb8ea75db0c91
RCTDeprecation: af44b104091a34482596cd9bd7e8d90c4e9b4bd7
@ -2842,7 +2812,7 @@ SPEC CHECKSUMS:
React: 1ba7d364ade7d883a1ec055bfc3606f35fdee17b
React-callinvoker: bc2a26f8d84fb01f003fc6de6c9337b64715f95b
React-Core: 7840d3a80b43a95c5e80ef75146bd70925ebab0f
React-Core-prebuilt: 96b4b7f11b336f3b9d498eca3460c82ee260b31e
React-Core-prebuilt: fe445abdc8b577160a3ecf632edd6545f00f10cf
React-CoreModules: 2eb010400b63b89e53a324ffb3c112e4c7c3ce42
React-cxxreact: a558e92199d26f145afa9e62c4233cf8e7950efe
React-debug: 755200a6e7f5e6e0a40ff8d215493d43cce285fc
@ -2922,7 +2892,7 @@ SPEC CHECKSUMS:
ReactCodegen: c8cd59a80d11b8c2f8e70fab3cdc62673ae56f1b
ReactCommon: 07572bf9e687c8a52fbe4a3641e9e3a1a477c78e
ReactNativeCameraKit: 2f24ad111e0f525a6529dd0eff5bf5d4454a24ba
ReactNativeDependencies: d91ea5381a1df88b4bc29bfb8a525ececa743c3d
ReactNativeDependencies: 9f044a84d7bddd9822da88447b4a37e7cc085862
RealmJS: 1c37c6bdfe060f4caa0f9175aa0eedb962622ee1
RNCAsyncStorage: 3a4f5e2777dae1688b781a487923a08569e27fe4
RNCClipboard: 88d7eeb555d1183915f0885bdbc5c97eb6f7f3ba
@ -2934,7 +2904,6 @@ SPEC CHECKSUMS:
RNKeychain: 667b7bd224f14055ae4cb52b41a82ad0ad70574f
RNLocalize: 1e6eb4290e89502c3d2193d82dfb7de14a9e672b
RNPermissions: 71ff057a9f7607de93c7b4e27db8a76cf03d2367
RNQrGenerator: dd31405ad364fc0950cda4e4e9c08293d26c7058
RNQuickAction: c2c8f379e614428be0babe4d53a575739667744d
RNReactNativeHapticFeedback: d6666654afe70a15d04ecb6257809a4081fab84c
RNReanimated: 66fa99647173f254f731e6aa59a0a2cc8c838ac1
@ -2944,7 +2913,6 @@ SPEC CHECKSUMS:
RNWatch: 28fe1f5e0c6410d45fd20925f4796fce05522e3f
RNWorklets: 30f9a363d681c776a5f9d74f6d21a6d1bb7bfe80
Yoga: c0b3f2c7e8d3e327e450223a2414ca3fa296b9a2
ZXingObjC: 8898711ab495761b2dbbdec76d90164a6d7e14c5
PODFILE CHECKSUM: 7e23f7f009125b117989e69fd28c8a04aa4eacc8

19
package-lock.json generated
View File

@ -73,7 +73,7 @@
"react-native": "0.84.1",
"react-native-biometrics": "3.0.1",
"react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442",
"react-native-camera-kit-no-google": "github:BlueWallet/react-native-camera-kit-no-google#e6dd85b4ea2a7b17da65ab8bfc762960d177b882",
"react-native-camera-kit-no-google": "github:BlueWallet/react-native-camera-kit-no-google#0ed049a62da29cf304019363ec9d9ef3a73652e6",
"react-native-capture-protection": "github:BlueWallet/react-native-capture-protection#bb78a40",
"react-native-context-menu-view": "github:BlueWallet/react-native-context-menu-view#144110b02afdb11b431741aef5da95e91b942a9b",
"react-native-default-preference": "github:BlueWallet/react-native-default-preference#6338a1f1235e4130b8cfc2dd3b53015eeff2870c",
@ -107,7 +107,6 @@
"react-test-renderer": "19.2.3",
"readable-stream": "3.6.2",
"realm": "20.2.0",
"rn-qr-generator": "github:BlueWallet/rn-qr-generator#d53be844c985759197da91b325f7a89d5ef02f90",
"silent-payments": "github:BlueWallet/SilentPayments#59a037",
"slip39": "github:BlueWallet/slip39-js#d316ee6",
"stream-browserify": "3.0.0",
@ -15886,9 +15885,9 @@
}
},
"node_modules/react-native-camera-kit-no-google": {
"version": "17.0.3",
"resolved": "git+ssh://git@github.com/BlueWallet/react-native-camera-kit-no-google.git#e6dd85b4ea2a7b17da65ab8bfc762960d177b882",
"integrity": "sha512-T+DtI1hLUZ1LQ+cVCD3LQR0J6t1bJGXo2gKF+JFt14R6nXjcbd+SZDjGjQGuRfP2TchRny/cf1GNQpgIc9QcaQ==",
"version": "17.0.4",
"resolved": "git+ssh://git@github.com/BlueWallet/react-native-camera-kit-no-google.git#0ed049a62da29cf304019363ec9d9ef3a73652e6",
"integrity": "sha512-w/8o4w5QGxTqrlUVjm/HunWCUi2BIh10xnUX/WB76YOhK1duJCF97lrGGQMGgkokFvjGQrjUTBDkYu6s4r0kSw==",
"license": "MIT",
"engines": {
"node": ">=18"
@ -16804,16 +16803,6 @@
"inherits": "^2.0.1"
}
},
"node_modules/rn-qr-generator": {
"version": "2.0.0",
"resolved": "git+ssh://git@github.com/BlueWallet/rn-qr-generator.git#d53be844c985759197da91b325f7a89d5ef02f90",
"integrity": "sha512-yLDNmNsvZxblXyd5kOBF81cXf7bGNANhGYyRdzvycFV4l8UNAE3fObQFfvaj3mSym9gBGh6uxRFB9h7Xji31rg==",
"license": "MIT",
"peerDependencies": {
"react": "*",
"react-native": ">=0.71.0"
}
},
"node_modules/run-parallel": {
"version": "1.2.0",
"funding": [

View File

@ -155,7 +155,7 @@
"react-native": "0.84.1",
"react-native-biometrics": "3.0.1",
"react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442",
"react-native-camera-kit-no-google": "github:BlueWallet/react-native-camera-kit-no-google#e6dd85b4ea2a7b17da65ab8bfc762960d177b882",
"react-native-camera-kit-no-google": "github:BlueWallet/react-native-camera-kit-no-google#0ed049a62da29cf304019363ec9d9ef3a73652e6",
"react-native-capture-protection": "github:BlueWallet/react-native-capture-protection#bb78a40",
"react-native-context-menu-view": "github:BlueWallet/react-native-context-menu-view#144110b02afdb11b431741aef5da95e91b942a9b",
"react-native-default-preference": "github:BlueWallet/react-native-default-preference#6338a1f1235e4130b8cfc2dd3b53015eeff2870c",
@ -189,7 +189,6 @@
"react-test-renderer": "19.2.3",
"readable-stream": "3.6.2",
"realm": "20.2.0",
"rn-qr-generator": "github:BlueWallet/rn-qr-generator#d53be844c985759197da91b325f7a89d5ef02f90",
"silent-payments": "github:BlueWallet/SilentPayments#59a037",
"slip39": "github:BlueWallet/slip39-js#d316ee6",
"stream-browserify": "3.0.0",

View File

@ -230,12 +230,12 @@ jest.mock('realm', () => {
};
});
jest.mock('rn-qr-generator', () => ({
detect: jest.fn(uri => {
if (uri === 'invalid-image') {
return Promise.reject(new Error('Failed to decode QR code'));
jest.mock('react-native-camera-kit-no-google', () => ({
detectQRCodeInImage: jest.fn(base64 => {
if (base64 === 'invalid-image') {
return Promise.reject(new Error('Invalid image data'));
}
return Promise.resolve({ values: ['mocked-qr-code'] });
return Promise.resolve('mocked-qr-code');
}),
}));