diff --git a/native/account/qr-auth/secondary-device-qr-code-scanner.react.js b/native/account/qr-auth/qr-auth-context-provider.js
copy from native/account/qr-auth/secondary-device-qr-code-scanner.react.js
copy to native/account/qr-auth/qr-auth-context-provider.js
--- a/native/account/qr-auth/secondary-device-qr-code-scanner.react.js
+++ b/native/account/qr-auth/qr-auth-context-provider.js
@@ -1,10 +1,8 @@
// @flow
import { useNavigation } from '@react-navigation/native';
-import { BarCodeScanner, type BarCodeEvent } from 'expo-barcode-scanner';
import invariant from 'invariant';
import * as React from 'react';
-import { View, Text } from 'react-native';
import { parseDataFromDeepLink } from 'lib/facts/links.js';
import {
@@ -29,50 +27,32 @@
} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js';
import { qrCodeAuthMessageTypes } from 'lib/types/tunnelbroker/qr-code-auth-message-types.js';
-import type { QRAuthNavigationProp } from './qr-auth-navigator.react.js';
-import TextInput from '../../components/text-input.react.js';
+import { QRAuthContext } from './qr-auth-context.js';
import { commCoreModule } from '../../native-modules.js';
-import HeaderRightTextButton from '../../navigation/header-right-text-button.react.js';
-import type { NavigationRoute } from '../../navigation/route-names.js';
import { useSelector } from '../../redux/redux-utils.js';
-import { useStyles, useColors } from '../../themes/colors.js';
import Alert from '../../utils/alert.js';
import {
composeTunnelbrokerQRAuthMessage,
parseTunnelbrokerQRAuthMessage,
} from '../../utils/qr-code-utils.js';
-import { deviceIsEmulator } from '../../utils/url-utils.js';
-
-const barCodeTypes = [BarCodeScanner.Constants.BarCodeType.qr];
type Props = {
- +navigation: QRAuthNavigationProp<'SecondaryDeviceQRCodeScanner'>,
- +route: NavigationRoute<'SecondaryDeviceQRCodeScanner'>,
+ +children: React.Node,
};
-
-// eslint-disable-next-line no-unused-vars
-function SecondaryDeviceQRCodeScanner(props: Props): React.Node {
- const [hasPermission, setHasPermission] = React.useState(null);
- const [scanned, setScanned] = React.useState(false);
- const [urlInput, setURLInput] = React.useState('');
-
- const styles = useStyles(unboundStyles);
- const { goBack, setOptions } = useNavigation();
-
- const tunnelbrokerContext = useTunnelbroker();
- const identityContext = React.useContext(IdentityClientContext);
- invariant(identityContext, 'identity context not set');
-
+function QRAuthContextProvider(props: Props): React.Node {
const aes256Key = React.useRef(null);
const secondaryDeviceID = React.useRef(null);
const secondaryDeviceType = React.useRef(null);
- const runDeviceListUpdate = useDeviceListUpdate();
-
const ownPeerDevices = useSelector(getOwnPeerDevices);
const keyserverDeviceID = getKeyserverDeviceID(ownPeerDevices);
+ const { goBack } = useNavigation();
+ const runDeviceListUpdate = useDeviceListUpdate();
+ const { addListener, removeListener, sendMessageToDevice } =
+ useTunnelbroker();
- const { panelForegroundTertiaryLabel } = useColors();
+ const identityContext = React.useContext(IdentityClientContext);
+ invariant(identityContext, 'identity context not set');
const tunnelbrokerMessageListener = React.useCallback(
async (message: TunnelbrokerToDeviceMessage) => {
@@ -118,29 +98,12 @@
);
React.useEffect(() => {
- tunnelbrokerContext.addListener(tunnelbrokerMessageListener);
+ addListener(tunnelbrokerMessageListener);
return () => {
- tunnelbrokerContext.removeListener(tunnelbrokerMessageListener);
+ removeListener(tunnelbrokerMessageListener);
};
- }, [tunnelbrokerMessageListener, tunnelbrokerContext]);
-
- React.useEffect(() => {
- void (async () => {
- const { status } = await BarCodeScanner.requestPermissionsAsync();
- setHasPermission(status === 'granted');
-
- if (status !== 'granted') {
- Alert.alert(
- 'No access to camera',
- 'Please allow Comm to access your camera in order to scan the QR code.',
- [{ text: 'OK' }],
- );
-
- goBack();
- }
- })();
- }, [goBack]);
+ }, [addListener, removeListener, tunnelbrokerMessageListener]);
const processDeviceListUpdate = React.useCallback(async () => {
try {
@@ -168,7 +131,7 @@
primaryDeviceID,
backupData,
});
- await tunnelbrokerContext.sendMessageToDevice({
+ await sendMessageToDevice({
deviceID: targetDeviceID,
payload: JSON.stringify(message),
});
@@ -237,63 +200,11 @@
identityContext,
keyserverDeviceID,
runDeviceListUpdate,
- tunnelbrokerContext,
+ sendMessageToDevice,
]);
- const onPressSave = React.useCallback(async () => {
- if (!urlInput) {
- return;
- }
-
- const parsedData = parseDataFromDeepLink(urlInput);
- const keysMatch = parsedData?.data?.keys;
-
- if (!parsedData || !keysMatch) {
- Alert.alert(
- 'Scan failed',
- 'QR code does not contain a valid pair of keys.',
- [{ text: 'OK' }],
- );
- return;
- }
-
- try {
- const keys = JSON.parse(decodeURIComponent(keysMatch));
- const { aes256, ed25519 } = keys;
- aes256Key.current = aes256;
- secondaryDeviceID.current = ed25519;
- secondaryDeviceType.current = parsedData.data.deviceType;
- } catch (err) {
- console.log('Failed to decode URI component:', err);
- return;
- }
- await processDeviceListUpdate();
- }, [processDeviceListUpdate, urlInput]);
-
- const buttonDisabled = !urlInput;
- React.useEffect(() => {
- if (!deviceIsEmulator) {
- return;
- }
- setOptions({
- headerRight: () => (
-
- ),
- });
- }, [buttonDisabled, onPressSave, setOptions]);
-
- const onChangeText = React.useCallback(
- (text: string) => setURLInput(text),
- [],
- );
-
const onConnect = React.useCallback(
- async (barCodeEvent: BarCodeEvent) => {
- const { data } = barCodeEvent;
+ async (data: string) => {
const parsedData = parseDataFromDeepLink(data);
const keysMatch = parsedData?.data?.keys;
@@ -322,112 +233,18 @@
[processDeviceListUpdate],
);
- const handleBarCodeScanned = React.useCallback(
- (barCodeEvent: BarCodeEvent) => {
- setScanned(true);
- Alert.alert(
- 'Connect with this device?',
- 'Are you sure you want to allow this device to log in to your account?',
- [
- {
- text: 'Cancel',
- style: 'cancel',
- onPress: goBack,
- },
- {
- text: 'Connect',
- onPress: () => onConnect(barCodeEvent),
- },
- ],
- { cancelable: false },
- );
- },
- [goBack, onConnect],
+ const contextValue = React.useMemo(
+ () => ({
+ onConnect,
+ }),
+ [onConnect],
);
- if (hasPermission === null) {
- return ;
- }
-
- if (deviceIsEmulator) {
- return (
-
- QR Code URL
-
-
-
-
- );
- }
- // Note: According to the BarCodeScanner Expo docs, we should adhere to two
- // guidances when using the BarCodeScanner:
- // 1. We should specify the potential barCodeTypes we want to scan for to
- // minimize battery usage.
- // 2. We should set the onBarCodeScanned callback to undefined if it scanned
- // in order to 'pause' the scanner from continuing to scan while we
- // process the data from the scan.
- // See: https://docs.expo.io/versions/latest/sdk/bar-code-scanner
return (
-
-
-
+
+ {props.children}
+
);
}
-const unboundStyles = {
- scannerContainer: {
- flex: 1,
- flexDirection: 'column',
- justifyContent: 'center',
- },
- scanner: {
- position: 'absolute',
- top: 0,
- left: 0,
- right: 0,
- bottom: 0,
- },
- textInputContainer: {
- paddingTop: 8,
- },
- header: {
- color: 'panelBackgroundLabel',
- fontSize: 12,
- fontWeight: '400',
- paddingBottom: 3,
- paddingHorizontal: 24,
- },
- inputContainer: {
- backgroundColor: 'panelForeground',
- flexDirection: 'row',
- justifyContent: 'space-between',
- paddingHorizontal: 24,
- paddingVertical: 12,
- borderBottomWidth: 1,
- borderColor: 'panelForegroundBorder',
- borderTopWidth: 1,
- },
- input: {
- color: 'panelForegroundLabel',
- flex: 1,
- fontFamily: 'Arial',
- fontSize: 16,
- paddingVertical: 0,
- borderBottomColor: 'transparent',
- },
-};
-
-export default SecondaryDeviceQRCodeScanner;
+export { QRAuthContextProvider };
diff --git a/native/account/qr-auth/qr-auth-context.js b/native/account/qr-auth/qr-auth-context.js
new file mode 100644
--- /dev/null
+++ b/native/account/qr-auth/qr-auth-context.js
@@ -0,0 +1,12 @@
+// @flow
+
+import * as React from 'react';
+
+export type QRAuthContextType = {
+ +onConnect: (data: string) => Promise,
+};
+
+const QRAuthContext: React.Context =
+ React.createContext();
+
+export { QRAuthContext };
diff --git a/native/account/qr-auth/qr-auth-navigator.react.js b/native/account/qr-auth/qr-auth-navigator.react.js
--- a/native/account/qr-auth/qr-auth-navigator.react.js
+++ b/native/account/qr-auth/qr-auth-navigator.react.js
@@ -7,6 +7,7 @@
import { createStackNavigator } from '@react-navigation/stack';
import * as React from 'react';
+import { QRAuthContextProvider } from './qr-auth-context-provider.js';
import SecondaryDeviceQRCodeScanner from './secondary-device-qr-code-scanner.react.js';
import type { RootNavigationProp } from '../../navigation/root-navigator.react.js';
import {
@@ -50,13 +51,15 @@
// eslint-disable-next-line no-unused-vars
function QRAuthNavigator(props: Props): React.Node {
return (
-
-
-
+
+
+
+
+
);
}
diff --git a/native/account/qr-auth/secondary-device-qr-code-scanner.react.js b/native/account/qr-auth/secondary-device-qr-code-scanner.react.js
--- a/native/account/qr-auth/secondary-device-qr-code-scanner.react.js
+++ b/native/account/qr-auth/secondary-device-qr-code-scanner.react.js
@@ -6,41 +6,13 @@
import * as React from 'react';
import { View, Text } from 'react-native';
-import { parseDataFromDeepLink } from 'lib/facts/links.js';
-import {
- getOwnPeerDevices,
- getKeyserverDeviceID,
-} from 'lib/selectors/user-selectors.js';
-import { useDeviceListUpdate } from 'lib/shared/device-list-utils.js';
-import { IdentityClientContext } from 'lib/shared/identity-client-context.js';
-import { useTunnelbroker } from 'lib/tunnelbroker/tunnelbroker-context.js';
-import {
- identityDeviceTypes,
- type IdentityDeviceType,
-} from 'lib/types/identity-service-types.js';
-import {
- tunnelbrokerToDeviceMessageTypes,
- type TunnelbrokerToDeviceMessage,
-} from 'lib/types/tunnelbroker/messages.js';
-import {
- peerToPeerMessageTypes,
- peerToPeerMessageValidator,
- type PeerToPeerMessage,
-} from 'lib/types/tunnelbroker/peer-to-peer-message-types.js';
-import { qrCodeAuthMessageTypes } from 'lib/types/tunnelbroker/qr-code-auth-message-types.js';
-
+import { QRAuthContext } from './qr-auth-context.js';
import type { QRAuthNavigationProp } from './qr-auth-navigator.react.js';
import TextInput from '../../components/text-input.react.js';
-import { commCoreModule } from '../../native-modules.js';
import HeaderRightTextButton from '../../navigation/header-right-text-button.react.js';
import type { NavigationRoute } from '../../navigation/route-names.js';
-import { useSelector } from '../../redux/redux-utils.js';
import { useStyles, useColors } from '../../themes/colors.js';
import Alert from '../../utils/alert.js';
-import {
- composeTunnelbrokerQRAuthMessage,
- parseTunnelbrokerQRAuthMessage,
-} from '../../utils/qr-code-utils.js';
import { deviceIsEmulator } from '../../utils/url-utils.js';
const barCodeTypes = [BarCodeScanner.Constants.BarCodeType.qr];
@@ -58,72 +30,11 @@
const styles = useStyles(unboundStyles);
const { goBack, setOptions } = useNavigation();
-
- const tunnelbrokerContext = useTunnelbroker();
- const identityContext = React.useContext(IdentityClientContext);
- invariant(identityContext, 'identity context not set');
-
- const aes256Key = React.useRef(null);
- const secondaryDeviceID = React.useRef(null);
- const secondaryDeviceType = React.useRef(null);
-
- const runDeviceListUpdate = useDeviceListUpdate();
-
- const ownPeerDevices = useSelector(getOwnPeerDevices);
- const keyserverDeviceID = getKeyserverDeviceID(ownPeerDevices);
-
const { panelForegroundTertiaryLabel } = useColors();
- const tunnelbrokerMessageListener = React.useCallback(
- async (message: TunnelbrokerToDeviceMessage) => {
- const encryptionKey = aes256Key.current;
- const targetDeviceID = secondaryDeviceID.current;
- if (!encryptionKey || !targetDeviceID) {
- return;
- }
- if (message.type !== tunnelbrokerToDeviceMessageTypes.MESSAGE_TO_DEVICE) {
- return;
- }
-
- let innerMessage: PeerToPeerMessage;
- try {
- innerMessage = JSON.parse(message.payload);
- } catch {
- return;
- }
- if (
- !peerToPeerMessageValidator.is(innerMessage) ||
- innerMessage.type !== peerToPeerMessageTypes.QR_CODE_AUTH_MESSAGE
- ) {
- return;
- }
-
- const payload = await parseTunnelbrokerQRAuthMessage(
- encryptionKey,
- innerMessage,
- );
- if (
- !payload ||
- payload.type !==
- qrCodeAuthMessageTypes.SECONDARY_DEVICE_REGISTRATION_SUCCESS
- ) {
- return;
- }
-
- Alert.alert('Device added', 'Device registered successfully', [
- { text: 'OK', onPress: goBack },
- ]);
- },
- [goBack],
- );
-
- React.useEffect(() => {
- tunnelbrokerContext.addListener(tunnelbrokerMessageListener);
-
- return () => {
- tunnelbrokerContext.removeListener(tunnelbrokerMessageListener);
- };
- }, [tunnelbrokerMessageListener, tunnelbrokerContext]);
+ const qrAuthContext = React.useContext(QRAuthContext);
+ invariant(qrAuthContext, 'qrAuthContext should be set');
+ const { onConnect } = qrAuthContext;
React.useEffect(() => {
void (async () => {
@@ -142,133 +53,13 @@
})();
}, [goBack]);
- const processDeviceListUpdate = React.useCallback(async () => {
- try {
- const { deviceID: primaryDeviceID, userID } =
- await identityContext.getAuthMetadata();
- if (!primaryDeviceID || !userID) {
- throw new Error('missing auth metadata');
- }
- const encryptionKey = aes256Key.current;
- const targetDeviceID = secondaryDeviceID.current;
- if (!encryptionKey || !targetDeviceID) {
- throw new Error('missing tunnelbroker message data');
- }
-
- const deviceType = secondaryDeviceType.current;
-
- const sendDeviceListUpdateSuccessMessage = async () => {
- let backupData = null;
- if (deviceType !== identityDeviceTypes.KEYSERVER) {
- backupData = await commCoreModule.getQRAuthBackupData();
- }
- const message = await composeTunnelbrokerQRAuthMessage(encryptionKey, {
- type: qrCodeAuthMessageTypes.DEVICE_LIST_UPDATE_SUCCESS,
- userID,
- primaryDeviceID,
- backupData,
- });
- await tunnelbrokerContext.sendMessageToDevice({
- deviceID: targetDeviceID,
- payload: JSON.stringify(message),
- });
- };
-
- const handleReplaceDevice = async () => {
- try {
- if (!keyserverDeviceID) {
- throw new Error('missing keyserver device ID');
- }
- await runDeviceListUpdate({
- type: 'replace',
- deviceIDToRemove: keyserverDeviceID,
- newDeviceID: targetDeviceID,
- });
- await sendDeviceListUpdateSuccessMessage();
- } catch (err) {
- console.log('Device replacement error:', err);
- Alert.alert(
- 'Adding device failed',
- 'Failed to update the device list',
- [{ text: 'OK' }],
- );
- goBack();
- }
- };
-
- if (
- deviceType !== identityDeviceTypes.KEYSERVER ||
- !keyserverDeviceID ||
- keyserverDeviceID === targetDeviceID
- ) {
- await runDeviceListUpdate({
- type: 'add',
- deviceID: targetDeviceID,
- });
- await sendDeviceListUpdateSuccessMessage();
- return;
- }
-
- Alert.alert(
- 'Existing keyserver detected',
- 'Do you want to replace your existing keyserver with this new one?',
- [
- {
- text: 'No',
- onPress: goBack,
- style: 'cancel',
- },
- {
- text: 'Replace',
- onPress: handleReplaceDevice,
- style: 'destructive',
- },
- ],
- );
- } catch (err) {
- console.log('Primary device error:', err);
- Alert.alert('Adding device failed', 'Failed to update the device list', [
- { text: 'OK' },
- ]);
- goBack();
- }
- }, [
- goBack,
- identityContext,
- keyserverDeviceID,
- runDeviceListUpdate,
- tunnelbrokerContext,
- ]);
-
const onPressSave = React.useCallback(async () => {
if (!urlInput) {
return;
}
- const parsedData = parseDataFromDeepLink(urlInput);
- const keysMatch = parsedData?.data?.keys;
-
- if (!parsedData || !keysMatch) {
- Alert.alert(
- 'Scan failed',
- 'QR code does not contain a valid pair of keys.',
- [{ text: 'OK' }],
- );
- return;
- }
-
- try {
- const keys = JSON.parse(decodeURIComponent(keysMatch));
- const { aes256, ed25519 } = keys;
- aes256Key.current = aes256;
- secondaryDeviceID.current = ed25519;
- secondaryDeviceType.current = parsedData.data.deviceType;
- } catch (err) {
- console.log('Failed to decode URI component:', err);
- return;
- }
- await processDeviceListUpdate();
- }, [processDeviceListUpdate, urlInput]);
+ await onConnect(urlInput);
+ }, [onConnect, urlInput]);
const buttonDisabled = !urlInput;
React.useEffect(() => {
@@ -291,40 +82,10 @@
[],
);
- const onConnect = React.useCallback(
- async (barCodeEvent: BarCodeEvent) => {
- const { data } = barCodeEvent;
- const parsedData = parseDataFromDeepLink(data);
- const keysMatch = parsedData?.data?.keys;
-
- if (!parsedData || !keysMatch) {
- Alert.alert(
- 'Scan failed',
- 'QR code does not contain a valid pair of keys.',
- [{ text: 'OK' }],
- );
- return;
- }
-
- try {
- const keys = JSON.parse(decodeURIComponent(keysMatch));
- const { aes256, ed25519 } = keys;
- aes256Key.current = aes256;
- secondaryDeviceID.current = ed25519;
- secondaryDeviceType.current = parsedData.data.deviceType;
- } catch (err) {
- console.log('Failed to decode URI component:', err);
- return;
- }
-
- await processDeviceListUpdate();
- },
- [processDeviceListUpdate],
- );
-
const handleBarCodeScanned = React.useCallback(
(barCodeEvent: BarCodeEvent) => {
setScanned(true);
+ const { data } = barCodeEvent;
Alert.alert(
'Connect with this device?',
'Are you sure you want to allow this device to log in to your account?',
@@ -336,7 +97,7 @@
},
{
text: 'Connect',
- onPress: () => onConnect(barCodeEvent),
+ onPress: () => onConnect(data),
},
],
{ cancelable: false },