From 618afe9840f64a652a3160bf3470b7e4f2543ab3 Mon Sep 17 00:00:00 2001 From: April Hall Date: Sat, 4 Jan 2025 15:49:52 -0500 Subject: [PATCH] feat: Setup websockets with socket.io --- bun.lockb | Bin 139852 -> 148781 bytes package.json | 4 ++- prodServer.ts | 11 +++++++ src/lib/hooks.server.ts | 29 ++++++++++++++++++ src/lib/server/webSocketUtils.ts | 51 +++++++++++++++++++++++++++++++ src/routes/+page.svelte | 46 ++++++++++++++++++++++++++-- tailwind.config.ts | 5 --- vite.config.ts | 31 +++++++++++++++++-- 8 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 prodServer.ts create mode 100644 src/lib/hooks.server.ts create mode 100644 src/lib/server/webSocketUtils.ts diff --git a/bun.lockb b/bun.lockb index 2ab113e730ec2d2e85e8b7a4d40c136aad956b1e..231a46a52f34a2f570e057d473c39c1ed71e5ec7 100755 GIT binary patch delta 30964 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 delta 25321 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 { + 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