From ae29804c6b0901c0ef80b6899c2f9ba0bfe10b5b Mon Sep 17 00:00:00 2001 From: Neil Fenton Date: Wed, 16 May 2018 11:22:29 -0400 Subject: [PATCH] Add ability to emit cross-origin links --- src/components/History.js | 14 ++++++- src/components/RouterHistoryContainer.js | 10 ++++- src/utils/onLink.js | 6 ++- test/utils/onLink.js | 51 +++++++++++++++++++++++- 4 files changed, 74 insertions(+), 7 deletions(-) diff --git a/src/components/History.js b/src/components/History.js index f3dc218..33af438 100644 --- a/src/components/History.js +++ b/src/components/History.js @@ -33,7 +33,14 @@ const History = createReactClass({ state: PropTypes.object, onChange: PropTypes.func, isWaiting: PropTypes.bool, - history: PropTypes.object + history: PropTypes.object, + shouldEmitCrossOriginLinks: PropTypes.bool, + }, + + getDefaultProps() { + return { + shouldEmitCrossOriginLinks: false, + }; }, shouldComponentUpdate({url, state, isWaiting}) { @@ -42,6 +49,7 @@ const History = createReactClass({ }, componentWillMount() { + const { shouldEmitCrossOriginLinks } = this.props; this.history = this.props.history || createHistory(); if (this.props.url == null) { const unlistenCurrent = this.history.listen(location => { @@ -52,7 +60,9 @@ const History = createReactClass({ } this.unlistenBeforeLocationChange = this.history.listenBefore(this.onBeforeLocationChange); - this.unsubscribeFromLinks = onLink((event) => { + this.unsubscribeFromLinks = onLink({ + shouldEmitCrossOriginLinks: shouldEmitCrossOriginLinks, + },(event) => { if (event.target.href) { // Anchor tags are frontend-only anyway, so they will get picked up // by history. Don't let them call `onChange`. diff --git a/src/components/RouterHistoryContainer.js b/src/components/RouterHistoryContainer.js index 4f67340..259f26c 100644 --- a/src/components/RouterHistoryContainer.js +++ b/src/components/RouterHistoryContainer.js @@ -15,6 +15,13 @@ import History from './History'; const RouterHistoryContainer = createReactClass({ propTypes: { routes: PropTypes.object.isRequired, + shouldEmitCrossOriginLinks: PropTypes.bool, + }, + + getDefaultProps() { + return { + shouldEmitCrossOriginLinks: false, + }; }, onChangeAddress(url, state) { @@ -25,7 +32,7 @@ const RouterHistoryContainer = createReactClass({ }, render() { - const { router } = this.props; + const { router, shouldEmitCrossOriginLinks } = this.props; const url = router.current ? router.current.url : null; const state = router.current ? router.current.state : undefined; @@ -35,6 +42,7 @@ const RouterHistoryContainer = createReactClass({ , { +const onLink = (config, handler) => { listeners.push(handler); @@ -80,7 +80,9 @@ const onLink = (handler) => { if (element.target) { return false; } // x-origin - if (!isSameOrigin(element.href)) { return false; } + if (config.shouldEmitCrossOriginLinks !== true && !isSameOrigin(element.href)) { + return false; + } return href; }; diff --git a/test/utils/onLink.js b/test/utils/onLink.js index b9a13d1..4c82148 100644 --- a/test/utils/onLink.js +++ b/test/utils/onLink.js @@ -13,11 +13,11 @@ test('onLink subscribe/click/unsubscribe', t => { Hello `; const onLinkSpy = sinon.spy(); - const unsubscribe = onLink(onLinkSpy); + const unsubscribe = onLink({ shouldEmitCrossOriginLinks: false }, onLinkSpy); var mouseEvent = new document.defaultView.MouseEvent('click', { view: window, bubbles: true, - cancelable: true + cancelable: true, }); document.getElementById('hello').dispatchEvent(mouseEvent); t.is(onLinkSpy.callCount, 1); @@ -26,3 +26,50 @@ test('onLink subscribe/click/unsubscribe', t => { document.getElementById('hello').dispatchEvent(mouseEvent); t.is(onLinkSpy.callCount, 1); }); + +test('onLink should emit cross-origin links when enabled', t => { + const node = document.createElement('div'); + node.id = 'onLinkContaine-1'; + document.body.appendChild(node); + node.innerHTML = ` + Hello + `; + + const onLinkSpy = sinon.spy(); + const unsubscribe = onLink({ shouldEmitCrossOriginLinks: true }, onLinkSpy); + + const mouseEvent = new document.defaultView.MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true, + }); + + document.getElementById('onlink-1').dispatchEvent(mouseEvent); + t.is(onLinkSpy.lastCall.args[0].target.href, 'http://www.google.com/hello'); + unsubscribe(); + + document.getElementById('onlink-1').dispatchEvent(mouseEvent); + t.is(onLinkSpy.callCount, 1); +}); + +test('onLink should NOT emit cross-origin links when enabled', t => { + const node = document.createElement('div'); + node.id = 'onLinkContainer-2'; + document.body.appendChild(node); + node.innerHTML = ` + Hello + `; + + const onLinkSpy = sinon.spy(); + const unsubscribe = onLink({ shouldEmitCrossOriginLinks: false }, onLinkSpy); + + const mouseEvent = new document.defaultView.MouseEvent('click', { + view: window, + bubbles: true, + cancelable: true, + }); + + document.getElementById('onlink-2').dispatchEvent(mouseEvent); + t.is(onLinkSpy.lastCall.args[0].href, undefined); + unsubscribe(); +});