From eaf888b02a1e0da630bb25fc7620124411cf8316 Mon Sep 17 00:00:00 2001 From: hjlarry Date: Tue, 20 Jan 2026 20:34:56 +0800 Subject: [PATCH] env var NEXT_PUBLIC_SOCKET_URL --- docker/.env.example | 2 ++ docker/docker-compose-template.yaml | 1 + docker/docker-compose.yaml | 2 ++ web/.env.example | 2 ++ web/README.md | 2 ++ .../core/__tests__/websocket-manager.test.ts | 26 +------------------ .../collaboration/core/websocket-manager.ts | 26 +++++++------------ .../workflow/collaboration/types/websocket.ts | 1 - web/app/layout.tsx | 1 + web/config/index.ts | 5 ++++ web/types/feature.ts | 1 + 11 files changed, 27 insertions(+), 42 deletions(-) diff --git a/docker/.env.example b/docker/.env.example index 98bd41a89a..0a02338ba6 100644 --- a/docker/.env.example +++ b/docker/.env.example @@ -406,6 +406,8 @@ CONSOLE_CORS_ALLOW_ORIGINS=* COOKIE_DOMAIN= # When the frontend and backend run on different subdomains, set NEXT_PUBLIC_COOKIE_DOMAIN=1. NEXT_PUBLIC_COOKIE_DOMAIN= +# WebSocket server URL. +NEXT_PUBLIC_SOCKET_URL=ws://localhost NEXT_PUBLIC_BATCH_CONCURRENCY=5 # ------------------------------ diff --git a/docker/docker-compose-template.yaml b/docker/docker-compose-template.yaml index 9659990383..1740161b7b 100644 --- a/docker/docker-compose-template.yaml +++ b/docker/docker-compose-template.yaml @@ -139,6 +139,7 @@ services: APP_API_URL: ${APP_API_URL:-} AMPLITUDE_API_KEY: ${AMPLITUDE_API_KEY:-} NEXT_PUBLIC_COOKIE_DOMAIN: ${NEXT_PUBLIC_COOKIE_DOMAIN:-} + NEXT_PUBLIC_SOCKET_URL: ${NEXT_PUBLIC_SOCKET_URL:-ws://localhost} SENTRY_DSN: ${WEB_SENTRY_DSN:-} NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0} TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000} diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml index 594d5ae660..53ddee1c58 100644 --- a/docker/docker-compose.yaml +++ b/docker/docker-compose.yaml @@ -110,6 +110,7 @@ x-shared-env: &shared-api-worker-env CONSOLE_CORS_ALLOW_ORIGINS: ${CONSOLE_CORS_ALLOW_ORIGINS:-*} COOKIE_DOMAIN: ${COOKIE_DOMAIN:-} NEXT_PUBLIC_COOKIE_DOMAIN: ${NEXT_PUBLIC_COOKIE_DOMAIN:-} + NEXT_PUBLIC_SOCKET_URL: ${NEXT_PUBLIC_SOCKET_URL:-ws://localhost} NEXT_PUBLIC_BATCH_CONCURRENCY: ${NEXT_PUBLIC_BATCH_CONCURRENCY:-5} STORAGE_TYPE: ${STORAGE_TYPE:-opendal} OPENDAL_SCHEME: ${OPENDAL_SCHEME:-fs} @@ -824,6 +825,7 @@ services: APP_API_URL: ${APP_API_URL:-} AMPLITUDE_API_KEY: ${AMPLITUDE_API_KEY:-} NEXT_PUBLIC_COOKIE_DOMAIN: ${NEXT_PUBLIC_COOKIE_DOMAIN:-} + NEXT_PUBLIC_SOCKET_URL: ${NEXT_PUBLIC_SOCKET_URL:-} SENTRY_DSN: ${WEB_SENTRY_DSN:-} NEXT_TELEMETRY_DISABLED: ${NEXT_TELEMETRY_DISABLED:-0} TEXT_GENERATION_TIMEOUT_MS: ${TEXT_GENERATION_TIMEOUT_MS:-60000} diff --git a/web/.env.example b/web/.env.example index df4e725c51..79d45bd0f9 100644 --- a/web/.env.example +++ b/web/.env.example @@ -14,6 +14,8 @@ NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api NEXT_PUBLIC_PUBLIC_API_PREFIX=http://localhost:5001/api # When the frontend and backend run on different subdomains, set NEXT_PUBLIC_COOKIE_DOMAIN=1. NEXT_PUBLIC_COOKIE_DOMAIN= +# WebSocket server URL. +NEXT_PUBLIC_SOCKET_URL=ws://localhost:5001 # The API PREFIX for MARKETPLACE NEXT_PUBLIC_MARKETPLACE_API_PREFIX=https://marketplace.dify.ai/api/v1 diff --git a/web/README.md b/web/README.md index 13780eec6c..d129980b05 100644 --- a/web/README.md +++ b/web/README.md @@ -43,6 +43,8 @@ NEXT_PUBLIC_EDITION=SELF_HOSTED # example: http://cloud.dify.ai/console/api NEXT_PUBLIC_API_PREFIX=http://localhost:5001/console/api NEXT_PUBLIC_COOKIE_DOMAIN= +# WebSocket server URL. +NEXT_PUBLIC_SOCKET_URL=ws://localhost:5001 # The URL for Web APP, refers to the Web App base URL of WEB service if web app domain is different from # console or api domain. # example: http://udify.app/api diff --git a/web/app/components/workflow/collaboration/core/__tests__/websocket-manager.test.ts b/web/app/components/workflow/collaboration/core/__tests__/websocket-manager.test.ts index f483f64971..b9982164c5 100644 --- a/web/app/components/workflow/collaboration/core/__tests__/websocket-manager.test.ts +++ b/web/app/components/workflow/collaboration/core/__tests__/websocket-manager.test.ts @@ -42,33 +42,13 @@ const createMockSocket = (id: string): MockSocket => { return socket } -const setGlobalWindow = (value?: typeof window): void => { - const globalWithWindow = globalThis as Partial & { window?: typeof window } - if (value) - globalWithWindow.window = value - else - delete globalWithWindow.window -} - describe('WebSocketClient', () => { - let originalWindow: typeof window | undefined - beforeEach(() => { vi.resetModules() ioMock.mockReset() - originalWindow = globalThis.window }) - afterEach(() => { - if (originalWindow) - setGlobalWindow(originalWindow) - else - setGlobalWindow(undefined) - }) - - it('connects with fallback url and registers base listeners when window is undefined', async () => { - setGlobalWindow(undefined) - + it('connects with default url and registers base listeners', async () => { const mockSocket = createMockSocket('socket-fallback') ioMock.mockImplementation(() => mockSocket) @@ -111,10 +91,6 @@ describe('WebSocketClient', () => { return mockSocket }) - setGlobalWindow({ - location: { protocol: 'https:', host: 'example.com' }, - } as unknown as typeof window) - const { WebSocketClient } = await import('../websocket-manager') const client = new WebSocketClient() client.connect('app-auth') diff --git a/web/app/components/workflow/collaboration/core/websocket-manager.ts b/web/app/components/workflow/collaboration/core/websocket-manager.ts index 7b8b783272..62ffe2cf0c 100644 --- a/web/app/components/workflow/collaboration/core/websocket-manager.ts +++ b/web/app/components/workflow/collaboration/core/websocket-manager.ts @@ -1,6 +1,7 @@ import type { Socket } from 'socket.io-client' import type { DebugInfo, WebSocketConfig } from '../types/websocket' import { io } from 'socket.io-client' +import { SOCKET_URL } from '@/config' type AckArgs = unknown[] @@ -46,21 +47,14 @@ export const emitWithAuthGuard = ( export class WebSocketClient { private connections: Map = new Map() private connecting: Set = new Set() - private config: WebSocketConfig + private readonly url: string + private readonly transports: WebSocketConfig['transports'] + private readonly withCredentials?: boolean constructor(config: WebSocketConfig = {}) { - const inferUrl = () => { - if (typeof window === 'undefined') - return 'ws://localhost:5001' - const scheme = window.location.protocol === 'https:' ? 'wss:' : 'ws:' - return `${scheme}//${window.location.host}` - } - this.config = { - url: config.url || process.env.NEXT_PUBLIC_SOCKET_URL || inferUrl(), - transports: config.transports || ['websocket'], - withCredentials: config.withCredentials !== false, - ...config, - } + this.url = SOCKET_URL + this.transports = config.transports || ['websocket'] + this.withCredentials = config.withCredentials !== false } connect(appId: string): Socket { @@ -87,11 +81,11 @@ export class WebSocketClient { withCredentials?: boolean } = { path: '/socket.io', - transports: this.config.transports, - withCredentials: this.config.withCredentials, + transports: this.transports, + withCredentials: this.withCredentials, } - const socket = io(this.config.url!, socketOptions) + const socket = io(this.url, socketOptions) this.connections.set(appId, socket) this.setupBaseEventListeners(socket, appId) diff --git a/web/app/components/workflow/collaboration/types/websocket.ts b/web/app/components/workflow/collaboration/types/websocket.ts index 37b3a8ad17..dd89df323f 100644 --- a/web/app/components/workflow/collaboration/types/websocket.ts +++ b/web/app/components/workflow/collaboration/types/websocket.ts @@ -1,5 +1,4 @@ export type WebSocketConfig = { - url?: string token?: string transports?: string[] withCredentials?: boolean diff --git a/web/app/layout.tsx b/web/app/layout.tsx index b970fddc7a..f7c3782a60 100644 --- a/web/app/layout.tsx +++ b/web/app/layout.tsx @@ -51,6 +51,7 @@ const LocaleLayout = async ({ [DatasetAttr.DATA_PUBLIC_EDITION]: process.env.NEXT_PUBLIC_EDITION, [DatasetAttr.DATA_PUBLIC_AMPLITUDE_API_KEY]: process.env.NEXT_PUBLIC_AMPLITUDE_API_KEY, [DatasetAttr.DATA_PUBLIC_COOKIE_DOMAIN]: process.env.NEXT_PUBLIC_COOKIE_DOMAIN, + [DatasetAttr.DATA_PUBLIC_SOCKET_URL]: process.env.NEXT_PUBLIC_SOCKET_URL, [DatasetAttr.DATA_PUBLIC_SUPPORT_MAIL_LOGIN]: process.env.NEXT_PUBLIC_SUPPORT_MAIL_LOGIN, [DatasetAttr.DATA_PUBLIC_SENTRY_DSN]: process.env.NEXT_PUBLIC_SENTRY_DSN, [DatasetAttr.DATA_PUBLIC_MAINTENANCE_NOTICE]: process.env.NEXT_PUBLIC_MAINTENANCE_NOTICE, diff --git a/web/config/index.ts b/web/config/index.ts index 08ce14b264..3564786578 100644 --- a/web/config/index.ts +++ b/web/config/index.ts @@ -165,6 +165,11 @@ const COOKIE_DOMAIN = getStringConfig( DatasetAttr.DATA_PUBLIC_COOKIE_DOMAIN, '', ).trim() +export const SOCKET_URL = getStringConfig( + process.env.NEXT_PUBLIC_SOCKET_URL, + DatasetAttr.DATA_PUBLIC_SOCKET_URL, + 'ws://localhost:5001', +).trim() export const BATCH_CONCURRENCY = getNumberConfig( process.env.NEXT_PUBLIC_BATCH_CONCURRENCY, diff --git a/web/types/feature.ts b/web/types/feature.ts index da0f4e5191..083bc8f7e0 100644 --- a/web/types/feature.ts +++ b/web/types/feature.ts @@ -110,6 +110,7 @@ export enum DatasetAttr { DATA_PUBLIC_EDITION = 'data-public-edition', DATA_PUBLIC_AMPLITUDE_API_KEY = 'data-public-amplitude-api-key', DATA_PUBLIC_COOKIE_DOMAIN = 'data-public-cookie-domain', + DATA_PUBLIC_SOCKET_URL = 'data-public-socket-url', DATA_PUBLIC_SUPPORT_MAIL_LOGIN = 'data-public-support-mail-login', DATA_PUBLIC_SENTRY_DSN = 'data-public-sentry-dsn', DATA_PUBLIC_MAINTENANCE_NOTICE = 'data-public-maintenance-notice',