From 60f06be2ac903d3901eded42cc6ccaaaff41a97a Mon Sep 17 00:00:00 2001 From: Aleksandr Grenishin Date: Tue, 16 Jul 2024 14:29:08 +0300 Subject: [PATCH] feat: async `secureDeliveryProxyUrlResolver` (#677) * feat: async `secureDeliveryProxyUrlResolver` * feat: async `secureDeliveryProxyUrlResolver` * refactor: use optional chaining to invoke fn --------- Co-authored-by: nd0ut --- abstract/Block.js | 20 ++++--- abstract/UploaderBlock.js | 8 +-- .../src/CloudImageEditorBlock.js | 2 +- .../src/EditorFilterControl.js | 54 +++++++++++-------- .../src/EditorImageCropper.js | 6 +-- .../CloudImageEditor/src/EditorImageFader.js | 32 +++++------ blocks/CloudImageEditor/src/EditorToolbar.js | 8 +-- blocks/FileItem/FileItem.js | 2 +- types/exported.d.ts | 2 +- 9 files changed, 75 insertions(+), 59 deletions(-) diff --git a/abstract/Block.js b/abstract/Block.js index 29df50e6..7bb841a4 100644 --- a/abstract/Block.js +++ b/abstract/Block.js @@ -236,26 +236,30 @@ export class Block extends BaseComponent { Data.deleteCtx(this.ctxName); this.localeManager?.destroy(); - this.a11y?.destroy(); } /** * @param {String} url - * @returns {String} + * @returns {Promise} * @protected */ - proxyUrl(url) { + async proxyUrl(url) { if (this.cfg.secureDeliveryProxy && this.cfg.secureDeliveryProxyUrlResolver) { console.warn( 'Both secureDeliveryProxy and secureDeliveryProxyUrlResolver are set. The secureDeliveryProxyUrlResolver will be used.', ); } if (this.cfg.secureDeliveryProxyUrlResolver) { - return this.cfg.secureDeliveryProxyUrlResolver(url, { - uuid: extractUuid(url), - cdnUrlModifiers: extractCdnUrlModifiers(url), - fileName: extractFilename(url), - }); + try { + return await this.cfg.secureDeliveryProxyUrlResolver(url, { + uuid: extractUuid(url), + cdnUrlModifiers: extractCdnUrlModifiers(url), + fileName: extractFilename(url), + }); + } catch (err) { + console.error('Failed to resolve secure delivery proxy URL. Falling back to the default URL.', err); + return url; + } } if (this.cfg.secureDeliveryProxy) { return applyTemplateData( diff --git a/abstract/UploaderBlock.js b/abstract/UploaderBlock.js index 87618d5f..efc04d29 100644 --- a/abstract/UploaderBlock.js +++ b/abstract/UploaderBlock.js @@ -281,10 +281,10 @@ export class UploaderBlock extends ActivityBlock { return entry.getValue('errors').length > 0; }); if ( - uploadCollection.size > 0 && - errorItems.length === 0 && - uploadCollection.size === loadedItems.length && - this.$['*collectionErrors'].length === 0 + uploadCollection.size > 0 && + errorItems.length === 0 && + uploadCollection.size === loadedItems.length && + this.$['*collectionErrors'].length === 0 ) { this.emit( EventType.COMMON_UPLOAD_SUCCESS, diff --git a/blocks/CloudImageEditor/src/CloudImageEditorBlock.js b/blocks/CloudImageEditor/src/CloudImageEditorBlock.js index 5a492f07..2f824abe 100644 --- a/blocks/CloudImageEditor/src/CloudImageEditorBlock.js +++ b/blocks/CloudImageEditor/src/CloudImageEditorBlock.js @@ -108,7 +108,7 @@ export class CloudImageEditorBlock extends Block { } try { - const cdnUrl = createCdnUrl(this.$['*originalUrl'], createCdnUrlModifiers('json')); + const cdnUrl = await this.proxyUrl(createCdnUrl(this.$['*originalUrl'], createCdnUrlModifiers('json'))); const json = await fetch(cdnUrl).then((response) => response.json()); const { width, height } = /** @type {{ width: number; height: number }} */ (json); diff --git a/blocks/CloudImageEditor/src/EditorFilterControl.js b/blocks/CloudImageEditor/src/EditorFilterControl.js index db7fdaa3..f706191e 100644 --- a/blocks/CloudImageEditor/src/EditorFilterControl.js +++ b/blocks/CloudImageEditor/src/EditorFilterControl.js @@ -1,3 +1,4 @@ +// @ts-check import { createCdnUrl, createCdnUrlModifiers } from '../../../utils/cdn-utils.js'; import { EditorButtonControl } from './EditorButtonControl.js'; import { FAKE_ORIGINAL_FILTER } from './EditorSlider.js'; @@ -5,15 +6,19 @@ import { COMMON_OPERATIONS, transformationsToOperations } from './lib/transforma import { preloadImage } from './lib/preloadImage.js'; export class EditorFilterControl extends EditorButtonControl { - init$ = { - ...this.init$, - active: false, - title: '', - icon: '', - isOriginal: false, - iconSize: '20', - 'on.click': null, - }; + constructor() { + super(); + + this.init$ = { + ...this.init$, + active: false, + title: '', + icon: '', + isOriginal: false, + iconSize: '20', + 'on.click': null, + }; + } _previewSrc() { let previewSize = parseInt(window.getComputedStyle(this).getPropertyValue('--l-base-min-width'), 10); @@ -24,6 +29,7 @@ export class EditorFilterControl extends EditorButtonControl { /** @type {import('./types.js').Transformations} */ let transformations = { ...this.$['*editorTransformations'] }; + // @ts-expect-error FIXME: fix this transformations[this._operation] = this._filter !== FAKE_ORIGINAL_FILTER ? { @@ -47,10 +53,10 @@ export class EditorFilterControl extends EditorButtonControl { * @param {IntersectionObserverEntry[]} entries * @param {IntersectionObserver} observer */ - _observerCallback(entries, observer) { + async _observerCallback(entries, observer) { let intersectionEntry = entries[0]; if (intersectionEntry.isIntersecting) { - let src = this.proxyUrl(this._previewSrc()); + let src = await this.proxyUrl(this._previewSrc()); let previewEl = this.ref['preview-el']; let { promise, cancel } = preloadImage(src); this._cancelPreload = cancel; @@ -66,14 +72,14 @@ export class EditorFilterControl extends EditorButtonControl { observer.unobserve(this); }); } else { - this._cancelPreload && this._cancelPreload(); + this._cancelPreload?.(); } } initCallback() { super.initCallback(); - this.$['on.click'] = (e) => { + this.$['on.click'] = () => { if (!this.$.active) { this.$['*sliderEl'].setOperation(this._operation, this._filter); this.$['*sliderEl'].apply(); @@ -85,12 +91,16 @@ export class EditorFilterControl extends EditorButtonControl { this.$['*currentFilter'] = this._filter; }; - this.defineAccessor('filter', (filter) => { - this._operation = 'filter'; - this._filter = filter; - this.$.isOriginal = filter === FAKE_ORIGINAL_FILTER; - this.$.icon = this.$.isOriginal ? 'original' : 'slider'; - }); + this.defineAccessor( + 'filter', + /** @param {string} filter */ + (filter) => { + this._operation = 'filter'; + this._filter = filter; + this.$.isOriginal = filter === FAKE_ORIGINAL_FILTER; + this.$.icon = this.$.isOriginal ? 'original' : 'slider'; + }, + ); this._observer = new window.IntersectionObserver(this._observerCallback.bind(this), { threshold: [0, 1], @@ -128,9 +138,9 @@ export class EditorFilterControl extends EditorButtonControl { } }); - this.sub('*networkProblems', (networkProblems) => { + this.sub('*networkProblems', async (networkProblems) => { if (!networkProblems) { - let src = this.proxyUrl(this._previewSrc()); + let src = await this.proxyUrl(this._previewSrc()); let previewEl = this.ref['preview-el']; if (previewEl.style.backgroundImage) { previewEl.style.backgroundImage = 'none'; @@ -143,7 +153,7 @@ export class EditorFilterControl extends EditorButtonControl { destroyCallback() { super.destroyCallback(); this._observer?.disconnect(); - this._cancelPreload && this._cancelPreload(); + this._cancelPreload?.(); } } diff --git a/blocks/CloudImageEditor/src/EditorImageCropper.js b/blocks/CloudImageEditor/src/EditorImageCropper.js index ee5f0e1b..039d6b5a 100644 --- a/blocks/CloudImageEditor/src/EditorImageCropper.js +++ b/blocks/CloudImageEditor/src/EditorImageCropper.js @@ -461,7 +461,7 @@ export class EditorImageCropper extends Block { * @param {import('./types.js').Transformations} transformations * @returns {Promise} */ - _waitForImage(originalUrl, transformations) { + async _waitForImage(originalUrl, transformations) { let width = this.offsetWidth; transformations = { ...transformations, @@ -470,13 +470,13 @@ export class EditorImageCropper extends Block { flip: undefined, mirror: undefined, }; - let src = this.proxyUrl(viewerImageSrc(originalUrl, width, transformations)); + let src = await this.proxyUrl(viewerImageSrc(originalUrl, width, transformations)); let { promise, cancel, image } = preloadImage(src); let stop = this._handleImageLoading(src); image.addEventListener('load', stop, { once: true }); image.addEventListener('error', stop, { once: true }); - this._cancelPreload && this._cancelPreload(); + this._cancelPreload?.(); this._cancelPreload = cancel; return promise diff --git a/blocks/CloudImageEditor/src/EditorImageFader.js b/blocks/CloudImageEditor/src/EditorImageFader.js index 25f4cd83..e93c4f0a 100644 --- a/blocks/CloudImageEditor/src/EditorImageFader.js +++ b/blocks/CloudImageEditor/src/EditorImageFader.js @@ -138,9 +138,9 @@ export class EditorImageFader extends Block { * @param {String} [options.filter] * @param {String} [options.operation] * @param {Number} [options.value] - * @returns {String} + * @returns {Promise} */ - _imageSrc({ url = this._url, filter = this._filter, operation, value } = {}) { + async _imageSrc({ url = this._url, filter = this._filter, operation, value } = {}) { let transformations = { ...this._transformations }; if (operation) { @@ -149,17 +149,17 @@ export class EditorImageFader extends Block { // do not use getBoundingClientRect because scale transform affects it let width = this.offsetWidth; - return this.proxyUrl(viewerImageSrc(url, width, transformations)); + return await this.proxyUrl(viewerImageSrc(url, width, transformations)); } /** * @private * @param {String} operation * @param {Number} value - * @returns {Keypoint} + * @returns {Promise} */ - _constructKeypoint(operation, value) { - let src = this._imageSrc({ operation, value }); + async _constructKeypoint(operation, value) { + let src = await this._imageSrc({ operation, value }); return { src, image: null, @@ -187,14 +187,14 @@ export class EditorImageFader extends Block { * @param {String | null} filter * @param {Number} value */ - _addKeypoint(operation, filter, value) { + async _addKeypoint(operation, filter, value) { let shouldSkip = () => !this._isSame(operation, filter) || this._value !== value || !!this._keypoints.find((kp) => kp.value === value); if (shouldSkip()) { return; } - let keypoint = this._constructKeypoint(operation, value); + let keypoint = await this._constructKeypoint(operation, value); let image = new Image(); image.src = keypoint.src; let stop = this._handleImageLoading(keypoint.src); @@ -308,10 +308,10 @@ export class EditorImageFader extends Block { } /** @param {import('./types.js').Transformations} transformations */ - setTransformations(transformations) { + async setTransformations(transformations) { this._transformations = transformations; if (this._previewImage) { - let src = this._imageSrc(); + let src = await this._imageSrc(); let stop = this._handleImageLoading(src); this._previewImage.src = src; this._previewImage.addEventListener('load', stop, { once: true }); @@ -335,11 +335,11 @@ export class EditorImageFader extends Block { * @param {Number} options.value * @param {String} [options.filter] */ - preload({ url, filter, operation, value }) { + async preload({ url, filter, operation, value }) { this._cancelBatchPreload && this._cancelBatchPreload(); let keypoints = keypointsRange(operation, value); - let srcList = keypoints.map((kp) => this._imageSrc({ url, filter, operation, value: kp })); + let srcList = await Promise.all(keypoints.map((kp) => this._imageSrc({ url, filter, operation, value: kp }))); let { cancel } = batchPreloadImages(srcList); this._cancelBatchPreload = cancel; @@ -400,7 +400,7 @@ export class EditorImageFader extends Block { * @param {String} [options.filter] * @param {Boolean} [options.fromViewer] */ - activate({ url, operation, value, filter, fromViewer }) { + async activate({ url, operation, value, filter, fromViewer }) { this._isActive = true; this._hidden = false; this._url = url; @@ -411,12 +411,14 @@ export class EditorImageFader extends Block { let isOriginal = typeof value !== 'number' && !filter; if (isOriginal) { - let src = this._imageSrc({ operation, value }); + let src = await this._imageSrc({ operation, value }); this._setOriginalSrc(src); this._container && this._container.remove(); return; } - this._keypoints = keypointsRange(operation, value).map((keyValue) => this._constructKeypoint(operation, keyValue)); + this._keypoints = await Promise.all( + keypointsRange(operation, value).map((keyValue) => this._constructKeypoint(operation, keyValue)), + ); this._update(operation, value); this._initNodes(); diff --git a/blocks/CloudImageEditor/src/EditorToolbar.js b/blocks/CloudImageEditor/src/EditorToolbar.js index bd36ac7f..19c7d76f 100644 --- a/blocks/CloudImageEditor/src/EditorToolbar.js +++ b/blocks/CloudImageEditor/src/EditorToolbar.js @@ -105,7 +105,7 @@ export class EditorToolbar extends Block { visible: 'uc-tab-toggles--visible', }, 'on.cancel': () => { - this._cancelPreload && this._cancelPreload(); + this._cancelPreload?.(); this.$['*on.cancel'](); }, 'on.apply': () => { @@ -271,11 +271,11 @@ export class EditorToolbar extends Block { } /** @private */ - _preloadEditedImage() { + async _preloadEditedImage() { if (this.$['*imgContainerEl'] && this.$['*originalUrl']) { let width = this.$['*imgContainerEl'].offsetWidth; - let src = this.proxyUrl(viewerImageSrc(this.$['*originalUrl'], width, this.$['*editorTransformations'])); - this._cancelPreload && this._cancelPreload(); + let src = await this.proxyUrl(viewerImageSrc(this.$['*originalUrl'], width, this.$['*editorTransformations'])); + this._cancelPreload?.(); let { cancel } = batchPreloadImages([src]); this._cancelPreload = () => { cancel(); diff --git a/blocks/FileItem/FileItem.js b/blocks/FileItem/FileItem.js index a1be9865..5cbf8ad2 100644 --- a/blocks/FileItem/FileItem.js +++ b/blocks/FileItem/FileItem.js @@ -132,7 +132,7 @@ export class FileItem extends UploaderBlock { if (entry.getValue('fileInfo') && entry.getValue('isImage')) { let size = this.cfg.thumbSize; - let thumbUrl = this.proxyUrl( + let thumbUrl = await this.proxyUrl( createCdnUrl( createOriginalUrl(this.cfg.cdnCname, this._entry.getValue('uuid')), createCdnUrlModifiers(entry.getValue('cdnUrlModifiers'), `scale_crop/${size}x${size}/center`), diff --git a/types/exported.d.ts b/types/exported.d.ts index 5ea490df..d99654bf 100644 --- a/types/exported.d.ts +++ b/types/exported.d.ts @@ -15,7 +15,7 @@ export type LocaleDefinitionOverride = Record; export type SecureDeliveryProxyUrlResolver = ( previewUrl: string, urlParts: { uuid: string; cdnUrlModifiers: string; fileName: string }, -) => string; +) => Promise | string; export type SecureUploadsSignatureAndExpire = { secureSignature: string; secureExpire: string }; export type SecureUploadsSignatureResolver = () => Promise; export type IconHrefResolver = (iconName: string) => string;