expo-linking 2.4.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/Linking.ts CHANGED
@@ -3,10 +3,17 @@ import { Platform, UnavailabilityError } from 'expo-modules-core';
3
3
  import invariant from 'invariant';
4
4
  import qs from 'qs';
5
5
  import { useEffect, useState } from 'react';
6
+ import { EmitterSubscription } from 'react-native';
6
7
  import URL from 'url-parse';
7
8
 
8
9
  import NativeLinking from './ExpoLinking';
9
- import { CreateURLOptions, ParsedURL, QueryParams, URLListener } from './Linking.types';
10
+ import {
11
+ CreateURLOptions,
12
+ ParsedURL,
13
+ QueryParams,
14
+ SendIntentExtras,
15
+ URLListener,
16
+ } from './Linking.types';
10
17
  import { hasCustomScheme, resolveScheme } from './Schemes';
11
18
 
12
19
  function validateURL(url: string): void {
@@ -17,7 +24,11 @@ function validateURL(url: string): void {
17
24
  function getHostUri(): string | null {
18
25
  if (Constants.manifest?.hostUri) {
19
26
  return Constants.manifest.hostUri;
27
+ // @ts-ignore: hostUri isn't defined on the expoClient type, because
28
+ // Constants.manifest is of type AppManifest while
29
+ // Constants.manifest2.extra.expoClient is of type ExpoConfig
20
30
  } else if (Constants.manifest2?.extra?.expoClient?.hostUri) {
31
+ // @ts-ignore
21
32
  return Constants.manifest2.extra.expoClient.hostUri;
22
33
  } else if (!hasCustomScheme()) {
23
34
  // we're probably not using up-to-date xdl, so just fake it for now
@@ -33,7 +44,8 @@ function isExpoHosted(): boolean {
33
44
  return !!(
34
45
  hostUri &&
35
46
  (/^(.*\.)?(expo\.io|exp\.host|exp\.direct|expo\.test)(:.*)?(\/.*)?$/.test(hostUri) ||
36
- Constants.manifest?.developer)
47
+ Constants.manifest?.developer ||
48
+ Constants.manifest2?.extra?.expoGo?.developer)
37
49
  );
38
50
  }
39
51
 
@@ -73,12 +85,12 @@ function ensureLeadingSlash(input: string, shouldAppend: boolean): string {
73
85
  return input;
74
86
  }
75
87
 
88
+ // @needsAudit
76
89
  /**
77
90
  * Create a URL that works for the environment the app is currently running in.
78
91
  * The scheme in bare and standalone must be defined in the app.json under `expo.scheme`.
79
92
  *
80
- * **Examples**
81
- *
93
+ * # Examples
82
94
  * - Bare: empty string
83
95
  * - Standalone, Custom: `yourscheme:///path`
84
96
  * - Web (dev): `https://localhost:19006/path`
@@ -87,28 +99,38 @@ function ensureLeadingSlash(input: string, shouldAppend: boolean): string {
87
99
  * - Expo Client (prod): `exp://exp.host/@yourname/your-app/--/path`
88
100
  *
89
101
  * @param path addition path components to append to the base URL.
90
- * @param queryParams An object of parameters that will be converted into a query string.
102
+ * @param queryParams An object with a set of query parameters. These will be merged with any
103
+ * Expo-specific parameters that are needed (e.g. release channel) and then appended to the URL
104
+ * as a query string.
105
+ * @param scheme Optional URI protocol to use in the URL `<scheme>:///`, when `undefined` the scheme
106
+ * will be chosen from the Expo config (`app.config.js` or `app.json`).
107
+ * @return A URL string which points to your app with the given deep link information.
108
+ * @deprecated An alias for [`createURL()`](#linkingcreateurlpath-namedparameters). This method is
109
+ * deprecated and will be removed in a future SDK version.
91
110
  */
92
111
  export function makeUrl(path: string = '', queryParams?: QueryParams, scheme?: string): string {
93
112
  return createURL(path, { queryParams, scheme, isTripleSlashed: true });
94
113
  }
95
114
 
115
+ // @needsAudit
96
116
  /**
97
- * Create a URL that works for the environment the app is currently running in.
98
- * The scheme in bare and standalone must be defined in the Expo config (app.config.js or app.json) under `expo.scheme`.
117
+ * Helper method for constructing a deep link into your app, given an optional path and set of query
118
+ * parameters. Creates a URI scheme with two slashes by default.
99
119
  *
100
- * **Examples**
120
+ * The scheme in bare and standalone must be defined in the Expo config (`app.config.js` or `app.json`)
121
+ * under `expo.scheme`.
101
122
  *
102
- * - Bare: `<scheme>://path` -- uses provided scheme or scheme from Expo config `scheme`.
123
+ * # Examples
124
+ * - Bare: `<scheme>://path` - uses provided scheme or scheme from Expo config `scheme`.
103
125
  * - Standalone, Custom: `yourscheme://path`
104
126
  * - Web (dev): `https://localhost:19006/path`
105
127
  * - Web (prod): `https://myapp.com/path`
106
128
  * - Expo Client (dev): `exp://128.0.0.1:19000/--/path`
107
129
  * - Expo Client (prod): `exp://exp.host/@yourname/your-app/--/path`
108
130
  *
109
- * @param path addition path components to append to the base URL.
110
- * @param scheme URI protocol `<scheme>://` that must be built into your native app.
111
- * @param queryParams An object of parameters that will be converted into a query string.
131
+ * @param path Addition path components to append to the base URL.
132
+ * @param namedParameters Additional options object.
133
+ * @return A URL string which points to your app with the given deep link information.
112
134
  */
113
135
  export function createURL(
114
136
  path: string,
@@ -161,7 +183,7 @@ export function createURL(
161
183
  if (typeof parsedParams === 'object') {
162
184
  paramsFromHostUri = parsedParams;
163
185
  }
164
- } catch (e) {}
186
+ } catch {}
165
187
  queryParams = {
166
188
  ...queryParams,
167
189
  ...paramsFromHostUri,
@@ -179,10 +201,11 @@ export function createURL(
179
201
  );
180
202
  }
181
203
 
204
+ // @needsAudit
182
205
  /**
183
- * Returns the components and query parameters for a given URL.
184
- *
185
- * @param url Input URL to parse
206
+ * Helper method for parsing out deep link information from a URL.
207
+ * @param url A URL that points to the currently running experience (e.g. an output of `Linking.createURL()`).
208
+ * @return A `ParsedURL` object.
186
209
  */
187
210
  export function parse(url: string): ParsedURL {
188
211
  validateURL(url);
@@ -231,28 +254,40 @@ export function parse(url: string): ParsedURL {
231
254
  };
232
255
  }
233
256
 
257
+ // @needsAudit
234
258
  /**
235
- * Add a handler to Linking changes by listening to the `url` event type
236
- * and providing the handler
237
- *
238
- * See https://reactnative.dev/docs/linking.html#addeventlistener
259
+ * Add a handler to `Linking` changes by listening to the `url` event type and providing the handler.
260
+ * It is recommended to use the [`useURL()`](#useurl) hook instead.
261
+ * @param type The only valid type is `'url'`.
262
+ * @param handler An [`URLListener`](#urllistener) function that takes an `event` object of the type
263
+ * [`EventType`](#eventype).
264
+ * @return An EmitterSubscription that has the remove method from EventSubscription
265
+ * @see [React Native Docs Linking page](https://reactnative.dev/docs/linking#addeventlistener).
239
266
  */
240
- export function addEventListener(type: string, handler: URLListener) {
241
- NativeLinking.addEventListener(type, handler);
267
+ export function addEventListener(type: 'url', handler: URLListener): EmitterSubscription {
268
+ return NativeLinking.addEventListener(type, handler);
242
269
  }
243
270
 
244
271
  /**
245
272
  * Remove a handler by passing the `url` event type and the handler.
273
+ * @param type The only valid type is `'url'`.
274
+ * @param handler An [`URLListener`](#urllistener) function that takes an `event` object of the type
275
+ * [`EventType`](#eventype).
276
+ * @see [React Native Docs Linking page](https://reactnative.dev/docs/linking#removeeventlistener).
246
277
  *
247
- * See https://reactnative.dev/docs/linking.html#removeeventlistener
278
+ * @deprecated Call `remove()` on the return value of `addEventListener()` instead.
248
279
  */
249
- export function removeEventListener(type: string, handler: URLListener) {
280
+ export function removeEventListener(type: 'url', handler: URLListener): void {
250
281
  NativeLinking.removeEventListener(type, handler);
251
282
  }
252
283
 
284
+ // @needsAudit
253
285
  /**
254
- * **Native:** Parses the link that opened the app. If no link opened the app, all the fields will be \`null\`.
255
- * **Web:** Parses the current window URL.
286
+ * Helper method which wraps React Native's `Linking.getInitialURL()` in `Linking.parse()`.
287
+ * Parses the deep link information out of the URL used to open the experience initially.
288
+ * If no link opened the app, all the fields will be `null`.
289
+ * > On the web it parses the current window URL.
290
+ * @return A promise that resolves with `ParsedURL` object.
256
291
  */
257
292
  export async function parseInitialURLAsync(): Promise<ParsedURL> {
258
293
  const initialUrl = await NativeLinking.getInitialURL();
@@ -268,25 +303,23 @@ export async function parseInitialURLAsync(): Promise<ParsedURL> {
268
303
  return parse(initialUrl);
269
304
  }
270
305
 
306
+ // @needsAudit
271
307
  /**
272
- * Launch an Android intent with optional extras
273
- *
308
+ * Launch an Android intent with extras.
309
+ * > Use [IntentLauncher](./intent-launcher) instead, `sendIntent` is only included in
310
+ * > `Linking` for API compatibility with React Native's Linking API.
274
311
  * @platform android
275
312
  */
276
- export async function sendIntent(
277
- action: string,
278
- extras?: { key: string; value: string | number | boolean }[]
279
- ): Promise<void> {
313
+ export async function sendIntent(action: string, extras?: SendIntentExtras[]): Promise<void> {
280
314
  if (Platform.OS === 'android') {
281
315
  return await NativeLinking.sendIntent(action, extras);
282
316
  }
283
317
  throw new UnavailabilityError('Linking', 'sendIntent');
284
318
  }
285
319
 
320
+ // @needsAudit
286
321
  /**
287
- * Attempt to open the system settings for an the app.
288
- *
289
- * @platform ios
322
+ * Open the operating system settings app and displays the app’s custom settings, if it has any.
290
323
  */
291
324
  export async function openSettings(): Promise<void> {
292
325
  if (Platform.OS === 'web') {
@@ -298,33 +331,49 @@ export async function openSettings(): Promise<void> {
298
331
  await openURL('app-settings:');
299
332
  }
300
333
 
334
+ // @needsAudit
301
335
  /**
302
- * If the app launch was triggered by an app link,
303
- * it will give the link url, otherwise it will give `null`
336
+ * Get the URL that was used to launch the app if it was launched by a link.
337
+ * @return The URL string that launched your app, or `null`.
304
338
  */
305
339
  export async function getInitialURL(): Promise<string | null> {
306
340
  return (await NativeLinking.getInitialURL()) ?? null;
307
341
  }
308
342
 
343
+ // @needsAudit
309
344
  /**
310
- * Try to open the given `url` with any of the installed apps.
345
+ * Attempt to open the given URL with an installed app. See the [Linking guide](/guides/linking)
346
+ * for more information.
347
+ * @param url A URL for the operating system to open, eg: `tel:5555555`, `exp://`.
348
+ * @return A `Promise` that is fulfilled with `true` if the link is opened operating system
349
+ * automatically or the user confirms the prompt to open the link. The `Promise` rejects if there
350
+ * are no applications registered for the URL or the user cancels the dialog.
311
351
  */
312
352
  export async function openURL(url: string): Promise<true> {
313
353
  validateURL(url);
314
354
  return await NativeLinking.openURL(url);
315
355
  }
316
356
 
357
+ // @needsAudit
317
358
  /**
318
359
  * Determine whether or not an installed app can handle a given URL.
319
- * On web this always returns true because there is no API for detecting what URLs can be opened.
360
+ * On web this always returns `true` because there is no API for detecting what URLs can be opened.
361
+ * @param url The URL that you want to test can be opened.
362
+ * @return A `Promise` object that is fulfilled with `true` if the URL can be handled, otherwise it
363
+ * `false` if not.
364
+ *
365
+ * The `Promise` will reject on Android if it was impossible to check if the URL can be opened, and
366
+ * on iOS if you didn't [add the specific scheme in the `LSApplicationQueriesSchemes` key inside **Info.plist**](/guides/linking#opening-links-to-other-apps).
320
367
  */
321
368
  export async function canOpenURL(url: string): Promise<boolean> {
322
369
  validateURL(url);
323
370
  return await NativeLinking.canOpenURL(url);
324
371
  }
325
372
 
373
+ // @needsAudit
326
374
  /**
327
375
  * Returns the initial URL followed by any subsequent changes to the URL.
376
+ * @return Returns the initial URL or `null`.
328
377
  */
329
378
  export function useURL(): string | null {
330
379
  const [url, setLink] = useState<string | null>(null);
@@ -335,22 +384,12 @@ export function useURL(): string | null {
335
384
 
336
385
  useEffect(() => {
337
386
  getInitialURL().then((url) => setLink(url));
338
- addEventListener('url', onChange);
339
- return () => removeEventListener('url', onChange);
387
+ const subscription = addEventListener('url', onChange);
388
+ return () => subscription.remove();
340
389
  }, []);
341
390
 
342
391
  return url;
343
392
  }
344
393
 
345
- /**
346
- * Returns the initial URL followed by any subsequent changes to the URL.
347
- * @deprecated Use `useURL` instead.
348
- */
349
- export function useUrl(): string | null {
350
- console.warn(
351
- `Linking.useUrl has been deprecated in favor of Linking.useURL. This API will be removed in SDK 44.`
352
- );
353
- return useURL();
354
- }
355
-
356
394
  export * from './Linking.types';
395
+ export * from './Schemes';
@@ -1,14 +1,23 @@
1
1
  import { ParsedQs } from 'qs';
2
2
 
3
+ // @docsMissing
3
4
  export type QueryParams = ParsedQs;
4
5
 
6
+ // @needsAudit @docsMissing
5
7
  export type ParsedURL = {
6
8
  scheme: string | null;
7
9
  hostname: string | null;
10
+ /**
11
+ * The path into the app specified by the URL.
12
+ */
8
13
  path: string | null;
14
+ /**
15
+ * The set of query parameters specified by the query string of the url used to open the app.
16
+ */
9
17
  queryParams: QueryParams | null;
10
18
  };
11
19
 
20
+ // @needsAudit
12
21
  export type CreateURLOptions = {
13
22
  /**
14
23
  * URI protocol `<scheme>://` that must be built into your native app.
@@ -24,8 +33,17 @@ export type CreateURLOptions = {
24
33
  isTripleSlashed?: boolean;
25
34
  };
26
35
 
27
- export type EventType = { url: string; nativeEvent?: MessageEvent };
36
+ // @docsMissing
37
+ export type EventType = {
38
+ url: string;
39
+ nativeEvent?: MessageEvent;
40
+ };
28
41
 
42
+ // @docsMissing
29
43
  export type URLListener = (event: EventType) => void;
30
44
 
45
+ // @docsMissing
31
46
  export type NativeURLListener = (nativeEvent: MessageEvent) => void;
47
+
48
+ // @docsMissing
49
+ export type SendIntentExtras = { key: string; value: string | number | boolean };
package/src/Schemes.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import Constants, { ExecutionEnvironment } from 'expo-constants';
2
2
  import { Platform } from 'expo-modules-core';
3
3
 
4
- const LINKING_GUIDE_URL = `https://docs.expo.io/guides/linking/`;
4
+ const LINKING_GUIDE_URL = `https://docs.expo.dev/guides/linking/`;
5
5
 
6
+ // @docsMissing
6
7
  export function hasCustomScheme(): boolean {
7
8
  if (Constants.executionEnvironment === ExecutionEnvironment.Bare) {
8
9
  // Bare always uses a custom scheme.
@@ -101,21 +102,25 @@ function getNativeAppIdScheme(): string | null {
101
102
  );
102
103
  }
103
104
 
105
+ // @needsAudit
106
+ /**
107
+ * Ensure the user has linked the expo-constants manifest in bare workflow.
108
+ */
104
109
  export function hasConstantsManifest(): boolean {
105
- // Ensure the user has linked the expo-constants manifest in bare workflow.
106
110
  return (
107
111
  !!Object.keys(Constants.manifest ?? {}).length ||
108
112
  !!Object.keys(Constants.manifest2 ?? {}).length
109
113
  );
110
114
  }
111
115
 
112
- export function resolveScheme(props: { scheme?: string; isSilent?: boolean }): string {
116
+ // @docsMissing
117
+ export function resolveScheme(options: { scheme?: string; isSilent?: boolean }): string {
113
118
  if (
114
119
  Constants.executionEnvironment !== ExecutionEnvironment.StoreClient &&
115
120
  !hasConstantsManifest()
116
121
  ) {
117
122
  throw new Error(
118
- `expo-linking needs access to the expo-constants manifest (app.json or app.config.js) to determine what URI scheme to use. Setup the manifest and rebuild: https://github.com/expo/expo/blob/master/packages/expo-constants/README.md`
123
+ `expo-linking needs access to the expo-constants manifest (app.json or app.config.js) to determine what URI scheme to use. Setup the manifest and rebuild: https://github.com/expo/expo/blob/main/packages/expo-constants/README.md`
119
124
  );
120
125
  }
121
126
 
@@ -123,7 +128,7 @@ export function resolveScheme(props: { scheme?: string; isSilent?: boolean }): s
123
128
  const nativeAppId = getNativeAppIdScheme();
124
129
 
125
130
  if (!manifestSchemes.length) {
126
- if (__DEV__ && !props.isSilent) {
131
+ if (__DEV__ && !options.isSilent) {
127
132
  // Assert a config warning if no scheme is setup yet. `isSilent` is used for warnings, but we'll ignore it for exceptions.
128
133
  console.warn(
129
134
  `Linking requires a build-time setting \`scheme\` in the project's Expo config (app.config.js or app.json) for production apps, if it's left blank, your app may crash. The scheme does not apply to development in the Expo client but you should add it as soon as you start working with Linking to avoid creating a broken build. Learn more: ${LINKING_GUIDE_URL}`
@@ -138,10 +143,10 @@ export function resolveScheme(props: { scheme?: string; isSilent?: boolean }): s
138
143
 
139
144
  // In the Expo client...
140
145
  if (Constants.executionEnvironment === ExecutionEnvironment.StoreClient) {
141
- if (props.scheme) {
146
+ if (options.scheme) {
142
147
  // This enables users to use the fb or google redirects on iOS in the Expo client.
143
- if (EXPO_CLIENT_SCHEMES.includes(props.scheme)) {
144
- return props.scheme;
148
+ if (EXPO_CLIENT_SCHEMES.includes(options.scheme)) {
149
+ return options.scheme;
145
150
  }
146
151
  // Silently ignore to make bare workflow development easier.
147
152
  }
@@ -151,15 +156,15 @@ export function resolveScheme(props: { scheme?: string; isSilent?: boolean }): s
151
156
 
152
157
  const schemes = [...manifestSchemes, nativeAppId].filter(Boolean);
153
158
 
154
- if (props.scheme) {
159
+ if (options.scheme) {
155
160
  if (__DEV__) {
156
161
  // Bare workflow development assertion about the provided scheme matching the Expo config.
157
- if (!schemes.includes(props.scheme) && !props.isSilent) {
162
+ if (!schemes.includes(options.scheme) && !options.isSilent) {
158
163
  // TODO: Will this cause issues for things like Facebook or Google that use `reversed-client-id://` or `fb<FBID>:/`?
159
164
  // Traditionally these APIs don't use the Linking API directly.
160
165
  console.warn(
161
166
  `The provided Linking scheme '${
162
- props.scheme
167
+ options.scheme
163
168
  }' does not appear in the list of possible URI schemes in your Expo config. Expected one of: ${schemes
164
169
  .map((scheme) => `'${scheme}'`)
165
170
  .join(', ')}`
@@ -167,7 +172,7 @@ export function resolveScheme(props: { scheme?: string; isSilent?: boolean }): s
167
172
  }
168
173
  }
169
174
  // Return the user provided value.
170
- return props.scheme;
175
+ return options.scheme;
171
176
  }
172
177
  // If no scheme is provided, we'll guess what the scheme is based on the manifest.
173
178
  // This is to attempt to keep managed apps working across expo build and EAS build.
@@ -175,7 +180,7 @@ export function resolveScheme(props: { scheme?: string; isSilent?: boolean }): s
175
180
  // be using one of defined schemes.
176
181
 
177
182
  // If the native app id is the only scheme,
178
- if (!!nativeAppId && !manifestSchemes.length && !props.isSilent) {
183
+ if (!!nativeAppId && !manifestSchemes.length && !options.isSilent) {
179
184
  // Assert a config warning if no scheme is setup yet.
180
185
  // This warning only applies to managed workflow EAS apps, as bare workflow
181
186
  console.warn(
@@ -195,7 +200,7 @@ export function resolveScheme(props: { scheme?: string; isSilent?: boolean }): s
195
200
  // Throw in production, use the __DEV__ flag so users can test this functionality with `expo start --no-dev`
196
201
  throw new Error(errorMessage);
197
202
  }
198
- if (extraSchemes.length && !props.isSilent) {
203
+ if (extraSchemes.length && !options.isSilent) {
199
204
  console.warn(
200
205
  `Linking found multiple possible URI schemes in your Expo config.\nUsing '${scheme}'. Ignoring: ${[
201
206
  ...extraSchemes,