diff --git a/web/media/encryption-utils.js b/web/media/encryption-utils.js --- a/web/media/encryption-utils.js +++ b/web/media/encryption-utils.js @@ -1,6 +1,7 @@ // @flow import invariant from 'invariant'; +import { thumbHashToDataURL } from 'thumbhash'; import { hexToUintArray, uintArrayToHexString } from 'lib/media/data-utils.js'; import { fileInfoFromData } from 'lib/media/file-utils.js'; @@ -13,6 +14,7 @@ import { calculatePaddedLength, pad, unpad } from 'lib/utils/pkcs7-padding.js'; import * as AES from './aes-crypto-utils.js'; +import { base64DecodeBuffer } from '../utils/base64-utils.js'; const PADDING_THRESHOLD = 5000000; // 5MB @@ -236,4 +238,16 @@ return { steps, result: { success: true, uri: objectURL } }; } -export { encryptFile, decryptMedia }; +async function decryptThumbhashToDataURL( + encryptedThumbHash: string, + keyHex: string, +): Promise { + const encryptedData = base64DecodeBuffer(encryptedThumbHash); + const thumbhashBytes = await AES.decrypt( + hexToUintArray(keyHex), + encryptedData, + ); + return thumbHashToDataURL(thumbhashBytes); +} + +export { encryptFile, decryptMedia, decryptThumbhashToDataURL }; diff --git a/web/media/media-utils.js b/web/media/media-utils.js --- a/web/media/media-utils.js +++ b/web/media/media-utils.js @@ -1,5 +1,8 @@ // @flow +import * as React from 'react'; +import { thumbHashToDataURL } from 'thumbhash'; + import type { MediaType, Dimensions, @@ -9,7 +12,9 @@ import { getMessageForException } from 'lib/utils/errors.js'; import { probeFile } from './blob-utils.js'; +import { decryptThumbhashToDataURL } from './encryption-utils.js'; import { getOrientation } from './image-utils.js'; +import { base64DecodeBuffer } from '../utils/base64-utils.js'; async function preloadImage(uri: string): Promise<{ steps: $ReadOnlyArray, @@ -193,4 +198,36 @@ }; } -export { preloadImage, validateFile }; +function usePlaceholder(thumbHash: ?string, encryptionKey: ?string): ?string { + const [placeholder, setPlaceholder] = React.useState(null); + + React.useEffect(() => { + if (!thumbHash) { + setPlaceholder(null); + return; + } + + if (!encryptionKey) { + const binaryThumbHash = base64DecodeBuffer(thumbHash); + const placeholderImage = thumbHashToDataURL(binaryThumbHash); + setPlaceholder(placeholderImage); + return; + } + + (async () => { + try { + const decryptedThumbHash = await decryptThumbhashToDataURL( + thumbHash, + encryptionKey, + ); + setPlaceholder(decryptedThumbHash); + } catch { + setPlaceholder(null); + } + })(); + }, [thumbHash, encryptionKey]); + + return placeholder; +} + +export { preloadImage, validateFile, usePlaceholder };