feat: Setup websockets with socket.io

This commit is contained in:
April Hall 2025-01-04 15:49:52 -05:00
parent 861418db70
commit 618afe9840
Signed by: arithefirst
GPG Key ID: 4508A15C4DB91C5B
8 changed files with 167 additions and 10 deletions

BIN
bun.lockb

Binary file not shown.

View File

@ -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"
}
}

11
prodServer.ts Normal file
View File

@ -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);

29
src/lib/hooks.server.ts Normal file
View File

@ -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;

View File

@ -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<ExtendedWebSocket> {};
export type ExtendedWebSocketServer = Server<ExtendedWebSocket>;
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;
};

View File

@ -1,2 +1,44 @@
<h1>Welcome to SvelteKit</h1>
<p>Visit <a href="https://svelte.dev/docs/kit">svelte.dev/docs/kit</a> to read the documentation</p>
<script lang="ts">
import { io } from 'socket.io-client';
let socket: ReturnType<typeof io> | null = null;
let log: string[] = [];
function logEvent(str: string) {
log = [...log, str];
}
function establishSocketIOConnection() {
if (socket) return;
socket = io();
socket.on('connect', () => {
console.log('[ws] connection open');
logEvent('[ws] connection open');
});
socket.on('disconnect', () => {
console.log('[ws] connection closed');
logEvent('[ws] connection closed');
});
socket.on('message', (data: string) => {
console.log('[ws] message received', data);
logEvent(`[ws] message received: ${data}`);
});
}
</script>
<main>
<h1 class="text-lg"># SvelteKit with Socket.IO Integration</h1>
<button class="button" on:click={() => establishSocketIOConnection()}>
Establish Socket.IO connection
</button>
<ul>
{#each log as event}
<li>{event}</li>
{/each}
</ul>
</main>

View File

@ -2,10 +2,5 @@ import type { Config } from 'tailwindcss';
export default {
content: ['./src/**/*.{html,js,svelte,ts}'],
theme: {
extend: {}
},
plugins: []
} satisfies Config;

View File

@ -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);
}
},
]
});