Bladeren bron

fix(asset-server-plugin): Gracefully handle unsupported image previews

Fixes #1563. If Sharp cannot process a given image file, it will fall back to the binary file
preview.
Michael Bromley 3 jaren geleden
bovenliggende
commit
91b69f0634

+ 16 - 1
packages/asset-server-plugin/e2e/asset-server-plugin.e2e-spec.ts

@@ -22,7 +22,7 @@ describe('AssetServerPlugin', () => {
 
     const { server, adminClient, shopClient } = createTestEnvironment(
         mergeConfig(testConfig(), {
-            logger: new DefaultLogger({ level: LogLevel.Info }),
+            // logger: new DefaultLogger({ level: LogLevel.Info }),
             plugins: [
                 AssetServerPlugin.init({
                     assetUploadDir: path.join(__dirname, TEST_ASSET_DIR),
@@ -257,6 +257,21 @@ describe('AssetServerPlugin', () => {
             await testMimeTypeOfAssetWithExt('webp', 'image/webp');
         });
     });
+
+    // https://github.com/vendure-ecommerce/vendure/issues/1563
+    it('falls back to binary preview if image file cannot be processed', async () => {
+        const filesToUpload = [path.join(__dirname, `fixtures/assets/bad-image.jpg`)];
+        const { createAssets }: CreateAssets.Mutation = await adminClient.fileUploadMutation({
+            mutation: CREATE_ASSETS,
+            filePaths: filesToUpload,
+            mapVariables: filePaths => ({
+                input: filePaths.map(p => ({ file: null })),
+            }),
+        });
+
+        expect(createAssets.length).toBe(1);
+        expect(createAssets[0].name).toBe('bad-image.jpg');
+    });
 });
 
 export const CREATE_ASSETS = gql`

+ 1 - 0
packages/asset-server-plugin/e2e/fixtures/assets/bad-image.jpg

@@ -0,0 +1 @@
+this is not an image file! haha.

+ 1 - 1
packages/asset-server-plugin/src/plugin.ts

@@ -256,7 +256,7 @@ export class AssetServerPlugin implements NestModule, OnApplicationBootstrap {
                         res.send(imageBuffer);
                         return;
                     } catch (e) {
-                        Logger.error(e, 'AssetServerPlugin', e.stack);
+                        Logger.error(e, loggerCtx, e.stack);
                         res.status(500).send(e.message);
                         return;
                     }

+ 37 - 21
packages/asset-server-plugin/src/sharp-asset-preview-strategy.ts

@@ -1,8 +1,10 @@
 import { AssetType } from '@vendure/common/lib/generated-types';
-import { AssetPreviewStrategy, getAssetType, RequestContext } from '@vendure/core';
+import { AssetPreviewStrategy, getAssetType, Logger, RequestContext } from '@vendure/core';
 import path from 'path';
 import sharp from 'sharp';
 
+import { loggerCtx } from './constants';
+
 export class SharpAssetPreviewStrategy implements AssetPreviewStrategy {
     constructor(
         private config: {
@@ -16,30 +18,32 @@ export class SharpAssetPreviewStrategy implements AssetPreviewStrategy {
         const { maxWidth, maxHeight } = this.config;
 
         if (assetType === AssetType.IMAGE) {
-            const image = sharp(data);
-            const metadata = await image.metadata();
-            const width = metadata.width || 0;
-            const height = metadata.height || 0;
-            if (maxWidth < width || maxHeight < height) {
-                return image.rotate().resize(maxWidth, maxHeight, { fit: 'inside' }).toBuffer();
-            } else {
-                if (mimeType === 'image/svg+xml') {
-                    // Convert the SVG to a raster for the preview
-                    return image.toBuffer();
+            try {
+                const image = sharp(data);
+                const metadata = await image.metadata();
+                const width = metadata.width || 0;
+                const height = metadata.height || 0;
+                if (maxWidth < width || maxHeight < height) {
+                    return image.rotate().resize(maxWidth, maxHeight, { fit: 'inside' }).toBuffer();
                 } else {
-                    return image.rotate().toBuffer();
+                    if (mimeType === 'image/svg+xml') {
+                        // Convert the SVG to a raster for the preview
+                        return image.toBuffer();
+                    } else {
+                        return image.rotate().toBuffer();
+                    }
                 }
+            } catch (err: any) {
+                Logger.error(
+                    `An error occurred when generating preview for image with mimeType ${mimeType}: ${
+                        err.message ?? err.toString()
+                    }`,
+                    loggerCtx,
+                );
+                return this.generateBinaryFilePreview(mimeType);
             }
         } else {
-            return sharp(path.join(__dirname, 'file-icon.png'))
-                .resize(800, 800, { fit: 'outside' })
-                .composite([
-                    {
-                        input: this.generateMimeTypeOverlay(mimeType),
-                        gravity: sharp.gravity.center,
-                    },
-                ])
-                .toBuffer();
+            return this.generateBinaryFilePreview(mimeType);
         }
     }
 
@@ -57,4 +61,16 @@ export class SharpAssetPreviewStrategy implements AssetPreviewStrategy {
               <text x="400" y="110"  text-anchor="middle" width="800">${mimeType}</text>
             </svg>`);
     }
+
+    private generateBinaryFilePreview(mimeType: string): Promise<Buffer> {
+        return sharp(path.join(__dirname, 'file-icon.png'))
+            .resize(800, 800, { fit: 'outside' })
+            .composite([
+                {
+                    input: this.generateMimeTypeOverlay(mimeType),
+                    gravity: sharp.gravity.center,
+                },
+            ])
+            .toBuffer();
+    }
 }