diff --git a/bun.lockb b/bun.lockb index 2ab113e..231a46a 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/package.json b/package.json index 5f8f316..c2c1f64 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,8 @@ }, "dependencies": { "drizzle-orm": "^0.33.0", - "postgres": "^3.4.4" + "postgres": "^3.4.4", + "socket.io": "^4.8.1", + "socket.io-client": "^4.8.1" } } diff --git a/prodServer.ts b/prodServer.ts new file mode 100644 index 0000000..ae4d583 --- /dev/null +++ b/prodServer.ts @@ -0,0 +1,11 @@ +import * as path from 'path'; +import * as url from 'url'; +import { createWSSGlobalInstance, onHttpServerUpgrade } from './src/lib/server/webSocketUtils'; + +const __filename = url.fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +createWSSGlobalInstance(); + +const { server } = await import(path.resolve(__dirname, './build/index.js')); +server.server.on('upgrade', onHttpServerUpgrade); diff --git a/src/lib/hooks.server.ts b/src/lib/hooks.server.ts new file mode 100644 index 0000000..718d1d0 --- /dev/null +++ b/src/lib/hooks.server.ts @@ -0,0 +1,29 @@ +import { building } from '$app/environment'; +import type { Handle } from '@sveltejs/kit'; +import { Server as SocketIOServer } from 'socket.io'; + +let io: SocketIOServer | undefined; + +const startupSocketIOServer = (httpServer: never) => { + if (io) return; + console.log('[socket.io:kit] setup'); + io = new SocketIOServer(httpServer); + io.on('connection', (socket) => { + console.log(`[socket.io:kit] client connected (${socket.id})`); + socket.emit('message', `Hello from SvelteKit ${new Date().toLocaleString()} (${socket.id})`); + + socket.on('disconnect', () => { + console.log(`[socket.io:kit] client disconnected (${socket.id})`); + }); + }); +}; + +export const handle = (async ({ event, resolve }) => { + if (!building) { + startupSocketIOServer(event.locals.httpServer); + event.locals.io = io; + } + return resolve(event, { + filterSerializedResponseHeaders: (name) => name === 'content-type' + }); +}) satisfies Handle; \ No newline at end of file diff --git a/src/lib/server/webSocketUtils.ts b/src/lib/server/webSocketUtils.ts new file mode 100644 index 0000000..c187bdf --- /dev/null +++ b/src/lib/server/webSocketUtils.ts @@ -0,0 +1,51 @@ +import { parse } from 'url'; +import { WebSocketServer } from 'ws'; +import { nanoid } from 'nanoid'; +import type { Server, WebSocket as WebSocketBase } from 'ws'; +import type { IncomingMessage } from 'http'; +import type { Duplex } from 'stream'; + +export const GlobalThisWSS = Symbol.for('sveltekit.wss'); + +export interface ExtendedWebSocket extends WebSocketBase { + socketId: string; + // userId: string; +}; + +// You can define server-wide functions or class instances here +// export interface ExtendedServer extends Server {}; + +export type ExtendedWebSocketServer = Server; + +export type ExtendedGlobal = typeof globalThis & { + [GlobalThisWSS]: ExtendedWebSocketServer; +}; + +export const onHttpServerUpgrade = (req: IncomingMessage, sock: Duplex, head: Buffer) => { + const pathname = req.url ? parse(req.url).pathname : null; + if (pathname !== '/websocket') return; + + const wss = (globalThis as ExtendedGlobal)[GlobalThisWSS]; + + wss.handleUpgrade(req, sock, head, (ws) => { + console.log('[handleUpgrade] creating new connecttion'); + wss.emit('connection', ws, req); + }); +}; + +export const createWSSGlobalInstance = () => { + const wss = new WebSocketServer({ noServer: true }) as ExtendedWebSocketServer; + + (globalThis as ExtendedGlobal)[GlobalThisWSS] = wss; + + wss.on('connection', (ws) => { + ws.socketId = nanoid(); + console.log(`[wss:global] client connected (${ws.socketId})`); + + ws.on('close', () => { + console.log(`[wss:global] client disconnected (${ws.socketId})`); + }); + }); + + return wss; +}; diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index cc88df0..b46ad5c 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,2 +1,44 @@ -

Welcome to SvelteKit

-

Visit svelte.dev/docs/kit to read the documentation

+ + +
+

# SvelteKit with Socket.IO Integration

+ + + +
    + {#each log as event} +
  • {event}
  • + {/each} +
+
\ No newline at end of file diff --git a/tailwind.config.ts b/tailwind.config.ts index aa4bc77..140eb5f 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -2,10 +2,5 @@ import type { Config } from 'tailwindcss'; export default { content: ['./src/**/*.{html,js,svelte,ts}'], - - theme: { - extend: {} - }, - plugins: [] } satisfies Config; diff --git a/vite.config.ts b/vite.config.ts index bbf8c7d..d40ab7f 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -1,6 +1,33 @@ import { sveltekit } from '@sveltejs/kit/vite'; import { defineConfig } from 'vite'; +import { Server as SocketIOServer } from 'socket.io'; + +function setupSocketIOServer(httpServer: never) { + if (!httpServer) { + throw new Error('HTTP server is not available'); + } + const io = new SocketIOServer(httpServer); + io.on('connection', (socket) => { + console.log(`[socket.io] client connected (${socket.id})`); + io.emit('message', `Hello from SvelteKit ${new Date().toLocaleString()} (${socket.id})`); + + socket.on('disconnect', () => { + console.log(`[socket.io] client disconnected (${socket.id})`); + }); + }); +} export default defineConfig({ - plugins: [sveltekit()] -}); + plugins: [ + sveltekit(), + { + name: 'integratedSocketIOServer', + configureServer(server) { + setupSocketIOServer(server.httpServer); + }, + configurePreviewServer(server) { + setupSocketIOServer(server.httpServer); + } + }, + ] +}); \ No newline at end of file