From b4ca6421a0b0ad5043358fe3fe5c60f28f1d52f7 Mon Sep 17 00:00:00 2001 From: Martin Kraft Date: Fri, 24 Aug 2018 15:02:07 -0400 Subject: [PATCH] MM-11318: Expands shortened links inline. (#1993) * MM-11318: Expands shortened links inline. * MM-11318: Accepts only image/* mime types. Switches to HEAD request. * MM-11318: Fix for YouTube links. * MM-11318: Renames function because it handles non-images. * MM-11318: Checks for unmounted. Doesn't request known youtube redirects. --- .../post_body_additional_content.js | 40 ++++++++++++++++--- app/utils/url.js | 20 ++++++++++ 2 files changed, 54 insertions(+), 6 deletions(-) diff --git a/app/components/post_body_additional_content/post_body_additional_content.js b/app/components/post_body_additional_content/post_body_additional_content.js index 60c9968c3..e9e5d6c9a 100644 --- a/app/components/post_body_additional_content/post_body_additional_content.js +++ b/app/components/post_body_additional_content/post_body_additional_content.js @@ -22,7 +22,7 @@ import CustomPropTypes from 'app/constants/custom_prop_types'; import {emptyFunction} from 'app/utils/general'; import ImageCacheManager from 'app/utils/image_cache_manager'; import {previewImageAtIndex, calculateDimensions} from 'app/utils/images'; -import {getYouTubeVideoId, isImageLink, isYoutubeLink} from 'app/utils/url'; +import {getYouTubeVideoId, isImageLink, isYoutubeLink, getShortenedLink} from 'app/utils/url'; const VIEWPORT_IMAGE_OFFSET = 66; const VIEWPORT_IMAGE_REPLY_OFFSET = 13; @@ -67,6 +67,7 @@ export default class PostBodyAdditionalContent extends PureComponent { linkLoaded: false, width: 0, height: 0, + shortenedLink: null, }; this.mounted = false; @@ -87,7 +88,7 @@ export default class PostBodyAdditionalContent extends PureComponent { } } - load = (props) => { + load = async (props) => { const {link} = props; if (link) { let imageUrl; @@ -97,6 +98,20 @@ export default class PostBodyAdditionalContent extends PureComponent { const videoId = getYouTubeVideoId(link); imageUrl = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`; ImageCacheManager.cache(null, `https://i.ytimg.com/vi/${videoId}/default.jpg`, () => true); + } else { + const shortenedLink = await getShortenedLink(link); + if (shortenedLink) { + if (isImageLink(shortenedLink)) { + imageUrl = shortenedLink; + } else if (isYoutubeLink(shortenedLink)) { + const videoId = getYouTubeVideoId(shortenedLink); + imageUrl = `https://i.ytimg.com/vi/${videoId}/hqdefault.jpg`; + ImageCacheManager.cache(null, `https://i.ytimg.com/vi/${videoId}/default.jpg`, () => true); + } + if (this.mounted) { + this.setState({shortenedLink}); + } + } } if (imageUrl) { @@ -157,7 +172,11 @@ export default class PostBodyAdditionalContent extends PureComponent { }; generateToggleableEmbed = (isImage, isYouTube) => { - const {link} = this.props; + let {link} = this.props; + const {shortenedLink} = this.state; + if (shortenedLink) { + link = shortenedLink; + } const {width, height, uri} = this.state; const imgHeight = height; @@ -326,7 +345,12 @@ export default class PostBodyAdditionalContent extends PureComponent { }; handlePreviewImage = () => { - const {link, navigator} = this.props; + const {shortenedLink} = this.state; + let {link} = this.props; + const {navigator} = this.props; + if (shortenedLink) { + link = shortenedLink; + } const { originalHeight, originalWidth, @@ -391,8 +415,12 @@ export default class PostBodyAdditionalContent extends PureComponent { }; render() { - const {link, openGraphData, postProps} = this.props; - const {linkLoadError} = this.state; + let {link} = this.props; + const {openGraphData, postProps} = this.props; + const {linkLoadError, shortenedLink} = this.state; + if (shortenedLink) { + link = shortenedLink; + } const {attachments} = postProps; if (!link && !attachments) { diff --git a/app/utils/url.js b/app/utils/url.js index d33fc118b..e87a7b256 100644 --- a/app/utils/url.js +++ b/app/utils/url.js @@ -43,6 +43,26 @@ export function isYoutubeLink(link) { return link.trim().match(ytRegex); } +export async function getShortenedLink(link) { + return new Promise(((resolve, reject) => { + const xhr = new XMLHttpRequest(); + + xhr.onreadystatechange = () => { + if (xhr.readyState !== XMLHttpRequest.DONE) { + return; + } + resolve(xhr.responseURL); + }; + + xhr.onerror = () => { + reject(''); + }; + + xhr.open('HEAD', link); + xhr.send(); + })); +} + export function isImageLink(link) { let linkWithoutQuery = link; if (link.indexOf('?') !== -1) {