diff --git a/native/navigation/invite-link-handler.react.js b/native/navigation/invite-link-handler.react.js
new file mode 100644
index 000000000..98f0b0e41
--- /dev/null
+++ b/native/navigation/invite-link-handler.react.js
@@ -0,0 +1,82 @@
+// @flow
+
+import { useNavigation } from '@react-navigation/native';
+import * as React from 'react';
+import { Linking } from 'react-native';
+
+import {
+ verifyInviteLink,
+ verifyInviteLinkActionTypes,
+} from 'lib/actions/link-actions.js';
+import { isLoggedIn } from 'lib/selectors/user-selectors.js';
+import {
+ useDispatchActionPromise,
+ useServerCall,
+} from 'lib/utils/action-utils.js';
+
+import { InviteLinkModalRouteName } from './route-names.js';
+import { useSelector } from '../redux/redux-utils.js';
+
+function InviteLinkHandler(): null {
+ const [currentLink, setCurrentLink] = React.useState(null);
+
+ React.useEffect(() => {
+ // This listener listens for an event where a user clicked a link when the
+ // app was running
+ Linking.addEventListener('url', ({ url }) => setCurrentLink(url));
+ // We're also checking if the app was opened by using an invite link.
+ // In that case the listener won't be called and we're instead checking
+ // if the initial URL is set.
+ (async () => {
+ const initialURL = await Linking.getInitialURL();
+ if (initialURL) {
+ setCurrentLink(initialURL);
+ }
+ })();
+ }, []);
+
+ const loggedIn = useSelector(isLoggedIn);
+ const dispatchActionPromise = useDispatchActionPromise();
+ const validateLink = useServerCall(verifyInviteLink);
+ const navigation = useNavigation();
+ React.useEffect(() => {
+ (async () => {
+ if (!loggedIn || !currentLink) {
+ return;
+ }
+ // We're setting this to null so that we ensure that each link click
+ // results in at most one validation and navigation.
+ setCurrentLink(null);
+
+ const secret = parseSecret(currentLink);
+ if (!secret) {
+ return;
+ }
+
+ 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,
+ },
+ });
+ })();
+ }, [currentLink, dispatchActionPromise, loggedIn, navigation, validateLink]);
+
+ return null;
+}
+
+const urlRegex = /invite\/(\S+)$/;
+function parseSecret(url: string) {
+ const match = urlRegex.exec(url);
+ return match?.[1];
+}
+
+export default InviteLinkHandler;
diff --git a/native/navigation/navigation-handler.react.js b/native/navigation/navigation-handler.react.js
index a90e3c9f3..33d576248 100644
--- a/native/navigation/navigation-handler.react.js
+++ b/native/navigation/navigation-handler.react.js
@@ -1,86 +1,88 @@
// @flow
import * as React from 'react';
import { isLoggedIn } from 'lib/selectors/user-selectors.js';
import { logInActionType, logOutActionType } from './action-types.js';
+import InviteLinkHandler from './invite-link-handler.react.js';
import ModalPruner from './modal-pruner.react.js';
import NavFromReduxHandler from './nav-from-redux-handler.react.js';
import { useIsAppLoggedIn } from './nav-selectors.js';
import { NavContext, type NavAction } from './navigation-context.js';
import PolicyAcknowledgmentHandler from './policy-acknowledgment-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';
import type { AppState } from '../redux/state-types.js';
import { usePersistedStateLoaded } from '../selectors/app-state-selectors.js';
const NavigationHandler: React.ComponentType<{}> = React.memo<{}>(
function NavigationHandler() {
const navContext = React.useContext(NavContext);
const persistedStateLoaded = usePersistedStateLoaded();
const devTools = __DEV__ ? : null;
if (!navContext || !persistedStateLoaded) {
if (__DEV__) {
return (
<>
{devTools}
>
);
} else {
return null;
}
}
const { dispatch } = navContext;
return (
<>
+
{devTools}
>
);
},
);
NavigationHandler.displayName = 'NavigationHandler';
type LogInHandlerProps = {
+dispatch: (action: NavAction) => void,
};
const LogInHandler = React.memo(function LogInHandler(
props: LogInHandlerProps,
) {
const { dispatch } = props;
const hasCurrentUserInfo = useSelector(isLoggedIn);
const hasUserCookie = useSelector(
(state: AppState) => !!(state.cookie && state.cookie.startsWith('user=')),
);
const loggedIn = hasCurrentUserInfo && hasUserCookie;
const navLoggedIn = useIsAppLoggedIn();
const prevLoggedInRef = React.useRef();
React.useEffect(() => {
if (loggedIn === prevLoggedInRef.current) {
return;
}
prevLoggedInRef.current = loggedIn;
if (loggedIn && !navLoggedIn) {
dispatch({ type: (logInActionType: 'LOG_IN') });
} else if (!loggedIn && navLoggedIn) {
dispatch({ type: (logOutActionType: 'LOG_OUT') });
}
}, [navLoggedIn, loggedIn, dispatch]);
return null;
});
LogInHandler.displayName = 'LogInHandler';
export default NavigationHandler;