diff --git a/lib/hooks/upload-hooks.js b/lib/hooks/upload-hooks.js --- a/lib/hooks/upload-hooks.js +++ b/lib/hooks/upload-hooks.js @@ -35,6 +35,7 @@ assignBlobHolder, generateBlobHolder, makeBlobServiceEndpointURL, + makeFarcasterBlobMediaURI, makeBlobServiceURI, } from '../utils/blob-service.js'; import { getMessageForException } from '../utils/errors.js'; @@ -278,9 +279,7 @@ } return { id: response.mediaID, - uri: makeBlobServiceEndpointURL(blobService.httpEndpoints.GET_MEDIA, { - mediaID: response.mediaID, - }), + uri: makeFarcasterBlobMediaURI(response.mediaID, { thumbHash }), mediaType, dimensions, loop: loop ?? false, diff --git a/lib/utils/blob-service.js b/lib/utils/blob-service.js --- a/lib/utils/blob-service.js +++ b/lib/utils/blob-service.js @@ -54,17 +54,35 @@ const path = replacePathParams(endpoint.path, params); return `${blobServiceConfig.url}${path}`; } - function getBlobFetchableURL(blobHash: string): string { return makeBlobServiceEndpointURL(blobServiceConfig.httpEndpoints.GET_BLOB, { blobHash, }); } -function makeFarcasterBlobMediaURI(mediaID: string): string { - return makeBlobServiceEndpointURL(blobServiceConfig.httpEndpoints.GET_MEDIA, { - mediaID, - }); +function makeFarcasterBlobMediaURI( + mediaID: string, + metadata: { +thumbHash?: ?string } = {}, +): string { + const { thumbHash } = metadata; + + const baseURL = makeBlobServiceEndpointURL( + blobServiceConfig.httpEndpoints.GET_MEDIA, + { mediaID }, + ); + + if (!thumbHash) { + return baseURL; + } + + // encode base64-url + urlencode padding + const encodedThumbHash = thumbHash + .replaceAll('+', '-') + .replaceAll('/', '_') + .replaceAll('=', '%3D'); + + const urlFragment = `#thumbHash=${encodedThumbHash}`; + return `${baseURL}${urlFragment}`; } /** diff --git a/lib/utils/convert-farcaster-message-to-comm-messages.js b/lib/utils/convert-farcaster-message-to-comm-messages.js --- a/lib/utils/convert-farcaster-message-to-comm-messages.js +++ b/lib/utils/convert-farcaster-message-to-comm-messages.js @@ -53,7 +53,7 @@ id: uuid.v4(), uri: med.staticRaster, type: 'photo', - thumbHash: null, + thumbHash: findThumbHash(med.staticRaster), dimensions: { height: med.height, width: med.width, @@ -167,4 +167,19 @@ return input.replaceAll(url, '').replaceAll(mirrorOriginalUrl, ''); } +const thumbHashRegex = /thumbHash=(.+)($|&)/; + +function findThumbHash(url: string): ?string { + const matchResult = url.match(thumbHashRegex); + if (!matchResult || matchResult.length < 2) { + return null; + } + + // decode base64-url and unescape padding + return matchResult[1] + .replaceAll('-', '+') + .replaceAll('_', '/') + .replaceAll('%3D', '='); +} + export { convertFarcasterMessageToCommMessages }; 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 @@ -260,9 +260,13 @@ } if (!encryptionKey) { - const binaryThumbHash = base64DecodeBuffer(thumbHash); - const placeholderImage = thumbHashToDataURL(binaryThumbHash); - setPlaceholder(placeholderImage); + try { + const binaryThumbHash = base64DecodeBuffer(thumbHash); + const placeholderImage = thumbHashToDataURL(binaryThumbHash); + setPlaceholder(placeholderImage); + } catch (e) { + console.warn('Failed to set placeholder thumbHash:', e); + } return; }