From d79ead0d700eed7d621da7249a2db9e1580cc7d8 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Sun, 1 Feb 2026 09:54:11 +0200 Subject: [PATCH 1/4] fix: use server upgrade event to access head buffer (#12) Change API from middleware pattern to setup function to fix WebSocket connections timing out on HTTPS servers. The middleware pattern couldn't access the upgrade head buffer, causing Node.js to not properly recognize WebSocket upgrades. BREAKING CHANGE: API changed from `app.use(tinyws())` to `tinyws(app, server)` - Add paths option to restrict WebSocket handling to specific routes - Export TinyWSOptions interface for TypeScript users - Update examples and documentation Co-Authored-By: Claude Opus 4.5 --- README.md | 19 ++++++-- bun.lockb | Bin 129853 -> 132597 bytes examples/basic.ts | 5 +- examples/express.ts | 5 +- examples/polka.ts | 5 +- src/index.ts | 63 ++++++++++++++++++++------ tests/index.test.ts | 108 ++++++++++++++++++++++++++++++++++++++------ 7 files changed, 164 insertions(+), 41 deletions(-) diff --git a/README.md b/README.md index 5c9bbbb..c5af7f3 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,7 @@ pnpm i ws tinyws import { App, Request } from '@tinyhttp/app' import { tinyws, TinyWSRequest } from 'tinyws' -const app = new App() - -app.use(tinyws()) +const app = new App() app.use('/ws', async (req, res) => { if (req.ws) { @@ -55,7 +53,20 @@ app.use('/ws', async (req, res) => { } }) -app.listen(3000) +const server = app.listen(3000) +tinyws(app, server) +``` + +### Restricting WebSocket to specific paths + +You can restrict WebSocket handling to specific paths using the `paths` option: + +```ts +// Single path +tinyws(app, server, { paths: '/ws' }) + +// Multiple paths +tinyws(app, server, { paths: ['/ws', '/socket'] }) ``` See [examples](examples) for express and polka integration. diff --git a/bun.lockb b/bun.lockb index 17c4525d1404858edf3b0ad19cb07bce34d287f4..e3500f64ceb80c27cba417d6e0fd3efe6c10871f 100755 GIT binary patch delta 5512 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 diff --git a/examples/basic.ts b/examples/basic.ts index 96918a5..9e3f6ae 100644 --- a/examples/basic.ts +++ b/examples/basic.ts @@ -4,8 +4,6 @@ import { type TinyWSRequest, tinyws } from '../src/index' const app = new App() -app.use(tinyws()) - app.use('/hmr', async (req, res) => { if (req.ws) { const ws = await req.ws() @@ -15,4 +13,5 @@ app.use('/hmr', async (req, res) => { res.send('Hello from HTTP!') }) -app.listen(3000) +const server = app.listen(3000) +tinyws(app, server) diff --git a/examples/express.ts b/examples/express.ts index 9d81095..dadc6e4 100644 --- a/examples/express.ts +++ b/examples/express.ts @@ -12,7 +12,7 @@ declare global { const app = express() -app.use('/hmr', tinyws(), async (req, res) => { +app.use('/hmr', async (req, res) => { if (req.ws) { const ws = await req.ws() @@ -21,4 +21,5 @@ app.use('/hmr', tinyws(), async (req, res) => { res.send('Hello from HTTP!') }) -app.listen(3000) +const server = app.listen(3000) +tinyws({ handler: app }, server) diff --git a/examples/polka.ts b/examples/polka.ts index eaa2cd0..8430573 100644 --- a/examples/polka.ts +++ b/examples/polka.ts @@ -4,8 +4,6 @@ import { type TinyWSRequest, tinyws } from '../src/index' const app = polka() -app.use(tinyws()) - app.use('/hmr', async (req, res) => { if (req.ws) { const ws = await req.ws() @@ -15,4 +13,5 @@ app.use('/hmr', async (req, res) => { res.end('Hello from HTTP!') }) -app.listen(3000) +const server = app.listen(3000) +tinyws({ handler: app.handler }, server) diff --git a/src/index.ts b/src/index.ts index dd11d68..2e70a53 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,30 +1,65 @@ -import type * as http from 'node:http' +import * as http from 'node:http' +import type { Socket } from 'node:net' import type { ServerOptions, WebSocket } from 'ws' -import { WebSocketServer as Server } from 'ws' +import { WebSocketServer } from 'ws' export interface TinyWSRequest extends http.IncomingMessage { ws: () => Promise } +export interface TinyWSOptions extends ServerOptions { + paths?: string | string[] +} + /** * tinyws - adds `req.ws` method that resolves when websocket request appears - * @param wsOptions + * @param app - The application instance with a handler function + * @param server - The HTTP server instance + * @param options - Optional WebSocket server options and paths to restrict WebSocket handling + * @param wss - Optional existing WebSocketServer instance + * @returns The WebSocketServer instance */ -export const tinyws = - (wsOptions?: ServerOptions, wss: Server = new Server({ ...wsOptions, noServer: true })) => - async (req: TinyWSRequest, _: unknown, next: () => void | Promise) => { - const upgradeHeader = (req.headers.upgrade || '').split(',').map((s) => s.trim()) - - // When upgrade header contains "websocket" it's index is 0 - if (upgradeHeader.indexOf('websocket') === 0) { - req.ws = () => +export const tinyws = ( + app: { handler: (req: any, res: any) => void }, + server: http.Server, + options?: TinyWSOptions, + wss: WebSocketServer = new WebSocketServer({ ...options, noServer: true }) +) => { + const { paths, ...wsOptions } = options || {} + const allowedPaths = paths ? (Array.isArray(paths) ? paths : [paths]) : null + + const upgradeHandler = (request: http.IncomingMessage, socket: Socket, head: Buffer) => { + const response = new http.ServerResponse(request) + response.assignSocket(socket) + + // Copy the head buffer to avoid keeping the entire slab buffer alive + const copyOfHead = Buffer.alloc(head.length) + head.copy(copyOfHead) + + response.on('finish', () => { + if (response.socket !== null) { + response.socket.destroy() + } + }) + + const upgradeHeader = (request.headers.upgrade || '').split(',').map((s) => s.trim()) + const requestPath = request.url?.split('?')[0] || '/' + + const pathMatches = allowedPaths === null || allowedPaths.some((p) => requestPath.startsWith(p)) + + if (upgradeHeader.indexOf('websocket') === 0 && pathMatches) { + ;(request as TinyWSRequest).ws = () => new Promise((resolve) => { - wss.handleUpgrade(req, req.socket, Buffer.alloc(0), (ws) => { - wss.emit('connection', ws, req) + wss.handleUpgrade(request, socket, copyOfHead, (ws) => { + wss.emit('connection', ws, request) resolve(ws) }) }) } - await next() + app.handler(request, response) } + + server.on('upgrade', upgradeHandler) + return wss +} diff --git a/tests/index.test.ts b/tests/index.test.ts index 44ae760..6a13e0b 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -2,30 +2,30 @@ import { it } from 'bun:test' import * as assert from 'node:assert' import { once } from 'node:events' import { App, type Request } from '@tinyhttp/app' -import { type Server, type ServerOptions, WebSocketServer } from 'ws' +import { type Server, type ServerOptions, WebSocket, WebSocketServer } from 'ws' import { type TinyWSRequest, tinyws } from '../src/index' const s = (handler: (req: TinyWSRequest) => void, opts?: ServerOptions, inst?: Server) => { const app = new App() - app.use(tinyws(opts, inst)) app.use('/ws', async (req) => { if (typeof req.ws !== 'undefined') { handler(req) } }) - return app + return { app, opts, inst } } it('should respond with a message', async () => { - const app = s(async (req) => { + const { app } = s(async (req) => { const ws = await req?.ws() return ws.send('hello there') }) const server = app.listen(4443, undefined, 'localhost') + tinyws(app, server) const ws = new WebSocket('ws://localhost:4443/ws') @@ -37,15 +37,16 @@ it('should respond with a message', async () => { }) it('should resolve a `.ws` property', async () => { - const app = s(async (req) => { + const { app } = s(async (req) => { const ws = await req.ws() - assert.ok(ws instanceof WebSocket) + assert.ok(typeof ws.send === 'function') return ws.send('hello there') }) const server = app.listen(4444, undefined, 'localhost') + tinyws(app, server) const ws = new WebSocket('ws://localhost:4444/ws') @@ -56,11 +57,11 @@ it('should resolve a `.ws` property', async () => { }) it('should pass ws options', async () => { - const app = s( + const { app, opts } = s( async (req) => { const ws = await req.ws() - assert.ok(ws instanceof WebSocket) + assert.ok(typeof ws.send === 'function') ws.on('error', (err) => { assert.match(err.message, /Max payload size exceeded/) @@ -74,6 +75,7 @@ it('should pass ws options', async () => { ) const server = app.listen(4445, undefined, 'localhost') + tinyws(app, server, opts) const ws = new WebSocket('ws://localhost:4445/ws') @@ -86,15 +88,16 @@ it('should pass ws options', async () => { }) it('should accept messages', async () => { - const app = s(async (req) => { + const { app } = s(async (req) => { const ws = await req.ws() - assert.ok(ws instanceof WebSocket) + assert.ok(typeof ws.send === 'function') return ws.on('message', (msg) => ws.send(`You sent: ${msg}`)) }) const server = app.listen(4446, undefined, 'localhost') + tinyws(app, server) const ws = new WebSocket('ws://localhost:4446/ws') @@ -114,14 +117,14 @@ it('supports passing a server instance', async () => { const wss = new WebSocketServer({ noServer: true }) wss.on('connection', (socket) => { - assert.ok(socket instanceof WebSocket) + assert.ok(typeof socket.send === 'function') }) - const app = s( + const { app, inst } = s( async (req) => { const ws = await req.ws() - assert.ok(ws instanceof WebSocket) + assert.ok(typeof ws.send === 'function') return ws.send('hello there') }, @@ -130,6 +133,7 @@ it('supports passing a server instance', async () => { ) const server = app.listen(4447, undefined, 'localhost') + tinyws(app, server, {}, inst) const ws = new WebSocket('ws://localhost:4447/ws') @@ -139,6 +143,80 @@ it('supports passing a server instance', async () => { ws.close() }) -it('returns a middleware function', () => { - assert.ok(typeof tinyws() === 'function') +it('returns a WebSocketServer instance', () => { + const app = new App() + const server = app.listen(4448, undefined, 'localhost') + const wss = tinyws(app, server) + assert.ok(wss instanceof WebSocketServer) + server.close() +}) + +it('restricts WebSocket to specified paths', async () => { + const app = new App() + + app.use('/ws', async (req, res) => { + if (req.ws) { + const ws = await req.ws() + return ws.send('allowed') + } + res.send('no ws') + }) + + app.use('/other', async (req, res) => { + if (req.ws) { + const ws = await req.ws() + return ws.send('should not happen') + } + res.send('no ws on other') + }) + + const server = app.listen(4449, undefined, 'localhost') + tinyws(app, server, { paths: '/ws' }) + + // Connection to /ws should work + const ws1 = new WebSocket('ws://localhost:4449/ws') + const [data] = await once(ws1, 'message') + assert.equal(data.toString(), 'allowed') + ws1.close() + + // Connection to /other should not have req.ws + const ws2 = new WebSocket('ws://localhost:4449/other') + const [err] = await once(ws2, 'error') + assert.ok(err) + ws2.close() + + server.close() +}) + +it('supports multiple paths', async () => { + const app = new App() + + app.use('/ws1', async (req) => { + if (req.ws) { + const ws = await req.ws() + return ws.send('ws1') + } + }) + + app.use('/ws2', async (req) => { + if (req.ws) { + const ws = await req.ws() + return ws.send('ws2') + } + }) + + const server = app.listen(4450, undefined, 'localhost') + tinyws(app, server, { paths: ['/ws1', '/ws2'] }) + + const ws1 = new WebSocket('ws://localhost:4450/ws1') + const [data1] = await once(ws1, 'message') + assert.equal(data1.toString(), 'ws1') + ws1.close() + + const ws2 = new WebSocket('ws://localhost:4450/ws2') + const [data2] = await once(ws2, 'message') + assert.equal(data2.toString(), 'ws2') + ws2.close() + + server.close() }) From c23202e65ec825cbca32a95ba0b19c7270e5633d Mon Sep 17 00:00:00 2001 From: v1rtl Date: Sun, 1 Feb 2026 09:59:04 +0200 Subject: [PATCH 2/4] chore: fix CI and add release workflow - Fix YAML indentation bug in main.yml - Run CI on all pushes/PRs, not just master - Add lint step to CI - Update package.json scripts to use biome and bun test - Add release.yml for npm publishing on git tags Co-Authored-By: Claude Opus 4.5 --- .github/workflows/main.yml | 18 ++++-------------- .github/workflows/release.yml | 32 ++++++++++++++++++++++++++++++++ biome.json | 7 +------ package.json | 26 +++++++------------------- 4 files changed, 44 insertions(+), 39 deletions(-) create mode 100644 .github/workflows/release.yml diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1f80525..23e1105 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -1,32 +1,22 @@ -# This is a basic workflow to help you get started with Actions - name: CI -# Controls when the action will run. Triggers the workflow on push or pull request -# events but only for the master branch on: push: - branches: [master] pull_request: - branches: [master] -# A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: - # This workflow contains a single job called "test" test: - # The type of runner that the job will run on runs-on: ubuntu-latest - - # Steps represent a sequence of tasks that will be executed as part of the job steps: - # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it - uses: actions/checkout@v4 - uses: oven-sh/setup-bun@v2 - with: - bun-version: latest + with: + bun-version: latest - run: bun i + - run: bun run lint - run: bun test --coverage - name: Coveralls + if: github.ref == 'refs/heads/master' uses: coverallsapp/github-action@master with: github-token: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..5da956e --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,32 @@ +name: Release + +on: + push: + tags: + - 'v*' + +permissions: + id-token: write + contents: read + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: '22' + registry-url: 'https://registry.npmjs.org' + + - uses: oven-sh/setup-bun@v2 + with: + bun-version: latest + + - run: bun i + - run: bun run build + + - run: npm publish --provenance --access public + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} diff --git a/biome.json b/biome.json index d9c5392..9c1526f 100644 --- a/biome.json +++ b/biome.json @@ -1,12 +1,7 @@ { "$schema": "https://biomejs.dev/schemas/1.8.2/schema.json", "files": { - "ignore": [ - "node_modules", - "dist", - "coverage", - ".pnpm-store" - ] + "ignore": ["node_modules", "dist", "coverage", ".pnpm-store"] }, "formatter": { "enabled": true, diff --git a/package.json b/package.json index eb77d45..8bbc9cb 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,7 @@ "name": "tinyws", "version": "0.1.0", "description": "Tiny WebSocket middleware for Node.js based on ws.", - "files": [ - "dist" - ], + "files": ["dist"], "engines": { "node": ">=12.4" }, @@ -13,27 +11,17 @@ "types": "dist/index.d.ts", "scripts": { "build": "tsc -p tsconfig.build.json", - "test": "uvu -r tsm tests", - "test:coverage": "c8 --include=src pnpm test", - "test:report": "c8 report --reporter=text-lcov > coverage.lcov", - "lint": "eslint \"./**/*.ts\"", - "format": "prettier --write \"./**/*.ts\"", - "prepublishOnly": "npm run test && npm run lint && npm run build" + "test": "bun test", + "test:coverage": "bun test --coverage", + "lint": "biome check .", + "format": "biome format --write .", + "prepublishOnly": "bun test && bun run lint && bun run build" }, "repository": { "type": "git", "url": "git+https://github.com/talentlessguy/tinyws.git" }, - "keywords": [ - "ws", - "express", - "tinyhttp", - "websocket", - "middleware", - "polka", - "http", - "server" - ], + "keywords": ["ws", "express", "tinyhttp", "websocket", "middleware", "polka", "http", "server"], "author": "v1rtl (https://v1rtl.site)", "license": "MIT", "bugs": { From 85d29b879415e83c7a8ebff6c0a59cde3a399d26 Mon Sep 17 00:00:00 2001 From: v1rtl Date: Sun, 1 Feb 2026 09:59:52 +0200 Subject: [PATCH 3/4] chore: remove packageManager field Co-Authored-By: Claude Opus 4.5 --- package.json | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/package.json b/package.json index 8bbc9cb..408d38f 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,7 @@ "build": "tsc -p tsconfig.build.json", "test": "bun test", "test:coverage": "bun test --coverage", - "lint": "biome check .", - "format": "biome format --write .", + "lint": "biome check --write .", "prepublishOnly": "bun test && bun run lint && bun run build" }, "repository": { @@ -46,6 +45,5 @@ }, "peerDependencies": { "ws": ">=8" - }, - "packageManager": "pnpm@9.3.0+sha256.e1f9e8d1a16607a46dd3c158b5f7a7dc7945501d1c6222d454d63d033d1d918f" + } } From 6bee045cc7535469a8de663c7aae25f623597abe Mon Sep 17 00:00:00 2001 From: v1rtl Date: Sun, 1 Feb 2026 10:02:52 +0200 Subject: [PATCH 4/4] chore: bump dependencies Co-Authored-By: Claude Opus 4.5 --- bun.lockb | Bin 132597 -> 134817 bytes package.json | 22 +++++++++++----------- tests/index.test.ts | 7 ++++--- 3 files changed, 15 insertions(+), 14 deletions(-) diff --git a/bun.lockb b/bun.lockb index e3500f64ceb80c27cba417d6e0fd3efe6c10871f..ed7cad77adf3046d04eda0ac6ab9d511e5afe12a 100755 GIT binary patch delta 29944 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 delta 28583 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 hZU0?RR%m6Uu2M>BDy%NAwkjpC(}U&0M=8" diff --git a/tests/index.test.ts b/tests/index.test.ts index 6a13e0b..f4c1c38 100644 --- a/tests/index.test.ts +++ b/tests/index.test.ts @@ -181,9 +181,10 @@ it('restricts WebSocket to specified paths', async () => { // Connection to /other should not have req.ws const ws2 = new WebSocket('ws://localhost:4449/other') - const [err] = await once(ws2, 'error') - assert.ok(err) - ws2.close() + await new Promise((resolve) => { + ws2.on('error', () => resolve()) + ws2.on('close', () => resolve()) + }) server.close() })