Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions packages/react-native/Libraries/Linking/Linking.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,13 @@ export interface LinkingImpl extends NativeEventEmitter {
action: string,
extras?: Array<{key: string; value: string | number | boolean}>,
): Promise<void>;

/**
* Open a URL as a universal link if possible.
* Returns a Promise that resolves to a boolean indicating whether the URL was opened.
* @platform ios
*/
openUniversalLink(url: string): Promise<boolean>;
}

export const Linking: LinkingImpl;
Expand Down
17 changes: 17 additions & 0 deletions packages/react-native/Libraries/Linking/Linking.js
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,23 @@ class LinkingImpl extends NativeEventEmitter<LinkingEventDefinitions> {
}
}

/**
* Open a URL as a universal link if possible.
* Returns a Promise that resolves to a boolean indicating whether the URL was opened.
*
* @platform ios
*
* See https://reactnative.dev/docs/linking#openuniversallink
*/
openUniversalLink(url: string): Promise<boolean> {
this._validateURL(url);
if (Platform.OS === 'ios') {
return nullthrows(NativeLinkingManager).openUniversalLink(url);
} else {
return new Promise((resolve, reject) => reject(new Error('Unsupported')));
}
}

_validateURL(url: string): void {
invariant(
typeof url === 'string',
Expand Down
14 changes: 14 additions & 0 deletions packages/react-native/Libraries/LinkingIOS/RCTLinkingManager.mm
Original file line number Diff line number Diff line change
Expand Up @@ -177,6 +177,20 @@ - (void)handleOpenURLNotification:(NSNotification *)notification
}];
}

RCT_EXPORT_METHOD(openUniversalLink : (NSURL *)URL resolve : (RCTPromiseResolveBlock)resolve reject : (RCTPromiseRejectBlock)reject)
{
if (URL == nil) {
reject(RCTErrorUnspecified, @"Invalid URL passed to openUniversalLink", nil);
return;
}

[RCTSharedApplication() openURL:URL
options:@{UIApplicationOpenURLOptionUniversalLinksOnly : @YES}
completionHandler:^(BOOL success) {
resolve(@(success));
}];
}

RCT_EXPORT_METHOD(
sendIntent : (NSString *)action extras : (NSArray *_Nullable)extras resolve : (RCTPromiseResolveBlock)
resolve reject : (RCTPromiseRejectBlock)reject)
Expand Down
5 changes: 3 additions & 2 deletions packages/react-native/ReactNativeApi.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @generated SignedSource<<bffba0612c645ddb6166e1f5bb1187f2>>
* @generated SignedSource<<fe9bbe901b2e0cae1f53a77d2cdcf74a>>
*
* This file was generated by scripts/js-api/build-types/index.js.
*/
Expand Down Expand Up @@ -3019,6 +3019,7 @@ declare class LinkingImpl extends NativeEventEmitter<LinkingEventDefinitions> {
constructor()
getInitialURL(): Promise<null | string | undefined>
openSettings(): Promise<void>
openUniversalLink(url: string): Promise<boolean>
openURL(url: string): Promise<void>
sendIntent(
action: string,
Expand Down Expand Up @@ -6070,7 +6071,7 @@ export {
LayoutChangeEvent, // c674f902
LayoutConformanceProps, // 055f03b8
LayoutRectangle, // 6601b294
Linking, // b5645d2b
Linking, // 569f787d
ListRenderItem, // b5353fd8
ListRenderItemInfo, // e8595b03
ListViewToken, // 833d3481
Expand Down
4 changes: 4 additions & 0 deletions packages/react-native/jest/mocks/Linking.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ const Linking = {
$FlowFixMe,
>,
sendIntent: jest.fn() as JestMockFn<$FlowFixMe, $FlowFixMe>,
openUniversalLink: jest.fn(() => Promise.resolve(false)) as JestMockFn<
$FlowFixMe,
$FlowFixMe,
>,
};

export default Linking;
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ export interface Spec extends TurboModule {
+openURL: (url: string) => Promise<void>;
+openSettings: () => Promise<void>;

// iOS only
+openUniversalLink: (url: string) => Promise<boolean>;

// Events
+addListener: (eventName: string) => void;
+removeListeners: (count: number) => void;
Expand Down
54 changes: 54 additions & 0 deletions packages/rn-tester/js/examples/Linking/LinkingExample.js
Original file line number Diff line number Diff line change
Expand Up @@ -65,6 +65,40 @@ class OpenSettingsExample extends React.Component<$ReadOnly<{}>> {
}
}

const OpenUniversalLinkButton = ({
url,
}: $ReadOnly<{
url: string,
}>) => {
const [isOpening, setIsOpening] = useState(false);

const handlePress = async () => {
setIsOpening(true);
try {
const opened = await Linking.openUniversalLink(url);
if (!opened) {
console.log(
`Could not open universal link: ${url}. The app may not be installed or the URL is not a valid universal link.`,
);
}
} catch (e) {
console.log(`Error opening universal link: ${e.message}`);
} finally {
setIsOpening(false);
}
};

return (
<TouchableOpacity onPress={handlePress}>
<View style={[styles.button, styles.buttonUniversalLink]}>
<RNTesterText style={styles.text}>
{isOpening ? `Opening ${url}...` : `Open Universal Link: ${url}`}
</RNTesterText>
</View>
</TouchableOpacity>
);
};

const SendIntentButton = ({
action,
extras,
Expand Down Expand Up @@ -143,6 +177,9 @@ const styles = StyleSheet.create({
buttonIntent: {
backgroundColor: '#009688',
},
buttonUniversalLink: {
backgroundColor: '#FF5722',
},
text: {
color: 'white',
},
Expand Down Expand Up @@ -170,4 +207,21 @@ exports.examples = [
return <OpenSettingsExample />;
},
},
{
title: 'Open Universal Link (iOS only)',
description:
'Opens a URL as a universal link. Returns true if the URL was opened by an app, false otherwise.',
render(): React.MixedElement {
return Platform.OS === 'ios' ? (
<View>
<OpenUniversalLinkButton url={'https://www.apple.com'} />
<OpenUniversalLinkButton
url={'https://maps.apple/p/BtG72tN4IzUEXM'}
/>
</View>
) : (
<RNTesterText>This feature is only available on iOS.</RNTesterText>
);
},
},
] as Array<RNTesterModuleExample>;