diff --git a/apps/sim/app/api/auth/socket-token/route.ts b/apps/sim/app/api/auth/socket-token/route.ts index 6aeae89b921..9a2102aafb4 100644 --- a/apps/sim/app/api/auth/socket-token/route.ts +++ b/apps/sim/app/api/auth/socket-token/route.ts @@ -1,6 +1,9 @@ import { headers } from 'next/headers' import { NextResponse } from 'next/server' import { auth } from '@/lib/auth' +import { createLogger } from '@/lib/logs/console/logger' + +const logger = createLogger('SocketToken') export async function POST() { try { @@ -10,11 +13,23 @@ export async function POST() { }) if (!response) { + logger.error('Failed to generate socket token - no response from auth API') return NextResponse.json({ error: 'Failed to generate token' }, { status: 500 }) } return NextResponse.json({ token: response.token }) } catch (error) { + logger.error('Error generating socket token:', { + error, + errorMessage: error instanceof Error ? error.message : 'Unknown error', + errorStack: error instanceof Error ? error.stack : undefined, + }) + + // Return more specific error if it's an auth issue + if (error instanceof Error && error.message.includes('Unauthorized')) { + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + return NextResponse.json({ error: 'Failed to generate token' }, { status: 500 }) } } diff --git a/apps/sim/app/api/workspaces/route.ts b/apps/sim/app/api/workspaces/route.ts index b184ca6e864..736f061e159 100644 --- a/apps/sim/app/api/workspaces/route.ts +++ b/apps/sim/app/api/workspaces/route.ts @@ -10,76 +10,137 @@ const logger = createLogger('Workspaces') // Get all workspaces for the current user export async function GET() { - const session = await getSession() + try { + const session = await getSession() - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } + if (!session?.user?.id) { + logger.warn('GET /api/workspaces - No session or user ID', { session }) + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } + + logger.info('GET /api/workspaces - Fetching workspaces for user', { userId: session.user.id }) - // Get all workspaces where the user has permissions - const userWorkspaces = await db - .select({ - workspace: workspace, - permissionType: permissions.permissionType, + // Get all workspaces where the user has permissions + const userWorkspaces = await db + .select({ + workspace: workspace, + permissionType: permissions.permissionType, + }) + .from(permissions) + .innerJoin(workspace, eq(permissions.entityId, workspace.id)) + .where(and(eq(permissions.userId, session.user.id), eq(permissions.entityType, 'workspace'))) + .orderBy(desc(workspace.createdAt)) + + logger.info('GET /api/workspaces - Found workspaces', { + userId: session.user.id, + count: userWorkspaces.length }) - .from(permissions) - .innerJoin(workspace, eq(permissions.entityId, workspace.id)) - .where(and(eq(permissions.userId, session.user.id), eq(permissions.entityType, 'workspace'))) - .orderBy(desc(workspace.createdAt)) - if (userWorkspaces.length === 0) { - // Create a default workspace for the user - const defaultWorkspace = await createDefaultWorkspace(session.user.id, session.user.name) + if (userWorkspaces.length === 0) { + // Create a default workspace for the user + logger.info('GET /api/workspaces - Creating default workspace', { userId: session.user.id }) + const defaultWorkspace = await createDefaultWorkspace(session.user.id, session.user.name) - // Migrate existing workflows to the default workspace - await migrateExistingWorkflows(session.user.id, defaultWorkspace.id) + // Migrate existing workflows to the default workspace + await migrateExistingWorkflows(session.user.id, defaultWorkspace.id) - return NextResponse.json({ workspaces: [defaultWorkspace] }) - } + return NextResponse.json({ workspaces: [defaultWorkspace] }) + } - // If user has workspaces but might have orphaned workflows, migrate them - await ensureWorkflowsHaveWorkspace(session.user.id, userWorkspaces[0].workspace.id) + // If user has workspaces but might have orphaned workflows, migrate them + await ensureWorkflowsHaveWorkspace(session.user.id, userWorkspaces[0].workspace.id) - // Format the response with permission information - const workspacesWithPermissions = userWorkspaces.map( - ({ workspace: workspaceDetails, permissionType }) => ({ - ...workspaceDetails, - role: permissionType === 'admin' ? 'owner' : 'member', // Map admin to owner for compatibility - permissions: permissionType, - }) - ) + // Format the response with permission information + const workspacesWithPermissions = userWorkspaces.map( + ({ workspace: workspaceDetails, permissionType }) => ({ + ...workspaceDetails, + role: permissionType === 'admin' ? 'owner' : 'member', // Map admin to owner for compatibility + permissions: permissionType, + }) + ) - return NextResponse.json({ workspaces: workspacesWithPermissions }) + return NextResponse.json({ workspaces: workspacesWithPermissions }) + } catch (error) { + logger.error('GET /api/workspaces - Error fetching workspaces', { + error, + errorMessage: error instanceof Error ? error.message : 'Unknown error', + errorStack: error instanceof Error ? error.stack : undefined + }) + + // Return a more specific error message if possible + if (error instanceof Error && error.message.includes('Database')) { + return NextResponse.json( + { error: 'Database connection error. Please try again later.' }, + { status: 503 } + ) + } + + return NextResponse.json( + { error: 'Failed to fetch workspaces' }, + { status: 500 } + ) + } } // POST /api/workspaces - Create a new workspace export async function POST(req: Request) { - const session = await getSession() + try { + const session = await getSession() - if (!session?.user?.id) { - return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) - } + if (!session?.user?.id) { + logger.warn('POST /api/workspaces - No session or user ID') + return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }) + } - try { const { name } = await req.json() if (!name) { return NextResponse.json({ error: 'Name is required' }, { status: 400 }) } + logger.info('POST /api/workspaces - Creating workspace', { + userId: session.user.id, + name + }) + const newWorkspace = await createWorkspace(session.user.id, name) + logger.info('POST /api/workspaces - Workspace created successfully', { + userId: session.user.id, + workspaceId: newWorkspace.id + }) + return NextResponse.json({ workspace: newWorkspace }) } catch (error) { - console.error('Error creating workspace:', error) - return NextResponse.json({ error: 'Failed to create workspace' }, { status: 500 }) + logger.error('POST /api/workspaces - Error creating workspace', { + error, + errorMessage: error instanceof Error ? error.message : 'Unknown error', + errorStack: error instanceof Error ? error.stack : undefined + }) + + if (error instanceof Error && error.message.includes('Database')) { + return NextResponse.json( + { error: 'Database connection error. Please try again later.' }, + { status: 503 } + ) + } + + return NextResponse.json( + { error: 'Failed to create workspace' }, + { status: 500 } + ) } } // Helper function to create a default workspace async function createDefaultWorkspace(userId: string, userName?: string | null) { - const workspaceName = userName ? `${userName}'s Workspace` : 'My Workspace' - return createWorkspace(userId, workspaceName) + try { + const workspaceName = userName ? `${userName}'s Workspace` : 'My Workspace' + return await createWorkspace(userId, workspaceName) + } catch (error) { + logger.error('Failed to create default workspace', { userId, userName, error }) + throw error + } } // Helper function to create a workspace diff --git a/apps/sim/contexts/socket-context.tsx b/apps/sim/contexts/socket-context.tsx index 56160cf3e9c..d877e4928b7 100644 --- a/apps/sim/contexts/socket-context.tsx +++ b/apps/sim/contexts/socket-context.tsx @@ -132,17 +132,28 @@ export function SocketProvider({ children, user }: SocketProviderProps) { // Helper function to generate a fresh socket token const generateSocketToken = async (): Promise => { - // Avoid overlapping token requests - const res = await fetch('/api/auth/socket-token', { - method: 'POST', - credentials: 'include', - headers: { 'cache-control': 'no-store' }, - }) - if (!res.ok) throw new Error('Failed to generate socket token') - const body = await res.json().catch(() => ({})) - const token = body?.token - if (!token || typeof token !== 'string') throw new Error('Invalid socket token') - return token + try { + // Avoid overlapping token requests + const res = await fetch('/api/auth/socket-token', { + method: 'POST', + credentials: 'include', + headers: { 'cache-control': 'no-store' }, + }) + if (!res.ok) { + logger.warn('Failed to generate socket token, status:', res.status) + return '' // Return empty string to allow connection attempt without token + } + const body = await res.json().catch(() => ({})) + const token = body?.token + if (!token || typeof token !== 'string') { + logger.warn('Invalid socket token received') + return '' // Return empty string to allow connection attempt without token + } + return token + } catch (error) { + logger.warn('Error generating socket token:', error) + return '' // Return empty string to allow connection attempt without token + } } // Initialize socket when user is available - only once per session @@ -164,10 +175,20 @@ export function SocketProvider({ children, user }: SocketProviderProps) { // Generate initial token for socket authentication const token = await generateSocketToken() - const socketUrl = getEnv('NEXT_PUBLIC_SOCKET_URL') || 'http://localhost:3002' + // Determine the socket URL - if not explicitly set, use relative path in production + let socketUrl = getEnv('NEXT_PUBLIC_SOCKET_URL') + + if (!socketUrl) { + // In production, use relative path to connect to the same domain + if (typeof window !== 'undefined' && window.location.hostname !== 'localhost') { + socketUrl = '' // Empty string means same origin + } else { + socketUrl = 'http://localhost:3002' + } + } logger.info('Attempting to connect to Socket.IO server', { - url: socketUrl, + url: socketUrl || 'same-origin', userId: user?.id || 'no-user', hasToken: !!token, timestamp: new Date().toISOString(), @@ -227,13 +248,22 @@ export function SocketProvider({ children, user }: SocketProviderProps) { socketInstance.on('connect_error', (error: any) => { setIsConnecting(false) - logger.error('Socket connection error:', { - message: error.message, - stack: error.stack, - description: error.description, - type: error.type, - transport: error.transport, - }) + + // Log connection errors but don't spam the console if socket server is not available + if (error.type === 'TransportError' || error.message?.includes('websocket error')) { + logger.debug('Socket connection error (server may not be available):', { + message: error.message, + type: error.type, + }) + } else { + logger.error('Socket connection error:', { + message: error.message, + stack: error.stack, + description: error.description, + type: error.type, + transport: error.transport, + }) + } // Authentication errors now indicate either session expiry or token generation issues if (