diff --git a/docker-compose.yml b/docker-compose.yml index 3a35208..064c58f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -21,10 +21,19 @@ services: - proxy labels: - 'traefik.enable=true' + # Main application route - 'traefik.http.routers.youtubepedia.rule=Host(`youtubepedia.barron.agency`)' - 'traefik.http.routers.youtubepedia.entrypoints=websecure' - 'traefik.http.routers.youtubepedia.tls.certresolver=letsencrypt' - 'traefik.http.services.youtubepedia.loadbalancer.server.port=3000' + # WebSocket route + - 'traefik.http.routers.youtubepedia-ws.rule=Host(`youtubepedia.barron.agency`) && PathPrefix(`/socket.io`)' + - 'traefik.http.routers.youtubepedia-ws.entrypoints=websecure' + - 'traefik.http.routers.youtubepedia-ws.tls.certresolver=letsencrypt' + - 'traefik.http.services.youtubepedia-ws.loadbalancer.server.port=3000' + # Enable WebSocket protocol + - 'traefik.http.middlewares.sio.headers.customrequestheaders.X-Forwarded-Proto=https' + - 'traefik.http.routers.youtubepedia-ws.middlewares=sio' networks: proxy: diff --git a/src/hooks.server.ts b/src/hooks.server.ts index fbb7e6f..7eff68c 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,22 +1,24 @@ +/* eslint-disable no-var */ +/* eslint-disable @typescript-eslint/no-explicit-any */ import type { Handle } from '@sveltejs/kit'; import { locale } from 'svelte-i18n'; import { lucia } from '$lib/server/auth'; import { initSocketIO } from '@/server/socket'; -import { createServer } from 'http'; -const httpServer = createServer(); -initSocketIO(httpServer); - -httpServer.listen(3001, () => { - console.log('Socket.IO server listening on port 3001'); -}); - -let isSocketInitialized = false; +declare global { + var __socketio: import('socket.io').Server | null; +} export const handle: Handle = async ({ event, resolve }) => { - if (!isSocketInitialized && event.platform?.server) { - isSocketInitialized = true; - initSocketIO(event.platform.server); + // Initialize socket.io if not already initialized + if (!global.__socketio && event.platform?.server) { + try { + console.log('Initializing Socket.IO from hooks...'); + initSocketIO(event.platform.server); + console.log('Socket.IO initialization from hooks completed'); + } catch (error) { + console.error('Failed to initialize Socket.IO from hooks:', error); + } } const lang = diff --git a/src/lib/server/socket.ts b/src/lib/server/socket.ts index 6b90ac5..ac7545a 100644 --- a/src/lib/server/socket.ts +++ b/src/lib/server/socket.ts @@ -1,50 +1,57 @@ +/* eslint-disable no-var */ import { Server } from 'socket.io'; import type { Server as HTTPServer } from 'http'; import { dev } from '$app/environment'; -let io: Server | null = null; -let initializationPromise: Promise | null = null; +// Access the global io instance +declare global { + var __socketio: Server | null; +} -export const initSocketIO = (httpServer: HTTPServer): Promise => { - if (!initializationPromise) { - initializationPromise = new Promise((resolve) => { - if (!io) { - console.log('Initializing Socket.IO server...'); - io = new Server(httpServer, { - path: '/socket.io', - cors: { - origin: dev ? ['http://localhost:3000'] : ['https://youtubepedia.barron.agency'], - methods: ['GET', 'POST'], - credentials: true - }, - transports: ['websocket', 'polling'] - }); - - io.on('connection', (socket) => { - console.log('Client connected', socket.id); +export const getIO = (): Server => { + if (dev) { + // In development, use the Vite-initialized socket + if (!global.__socketio) { + throw new Error('Socket.IO not initialized yet'); + } + return global.__socketio; + } else { + // In production, initialize normally + if (!global.__socketio) { + throw new Error('Socket.IO not initialized yet'); + } + return global.__socketio; + } +}; - const userId = socket.handshake.auth.userId; - if (userId) { - socket.join(userId); - console.log(`User ${userId} joined their room`); - } +export const initSocketIO = (httpServer: HTTPServer): void => { + if (!global.__socketio) { + console.log('Initializing Socket.IO server...'); + const io = new Server(httpServer, { + path: '/socket.io', + cors: { + origin: dev ? ['http://localhost:3000'] : ['https://youtubepedia.barron.agency'], + methods: ['GET', 'POST'], + credentials: true + }, + transports: ['websocket', 'polling'] + }); - socket.on('disconnect', () => { - console.log('Client disconnected', socket.id); - }); - }); + io.on('connection', (socket) => { + console.log('Client connected', socket.id); - console.log('Socket.IO server initialized successfully'); + const userId = socket.handshake.auth.userId; + if (userId) { + socket.join(userId); + console.log(`User ${userId} joined their room`); } - resolve(io); + + socket.on('disconnect', () => { + console.log('Client disconnected', socket.id); + }); }); - } - return initializationPromise; -}; -export const getIO = (): Server => { - if (!io) { - throw new Error('Socket.IO has not been initialized'); + global.__socketio = io; + console.log('Socket.IO server initialized successfully'); } - return io; }; diff --git a/src/lib/socket.ts b/src/lib/socket.ts index 3a70b57..7e1fb80 100644 --- a/src/lib/socket.ts +++ b/src/lib/socket.ts @@ -3,13 +3,13 @@ import { io, type Socket } from 'socket.io-client'; export const initSocket = (userId: string): Socket | null => { if (browser) { - const socketUrl = `${window.location.protocol}//${window.location.hostname}:3001`; - - const socket = io(socketUrl, { + const socket = io(window.location.origin, { + path: '/socket.io', auth: { userId }, reconnection: true, reconnectionDelay: 1000, - reconnectionAttempts: 5 + reconnectionAttempts: 5, + transports: ['websocket', 'polling'] }); socket.on('connect', () => { @@ -23,4 +23,4 @@ export const initSocket = (userId: string): Socket | null => { return socket; } return null; -}; +}; \ No newline at end of file diff --git a/src/routes/(main)/dashboard/chat/[id]/+page.server.ts b/src/routes/(main)/dashboard/chat/[id]/+page.server.ts index 5cddf54..66f9008 100644 --- a/src/routes/(main)/dashboard/chat/[id]/+page.server.ts +++ b/src/routes/(main)/dashboard/chat/[id]/+page.server.ts @@ -1,4 +1,4 @@ -import { error, fail, type Actions } from '@sveltejs/kit'; +import { error, fail } from '@sveltejs/kit'; import type { PageServerLoad } from './$types'; import prisma from '@/utils/prisma'; import { getIO } from '@/server/socket'; @@ -67,8 +67,13 @@ export const actions = { } }); - const io = getIO(); - io.to(userId).emit('new-question', questionChat); + // Emit the question event + try { + const io = getIO(); + io.to(userId).emit('new-question', questionChat); + } catch (socketError) { + console.error('Socket error (non-fatal):', socketError); + } const response = await fetch('/api/ask', { method: 'POST', @@ -108,7 +113,13 @@ export const actions = { } }); - io.to(userId).emit('new-answer', answerChat); + // Emit the answer event + try { + const io = getIO(); + io.to(userId).emit('new-answer', answerChat); + } catch (socketError) { + console.error('Socket error for answer (non-fatal):', socketError); + } return { success: true, @@ -123,4 +134,4 @@ export const actions = { }); } } -} satisfies Actions; +}; diff --git a/vite.config.ts b/vite.config.ts index 76420d1..0df938d 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,35 +1,46 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; import type { ViteDevServer } from 'vite'; import { Server } from 'socket.io'; import type { ServerOptions } from 'socket.io'; +let io: Server | null = null; + const webSocketServer = { name: 'webSocketServer', configureServer(server: ViteDevServer) { if (!server.httpServer) return; - const io = new Server(server.httpServer, { - cors: { - origin: '*', - methods: ['GET', 'POST'], - credentials: true - } - } as Partial); - - io.on('connection', (socket) => { - console.log('Client connected', socket.id); - - const userId = socket.handshake.auth.userId; - if (userId) { - socket.join(userId); - console.log(`User ${userId} joined their room`); - } - - socket.on('disconnect', () => { - console.log('Client disconnected', socket.id); + if (!io) { + console.log('Initializing Socket.IO server in Vite...'); + io = new Server(server.httpServer, { + cors: { + origin: '*', + methods: ['GET', 'POST'], + credentials: true + } + } as Partial); + + // Store the io instance on the global object for access from other files + (global as any).__socketio = io; + + io.on('connection', (socket) => { + console.log('Client connected', socket.id); + + const userId = socket.handshake.auth.userId; + if (userId) { + socket.join(userId); + console.log(`User ${userId} joined their room`); + } + + socket.on('disconnect', () => { + console.log('Client disconnected', socket.id); + }); }); - }); + + console.log('Socket.IO server initialized successfully in Vite'); + } } };