From e493af50cf778e324036cea2324bce680787e956 Mon Sep 17 00:00:00 2001 From: Sudhanshu Gautam Date: Mon, 4 Jan 2021 21:39:15 +0530 Subject: [PATCH] [add] Linking: event listeners Fix ##1650 Close #1863 --- .../docs/src/apis/Linking/Linking.stories.mdx | 24 ++++++- .../docs/src/apis/Linking/examples/OpenURL.js | 16 +++++ .../src/exports/Linking/index.js | 65 ++++++++++++++++--- 3 files changed, 96 insertions(+), 9 deletions(-) diff --git a/packages/docs/src/apis/Linking/Linking.stories.mdx b/packages/docs/src/apis/Linking/Linking.stories.mdx index 11d872dc..25b621f1 100644 --- a/packages/docs/src/apis/Linking/Linking.stories.mdx +++ b/packages/docs/src/apis/Linking/Linking.stories.mdx @@ -9,6 +9,14 @@ Linking gives you a general interface for securely opening external URLs from Ja ## Methods +### addEventListener(event, callback) + +Adds a event listener for the specified event. The callback will be called when the said event is dispatched. + +### removeEventListener(event, callback) + +Removes a previously added event listener for the specified event. The callback must be the same object as the one passed to `addEventListener`. + ### canOpenURL(url) Returns a `Promise` that resolves to a boolean indicating whether the app can open the URL. @@ -23,9 +31,23 @@ Try to open the given url in a secure fashion. The method returns a `Promise` object. If the url opens, the promise is resolved. If not, the promise is rejected. +> On opening URL successfully, dispatches the `onOpen` event. + +## Events + +### `onOpen` + +Dispatched when a call to `Linking.openURL` succeeds. Type signature of the callback is: + +```js +(url: string) => void +``` + ## Example - +> Listener added to the`onOpen` event. Check the console for the event log after a URL is opened. + + diff --git a/packages/docs/src/apis/Linking/examples/OpenURL.js b/packages/docs/src/apis/Linking/examples/OpenURL.js index 49d1f117..d87be817 100644 --- a/packages/docs/src/apis/Linking/examples/OpenURL.js +++ b/packages/docs/src/apis/Linking/examples/OpenURL.js @@ -1,15 +1,31 @@ +/** + * @flow + */ + import { Linking, StyleSheet, Text, View } from 'react-native'; import React, { PureComponent } from 'react'; const url = 'https://mathiasbynens.github.io/rel-noopener/malicious.html'; class OpenURLExample extends PureComponent { + componentDidMount() { + Linking.addEventListener('onOpen', this.onOpenURL); + } + + componentWillUnmount() { + Linking.removeEventListener('onOpen', this.onOpenURL); + } + handlePress() { Linking.canOpenURL(url).then(supported => { return Linking.openURL(url); }); } + onOpenURL(url: string) { + console.log(`%c opened the url: ${url} `, 'background: #D32F2F; color: #FFFFFF'); + } + render() { return ( diff --git a/packages/react-native-web/src/exports/Linking/index.js b/packages/react-native-web/src/exports/Linking/index.js index c846e2d2..8aaa4fed 100644 --- a/packages/react-native-web/src/exports/Linking/index.js +++ b/packages/react-native-web/src/exports/Linking/index.js @@ -13,28 +13,77 @@ import invariant from 'fbjs/lib/invariant'; const initialURL = canUseDOM ? window.location.href : ''; -const Linking = { - addEventListener() {}, - removeEventListener() {}, +type Callback = (...args: any) => void; + +type OnOpenCallback = (event: 'onOpen', callback: (url: string) => void) => void; +type GenericCallback = (event: string, callback: Callback) => void; + +class Linking { + /** + * An object mapping of event name + * and all the callbacks subscribing to it + */ + _eventCallbacks: { [key: string]: Array } = {}; + + _dispatchEvent(event: string, ...data: any) { + const listeners = this._eventCallbacks[event]; + if (listeners != null && Array.isArray(listeners)) { + listeners.map(listener => { + listener(...data); + }); + } + } + + /** + * Adds a event listener for the specified event. The callback will be called when the + * said event is dispatched. + */ + addEventListener: OnOpenCallback | GenericCallback = (event: string, callback: Callback) => { + if (!this._eventCallbacks[event]) { + this._eventCallbacks[event] = [callback]; + return; + } + this._eventCallbacks[event].push(callback); + }; + + /** + * Removes a previously added event listener for the specified event. The callback must + * be the same object as the one passed to `addEventListener`. + */ + removeEventListener: OnOpenCallback | GenericCallback = (event: string, callback: Callback) => { + const callbacks = this._eventCallbacks[event]; + const filteredCallbacks = callbacks.filter(c => c.toString() !== callback.toString()); + this._eventCallbacks[event] = filteredCallbacks; + }; + canOpenURL(): Promise { return Promise.resolve(true); - }, + } + getInitialURL(): Promise { return Promise.resolve(initialURL); - }, + } + + /** + * Try to open the given url in a secure fashion. The method returns a Promise object. + * If the url opens, the promise is resolved. If not, the promise is rejected. + * Dispatches the `onOpen` event if `url` is opened successfully + */ openURL(url: string): Promise { try { open(url); + this._dispatchEvent('onOpen', url); return Promise.resolve(); } catch (e) { return Promise.reject(e); } - }, + } + _validateURL(url: string) { invariant(typeof url === 'string', 'Invalid URL: should be a string. Was: ' + url); invariant(url, 'Invalid URL: cannot be empty'); } -}; +} const open = url => { if (canUseDOM) { @@ -42,4 +91,4 @@ const open = url => { } }; -export default Linking; +export default (new Linking(): Linking);