xref: /expo/packages/expo-linking/src/Linking.ts (revision 2f31cd71)
1import { UnavailabilityError } from 'expo-modules-core';
2import { useEffect, useState } from 'react';
3import { EmitterSubscription, Platform } from 'react-native';
4
5import NativeLinking from './ExpoLinking';
6import { ParsedURL, SendIntentExtras, URLListener } from './Linking.types';
7import { parse } from './createURL';
8import { validateURL } from './validateURL';
9
10// @needsAudit
11/**
12 * Add a handler to `Linking` changes by listening to the `url` event type and providing the handler.
13 * It is recommended to use the [`useURL()`](#useurl) hook instead.
14 * @param type The only valid type is `'url'`.
15 * @param handler An [`URLListener`](#urllistener) function that takes an `event` object of the type
16 * [`EventType`](#eventype).
17 * @return An EmitterSubscription that has the remove method from EventSubscription
18 * @see [React Native Docs Linking page](https://reactnative.dev/docs/linking#addeventlistener).
19 */
20export function addEventListener(type: 'url', handler: URLListener): EmitterSubscription {
21  return NativeLinking.addEventListener(type, handler);
22}
23
24// @needsAudit
25/**
26 * Helper method which wraps React Native's `Linking.getInitialURL()` in `Linking.parse()`.
27 * Parses the deep link information out of the URL used to open the experience initially.
28 * If no link opened the app, all the fields will be `null`.
29 * > On the web it parses the current window URL.
30 * @return A promise that resolves with `ParsedURL` object.
31 */
32export async function parseInitialURLAsync(): Promise<ParsedURL> {
33  const initialUrl = await NativeLinking.getInitialURL();
34  if (!initialUrl) {
35    return {
36      scheme: null,
37      hostname: null,
38      path: null,
39      queryParams: null,
40    };
41  }
42
43  return parse(initialUrl);
44}
45
46// @needsAudit
47/**
48 * Launch an Android intent with extras.
49 * > Use [IntentLauncher](./intent-launcher) instead, `sendIntent` is only included in
50 * > `Linking` for API compatibility with React Native's Linking API.
51 * @platform android
52 */
53export async function sendIntent(action: string, extras?: SendIntentExtras[]): Promise<void> {
54  if (Platform.OS === 'android') {
55    return await NativeLinking.sendIntent(action, extras);
56  }
57  throw new UnavailabilityError('Linking', 'sendIntent');
58}
59
60// @needsAudit
61/**
62 * Open the operating system settings app and displays the app’s custom settings, if it has any.
63 */
64export async function openSettings(): Promise<void> {
65  if (Platform.OS === 'web') {
66    throw new UnavailabilityError('Linking', 'openSettings');
67  }
68  if (NativeLinking.openSettings) {
69    return await NativeLinking.openSettings();
70  }
71  await openURL('app-settings:');
72}
73
74// @needsAudit
75/**
76 * Get the URL that was used to launch the app if it was launched by a link.
77 * @return The URL string that launched your app, or `null`.
78 */
79export async function getInitialURL(): Promise<string | null> {
80  return (await NativeLinking.getInitialURL()) ?? null;
81}
82
83// @needsAudit
84/**
85 * Attempt to open the given URL with an installed app. See the [Linking guide](/guides/linking)
86 * for more information.
87 * @param url A URL for the operating system to open, eg: `tel:5555555`, `exp://`.
88 * @return A `Promise` that is fulfilled with `true` if the link is opened operating system
89 * automatically or the user confirms the prompt to open the link. The `Promise` rejects if there
90 * are no applications registered for the URL or the user cancels the dialog.
91 */
92export async function openURL(url: string): Promise<true> {
93  validateURL(url);
94  return await NativeLinking.openURL(url);
95}
96
97// @needsAudit
98/**
99 * Determine whether or not an installed app can handle a given URL.
100 * On web this always returns `true` because there is no API for detecting what URLs can be opened.
101 * @param url The URL that you want to test can be opened.
102 * @return A `Promise` object that is fulfilled with `true` if the URL can be handled, otherwise it
103 * `false` if not.
104 *
105 * The `Promise` will reject on Android if it was impossible to check if the URL can be opened, and
106 * on iOS if you didn't [add the specific scheme in the `LSApplicationQueriesSchemes` key inside **Info.plist**](/guides/linking#linking-from-your-app).
107 */
108export async function canOpenURL(url: string): Promise<boolean> {
109  validateURL(url);
110  return await NativeLinking.canOpenURL(url);
111}
112
113// @needsAudit
114/**
115 * Returns the initial URL followed by any subsequent changes to the URL.
116 * @return Returns the initial URL or `null`.
117 */
118export function useURL(): string | null {
119  const [url, setLink] = useState<string | null>(null);
120
121  function onChange(event: { url: string }) {
122    setLink(event.url);
123  }
124
125  useEffect(() => {
126    getInitialURL().then((url) => setLink(url));
127    const subscription = addEventListener('url', onChange);
128    return () => subscription.remove();
129  }, []);
130
131  return url;
132}
133
134export * from './Linking.types';
135export * from './Schemes';
136export { parse, createURL } from './createURL';
137