From 2cb8fb2dd9e5278611f63d376fd0b703cc164fa5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Mon, 8 Feb 2016 16:44:27 +0100 Subject: [PATCH 1/6] Replace bootstrap button with proper dl-link --- js/components/ascribe_detail/media_container.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/js/components/ascribe_detail/media_container.js b/js/components/ascribe_detail/media_container.js index 1e9ba0a1..e22069e1 100644 --- a/js/components/ascribe_detail/media_container.js +++ b/js/components/ascribe_detail/media_container.js @@ -131,9 +131,9 @@ let MediaContainer = React.createClass({ show={['video', 'audio', 'image'].indexOf(mimetype) === -1 || content.acl.acl_download} aclObject={content.acl} aclName="acl_download"> - + {embed}

From f2c7f02480a00a27a3ce84f9a155c9377dfd5f2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Wed, 17 Feb 2016 11:38:01 +0100 Subject: [PATCH 2/6] Add component for signing S3 url --- .../ascribe_buttons/s3_download_button.js | 91 +++++++++++++++++++ .../ascribe_detail/media_container.js | 19 ++-- js/constants/api_urls.js | 3 +- js/constants/application_constants.js | 2 +- js/fetchers/s3_fetcher.js | 7 ++ js/utils/requests.js | 2 +- 6 files changed, 109 insertions(+), 15 deletions(-) create mode 100644 js/components/ascribe_buttons/s3_download_button.js diff --git a/js/components/ascribe_buttons/s3_download_button.js b/js/components/ascribe_buttons/s3_download_button.js new file mode 100644 index 00000000..453bc6a8 --- /dev/null +++ b/js/components/ascribe_buttons/s3_download_button.js @@ -0,0 +1,91 @@ +'use strict'; + +import React from 'react'; + +import S3Fetcher from '../../fetchers/s3_fetcher'; + +import Glyphicon from 'react-bootstrap/lib/Glyphicon'; + +import AppConstants from '../../constants/application_constants'; + +import { getLangText } from '../../utils/lang_utils'; +import { queryParamsToArgs } from '../../utils/url_utils'; + + +const { string } = React.PropTypes; + +const S3DownloadButton = React.createClass({ + propTypes: { + url: string, + title: string, + artistName: string, + fileExtension: string + }, + + getInitialState() { + return { + downloadUrl: null + }; + }, + + componentDidMount() { + /** + * Initially, we request a signed url from + * the backend + */ + this.signUrl(); + }, + + transformS3UrlToS3Key(url) { + return url.replace(`https://${AppConstants.cloudfrontDomain}/`, ''); + }, + + signUrl(next) { + const { url, title, artistName } = this.props; + + S3Fetcher + .signUrl(this.transformS3UrlToS3Key(url), title, artistName) + .then(({ signed_url: downloadUrl }) => this.setState({ downloadUrl }, next)) + .catch(console.logGlobal); + }, + + reSignUrl(event) { + /** + * The signed url, however can expire, which is why + * we need to renew it when it expires. + */ + const { downloadUrl } = this.state; + const { expires } = queryParamsToArgs(downloadUrl.split('?')[1]); + const nowInSeconds = new Date().getTime() / 1000; + + if(nowInSeconds > expires) { + event.preventDefault(); + this.signUrl(() => this.refs.downloadButton.getDOMNode().click()); + } + }, + + render() { + const { fileExtension, url } = this.props; + const { downloadUrl } = this.state; + + return ( + + + {/* + If it turns out that `fileExtension` is an empty string, we're just + using the label 'file'. + */} + {getLangText('Download')} .{fileExtension || 'file'} + + + ); + } +}); + +export default S3DownloadButton; \ No newline at end of file diff --git a/js/components/ascribe_detail/media_container.js b/js/components/ascribe_detail/media_container.js index e22069e1..5511fdb4 100644 --- a/js/components/ascribe_detail/media_container.js +++ b/js/components/ascribe_detail/media_container.js @@ -3,13 +3,14 @@ import React from 'react'; import Button from 'react-bootstrap/lib/Button'; -import Glyphicon from 'react-bootstrap/lib/Glyphicon'; import MediaPlayer from './../ascribe_media/media_player'; import FacebookShareButton from '../ascribe_social_share/facebook_share_button'; import TwitterShareButton from '../ascribe_social_share/twitter_share_button'; +import S3DownloadButton from '../ascribe_buttons/s3_download_button'; + import CollapsibleButton from './../ascribe_collapsible/collapsible_button'; import AclProxy from '../acl_proxy'; @@ -131,17 +132,11 @@ let MediaContainer = React.createClass({ show={['video', 'audio', 'image'].indexOf(mimetype) === -1 || content.acl.acl_download} aclObject={content.acl} aclName="acl_download"> - - {/* - If it turns out that `fileExtension` is an empty string, we're just - using the label 'file'. - */} - {getLangText('Download')} .{fileExtension || 'file'} - + {embed}

diff --git a/js/constants/api_urls.js b/js/constants/api_urls.js index f7bac317..29e37724 100644 --- a/js/constants/api_urls.js +++ b/js/constants/api_urls.js @@ -76,7 +76,8 @@ let ApiUrls = { 'webhooks': AppConstants.apiEndpoint + 'webhooks/', 'webhooks_events': AppConstants.apiEndpoint + 'webhooks/events/', 'whitelabel_settings': AppConstants.apiEndpoint + 'whitelabel/settings/${subdomain}/', - 'delete_s3_file': AppConstants.serverUrl + 's3/delete/' + 'delete_s3_file': AppConstants.serverUrl + 's3/delete/', + 'sign_url_s3': AppConstants.serverUrl + 's3/sign_url/' }; diff --git a/js/constants/application_constants.js b/js/constants/application_constants.js index 332eee49..2fee15c3 100644 --- a/js/constants/application_constants.js +++ b/js/constants/application_constants.js @@ -119,7 +119,7 @@ const constants = { 'twitter': { 'sdkUrl': 'https://platform.twitter.com/widgets.js' }, - + 'cloudfrontDomain': 'd1qjsxua1o9x03.cloudfront.net', 'errorMessagesToIgnore': [ 'Authentication credentials were not provided.', 'Informations d\'authentification non fournies.' diff --git a/js/fetchers/s3_fetcher.js b/js/fetchers/s3_fetcher.js index e6f50b72..1a02727e 100644 --- a/js/fetchers/s3_fetcher.js +++ b/js/fetchers/s3_fetcher.js @@ -11,6 +11,13 @@ let S3Fetcher = { key, bucket }); + }, + signUrl(key, title, artistName) { + return requests.get('sign_url_s3', { + 'artist_name': artistName, + key, + title + }); } }; diff --git a/js/utils/requests.js b/js/utils/requests.js index fec31c00..5552731a 100644 --- a/js/utils/requests.js +++ b/js/utils/requests.js @@ -70,7 +70,7 @@ class Requests { } }).catch(reject); }); - } + }; } getUrl(url) { From 23efaa368e43ccbbda89a079430e6b4701be82e8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Mon, 22 Feb 2016 12:18:40 +0100 Subject: [PATCH 3/6] Apply PR feedback --- .../ascribe_buttons/s3_download_button.js | 30 +++++++++---------- js/fetchers/s3_fetcher.js | 4 +-- 2 files changed, 16 insertions(+), 18 deletions(-) diff --git a/js/components/ascribe_buttons/s3_download_button.js b/js/components/ascribe_buttons/s3_download_button.js index 453bc6a8..5ae4f807 100644 --- a/js/components/ascribe_buttons/s3_download_button.js +++ b/js/components/ascribe_buttons/s3_download_button.js @@ -69,23 +69,21 @@ const S3DownloadButton = React.createClass({ const { downloadUrl } = this.state; return ( - - - {/* - If it turns out that `fileExtension` is an empty string, we're just - using the label 'file'. - */} - {getLangText('Download')} .{fileExtension || 'file'} - - + + {/* + If it turns out that `fileExtension` is an empty string, we're just + using the label 'file'. + */} + {getLangText('Download')} .{fileExtension || 'file'} + ); } }); -export default S3DownloadButton; \ No newline at end of file +export default S3DownloadButton; diff --git a/js/fetchers/s3_fetcher.js b/js/fetchers/s3_fetcher.js index 1a02727e..bde6f6ba 100644 --- a/js/fetchers/s3_fetcher.js +++ b/js/fetchers/s3_fetcher.js @@ -14,9 +14,9 @@ let S3Fetcher = { }, signUrl(key, title, artistName) { return requests.get('sign_url_s3', { - 'artist_name': artistName, key, - title + title, + 'artist_name': artistName }); } }; From b2a21c5cc15e8d16f60110193a9863219092ad43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Mon, 22 Feb 2016 17:38:02 +0100 Subject: [PATCH 4/6] Resign url based on setInterval --- .../ascribe_buttons/s3_download_button.js | 49 ++++++++++++------- 1 file changed, 32 insertions(+), 17 deletions(-) diff --git a/js/components/ascribe_buttons/s3_download_button.js b/js/components/ascribe_buttons/s3_download_button.js index 5ae4f807..72b22115 100644 --- a/js/components/ascribe_buttons/s3_download_button.js +++ b/js/components/ascribe_buttons/s3_download_button.js @@ -24,7 +24,8 @@ const S3DownloadButton = React.createClass({ getInitialState() { return { - downloadUrl: null + downloadUrl: null, + signatureExpiryTimerId: null }; }, @@ -36,6 +37,36 @@ const S3DownloadButton = React.createClass({ this.signUrl(); }, + componentWillUnmount() { + window.clearInterval(this.state.signatureExpiryTime); + }, + + componentDidUpdate() { + const { signatureExpiryTimerId, downloadUrl } = this.state; + + if(!signatureExpiryTimerId && downloadUrl) { + /** + * The signed url, however can expire, which is why + * we need to renew it when it expires. + */ + const { downloadUrl } = this.state; + const expires = parseInt(queryParamsToArgs(downloadUrl.split('?')[1]).expires, 10); + const now = new Date().getTime() / 1000; + + /** + * Amazon uses seconds as their signature unix timestamp + * while `setInterval` uses milliseconds. Therefore we need to + * multiply with 1000. + */ + const interval = (expires - now) * 1000; + + this.setState({ + // Substract 5s to make sure there is a big enough window to sign again before expiration + signatureExpiryTimerId: window.setInterval(this.signUrl, interval - 5000) + }); + } + }, + transformS3UrlToS3Key(url) { return url.replace(`https://${AppConstants.cloudfrontDomain}/`, ''); }, @@ -49,21 +80,6 @@ const S3DownloadButton = React.createClass({ .catch(console.logGlobal); }, - reSignUrl(event) { - /** - * The signed url, however can expire, which is why - * we need to renew it when it expires. - */ - const { downloadUrl } = this.state; - const { expires } = queryParamsToArgs(downloadUrl.split('?')[1]); - const nowInSeconds = new Date().getTime() / 1000; - - if(nowInSeconds > expires) { - event.preventDefault(); - this.signUrl(() => this.refs.downloadButton.getDOMNode().click()); - } - }, - render() { const { fileExtension, url } = this.props; const { downloadUrl } = this.state; @@ -74,7 +90,6 @@ const S3DownloadButton = React.createClass({ download className="btn btn-xs btn-default ascribe-margin-1px" target="_blank" - onClick={this.reSignUrl} href={downloadUrl || url}> {/* If it turns out that `fileExtension` is an empty string, we're just From e863349531064321bb6c63a772a1f16a50131397 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Tue, 1 Mar 2016 12:55:27 +0100 Subject: [PATCH 5/6] Refactor to comply with PR feedback --- .../ascribe_buttons/s3_download_button.js | 55 +++++++++---------- js/stores/notification_store.js | 4 -- 2 files changed, 27 insertions(+), 32 deletions(-) diff --git a/js/components/ascribe_buttons/s3_download_button.js b/js/components/ascribe_buttons/s3_download_button.js index 72b22115..9f7c355a 100644 --- a/js/components/ascribe_buttons/s3_download_button.js +++ b/js/components/ascribe_buttons/s3_download_button.js @@ -41,42 +41,41 @@ const S3DownloadButton = React.createClass({ window.clearInterval(this.state.signatureExpiryTime); }, - componentDidUpdate() { - const { signatureExpiryTimerId, downloadUrl } = this.state; - - if(!signatureExpiryTimerId && downloadUrl) { - /** - * The signed url, however can expire, which is why - * we need to renew it when it expires. - */ - const { downloadUrl } = this.state; - const expires = parseInt(queryParamsToArgs(downloadUrl.split('?')[1]).expires, 10); - const now = new Date().getTime() / 1000; - - /** - * Amazon uses seconds as their signature unix timestamp - * while `setInterval` uses milliseconds. Therefore we need to - * multiply with 1000. - */ - const interval = (expires - now) * 1000; - - this.setState({ - // Substract 5s to make sure there is a big enough window to sign again before expiration - signatureExpiryTimerId: window.setInterval(this.signUrl, interval - 5000) - }); - } - }, - transformS3UrlToS3Key(url) { return url.replace(`https://${AppConstants.cloudfrontDomain}/`, ''); }, - signUrl(next) { + signUrl() { const { url, title, artistName } = this.props; S3Fetcher .signUrl(this.transformS3UrlToS3Key(url), title, artistName) - .then(({ signed_url: downloadUrl }) => this.setState({ downloadUrl }, next)) + .then(({ signed_url: downloadUrl }) => { + const { signatureExpiryTimerId } = this.state; + let newState = { downloadUrl }; + + if(!signatureExpiryTimerId) { + /** + * The signed url, however can expire, which is why + * we need to renew it when it expires. + */ + const expires = parseInt(queryParamsToArgs(downloadUrl.split('?')[1]).expires, 10); + const now = new Date().getTime() / 1000; + + /** + * Amazon uses seconds as their signature unix timestamp + * while `setInterval` uses milliseconds. Therefore we need to + * multiply with 1000. + */ + const interval = (expires - now) * 1000; + + Object.assign(newState, { + // Substract 5s to make sure there is a big enough window to sign again before expiration + signatureExpiryTimerId: window.setInterval(this.signUrl, interval - 5000) + }); + } + this.setState(newState); + }) .catch(console.logGlobal); }, diff --git a/js/stores/notification_store.js b/js/stores/notification_store.js index 7a9add9c..515a3730 100644 --- a/js/stores/notification_store.js +++ b/js/stores/notification_store.js @@ -34,10 +34,6 @@ class NotificationStore { this.editionListNotifications = res.notifications; } - onFlushPieceListNotifications() { - this.editionListNotifications = []; - } - onUpdateEditionNotifications(res) { this.editionNotifications = res.notification; } From 64d0dd008ecdebd48ce38964b7fcb4fa66c41fda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20Daubensch=C3=BCtz?= Date: Thu, 3 Mar 2016 10:04:45 +0100 Subject: [PATCH 6/6] Correct timer variable name --- js/components/ascribe_buttons/s3_download_button.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/js/components/ascribe_buttons/s3_download_button.js b/js/components/ascribe_buttons/s3_download_button.js index 9f7c355a..8bc12f92 100644 --- a/js/components/ascribe_buttons/s3_download_button.js +++ b/js/components/ascribe_buttons/s3_download_button.js @@ -38,7 +38,7 @@ const S3DownloadButton = React.createClass({ }, componentWillUnmount() { - window.clearInterval(this.state.signatureExpiryTime); + window.clearInterval(this.state.signatureExpiryTimerId); }, transformS3UrlToS3Key(url) {