diff --git a/lib/facts/links.js b/lib/facts/links.js
--- a/lib/facts/links.js
+++ b/lib/facts/links.js
@@ -5,18 +5,6 @@
return `https://comm.app/invite/${secret}`;
}
-function parseSecretFromInviteLinkURL(url: string): ?string {
- const urlRegex = /invite\/(\S+)$/;
- const match = urlRegex.exec(url);
- return match?.[1];
-}
-
-function parseInstallReferrerFromInviteLinkURL(referrer: string): ?string {
- const referrerRegex = /utm_source=(invite\/(\S+))$/;
- const match = referrerRegex.exec(referrer);
- return match?.[1];
-}
-
/* QR Code */
function qrCodeLinkUrl(aes256Param: string, ed25519Param: string): string {
const keys = {
@@ -27,16 +15,49 @@
return `comm://qr-code/${keysString}`;
}
-function parseKeysFromQRCodeURL(url: string): ?string {
- const urlRegex = /qr-code\/(\S+)$/;
- const match = urlRegex.exec(url);
+/* Deep Link Utils */
+function parseInstallReferrerFromInviteLinkURL(referrer: string): ?string {
+ const referrerRegex = /utm_source=(invite\/(\S+))$/;
+ const match = referrerRegex.exec(referrer);
return match?.[1];
}
+type ParsedInviteLinkData = {
+ +type: 'invite-link',
+ +data: { +secret: string },
+};
+type ParsedQRCodeData = {
+ +type: 'qr-code',
+ +data: { +keys: string },
+};
+export type ParsedDeepLinkData = ParsedInviteLinkData | ParsedQRCodeData | null;
+
+function parseDataFromDeepLink(url: string): ParsedDeepLinkData {
+ const inviteLinkSecretRegex = /invite\/(\S+)$/;
+ const qrCodeKeysRegex = /qr-code\/(\S+)$/;
+
+ const inviteLinkSecretMatch = inviteLinkSecretRegex.exec(url);
+ if (inviteLinkSecretMatch) {
+ return {
+ type: 'invite-link',
+ data: { secret: inviteLinkSecretMatch[1] },
+ };
+ }
+
+ const qrCodeKeysMatch = qrCodeKeysRegex.exec(url);
+ if (qrCodeKeysMatch) {
+ return {
+ type: 'qr-code',
+ data: { keys: qrCodeKeysMatch[1] },
+ };
+ }
+
+ return null;
+}
+
export {
inviteLinkUrl,
- parseSecretFromInviteLinkURL,
- parseInstallReferrerFromInviteLinkURL,
qrCodeLinkUrl,
- parseKeysFromQRCodeURL,
+ parseInstallReferrerFromInviteLinkURL,
+ parseDataFromDeepLink,
};
diff --git a/native/navigation/deep-links-context-provider.react.js b/native/navigation/deep-links-context-provider.react.js
--- a/native/navigation/deep-links-context-provider.react.js
+++ b/native/navigation/deep-links-context-provider.react.js
@@ -10,8 +10,9 @@
verifyInviteLinkActionTypes,
} from 'lib/actions/link-actions.js';
import {
- parseSecretFromInviteLinkURL,
parseInstallReferrerFromInviteLinkURL,
+ parseDataFromDeepLink,
+ type ParsedDeepLinkData,
} from 'lib/facts/links.js';
import { isLoggedIn } from 'lib/selectors/user-selectors.js';
import type { SetState } from 'lib/types/hook-types.js';
@@ -20,7 +21,10 @@
useServerCall,
} from 'lib/utils/action-utils.js';
-import { InviteLinkModalRouteName } from './route-names.js';
+import {
+ InviteLinkModalRouteName,
+ SecondaryDeviceQRCodeScannerRouteName,
+} from './route-names.js';
import { useSelector } from '../redux/redux-utils.js';
import { useOnFirstLaunchEffect } from '../utils/hooks.js';
@@ -48,7 +52,7 @@
const subscription = Linking.addEventListener('url', ({ url }) =>
setCurrentLink(url),
);
- // We're also checking if the app was opened by using an invite link.
+ // We're also checking if the app was opened by using a link.
// In that case the listener won't be called and we're instead checking
// if the initial URL is set.
(async () => {
@@ -91,25 +95,30 @@
// results in at most one validation and navigation.
setCurrentLink(null);
- const secret = parseSecretFromInviteLinkURL(currentLink);
- if (!secret) {
+ const parsedData: ParsedDeepLinkData = parseDataFromDeepLink(currentLink);
+ if (!parsedData) {
return;
}
- const validateLinkPromise = validateLink({ secret });
- dispatchActionPromise(verifyInviteLinkActionTypes, validateLinkPromise);
- const result = await validateLinkPromise;
- if (result.status === 'already_joined') {
- return;
+ if (parsedData.type === 'invite-link') {
+ const { secret } = parsedData.data;
+ const validateLinkPromise = validateLink({ secret });
+ dispatchActionPromise(verifyInviteLinkActionTypes, validateLinkPromise);
+ const result = await validateLinkPromise;
+ if (result.status === 'already_joined') {
+ return;
+ }
+
+ navigation.navigate<'InviteLinkModal'>({
+ name: InviteLinkModalRouteName,
+ params: {
+ invitationDetails: result,
+ secret,
+ },
+ });
+ } else if (parsedData.type === 'qr-code') {
+ navigation.navigate(SecondaryDeviceQRCodeScannerRouteName);
}
-
- navigation.navigate<'InviteLinkModal'>({
- name: InviteLinkModalRouteName,
- params: {
- invitationDetails: result,
- secret,
- },
- });
})();
}, [currentLink, dispatchActionPromise, loggedIn, navigation, validateLink]);
diff --git a/native/navigation/navigation-handler.react.js b/native/navigation/navigation-handler.react.js
--- a/native/navigation/navigation-handler.react.js
+++ b/native/navigation/navigation-handler.react.js
@@ -11,7 +11,6 @@
import { useIsAppLoggedIn } from './nav-selectors.js';
import { NavContext, type NavAction } from './navigation-context.js';
import PolicyAcknowledgmentHandler from './policy-acknowledgment-handler.react.js';
-import QRCodeLinkHandler from './qr-code-link-handler.react.js';
import ThreadScreenTracker from './thread-screen-tracker.react.js';
import DevTools from '../redux/dev-tools.react.js';
import { useSelector } from '../redux/redux-utils.js';
@@ -44,7 +43,6 @@
-
{devTools}
>
);
diff --git a/native/navigation/qr-code-link-handler.react.js b/native/navigation/qr-code-link-handler.react.js
deleted file mode 100644
--- a/native/navigation/qr-code-link-handler.react.js
+++ /dev/null
@@ -1,51 +0,0 @@
-// @flow
-
-import { useNavigation } from '@react-navigation/native';
-import * as React from 'react';
-import { Linking } from 'react-native';
-
-import { parseKeysFromQRCodeURL } from 'lib/facts/links.js';
-import { isLoggedIn } from 'lib/selectors/user-selectors.js';
-
-import { SecondaryDeviceQRCodeScannerRouteName } from './route-names.js';
-import { useSelector } from '../redux/redux-utils.js';
-
-function QRCodeLinkHandler(): null {
- const [currentLink, setCurrentLink] = React.useState(null);
-
- React.useEffect(() => {
- const subscription = Linking.addEventListener('url', ({ url }) =>
- setCurrentLink(url),
- );
- (async () => {
- const initialURL = await Linking.getInitialURL();
- if (initialURL) {
- setCurrentLink(initialURL);
- }
- })();
-
- return () => subscription.remove();
- }, []);
-
- const loggedIn = useSelector(isLoggedIn);
- const { navigate } = useNavigation();
-
- React.useEffect(() => {
- if (!loggedIn || !currentLink) {
- return;
- }
-
- setCurrentLink(null);
-
- const keys = parseKeysFromQRCodeURL(currentLink);
- if (!keys) {
- return;
- }
-
- navigate(SecondaryDeviceQRCodeScannerRouteName);
- }, [currentLink, loggedIn, navigate]);
-
- return null;
-}
-
-export default QRCodeLinkHandler;
diff --git a/native/profile/secondary-device-qr-code-scanner.react.js b/native/profile/secondary-device-qr-code-scanner.react.js
--- a/native/profile/secondary-device-qr-code-scanner.react.js
+++ b/native/profile/secondary-device-qr-code-scanner.react.js
@@ -5,7 +5,7 @@
import * as React from 'react';
import { View } from 'react-native';
-import { parseKeysFromQRCodeURL } from 'lib/facts/links.js';
+import { parseDataFromDeepLink } from 'lib/facts/links.js';
import { useStyles } from '../themes/colors.js';
import Alert from '../utils/alert.js';
@@ -39,9 +39,10 @@
const onConnect = React.useCallback((barCodeEvent: BarCodeEvent) => {
const { data } = barCodeEvent;
- const keysMatch = parseKeysFromQRCodeURL(data);
+ const parsedData = parseDataFromDeepLink(data);
+ const keysMatch = parsedData?.data?.keys;
- if (!keysMatch) {
+ if (!parsedData || !keysMatch) {
Alert.alert(
'Scan failed',
'QR code does not contain a valid pair of keys.',