Skip to content

Commit

Permalink
feat: add a way to filter out deep links from being handled
Browse files Browse the repository at this point in the history
This is useful for libraries like `expo-auth-session` which also use links for authentication.

Usage:

```js
const linking = {
  prefixes: ['myapp://'],
  filter: (url) => !url.includes('+expo-auth-session'),
};
```
  • Loading branch information
satya164 committed Jul 20, 2021
1 parent e3c514d commit c322b05
Show file tree
Hide file tree
Showing 2 changed files with 69 additions and 47 deletions.
27 changes: 24 additions & 3 deletions packages/native/src/types.tsx
Expand Up @@ -27,7 +27,8 @@ export type LinkingOptions<ParamList extends {}> = {
/**
* The prefixes are stripped from the URL before parsing them.
* Usually they are the `scheme` + `host` (e.g. `myapp://chat?user=jane`)
* Only applicable on Android and iOS.
*
* This is not supported on Web.
*
* @example
* ```js
Expand All @@ -41,6 +42,24 @@ export type LinkingOptions<ParamList extends {}> = {
* ```
*/
prefixes: string[];
/**
* Optional function which takes an incoming URL returns a boolean
* indicating whether React Navigation should handle it.
*
* This can be used to disable deep linking for specific URLs.
* e.g. URLs used for authentication, and not for deep linking to screens.
*
* This is not supported on Web.
*
* @example
* ```js
* {
* // Filter out URLs used by expo-auth-session
* filter: (url) => !url.includes('+expo-auth-session')
* }
* ```
*/
filter?: (url: string) => boolean;
/**
* Config to fine-tune how to parse the path.
*
Expand All @@ -61,7 +80,8 @@ export type LinkingOptions<ParamList extends {}> = {
/**
* Custom function to get the initial URL used for linking.
* Uses `Linking.getInitialURL()` by default.
* Not supported on Web.
*
* This is not supported on Web.
*
* @example
* ```js
Expand All @@ -78,7 +98,8 @@ export type LinkingOptions<ParamList extends {}> = {
/**
* Custom function to get subscribe to URL updates.
* Uses `Linking.addEventListener('url', callback)` by default.
* Not supported on Web.
*
* This is not supported on Web.
*
* @example
* ```js
Expand Down
89 changes: 45 additions & 44 deletions packages/native/src/useLinking.native.tsx
Expand Up @@ -24,6 +24,7 @@ export default function useLinking(
independent,
enabled = true,
prefixes,
filter,
config,
getInitialURL = () =>
Promise.race([
Expand Down Expand Up @@ -86,6 +87,7 @@ export default function useLinking(
// Not re-creating `getInitialState` is important coz it makes it easier for the user to use in an effect
const enabledRef = React.useRef(enabled);
const prefixesRef = React.useRef(prefixes);
const filterRef = React.useRef(filter);
const configRef = React.useRef(config);
const getInitialURLRef = React.useRef(getInitialURL);
const getStateFromPathRef = React.useRef(getStateFromPath);
Expand All @@ -94,12 +96,28 @@ export default function useLinking(
React.useEffect(() => {
enabledRef.current = enabled;
prefixesRef.current = prefixes;
filterRef.current = filter;
configRef.current = config;
getInitialURLRef.current = getInitialURL;
getStateFromPathRef.current = getStateFromPath;
getActionFromStateRef.current = getActionFromState;
});

const getStateFromURL = React.useCallback(
(url: string | null | undefined) => {
if (!url || (filterRef.current && !filterRef.current(url))) {
return undefined;
}

const path = extractPathFromURL(prefixesRef.current, url);

return path
? getStateFromPathRef.current(path, configRef.current)
: undefined;
},
[]
);

const getInitialState = React.useCallback(() => {
let state: ResultState | undefined;

Expand All @@ -108,21 +126,13 @@ export default function useLinking(

if (url != null && typeof url !== 'string') {
return url.then((url) => {
const path = url
? extractPathFromURL(prefixesRef.current, url)
: null;
const state = getStateFromURL(url);

return path
? getStateFromPathRef.current(path, configRef.current)
: undefined;
return state;
});
}

const path = url ? extractPathFromURL(prefixesRef.current, url) : null;

state = path
? getStateFromPathRef.current(path, configRef.current)
: undefined;
state = getStateFromURL(url);
}

const thenable = {
Expand All @@ -135,58 +145,49 @@ export default function useLinking(
};

return thenable as PromiseLike<ResultState | undefined>;
}, []);
}, [getStateFromURL]);

React.useEffect(() => {
const listener = (url: string) => {
if (!enabled) {
return;
}

const path = extractPathFromURL(prefixesRef.current, url);
const navigation = ref.current;
const state = navigation ? getStateFromURL(url) : undefined;

if (navigation && state) {
// Make sure that the routes in the state exist in the root navigator
// Otherwise there's an error in the linking configuration
const rootState = navigation.getRootState();

if (navigation && path) {
const state = getStateFromPathRef.current(path, configRef.current);
if (state.routes.some((r) => !rootState?.routeNames.includes(r.name))) {
console.warn(
"The navigation state parsed from the URL contains routes not present in the root navigator. This usually means that the linking configuration doesn't match the navigation structure. See https://reactnavigation.org/docs/configuring-links for more details on how to specify a linking configuration."
);
return;
}

if (state) {
// Make sure that the routes in the state exist in the root navigator
// Otherwise there's an error in the linking configuration
const rootState = navigation.getRootState();
const action = getActionFromStateRef.current(state, configRef.current);

if (
state.routes.some((r) => !rootState?.routeNames.includes(r.name))
) {
if (action !== undefined) {
try {
navigation.dispatch(action);
} catch (e) {
// Ignore any errors from deep linking.
// This could happen in case of malformed links, navigation object not being initialized etc.
console.warn(
"The navigation state parsed from the URL contains routes not present in the root navigator. This usually means that the linking configuration doesn't match the navigation structure. See https://reactnavigation.org/docs/configuring-links for more details on how to specify a linking configuration."
`An error occurred when trying to handle the link '${url}': ${e.message}`
);
return;
}

const action = getActionFromStateRef.current(
state,
configRef.current
);

if (action !== undefined) {
try {
navigation.dispatch(action);
} catch (e) {
// Ignore any errors from deep linking.
// This could happen in case of malformed links, navigation object not being initialized etc.
console.warn(
`An error occurred when trying to handle the link '${path}': ${e.message}`
);
}
} else {
navigation.resetRoot(state);
}
} else {
navigation.resetRoot(state);
}
}
};

return subscribe(listener);
}, [enabled, ref, subscribe]);
}, [enabled, getStateFromURL, ref, subscribe]);

return {
getInitialState,
Expand Down

0 comments on commit c322b05

Please sign in to comment.