Skip to content

Commit 06b2b45

Browse files
authored
feat: add custom native intent for expo router (#36)
1 parent f299cbd commit 06b2b45

155 files changed

Lines changed: 6577 additions & 3589 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/ci.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
- name: Build package
5151
run: yarn prepare
5252

53-
build-web:
53+
smoke-mobile:
5454
runs-on: ubuntu-latest
5555
steps:
5656
- name: Checkout
@@ -59,6 +59,6 @@ jobs:
5959
- name: Setup
6060
uses: ./.github/actions/setup
6161

62-
- name: Build example for Web
62+
- name: Smoke build mobile example
6363
run: |
64-
yarn example expo export --platform web
64+
CI=1 yarn workspace @swmansion/react-native-detour-expo-router-native-intent expo export --platform ios --platform android

.gitignore

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -41,13 +41,6 @@ project.xcworkspace
4141
local.properties
4242
android.iml
4343

44-
# Cocoapods
45-
#
46-
example/ios/Pods
47-
48-
# Ruby
49-
example/vendor/
50-
5144
# node.js
5245
#
5346
node_modules/

CONTRIBUTING.md

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ We want this community to be friendly and respectful to each other. Please follo
99
This project is a monorepo managed using [Yarn workspaces](https://yarnpkg.com/features/workspaces). It contains the following packages:
1010

1111
- The library package in the root directory.
12-
- An example app in the `example/` directory.
12+
- Example apps in the `examples/` directory.
1313

1414
To get started with the project, make sure you have the correct version of [Node.js](https://nodejs.org/) installed. See the [`.nvmrc`](./.nvmrc) file for the version used in this project.
1515

@@ -21,7 +21,7 @@ yarn
2121

2222
> Since the project relies on Yarn workspaces, you cannot use [`npm`](https://github.com/npm/cli) for development without manually migrating.
2323
24-
The [example app](/example/) demonstrates usage of the library. You need to run it to test any changes you make.
24+
The [bare example app](/examples/expo-bare/) demonstrates usage of the library. You need to run it to test any changes you make.
2525

2626
It is configured to use the local version of the library, so any changes you make to the library's source code will be reflected in the example app. Changes to the library's JavaScript code will be reflected in the example app without a rebuild, but native code changes will require a rebuild of the example app.
2727

@@ -30,19 +30,19 @@ You can use various commands from the root directory to work with the project.
3030
To start the packager:
3131

3232
```sh
33-
yarn example start
33+
yarn examples:expo-bare start
3434
```
3535

3636
To run the example app on Android:
3737

3838
```sh
39-
yarn example android
39+
yarn examples:expo-bare android
4040
```
4141

4242
To run the example app on iOS:
4343

4444
```sh
45-
yarn example ios
45+
yarn examples:expo-bare ios
4646
```
4747

4848
To confirm that the app is running with the new architecture, you can check the Metro logs for a message like this:
@@ -56,7 +56,7 @@ Note the `"fabric":true` and `"concurrentRoot":true` properties.
5656
To run the example app on Web:
5757

5858
```sh
59-
yarn example web
59+
yarn examples:expo-bare web
6060
```
6161

6262
Make sure your code passes TypeScript and ESLint. Run the following to verify:
@@ -117,9 +117,9 @@ The `package.json` file contains various scripts for common tasks:
117117
- `yarn typecheck`: type-check files with TypeScript.
118118
- `yarn lint`: lint files with ESLint.
119119
- `yarn test`: run unit tests with Jest.
120-
- `yarn example start`: start the Metro server for the example app.
121-
- `yarn example android`: run the example app on Android.
122-
- `yarn example ios`: run the example app on iOS.
120+
- `yarn examples:expo-bare start`: start the Metro server for the bare example app.
121+
- `yarn examples:expo-bare android`: run the bare example app on Android.
122+
- `yarn examples:expo-bare ios`: run the bare example app on iOS.
123123

124124
### Sending a pull request
125125

README.md

Lines changed: 77 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ SDK for handling deferred links in React Native.
66

77
## Create an account
88

9-
You need a Detour account to generate app credentials and configure your links.
9+
You need a Detour account to generate app credentials and configure your links.
1010
Sign up here: [https://godetour.dev/auth/signup](https://godetour.dev/auth/signup)
1111

1212
## Quick links
@@ -42,7 +42,7 @@ npm install expo-localization react-native-device-info expo-clipboard @react-nat
4242
import { DetourProvider, type Config } from '@swmansion/react-native-detour';
4343

4444
const config: Config = {
45-
API_KEY: '<REPLACE_WITH_YOUR_API_KEY>',
45+
apiKey: '<REPLACE_WITH_YOUR_API_KEY>',
4646
appID: '<REPLACE_WITH_APP_ID_FROM_PLATFORM>',
4747
shouldUseClipboard: true,
4848
};
@@ -61,34 +61,86 @@ export default function RootLayout() {
6161
```js
6262
import { useDetourContext } from '@swmansion/react-native-detour';
6363
import * as SplashScreen from 'expo-splash-screen';
64-
import { Redirect, Stack } from 'expo-router';
64+
import { Stack, usePathname, useRouter } from 'expo-router';
6565

6666
SplashScreen.preventAutoHideAsync();
6767

6868
export function RootNavigator() {
6969
const { isLinkProcessed, linkRoute, clearLink } = useDetourContext();
70+
const pathname = usePathname();
71+
const router = useRouter();
7072

7173
useEffect(() => {
7274
if (isLinkProcessed) {
7375
SplashScreen.hide();
7476
}
7577
}, [isLinkProcessed]);
7678

79+
useEffect(() => {
80+
if (!isLinkProcessed || !linkRoute) return;
81+
if (pathname !== linkRoute) {
82+
router.replace(linkRoute);
83+
return;
84+
}
85+
clearLink(); // avoid redirecting again when returning to this screen
86+
}, [clearLink, isLinkProcessed, linkRoute, pathname, router]);
87+
7788
if (!isLinkProcessed) {
7889
return null;
7990
}
8091

81-
if (linkRoute) {
82-
clearLink(); // avoid redirecting again when returning to this screen
83-
return <Redirect href={linkRoute} />;
84-
}
85-
8692
return <Stack />;
8793
}
8894
```
8995

9096
Learn more about usage from our [docs](https://docs.swmansion.com/detour/docs/SDK/sdk-usage)
9197

98+
### Controlling which links Detour processes
99+
100+
Use `linkProcessingMode` to control which link sources the SDK listens to:
101+
102+
|Value|Universal/App links|Deferred links|Custom scheme links|
103+
|---|---|---|---|
104+
|`'all'` (default)||||
105+
|`'web-only'`||||
106+
|`'deferred-only'`||||
107+
108+
```js
109+
const config: Config = {
110+
apiKey: '<REPLACE_WITH_YOUR_API_KEY>',
111+
appID: '<REPLACE_WITH_APP_ID_FROM_PLATFORM>',
112+
// Process Universal/App links and deferred links, but let your own
113+
// navigation layer handle custom scheme links (e.g. myapp://...).
114+
linkProcessingMode: 'web-only',
115+
};
116+
```
117+
118+
Use `'deferred-only'` when Expo Router's `+native-intent.tsx` handler is already resolving runtime Universal/App links — this prevents double-processing.
119+
120+
## Examples
121+
122+
All example apps with Detour SDK integrated live in `examples/`:
123+
124+
- `examples/expo-bare`
125+
- `examples/expo-router`
126+
- `examples/expo-router-native-intent`
127+
- `examples/expo-router-advanced`
128+
- `examples/react-navigation`
129+
- `examples/react-navigation-advanced`
130+
131+
You can run them from repo root:
132+
133+
```sh
134+
yarn examples:expo-bare start
135+
yarn examples:expo-router start
136+
yarn examples:expo-router-native-intent start
137+
yarn examples:expo-router-advanced start
138+
yarn examples:react-navigation start
139+
yarn examples:react-navigation-advanced start
140+
```
141+
142+
If you want to know more details about a given example and how to configure it, please read the README in the appropriate example directory.
143+
92144
## Clearing handled links
93145

94146
If your app redirects based on `linkRoute` (especially in entry screens), call `clearLink()` after handling the route. This prevents repeated redirects when the user returns to the same screen.
@@ -111,7 +163,7 @@ export type Config = {
111163
/**
112164
* Your API key from the Detour dashboard.
113165
*/
114-
API_KEY: string;
166+
apiKey: string;
115167

116168
/**
117169
* Optional: A flag to determine if the provider should check the clipboard for a deferred link.
@@ -121,6 +173,15 @@ export type Config = {
121173
*/
122174
shouldUseClipboard?: boolean;
123175

176+
/**
177+
* Optional: Controls which link sources are handled by the SDK.
178+
* - 'all': deferred links + Universal/App links + custom scheme links (default)
179+
* - 'web-only': deferred links + Universal/App links, but NOT custom scheme links
180+
* - 'deferred-only': only deferred links (use when native-intent already handles runtime links)
181+
* Defaults to 'all'.
182+
*/
183+
linkProcessingMode?: 'all' | 'web-only' | 'deferred-only';
184+
124185
/**
125186
* Optional: A custom storage adapter. Defaults to AsyncStorage if not provided.
126187
*/
@@ -135,23 +196,25 @@ This type represents the object returned by the useDetourContext hook, containin
135196
```js
136197
export type DetourContextType = {
137198
/**
138-
* Boolean indicating if the deferred link, Universal/App Link or scheme deep link has been processed.
139-
* This is useful for conditionally rendering UI components.
199+
* Boolean indicating if the initial link (deferred, Universal/App Link, or scheme) has been processed.
200+
* Use this to gate navigation or hide the splash screen.
140201
*/
141202
isLinkProcessed: boolean;
142203

143204
/**
144-
* The deferred link, Universal/App Link or scheme deep link url. This can be a string or a URL object, or null if no link was found.
205+
* The raw link URL (string or URL object), or null if no link was found.
145206
*/
146207
linkUrl: string | URL | null;
147208

148209
/**
149-
* The detected route based on the link url, or null if no route was detected.
210+
* The parsed route path derived from the link (e.g. '/details/42'), or null if no link was found.
150211
*/
151212
linkRoute: string | null;
152213

153214
/**
154-
* The type of the detected link. Can be 'deferred', 'verified' or 'scheme'. This can be null if no link was found.
215+
* The type of the detected link: 'deferred', 'verified' (Universal/App link), or 'scheme'.
216+
* 'scheme' is only emitted when linkProcessingMode is 'all' (default).
217+
* Null if no link was found.
155218
*/
156219
linkType: LinkType | null;
157220

example/ios/.gitignore

Lines changed: 0 additions & 30 deletions
This file was deleted.

example/ios/.xcode.env

Lines changed: 0 additions & 11 deletions
This file was deleted.

example/ios/Podfile

Lines changed: 0 additions & 64 deletions
This file was deleted.

0 commit comments

Comments
 (0)