From bbde2be093c4ff7f8b13def1da1bba3830db7734 Mon Sep 17 00:00:00 2001 From: Vikhyath Mondreti Date: Fri, 27 Jun 2025 16:35:55 -0700 Subject: [PATCH] fix(workspace url): race condition --- .../[workspaceId]/w/[workflowId]/workflow.tsx | 23 +++++++- .../app/workspace/[workspaceId]/w/page.tsx | 52 +++++++++++++------ apps/sim/db/index.ts | 4 +- apps/sim/socket-server/database/operations.ts | 2 +- apps/sim/socket-server/rooms/manager.ts | 2 +- 5 files changed, 62 insertions(+), 21 deletions(-) diff --git a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx index f14b47466a..d1d6c4f6e5 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/[workflowId]/workflow.tsx @@ -861,7 +861,28 @@ const WorkflowContent = React.memo(() => { // Navigate to existing workflow or first available if (!workflows[currentId]) { logger.info(`Workflow ${currentId} not found, redirecting to first available workflow`) - router.replace(`/workspace/${workspaceId}/w/${workflowIds[0]}`) + + // Validate that workflows belong to the current workspace before redirecting + const workspaceWorkflows = workflowIds.filter(id => { + const workflow = workflows[id] + return workflow.workspaceId === workspaceId + }) + + if (workspaceWorkflows.length > 0) { + router.replace(`/workspace/${workspaceId}/w/${workspaceWorkflows[0]}`) + } else { + // No valid workflows for this workspace, redirect to workspace root + router.replace(`/workspace/${workspaceId}/w`) + } + return + } + + // Validate that the current workflow belongs to the current workspace + const currentWorkflow = workflows[currentId] + if (currentWorkflow && currentWorkflow.workspaceId !== workspaceId) { + logger.warn(`Workflow ${currentId} belongs to workspace ${currentWorkflow.workspaceId}, not ${workspaceId}`) + // Redirect to the correct workspace for this workflow + router.replace(`/workspace/${currentWorkflow.workspaceId}/w/${currentId}`) return } diff --git a/apps/sim/app/workspace/[workspaceId]/w/page.tsx b/apps/sim/app/workspace/[workspaceId]/w/page.tsx index 8792827f07..c62f8d909d 100644 --- a/apps/sim/app/workspace/[workspaceId]/w/page.tsx +++ b/apps/sim/app/workspace/[workspaceId]/w/page.tsx @@ -1,36 +1,56 @@ 'use client' -import { useEffect } from 'react' +import { useEffect, useState } from 'react' import { useParams, useRouter } from 'next/navigation' import { LoadingAgent } from '@/components/ui/loading-agent' import { useWorkflowRegistry } from '@/stores/workflows/registry/store' export default function WorkflowsPage() { const router = useRouter() - const { workflows, isLoading } = useWorkflowRegistry() + const { workflows, isLoading, loadWorkflows } = useWorkflowRegistry() + const [hasInitialized, setHasInitialized] = useState(false) const params = useParams() - const workspaceId = params.workspaceId + const workspaceId = params.workspaceId as string + // Initialize workspace workflows useEffect(() => { - // Wait for workflows to load - if (isLoading) return + const initializeWorkspace = async () => { + try { + await loadWorkflows(workspaceId) + setHasInitialized(true) + } catch (error) { + console.error('Failed to load workflows for workspace:', error) + setHasInitialized(true) // Still mark as initialized to show error state + } + } + + if (!hasInitialized) { + initializeWorkspace() + } + }, [workspaceId, loadWorkflows, hasInitialized]) + + // Handle redirection once workflows are loaded + useEffect(() => { + // Only proceed if we've initialized and workflows are not loading + if (!hasInitialized || isLoading) return const workflowIds = Object.keys(workflows) - // If we have workflows, redirect to the first one - if (workflowIds.length > 0) { - router.replace(`/workspace/${workspaceId}/w/${workflowIds[0]}`) - return + // Validate that workflows belong to the current workspace + const workspaceWorkflows = workflowIds.filter(id => { + const workflow = workflows[id] + return workflow.workspaceId === workspaceId + }) + + // If we have valid workspace workflows, redirect to the first one + if (workspaceWorkflows.length > 0) { + router.replace(`/workspace/${workspaceId}/w/${workspaceWorkflows[0]}`) } + }, [hasInitialized, isLoading, workflows, workspaceId, router]) - // If no workflows exist after loading is complete, this means the workspace creation - // didn't work properly or the user doesn't have any workspaces. - // Redirect to home to let the system handle workspace/workflow creation properly. - router.replace('/') - }, [workflows, isLoading, router, workspaceId]) - - // Show loading state while determining where to redirect + // Always show loading state until redirect happens + // There should always be a default workflow, so we never show "no workflows found" return (
diff --git a/apps/sim/db/index.ts b/apps/sim/db/index.ts index cdedbc0a09..15cb71e088 100644 --- a/apps/sim/db/index.ts +++ b/apps/sim/db/index.ts @@ -26,8 +26,8 @@ const connectionString = env.POSTGRES_URL ?? env.DATABASE_URL const postgresClient = postgres(connectionString, { prepare: false, // Disable prefetch as it is not supported for "Transaction" pool mode idle_timeout: 20, // Reduce idle timeout to 20 seconds to free up connections faster - connect_timeout: 10, // Reduce connect timeout to 10 seconds - max: 3, // Conservative limit - with multiple serverless functions, this prevents pool exhaustion + connect_timeout: 30, // Increase connect timeout to 30 seconds to handle network issues + max: 2, // Further reduced limit to prevent Supabase connection exhaustion onnotice: () => {}, // Disable notices to reduce noise }) diff --git a/apps/sim/socket-server/database/operations.ts b/apps/sim/socket-server/database/operations.ts index caf84c46cf..88d424a860 100644 --- a/apps/sim/socket-server/database/operations.ts +++ b/apps/sim/socket-server/database/operations.ts @@ -15,7 +15,7 @@ const socketDb = drizzle( postgres(connectionString, { prepare: false, idle_timeout: 10, // Shorter idle timeout for socket operations - connect_timeout: 5, // Faster connection timeout + connect_timeout: 20, // Increase connection timeout for socket operations max: 2, // Very small pool for socket server to avoid exhausting Supabase limit onnotice: () => {}, // Disable notices debug: false, // Disable debug for socket operations diff --git a/apps/sim/socket-server/rooms/manager.ts b/apps/sim/socket-server/rooms/manager.ts index 3159fe0d4f..7a5659dabb 100644 --- a/apps/sim/socket-server/rooms/manager.ts +++ b/apps/sim/socket-server/rooms/manager.ts @@ -13,7 +13,7 @@ const db = drizzle( postgres(connectionString, { prepare: false, idle_timeout: 15, - connect_timeout: 5, + connect_timeout: 20, // Increase connection timeout for room operations max: 1, // Minimal pool for room operations to conserve connections onnotice: () => {}, }),