mirror of
https://github.com/Monadical-SAS/reflector.git
synced 2026-04-23 05:35:18 +00:00
feat: add Caddy reverse proxy with auto HTTPS for LAN access and auto-derive WebSocket URL (#863)
* feat: add Caddy reverse proxy with auto HTTPS for LAN access and auto-derive WebSocket URL Add a Caddy service to docker-compose.standalone.yml that provides automatic HTTPS with local certificates, enabling secure access to both the frontend and API from the local network through a single entrypoint. Backend changes: - Add ROOT_PATH setting to FastAPI so the API can be served under /api prefix - Route frontend and API (/server-api) through Caddy reverse proxy Frontend changes: - Support WEBSOCKET_URL=auto to derive the WebSocket URL from API_URL automatically, using the page protocol (http→ws, https→wss) and host - Make WEBSOCKET_URL env var optional instead of required * style: pre-commit * fix: make standalone compose self-contained (drop !reset dependency) docker-compose.standalone.yml used !reset YAML tags to clear network_mode and volumes from the base compose. !reset requires Compose v2.24+ and breaks on Colima + brew-installed compose. Rewrite as a fully self-contained file with all services defined directly (server, worker, beat, redis, postgres, web, garage, cpu, gpu-nvidia, ollama, ollama-cpu). No longer overlays docker-compose.yml. Update setup-standalone.sh compose_cmd() to use only the standalone file instead of both files. * fix: update standalone docs to match self-contained compose usage --------- Co-authored-by: Igor Loskutov <igor.loskutoff@gmail.com>
This commit is contained in:
@@ -1,11 +1,142 @@
|
|||||||
# Standalone services for fully local deployment (no external dependencies).
|
# Self-contained standalone compose for fully local deployment (no external dependencies).
|
||||||
# Usage: docker compose -f docker-compose.yml -f docker-compose.standalone.yml up -d
|
# Usage: docker compose -f docker-compose.standalone.yml up -d
|
||||||
#
|
#
|
||||||
# On Linux with NVIDIA GPU, also pass: --profile ollama-gpu
|
# On Linux with NVIDIA GPU, also pass: --profile ollama-gpu
|
||||||
# On Linux without GPU: --profile ollama-cpu
|
# On Linux without GPU: --profile ollama-cpu
|
||||||
# On Mac: Ollama runs natively (Metal GPU) — no profile needed, services here unused.
|
# On Mac: Ollama runs natively (Metal GPU) — no profile needed, services here unused.
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
caddy:
|
||||||
|
image: caddy:2-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
ports:
|
||||||
|
- "3043:443"
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
volumes:
|
||||||
|
- ./scripts/standalone/Caddyfile:/etc/caddy/Caddyfile:ro
|
||||||
|
- caddy_data:/data
|
||||||
|
- caddy_config:/config
|
||||||
|
|
||||||
|
server:
|
||||||
|
build:
|
||||||
|
context: server
|
||||||
|
ports:
|
||||||
|
- "1250:1250"
|
||||||
|
extra_hosts:
|
||||||
|
- "host.docker.internal:host-gateway"
|
||||||
|
volumes:
|
||||||
|
- ./server/:/app/
|
||||||
|
- /app/.venv
|
||||||
|
env_file:
|
||||||
|
- ./server/.env
|
||||||
|
environment:
|
||||||
|
ENTRYPOINT: server
|
||||||
|
# Docker DNS names instead of localhost
|
||||||
|
DATABASE_URL: postgresql+asyncpg://reflector:reflector@postgres:5432/reflector
|
||||||
|
REDIS_HOST: redis
|
||||||
|
CELERY_BROKER_URL: redis://redis:6379/1
|
||||||
|
CELERY_RESULT_BACKEND: redis://redis:6379/1
|
||||||
|
# Standalone doesn't run Hatchet
|
||||||
|
HATCHET_CLIENT_SERVER_URL: ""
|
||||||
|
HATCHET_CLIENT_HOST_PORT: ""
|
||||||
|
# Self-hosted transcription/diarization via CPU service
|
||||||
|
TRANSCRIPT_BACKEND: modal
|
||||||
|
TRANSCRIPT_URL: http://cpu:8000
|
||||||
|
TRANSCRIPT_MODAL_API_KEY: local
|
||||||
|
DIARIZATION_BACKEND: modal
|
||||||
|
DIARIZATION_URL: http://cpu:8000
|
||||||
|
# Caddy reverse proxy prefix
|
||||||
|
ROOT_PATH: /server-api
|
||||||
|
depends_on:
|
||||||
|
postgres:
|
||||||
|
condition: service_healthy
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
worker:
|
||||||
|
build:
|
||||||
|
context: server
|
||||||
|
volumes:
|
||||||
|
- ./server/:/app/
|
||||||
|
- /app/.venv
|
||||||
|
env_file:
|
||||||
|
- ./server/.env
|
||||||
|
environment:
|
||||||
|
ENTRYPOINT: worker
|
||||||
|
HATCHET_CLIENT_SERVER_URL: ""
|
||||||
|
HATCHET_CLIENT_HOST_PORT: ""
|
||||||
|
TRANSCRIPT_BACKEND: modal
|
||||||
|
TRANSCRIPT_URL: http://cpu:8000
|
||||||
|
TRANSCRIPT_MODAL_API_KEY: local
|
||||||
|
DIARIZATION_BACKEND: modal
|
||||||
|
DIARIZATION_URL: http://cpu:8000
|
||||||
|
depends_on:
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
beat:
|
||||||
|
build:
|
||||||
|
context: server
|
||||||
|
volumes:
|
||||||
|
- ./server/:/app/
|
||||||
|
- /app/.venv
|
||||||
|
env_file:
|
||||||
|
- ./server/.env
|
||||||
|
environment:
|
||||||
|
ENTRYPOINT: beat
|
||||||
|
depends_on:
|
||||||
|
redis:
|
||||||
|
condition: service_started
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7.2
|
||||||
|
ports:
|
||||||
|
- 6379:6379
|
||||||
|
|
||||||
|
postgres:
|
||||||
|
image: postgres:17
|
||||||
|
command: postgres -c 'max_connections=200'
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
environment:
|
||||||
|
POSTGRES_USER: reflector
|
||||||
|
POSTGRES_PASSWORD: reflector
|
||||||
|
POSTGRES_DB: reflector
|
||||||
|
volumes:
|
||||||
|
- ./data/postgres:/var/lib/postgresql/data
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD-SHELL", "pg_isready -d reflector -U reflector"]
|
||||||
|
interval: 5s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 10
|
||||||
|
start_period: 15s
|
||||||
|
|
||||||
|
web:
|
||||||
|
image: reflector-frontend-standalone
|
||||||
|
build:
|
||||||
|
context: ./www
|
||||||
|
ports:
|
||||||
|
- "3000:3000"
|
||||||
|
command: ["node", "server.js"]
|
||||||
|
environment:
|
||||||
|
NODE_ENV: production
|
||||||
|
# Browser-facing URLs (host-accessible ports)
|
||||||
|
API_URL: /server-api
|
||||||
|
WEBSOCKET_URL: auto
|
||||||
|
SITE_URL: http://localhost:3000
|
||||||
|
# Server-side URLs (docker-network internal)
|
||||||
|
SERVER_API_URL: http://server:1250
|
||||||
|
KV_URL: redis://redis:6379
|
||||||
|
KV_USE_TLS: "false"
|
||||||
|
# Standalone: no external auth provider
|
||||||
|
FEATURE_REQUIRE_LOGIN: "false"
|
||||||
|
NEXTAUTH_URL: http://localhost:3000
|
||||||
|
NEXTAUTH_SECRET: standalone-local-secret
|
||||||
|
# Nullify partial auth vars inherited from base env_file
|
||||||
|
AUTHENTIK_ISSUER: ""
|
||||||
|
AUTHENTIK_REFRESH_TOKEN_URL: ""
|
||||||
|
|
||||||
garage:
|
garage:
|
||||||
image: dxflrs/garage:v1.1.0
|
image: dxflrs/garage:v1.1.0
|
||||||
ports:
|
ports:
|
||||||
@@ -23,102 +154,6 @@ services:
|
|||||||
retries: 5
|
retries: 5
|
||||||
start_period: 5s
|
start_period: 5s
|
||||||
|
|
||||||
ollama:
|
|
||||||
image: ollama/ollama:latest
|
|
||||||
profiles: ["ollama-gpu"]
|
|
||||||
ports:
|
|
||||||
- "11434:11434"
|
|
||||||
volumes:
|
|
||||||
- ollama_data:/root/.ollama
|
|
||||||
deploy:
|
|
||||||
resources:
|
|
||||||
reservations:
|
|
||||||
devices:
|
|
||||||
- driver: nvidia
|
|
||||||
count: all
|
|
||||||
capabilities: [gpu]
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
|
|
||||||
ollama-cpu:
|
|
||||||
image: ollama/ollama:latest
|
|
||||||
profiles: ["ollama-cpu"]
|
|
||||||
ports:
|
|
||||||
- "11434:11434"
|
|
||||||
volumes:
|
|
||||||
- ollama_data:/root/.ollama
|
|
||||||
restart: unless-stopped
|
|
||||||
healthcheck:
|
|
||||||
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
|
|
||||||
interval: 10s
|
|
||||||
timeout: 5s
|
|
||||||
retries: 5
|
|
||||||
|
|
||||||
# Override server to use standard compose networking instead of network_mode:host.
|
|
||||||
# host mode breaks on macOS Docker Desktop and prevents Docker DNS resolution.
|
|
||||||
server:
|
|
||||||
network_mode: !reset null
|
|
||||||
ports:
|
|
||||||
- "1250:1250"
|
|
||||||
extra_hosts:
|
|
||||||
- "host.docker.internal:host-gateway"
|
|
||||||
depends_on:
|
|
||||||
postgres:
|
|
||||||
condition: service_healthy
|
|
||||||
redis:
|
|
||||||
condition: service_started
|
|
||||||
environment:
|
|
||||||
# Override base compose's localhost URLs with Docker DNS names
|
|
||||||
DATABASE_URL: postgresql+asyncpg://reflector:reflector@postgres:5432/reflector
|
|
||||||
REDIS_HOST: redis
|
|
||||||
CELERY_BROKER_URL: redis://redis:6379/1
|
|
||||||
CELERY_RESULT_BACKEND: redis://redis:6379/1
|
|
||||||
# Standalone doesn't run Hatchet — blank out localhost URLs inherited from base
|
|
||||||
HATCHET_CLIENT_SERVER_URL: ""
|
|
||||||
HATCHET_CLIENT_HOST_PORT: ""
|
|
||||||
# Self-hosted transcription/diarization via CPU service
|
|
||||||
TRANSCRIPT_BACKEND: modal
|
|
||||||
TRANSCRIPT_URL: http://cpu:8000
|
|
||||||
TRANSCRIPT_MODAL_API_KEY: local
|
|
||||||
DIARIZATION_BACKEND: modal
|
|
||||||
DIARIZATION_URL: http://cpu:8000
|
|
||||||
|
|
||||||
worker:
|
|
||||||
environment:
|
|
||||||
TRANSCRIPT_BACKEND: modal
|
|
||||||
TRANSCRIPT_URL: http://cpu:8000
|
|
||||||
TRANSCRIPT_MODAL_API_KEY: local
|
|
||||||
DIARIZATION_BACKEND: modal
|
|
||||||
DIARIZATION_URL: http://cpu:8000
|
|
||||||
|
|
||||||
web:
|
|
||||||
image: reflector-frontend-standalone
|
|
||||||
build:
|
|
||||||
context: ./www
|
|
||||||
command: ["node", "server.js"]
|
|
||||||
volumes: !reset []
|
|
||||||
environment:
|
|
||||||
NODE_ENV: production
|
|
||||||
# Browser-facing URLs (host-accessible ports)
|
|
||||||
API_URL: http://localhost:1250
|
|
||||||
WEBSOCKET_URL: ws://localhost:1250
|
|
||||||
SITE_URL: http://localhost:3000
|
|
||||||
# Server-side URLs (docker-network internal)
|
|
||||||
SERVER_API_URL: http://server:1250
|
|
||||||
KV_URL: redis://redis:6379
|
|
||||||
KV_USE_TLS: "false"
|
|
||||||
# Standalone: no external auth provider
|
|
||||||
FEATURE_REQUIRE_LOGIN: "false"
|
|
||||||
NEXTAUTH_URL: http://localhost:3000
|
|
||||||
NEXTAUTH_SECRET: standalone-local-secret
|
|
||||||
# Nullify partial auth vars inherited from base env_file
|
|
||||||
AUTHENTIK_ISSUER: ""
|
|
||||||
AUTHENTIK_REFRESH_TOKEN_URL: ""
|
|
||||||
|
|
||||||
cpu:
|
cpu:
|
||||||
build:
|
build:
|
||||||
context: ./gpu/self_hosted
|
context: ./gpu/self_hosted
|
||||||
@@ -156,8 +191,45 @@ services:
|
|||||||
retries: 10
|
retries: 10
|
||||||
start_period: 120s
|
start_period: 120s
|
||||||
|
|
||||||
|
ollama:
|
||||||
|
image: ollama/ollama:latest
|
||||||
|
profiles: ["ollama-gpu"]
|
||||||
|
ports:
|
||||||
|
- "11434:11434"
|
||||||
|
volumes:
|
||||||
|
- ollama_data:/root/.ollama
|
||||||
|
deploy:
|
||||||
|
resources:
|
||||||
|
reservations:
|
||||||
|
devices:
|
||||||
|
- driver: nvidia
|
||||||
|
count: all
|
||||||
|
capabilities: [gpu]
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
|
ollama-cpu:
|
||||||
|
image: ollama/ollama:latest
|
||||||
|
profiles: ["ollama-cpu"]
|
||||||
|
ports:
|
||||||
|
- "11434:11434"
|
||||||
|
volumes:
|
||||||
|
- ollama_data:/root/.ollama
|
||||||
|
restart: unless-stopped
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "curl", "-f", "http://localhost:11434/api/tags"]
|
||||||
|
interval: 10s
|
||||||
|
timeout: 5s
|
||||||
|
retries: 5
|
||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
garage_data:
|
garage_data:
|
||||||
garage_meta:
|
garage_meta:
|
||||||
ollama_data:
|
ollama_data:
|
||||||
gpu_cache:
|
gpu_cache:
|
||||||
|
caddy_data:
|
||||||
|
caddy_config:
|
||||||
|
|||||||
@@ -191,7 +191,7 @@ Standalone runs without authentication (`FEATURE_REQUIRE_LOGIN=false`, `AUTH_BAC
|
|||||||
|
|
||||||
1. In `www/.env.local`: set `FEATURE_REQUIRE_LOGIN=true`, uncomment `AUTHENTIK_ISSUER` and `AUTHENTIK_REFRESH_TOKEN_URL`
|
1. In `www/.env.local`: set `FEATURE_REQUIRE_LOGIN=true`, uncomment `AUTHENTIK_ISSUER` and `AUTHENTIK_REFRESH_TOKEN_URL`
|
||||||
2. In `server/.env`: set `AUTH_BACKEND=authentik` (or your backend), configure `AUTH_JWT_AUDIENCE`
|
2. In `server/.env`: set `AUTH_BACKEND=authentik` (or your backend), configure `AUTH_JWT_AUDIENCE`
|
||||||
3. Restart: `docker compose -f docker-compose.yml -f docker-compose.standalone.yml up -d --force-recreate web server`
|
3. Restart: `docker compose -f docker-compose.standalone.yml up -d --force-recreate web server`
|
||||||
|
|
||||||
## What's NOT covered
|
## What's NOT covered
|
||||||
|
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ resolve_symlink() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
compose_cmd() {
|
compose_cmd() {
|
||||||
local compose_files="-f $ROOT_DIR/docker-compose.yml -f $ROOT_DIR/docker-compose.standalone.yml"
|
local compose_files="-f $ROOT_DIR/docker-compose.standalone.yml"
|
||||||
if [[ "$OS" == "Linux" ]] && [[ -n "${OLLAMA_PROFILE:-}" ]]; then
|
if [[ "$OS" == "Linux" ]] && [[ -n "${OLLAMA_PROFILE:-}" ]]; then
|
||||||
docker compose $compose_files --profile "$OLLAMA_PROFILE" "$@"
|
docker compose $compose_files --profile "$OLLAMA_PROFILE" "$@"
|
||||||
else
|
else
|
||||||
@@ -362,7 +362,7 @@ step_services() {
|
|||||||
# Check for port conflicts — stale processes silently shadow Docker port mappings.
|
# Check for port conflicts — stale processes silently shadow Docker port mappings.
|
||||||
# OrbStack/Docker Desktop bind ports for forwarding; ignore those PIDs.
|
# OrbStack/Docker Desktop bind ports for forwarding; ignore those PIDs.
|
||||||
local ports_ok=true
|
local ports_ok=true
|
||||||
for port in 3000 1250 5432 6379 3900 3903; do
|
for port in 3043 3000 1250 5432 6379 3900 3903; do
|
||||||
local pids
|
local pids
|
||||||
pids=$(lsof -ti :"$port" 2>/dev/null || true)
|
pids=$(lsof -ti :"$port" 2>/dev/null || true)
|
||||||
for pid in $pids; do
|
for pid in $pids; do
|
||||||
@@ -386,7 +386,7 @@ step_services() {
|
|||||||
rebuild_images
|
rebuild_images
|
||||||
|
|
||||||
# server runs alembic migrations on startup automatically (see runserver.sh)
|
# server runs alembic migrations on startup automatically (see runserver.sh)
|
||||||
compose_cmd up -d postgres redis garage cpu server worker beat web
|
compose_cmd up -d postgres redis garage cpu server worker beat web caddy
|
||||||
ok "Containers started"
|
ok "Containers started"
|
||||||
|
|
||||||
# Quick sanity check — catch containers that exit immediately (bad image, missing file, etc.)
|
# Quick sanity check — catch containers that exit immediately (bad image, missing file, etc.)
|
||||||
@@ -464,6 +464,14 @@ step_health() {
|
|||||||
echo ""
|
echo ""
|
||||||
ok "Frontend responding"
|
ok "Frontend responding"
|
||||||
|
|
||||||
|
# Caddy reverse proxy (self-signed TLS — curl needs -k)
|
||||||
|
if curl -sfk "https://localhost:3043" > /dev/null 2>&1; then
|
||||||
|
ok "Caddy proxy healthy (https://localhost:3043)"
|
||||||
|
else
|
||||||
|
warn "Caddy proxy not responding on https://localhost:3043"
|
||||||
|
warn "Check with: docker compose logs caddy"
|
||||||
|
fi
|
||||||
|
|
||||||
# Check LLM reachability from inside a container
|
# Check LLM reachability from inside a container
|
||||||
if compose_cmd exec -T server \
|
if compose_cmd exec -T server \
|
||||||
curl -sf "$LLM_URL_VALUE/models" > /dev/null 2>&1; then
|
curl -sf "$LLM_URL_VALUE/models" > /dev/null 2>&1; then
|
||||||
@@ -533,8 +541,8 @@ main() {
|
|||||||
echo -e " ${GREEN}Reflector is running!${NC}"
|
echo -e " ${GREEN}Reflector is running!${NC}"
|
||||||
echo "=========================================="
|
echo "=========================================="
|
||||||
echo ""
|
echo ""
|
||||||
echo " Frontend: http://localhost:3000"
|
echo " App: https://localhost:3043 (accept self-signed cert in browser)"
|
||||||
echo " API: http://localhost:1250"
|
echo " API: https://localhost:3043/server-api"
|
||||||
echo ""
|
echo ""
|
||||||
echo " To stop: docker compose down"
|
echo " To stop: docker compose down"
|
||||||
echo " To re-run: ./scripts/setup-standalone.sh"
|
echo " To re-run: ./scripts/setup-standalone.sh"
|
||||||
|
|||||||
16
scripts/standalone/Caddyfile
Normal file
16
scripts/standalone/Caddyfile
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
# Standalone Caddyfile — single-origin reverse proxy for local development.
|
||||||
|
# Routes:
|
||||||
|
# /server-api/* → FastAPI backend (server:1250)
|
||||||
|
# /* → Next.js frontend (web:3000)
|
||||||
|
|
||||||
|
localhost {
|
||||||
|
# API + WebSocket: server has ROOT_PATH=/server-api so path is preserved
|
||||||
|
handle /server-api/* {
|
||||||
|
reverse_proxy server:1250
|
||||||
|
}
|
||||||
|
|
||||||
|
# Everything else → frontend
|
||||||
|
handle {
|
||||||
|
reverse_proxy web:3000
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -59,7 +59,7 @@ else:
|
|||||||
logger.info("Sentry disabled")
|
logger.info("Sentry disabled")
|
||||||
|
|
||||||
# build app
|
# build app
|
||||||
app = FastAPI(lifespan=lifespan)
|
app = FastAPI(lifespan=lifespan, root_path=settings.ROOT_PATH)
|
||||||
app.add_middleware(
|
app.add_middleware(
|
||||||
CORSMiddleware,
|
CORSMiddleware,
|
||||||
allow_credentials=settings.CORS_ALLOW_CREDENTIALS or False,
|
allow_credentials=settings.CORS_ALLOW_CREDENTIALS or False,
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class Settings(BaseSettings):
|
|||||||
extra="ignore",
|
extra="ignore",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ROOT_PATH: str = "/"
|
||||||
|
|
||||||
# CORS
|
# CORS
|
||||||
UI_BASE_URL: str = "http://localhost:3000"
|
UI_BASE_URL: str = "http://localhost:3000"
|
||||||
CORS_ORIGIN: str = "*"
|
CORS_ORIGIN: str = "*"
|
||||||
|
|||||||
@@ -13,9 +13,33 @@ export const API_URL = !isBuildPhase
|
|||||||
? getClientEnv().API_URL
|
? getClientEnv().API_URL
|
||||||
: "http://localhost";
|
: "http://localhost";
|
||||||
|
|
||||||
export const WEBSOCKET_URL = !isBuildPhase
|
/**
|
||||||
? getClientEnv().WEBSOCKET_URL || "ws://127.0.0.1:1250"
|
* Derive a WebSocket URL from the API_URL.
|
||||||
: "ws://localhost";
|
* Handles full URLs (http://host/api, https://host/api) and relative paths (/api).
|
||||||
|
* For full URLs, ws/wss is derived from the URL's own protocol.
|
||||||
|
* For relative URLs, ws/wss is derived from window.location.protocol.
|
||||||
|
*/
|
||||||
|
const deriveWebSocketUrl = (apiUrl: string): string => {
|
||||||
|
if (typeof window === "undefined") {
|
||||||
|
return "ws://localhost";
|
||||||
|
}
|
||||||
|
const parsed = new URL(apiUrl, window.location.origin);
|
||||||
|
const wsProtocol = parsed.protocol === "https:" ? "wss:" : "ws:";
|
||||||
|
// Normalize: remove trailing slash from pathname
|
||||||
|
const pathname = parsed.pathname.replace(/\/+$/, "");
|
||||||
|
return `${wsProtocol}//${parsed.host}${pathname}`;
|
||||||
|
};
|
||||||
|
|
||||||
|
const resolveWebSocketUrl = (): string => {
|
||||||
|
if (isBuildPhase) return "ws://localhost";
|
||||||
|
const raw = getClientEnv().WEBSOCKET_URL;
|
||||||
|
if (!raw || raw === "auto") {
|
||||||
|
return deriveWebSocketUrl(API_URL);
|
||||||
|
}
|
||||||
|
return raw;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const WEBSOCKET_URL = resolveWebSocketUrl();
|
||||||
|
|
||||||
export const client = createClient<paths>({
|
export const client = createClient<paths>({
|
||||||
baseUrl: API_URL,
|
baseUrl: API_URL,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import {
|
|||||||
assertExists,
|
assertExists,
|
||||||
assertExistsAndNonEmptyString,
|
assertExistsAndNonEmptyString,
|
||||||
NonEmptyString,
|
NonEmptyString,
|
||||||
|
parseMaybeNonEmptyString,
|
||||||
parseNonEmptyString,
|
parseNonEmptyString,
|
||||||
} from "./utils";
|
} from "./utils";
|
||||||
import { isBuildPhase } from "./next";
|
import { isBuildPhase } from "./next";
|
||||||
@@ -74,14 +75,14 @@ export const getClientEnvServer = (): ClientEnvCommon => {
|
|||||||
if (isBuildPhase) {
|
if (isBuildPhase) {
|
||||||
return {
|
return {
|
||||||
API_URL: getNextEnvVar("API_URL"),
|
API_URL: getNextEnvVar("API_URL"),
|
||||||
WEBSOCKET_URL: getNextEnvVar("WEBSOCKET_URL"),
|
WEBSOCKET_URL: parseMaybeNonEmptyString(process.env.WEBSOCKET_URL ?? ""),
|
||||||
...features,
|
...features,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
clientEnv = {
|
clientEnv = {
|
||||||
API_URL: getNextEnvVar("API_URL"),
|
API_URL: getNextEnvVar("API_URL"),
|
||||||
WEBSOCKET_URL: getNextEnvVar("WEBSOCKET_URL"),
|
WEBSOCKET_URL: parseMaybeNonEmptyString(process.env.WEBSOCKET_URL ?? ""),
|
||||||
...features,
|
...features,
|
||||||
};
|
};
|
||||||
return clientEnv;
|
return clientEnv;
|
||||||
|
|||||||
Reference in New Issue
Block a user