From 09143c05c198b239cd35ccf977233b26ae24f6c8 Mon Sep 17 00:00:00 2001 From: Arjan Singh <1598641+arjansingh@users.noreply.github.com> Date: Wed, 29 Oct 2025 19:34:38 -0700 Subject: [PATCH] chore(vite.config): add GCS redirect for assets (#6389) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit ## Summary Add GCS redirect in vite's proxy so we can load workflows by dragging images in local cloud development. Previously this action would result in a CORS error when the app tried to download from `storage.googleapis.com` ┆Issue is synchronized with this [Notion page](https://www.notion.so/PR-6389-chore-vite-config-add-GCS-redirect-for-assets-29c6d73d36508148aaabd24de9698fc4) by [Unito](https://www.unito.io) --- vite.config.mts | 78 +++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 76 insertions(+), 2 deletions(-) diff --git a/vite.config.mts b/vite.config.mts index 96f101256..cffc21408 100644 --- a/vite.config.mts +++ b/vite.config.mts @@ -1,13 +1,16 @@ import tailwindcss from '@tailwindcss/vite' import vue from '@vitejs/plugin-vue' import { config as dotenvConfig } from 'dotenv' +import type { IncomingMessage, ServerResponse } from 'http' +import { Readable } from 'stream' +import type { ReadableStream as NodeReadableStream } from 'stream/web' import { visualizer } from 'rollup-plugin-visualizer' import { FileSystemIconLoader } from 'unplugin-icons/loaders' import IconsResolver from 'unplugin-icons/resolver' import Icons from 'unplugin-icons/vite' import Components from 'unplugin-vue-components/vite' import { defineConfig } from 'vite' -import type { UserConfig } from 'vite' +import type { ProxyOptions, UserConfig } from 'vite' import { createHtmlPlugin } from 'vite-plugin-html' import vueDevTools from 'vite-plugin-vue-devtools' @@ -49,10 +52,74 @@ const DEV_SEVER_FALLBACK_URL = const DEV_SERVER_COMFYUI_URL = DEV_SERVER_COMFYUI_ENV_URL || DEV_SEVER_FALLBACK_URL -// Cloud proxy configuration const cloudProxyConfig = DISTRIBUTION === 'cloud' ? { secure: false, changeOrigin: true } : {} +function handleGcsRedirect( + proxyRes: IncomingMessage, + _req: IncomingMessage, + res: ServerResponse +) { + const location = proxyRes.headers.location + const isGcsRedirect = + proxyRes.statusCode === 302 && + location?.includes('storage.googleapis.com') && + proxyRes.headers.via?.includes('google') + + // Not a GCS redirect - pass through normally + if (!isGcsRedirect || !location) { + Object.keys(proxyRes.headers).forEach((key) => { + const value = proxyRes.headers[key] + if (value !== undefined) { + res.setHeader(key, value) + } + }) + res.writeHead(proxyRes.statusCode || 200) + proxyRes.pipe(res) + return + } + + // GCS redirect detected - fetch server-side to avoid CORS + fetch(location) + .then(async (gcsResponse) => { + if (!gcsResponse.body) { + res.statusCode = 500 + res.end('Empty response from GCS') + return + } + + // Set response headers from GCS + res.statusCode = 200 + res.setHeader( + 'Content-Type', + gcsResponse.headers.get('content-type') || 'application/octet-stream' + ) + + const contentLength = gcsResponse.headers.get('content-length') + if (contentLength) { + res.setHeader('Content-Length', contentLength) + } + + // Convert Web ReadableStream to Node.js stream and pipe to client + const readable = Readable.fromWeb(gcsResponse.body as NodeReadableStream) + readable.pipe(res) + }) + .catch((error) => { + console.error('Error fetching from GCS:', error) + res.statusCode = 500 + res.end('Error fetching media') + }) +} + +const gcsRedirectProxyConfig: ProxyOptions = { + target: DEV_SERVER_COMFYUI_URL, + ...cloudProxyConfig, + selfHandleResponse: true, + configure: (proxy) => { + proxy.on('proxyRes', handleGcsRedirect) + } +} + export default defineConfig({ base: '', server: { @@ -80,6 +147,13 @@ export default defineConfig({ ...cloudProxyConfig }, + ...(DISTRIBUTION === 'cloud' + ? { + '/api/view': gcsRedirectProxyConfig, + '/api/viewvideo': gcsRedirectProxyConfig + } + : {}), + '/api': { target: DEV_SERVER_COMFYUI_URL, ...cloudProxyConfig,