From 903b0aa2a63acc3340fdde570201f5c424f4443c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafael=20C=C3=A1rdenas?= Date: Mon, 26 Aug 2024 10:24:44 -0600 Subject: [PATCH] feat: convert data: image uris into image files and upload to cdn (#245) * feat: convert data: image uris into image files and upload to cdn * test: remove obsolete --- src/token-processor/images/image-cache.ts | 36 ++++++++++++++--------- tests/token-queue/image-cache.test.ts | 7 ----- 2 files changed, 22 insertions(+), 21 deletions(-) diff --git a/src/token-processor/images/image-cache.ts b/src/token-processor/images/image-cache.ts index 59cedcbf..de1e9201 100644 --- a/src/token-processor/images/image-cache.ts +++ b/src/token-processor/images/image-cache.ts @@ -17,6 +17,21 @@ import { import { pipeline } from 'node:stream/promises'; import { Storage } from '@google-cloud/storage'; +/** Saves an image provided via a `data:` uri string to disk for processing. */ +function convertDataImage(uri: string, tmpPath: string): string { + const dataUrl = parseDataUrl(uri); + if (!dataUrl) { + throw new ImageParseError(`Data URL could not be parsed: ${uri}`); + } + if (!dataUrl.mediaType?.startsWith('image/')) { + throw new ImageParseError(`Token image is a Data URL with a non-image media type: ${uri}`); + } + const filePath = `${tmpPath}/image`; + const imageBuffer = Buffer.from(dataUrl.data, 'base64'); + fs.writeFileSync(filePath, imageBuffer); + return filePath; +} + async function downloadImage(imgUrl: string, tmpPath: string): Promise { return new Promise((resolve, reject) => { const filePath = `${tmpPath}/image`; @@ -98,15 +113,18 @@ export async function processImageCache( tokenNumber: bigint ): Promise { logger.info(`ImageCache processing token ${contractPrincipal} (${tokenNumber}) at ${imgUrl}`); - if (imgUrl.startsWith('data:')) return [imgUrl]; - try { const gcs = new Storage(); const gcsBucket = ENV.IMAGE_CACHE_GCS_BUCKET_NAME as string; const tmpPath = `tmp/${contractPrincipal}_${tokenNumber}`; fs.mkdirSync(tmpPath, { recursive: true }); - const original = await downloadImage(imgUrl, tmpPath); + let original: string; + if (imgUrl.startsWith('data:')) { + original = convertDataImage(imgUrl, tmpPath); + } else { + original = await downloadImage(imgUrl, tmpPath); + } const image1 = await transformImage(original); const remoteName1 = `${contractPrincipal}/${tokenNumber}.png`; @@ -149,17 +167,7 @@ export async function processImageCache( * @returns Normalized URL string */ export function normalizeImageUri(uri: string): string { - // Support images embedded in a Data URL - if (uri.startsWith('data:')) { - const dataUrl = parseDataUrl(uri); - if (!dataUrl) { - throw new ImageParseError(`Data URL could not be parsed: ${uri}`); - } - if (!dataUrl.mediaType?.startsWith('image/')) { - throw new ImageParseError(`Token image is a Data URL with a non-image media type: ${uri}`); - } - return uri; - } + if (uri.startsWith('data:')) return uri; const fetchableUrl = getFetchableDecentralizedStorageUrl(uri); return fetchableUrl.toString(); } diff --git a/tests/token-queue/image-cache.test.ts b/tests/token-queue/image-cache.test.ts index bd0e3446..addfe149 100644 --- a/tests/token-queue/image-cache.test.ts +++ b/tests/token-queue/image-cache.test.ts @@ -41,11 +41,4 @@ describe('Image cache', () => { ).rejects.toThrow(MetadataHttpError); await closeTestServer(server); }, 10000); - - test('ignores data: URL', async () => { - const url = 'data:123456'; - await expect(processImageCache(url, contract, tokenNumber)).resolves.toStrictEqual([ - 'data:123456', - ]); - }); });