diff --git a/sim/app/(auth)/verify/page.tsx b/sim/app/(auth)/verify/page.tsx index 3ec593fd4d..7a2459c8cf 100644 --- a/sim/app/(auth)/verify/page.tsx +++ b/sim/app/(auth)/verify/page.tsx @@ -1,10 +1,9 @@ import { isProd } from '@/lib/environment' import { VerifyContent } from './verify-content' +import { getBaseUrl } from '@/lib/urls/utils' export default function VerifyPage() { - const protocol = isProd ? 'https' : 'http' - const appUrl = process.env.NEXT_PUBLIC_APP_URL || 'localhost:3000' - const baseUrl = `${protocol}://${appUrl}` + const baseUrl = getBaseUrl() const hasResendKey = Boolean( process.env.RESEND_API_KEY && process.env.RESEND_API_KEY !== 'placeholder' diff --git a/sim/app/api/chat/edit/[id]/route.ts b/sim/app/api/chat/edit/[id]/route.ts index 24ebcbbee1..7577d6a5e6 100644 --- a/sim/app/api/chat/edit/[id]/route.ts +++ b/sim/app/api/chat/edit/[id]/route.ts @@ -7,6 +7,7 @@ import { chat } from '@/db/schema' import { createErrorResponse, createSuccessResponse } from '@/app/api/workflows/utils' import { z } from 'zod' import { encryptSecret } from '@/lib/utils' +import { getBaseDomain } from '@/lib/urls/utils' export const dynamic = 'force-dynamic' @@ -67,17 +68,15 @@ export async function GET( // Create a new result object without the password const { password, ...safeData } = chatInstance[0] - // Check if we're in development or production const isDevelopment = process.env.NODE_ENV === 'development' + const chatUrl = isDevelopment - ? `http://${chatInstance[0].subdomain}.localhost:3000` + ? `http://${chatInstance[0].subdomain}.${getBaseDomain()}` : `https://${chatInstance[0].subdomain}.simstudio.ai` - // For security, don't return the actual password value const result = { ...safeData, chatUrl, - // Include password presence flag but not the actual value hasPassword: !!password } @@ -228,12 +227,12 @@ export async function PATCH( .set(updateData) .where(eq(chat.id, chatId)) - // Return success response const updatedSubdomain = subdomain || existingChat[0].subdomain - // Check if we're in development or production + const isDevelopment = process.env.NODE_ENV === 'development' + const chatUrl = isDevelopment - ? `http://${updatedSubdomain}.localhost:3000` + ? `http://${updatedSubdomain}.${getBaseDomain()}` : `https://${updatedSubdomain}.simstudio.ai` logger.info(`Chat "${chatId}" updated successfully`) diff --git a/sim/app/w/[id]/components/control-bar/components/deploy-modal/components/chat-deploy/chat-deploy.tsx b/sim/app/w/[id]/components/control-bar/components/deploy-modal/components/chat-deploy/chat-deploy.tsx index 0ab8cfdef0..fb95ee9d39 100644 --- a/sim/app/w/[id]/components/control-bar/components/deploy-modal/components/chat-deploy/chat-deploy.tsx +++ b/sim/app/w/[id]/components/control-bar/components/deploy-modal/components/chat-deploy/chat-deploy.tsx @@ -32,6 +32,7 @@ import { Skeleton } from '@/components/ui/skeleton' import { Textarea } from '@/components/ui/textarea' import { createLogger } from '@/lib/logs/console-logger' import { cn } from '@/lib/utils' +import { getBaseDomain } from '@/lib/urls/utils' import { useNotificationStore } from '@/stores/notifications/store' import { OutputSelect } from '@/app/w/[id]/components/panel/components/chat/components/output-select/output-select' import { OutputConfig } from '@/stores/panel/chat/types' @@ -54,6 +55,11 @@ type AuthType = 'public' | 'password' | 'email' const isDevelopment = process.env.NODE_ENV === 'development' +const getDomainSuffix = (() => { + const suffix = isDevelopment ? `.${getBaseDomain()}` : '.simstudio.ai' + return () => suffix +})() + // Define Zod schema for API request validation const chatSchema = z.object({ workflowId: z.string().min(1, 'Workflow ID is required'), @@ -810,11 +816,20 @@ export function ChatDeploy({ } if (deployedChatUrl) { - // Extract just the subdomain from the URL const url = new URL(deployedChatUrl) const hostname = url.hostname const isDevelopmentUrl = hostname.includes('localhost') - const domainSuffix = isDevelopmentUrl ? '.localhost:3000' : '.simstudio.ai' + + let domainSuffix + if (isDevelopmentUrl) { + const baseDomain = getBaseDomain() + const baseHost = baseDomain.split(':')[0] + const port = url.port || (baseDomain.includes(':') ? baseDomain.split(':')[1] : '3000') + domainSuffix = `.${baseHost}:${port}` + } else { + domainSuffix = '.simstudio.ai' + } + const subdomainPart = isDevelopmentUrl ? hostname.split('.')[0] : hostname.split('.simstudio.ai')[0] @@ -911,7 +926,7 @@ export function ChatDeploy({ disabled={isDeploying} />
- {isDevelopment ? '.localhost:3000' : '.simstudio.ai'} + {getDomainSuffix()}
{!isCheckingSubdomain && subdomainAvailable === true && subdomain && (
diff --git a/sim/lib/urls/utils.ts b/sim/lib/urls/utils.ts new file mode 100644 index 0000000000..e817b18838 --- /dev/null +++ b/sim/lib/urls/utils.ts @@ -0,0 +1,36 @@ +/** + * Returns the base URL of the application, respecting environment variables for deployment environments + * @returns The base URL string (e.g., 'http://localhost:3000' or 'https://example.com') + */ +export function getBaseUrl(): string { + if (typeof window !== 'undefined') { + return window.location.origin + } + + const baseUrl = process.env.NEXT_PUBLIC_APP_URL + if (baseUrl) { + if (baseUrl.startsWith('http://') || baseUrl.startsWith('https://')) { + return baseUrl + } + + const isProd = process.env.NODE_ENV === 'production' + const protocol = isProd ? 'https://' : 'http://' + return `${protocol}${baseUrl}` + } + + return 'http://localhost:3000' +} + +/** + * Returns just the domain and port part of the application URL + * @returns The domain with port if applicable (e.g., 'localhost:3000' or 'simstudio.ai') + */ +export function getBaseDomain(): string { + try { + const url = new URL(getBaseUrl()) + return url.host // host includes port if specified + } catch (e) { + const isProd = process.env.NODE_ENV === 'production' + return isProd ? 'simstudio.ai' : 'localhost:3000' + } +} \ No newline at end of file diff --git a/sim/middleware.ts b/sim/middleware.ts index 5eb970f468..393938afb8 100644 --- a/sim/middleware.ts +++ b/sim/middleware.ts @@ -2,6 +2,7 @@ import { NextRequest, NextResponse } from 'next/server' import { getSessionCookie } from 'better-auth/cookies' import { verifyToken } from './lib/waitlist/token' import { createLogger } from '@/lib/logs/console-logger' +import { getBaseDomain } from '@/lib/urls/utils' const logger = createLogger('Middleware') @@ -15,7 +16,8 @@ const SUSPICIOUS_UA_PATTERNS = [ /^\(\)\s*{/, // Command execution attempt /\b(sqlmap|nikto|gobuster|dirb|nmap)\b/i // Known scanning tools ] -const BASE_DOMAIN = isDevelopment ? 'localhost:3000' : 'simstudio.ai' + +const BASE_DOMAIN = getBaseDomain() export async function middleware(request: NextRequest) { // Check for active session diff --git a/sim/tools/http/request.ts b/sim/tools/http/request.ts index 33158ae051..449538a4aa 100644 --- a/sim/tools/http/request.ts +++ b/sim/tools/http/request.ts @@ -1,23 +1,21 @@ import { HttpMethod, TableRow, ToolConfig } from '../types' import { createLogger } from '@/lib/logs/console-logger' import { RequestParams, RequestResponse } from './types' +import { getBaseUrl } from '@/lib/urls/utils' const logger = createLogger('HTTPRequestTool') // Function to get the appropriate referer based on environment const getReferer = (): string => { - // Check if we're in a browser environment if (typeof window !== 'undefined') { return window.location.origin } - // Use environment variable if available (server-side) - if (process.env.NEXT_PUBLIC_APP_URL) { - return process.env.NEXT_PUBLIC_APP_URL + try { + return getBaseUrl() + } catch (error) { + return 'http://localhost:3000' } - - // Fallback for development - return 'http://localhost:3000/' } /** diff --git a/sim/tools/mistral/parser.ts b/sim/tools/mistral/parser.ts index 45a0ba9a8a..d856e866b2 100644 --- a/sim/tools/mistral/parser.ts +++ b/sim/tools/mistral/parser.ts @@ -1,6 +1,7 @@ import { createLogger } from '@/lib/logs/console-logger' import { ToolConfig } from '../types' import { MistralParserInput, MistralParserOutput } from './types' +import { getBaseUrl } from '@/lib/urls/utils' const logger = createLogger('MistralParserTool') @@ -97,7 +98,8 @@ export const mistralParserTool: ToolConfig