-
Notifications
You must be signed in to change notification settings - Fork 1
Add secure-ws library #22
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
7213cd9
fe359a5
3d31022
64a4671
62657b4
6603205
42c7e43
1493375
1c36935
95ea7e8
36477e8
6ff441e
421f059
0958ebb
4a5939f
024f82a
f0f16c7
ee8cf47
f30b8c3
7bf514d
c32a879
644d138
568f2a9
37d83d9
5856992
a5b60c5
d26c68f
c8d6dbb
ec03b63
e84a201
5f60b33
b7e47d4
cf0c8fa
f36634f
c306e15
b083585
48ee8a7
ecb0ec7
9016a12
348cf65
05eeb34
3537477
6d02b62
e13aebc
398e7d6
2ce2dd2
5d003c0
fcb9da3
f03b152
5b2782b
c3c8494
01e45df
daec267
e1a5169
24fb513
3460514
f1adbc0
b1b8c8d
cc88a57
f0288c7
9f75f61
22da40a
9e7b856
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,91 @@ | ||||||||||||||||||||
| name: Release and Publish secure-ws | ||||||||||||||||||||
|
|
||||||||||||||||||||
| on: | ||||||||||||||||||||
| push: | ||||||||||||||||||||
| paths: | ||||||||||||||||||||
| - "secure-ws/**" | ||||||||||||||||||||
| - .github/workflows/npm-publish-secure-ws.yml | ||||||||||||||||||||
|
|
||||||||||||||||||||
| permissions: | ||||||||||||||||||||
| contents: write | ||||||||||||||||||||
| id-token: write | ||||||||||||||||||||
|
|
||||||||||||||||||||
| env: | ||||||||||||||||||||
|
rickygarg marked this conversation as resolved.
|
||||||||||||||||||||
| BRANCH_TAG: "${{ github.ref_name == 'main' && 'latest' || github.ref_name }}" | ||||||||||||||||||||
| BRANCH: ${{ github.ref_name }} | ||||||||||||||||||||
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | ||||||||||||||||||||
|
|
||||||||||||||||||||
| jobs: | ||||||||||||||||||||
| publish_and_release: | ||||||||||||||||||||
| name: Publish secure-ws package | ||||||||||||||||||||
| environment: "${{ github.ref_name == 'main' && 'Prod' || 'Dev' }}" | ||||||||||||||||||||
| runs-on: ubuntu-latest | ||||||||||||||||||||
|
rickygarg marked this conversation as resolved.
|
||||||||||||||||||||
| defaults: | ||||||||||||||||||||
| run: | ||||||||||||||||||||
| working-directory: "secure-ws" | ||||||||||||||||||||
| steps: | ||||||||||||||||||||
| - name: Generate token | ||||||||||||||||||||
| if: ${{ github.ref_name == 'main'}} | ||||||||||||||||||||
| id: generate_token | ||||||||||||||||||||
| uses: tibdex/github-app-token@v1 | ||||||||||||||||||||
| with: | ||||||||||||||||||||
| app_id: ${{ vars.FUNDABOT_APP_ID }} | ||||||||||||||||||||
| private_key: ${{ secrets.FUNDABOT_PRIVATE_KEY }} | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - uses: actions/setup-node@v6 | ||||||||||||||||||||
| with: | ||||||||||||||||||||
| node-version: 24 | ||||||||||||||||||||
| registry-url: https://registry.npmjs.org/ | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - uses: actions/checkout@v6 | ||||||||||||||||||||
| with: | ||||||||||||||||||||
| token: ${{ github.ref_name == 'main' && steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }} | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Install dependencies | ||||||||||||||||||||
| run: npm ci | ||||||||||||||||||||
|
|
||||||||||||||||||||
|
Comment on lines
+27
to
+46
|
||||||||||||||||||||
| - name: Build | ||||||||||||||||||||
| run: npm run build | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Version bump | ||||||||||||||||||||
| id: version | ||||||||||||||||||||
| uses: phips28/gh-action-bump-version@v9.1.0 | ||||||||||||||||||||
| with: | ||||||||||||||||||||
| major-wording: ${{ env.BRANCH == 'main' && '[bump major]' || '[bump major --force]' }} | ||||||||||||||||||||
| minor-wording: ${{ env.BRANCH == 'main' && '[bump minor]' || '[bump minor --force]' }} | ||||||||||||||||||||
| patch-wording: ${{ null }} | ||||||||||||||||||||
| rc-wording: ${{ null }} | ||||||||||||||||||||
| default: "${{ env.BRANCH == 'main' && 'patch' || 'prerelease' }}" | ||||||||||||||||||||
| PACKAGEJSON_DIR: "secure-ws" | ||||||||||||||||||||
| preid: "${{ env.BRANCH }}" | ||||||||||||||||||||
| skip-tag: "true" | ||||||||||||||||||||
|
Comment on lines
+58
to
+61
|
||||||||||||||||||||
| skip-push: "true" | ||||||||||||||||||||
| skip-commit: "true" | ||||||||||||||||||||
| bump-policy: "ignore" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Commit changes | ||||||||||||||||||||
| env: | ||||||||||||||||||||
| VERSION: ${{ steps.version.outputs.newTag }} | ||||||||||||||||||||
| run: | | ||||||||||||||||||||
| git config user.email "fundabot@fundwave.com" | ||||||||||||||||||||
| git config user.name "fundabot" | ||||||||||||||||||||
| git commit -a -m "CI: bumps secure-ws to $VERSION" -m "[skip ci]" | ||||||||||||||||||||
|
|
||||||||||||||||||||
| - name: Publish package to npm | ||||||||||||||||||||
|
||||||||||||||||||||
| - name: Publish package to npm | |
| - name: Publish package to npm | |
| if: ${{ github.ref_name == 'main'}} | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} |
Copilot
AI
Apr 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
npm publish requires npm authentication, but this workflow doesn't set NODE_AUTH_TOKEN / .npmrc (unlike the existing npm-publish.yml workflow). Add the npm token setup (or OIDC-based auth if that’s what you intend) so publishing doesn’t fail at runtime.
| - name: Publish package to npm | |
| - name: Publish package to npm | |
| env: | |
| NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} |
Copilot
AI
Apr 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On main you generate a GitHub App token for checkout, but the push step always uses ${{ env.GITHUB_TOKEN }} (set to secrets.GITHUB_TOKEN). If main is protected, this can prevent pushing the version-bump commit. Use steps.generate_token.outputs.token for the push step on main (or set env.GITHUB_TOKEN conditionally).
| github_token: ${{ env.GITHUB_TOKEN }} | |
| github_token: ${{ github.ref_name == 'main' && steps.generate_token.outputs.token || secrets.GITHUB_TOKEN }} |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,2 @@ | ||
| **/dist/** | ||
| **/node_modules/** |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -1,19 +1,23 @@ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| threshold: medium | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| fileignoreconfig: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: aws-credentials-utils/store-credentials.sh | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 784aec6e80314be796af73887ba630fbaf0e116cc4d11bd5cba787a20f9c4bbb | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: jwks-slim/README.md | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: a1954df51e49fc6a09d7fe04bac198fe60a4fb39b2569180475a749a72df472d | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: jwks-slim/package-lock.json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: d014d548f9f2997cbe1d7c4f7ec312f8c634e88788bc453ccc606ea70e081a77 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: jwks-slim/tests/cache.test.js | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 716f25000789d7ca18adfe07f968bae6eb2fbfa1c3d05e9e92897b9597ac5f17 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: jwks-slim/tests/mock.js | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 0d2fd2ec4847acda384c398e04e8aab9a615a8599b0a06d685e7fb100e71537c | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: jwks-slim/tests/keys.js | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 207c0d07dea4c069883822b2b268d3b34c8b18bdfd645b8aa3b2be20f280c270 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: jwks-slim/tests/index.test.js | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 992a5c1f6c254fd344ae52955aa0c1c22a59e0472fe836b0450ff499255ef6f8 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: aws-ssm/package-lock.json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 4cf91061e42ed9b1aafb17bdf68c9b4b24a7f86b191133ab4095fa635f8f01a0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| version: "" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: aws-credentials-utils/store-credentials.sh | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 784aec6e80314be796af73887ba630fbaf0e116cc4d11bd5cba787a20f9c4bbb | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: aws-ssm/package-lock.json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 4cf91061e42ed9b1aafb17bdf68c9b4b24a7f86b191133ab4095fa635f8f01a0 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: jwks-slim/README.md | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: a1954df51e49fc6a09d7fe04bac198fe60a4fb39b2569180475a749a72df472d | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: jwks-slim/package-lock.json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: d014d548f9f2997cbe1d7c4f7ec312f8c634e88788bc453ccc606ea70e081a77 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: jwks-slim/tests/cache.test.js | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 716f25000789d7ca18adfe07f968bae6eb2fbfa1c3d05e9e92897b9597ac5f17 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: jwks-slim/tests/index.test.js | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 992a5c1f6c254fd344ae52955aa0c1c22a59e0472fe836b0450ff499255ef6f8 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: jwks-slim/tests/keys.js | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 207c0d07dea4c069883822b2b268d3b34c8b18bdfd645b8aa3b2be20f280c270 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: jwks-slim/tests/mock.js | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 0d2fd2ec4847acda384c398e04e8aab9a615a8599b0a06d685e7fb100e71537c | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: secure-ws/mocks/express-response.ts | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 7774e72616894119c419f7ad86a3da579036b0391fd208766bcdcb96401b8d2c | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: websocket-provider/package-lock.json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| checksum: 605a2d92b91f08ee577d3b1e7ed38d27eef43573a044608c22466f05ab6eaff7 | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+2
to
+21
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| - filename: aws-credentials-utils/store-credentials.sh | |
| checksum: 784aec6e80314be796af73887ba630fbaf0e116cc4d11bd5cba787a20f9c4bbb | |
| - filename: aws-ssm/package-lock.json | |
| checksum: 4cf91061e42ed9b1aafb17bdf68c9b4b24a7f86b191133ab4095fa635f8f01a0 | |
| - filename: jwks-slim/README.md | |
| checksum: a1954df51e49fc6a09d7fe04bac198fe60a4fb39b2569180475a749a72df472d | |
| - filename: jwks-slim/package-lock.json | |
| checksum: d014d548f9f2997cbe1d7c4f7ec312f8c634e88788bc453ccc606ea70e081a77 | |
| - filename: jwks-slim/tests/cache.test.js | |
| checksum: 716f25000789d7ca18adfe07f968bae6eb2fbfa1c3d05e9e92897b9597ac5f17 | |
| - filename: jwks-slim/tests/index.test.js | |
| checksum: 992a5c1f6c254fd344ae52955aa0c1c22a59e0472fe836b0450ff499255ef6f8 | |
| - filename: jwks-slim/tests/keys.js | |
| checksum: 207c0d07dea4c069883822b2b268d3b34c8b18bdfd645b8aa3b2be20f280c270 | |
| - filename: jwks-slim/tests/mock.js | |
| checksum: 0d2fd2ec4847acda384c398e04e8aab9a615a8599b0a06d685e7fb100e71537c | |
| - filename: secure-ws/mocks/express-response.ts | |
| checksum: 7774e72616894119c419f7ad86a3da579036b0391fd208766bcdcb96401b8d2c | |
| - filename: websocket-provider/package-lock.json | |
| checksum: 605a2d92b91f08ee577d3b1e7ed38d27eef43573a044608c22466f05ab6eaff7 | |
| - filename: aws-credentials-utils/store-credentials.sh | |
| checksum: 784aec6e80314be796af73887ba630fbaf0e116cc4d11bd5cba787a20f9c4bbb | |
| - filename: aws-ssm/package-lock.json | |
| checksum: 4cf91061e42ed9b1aafb17bdf68c9b4b24a7f86b191133ab4095fa635f8f01a0 | |
| - filename: jwks-slim/README.md | |
| checksum: a1954df51e49fc6a09d7fe04bac198fe60a4fb39b2569180475a749a72df472d | |
| - filename: jwks-slim/package-lock.json | |
| checksum: d014d548f9f2997cbe1d7c4f7ec312f8c634e88788bc453ccc606ea70e081a77 | |
| - filename: jwks-slim/tests/cache.test.js | |
| checksum: 716f25000789d7ca18adfe07f968bae6eb2fbfa1c3d05e9e92897b9597ac5f17 | |
| - filename: jwks-slim/tests/index.test.js | |
| checksum: 992a5c1f6c254fd344ae52955aa0c1c22a59e0472fe836b0450ff499255ef6f8 | |
| - filename: jwks-slim/tests/keys.js | |
| checksum: 207c0d07dea4c069883822b2b268d3b34c8b18bdfd645b8aa3b2be20f280c270 | |
| - filename: jwks-slim/tests/mock.js | |
| checksum: 0d2fd2ec4847acda384c398e04e8aab9a615a8599b0a06d685e7fb100e71537c | |
| - filename: secure-ws/mocks/express-response.ts | |
| checksum: 7774e72616894119c419f7ad86a3da579036b0391fd208766bcdcb96401b8d2c | |
| - filename: websocket-provider/package-lock.json | |
| checksum: 605a2d92b91f08ee577d3b1e7ed38d27eef43573a044608c22466f05ab6eaff7 |
Copilot
AI
Apr 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file is newly added, but it is already being ignored by Talisman (fileignoreconfig). Adding ignores reduces secret-detection coverage going forward; it’s safer to remove this ignore entry and fix the underlying finding (or add a narrower ignore with justification) so new changes to this file continue to be scanned.
| - filename: websocket-provider/package-lock.json | |
| checksum: 605a2d92b91f08ee577d3b1e7ed38d27eef43573a044608c22466f05ab6eaff7 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # Exclude TypeScript source files and configs | ||
| src/ | ||
| *.ts | ||
| *.tsx | ||
| tsconfig.json | ||
| # Exclude node_modules | ||
| node_modules/ | ||
| # Exclude test files and folders | ||
| test/ | ||
| *.spec.* | ||
| *.test.* | ||
| # Exclude build scripts and configs | ||
| *.log | ||
| *.env | ||
| # Exclude editor and OS files | ||
| .DS_Store | ||
| .vscode/ | ||
| .idea/ | ||
| # Exclude other common files | ||
| coverage/ |
| Original file line number | Diff line number | Diff line change | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,46 @@ | ||||||||||
| # secure-ws | ||||||||||
|
|
||||||||||
| secure-ws is a TypeScript library for secure WebSocket servers that lets you run Express-style middleware during the upgrade handshake, ensuring unauthenticated connections are never left open. | ||||||||||
|
Comment on lines
+1
to
+3
|
||||||||||
|
|
||||||||||
|
Comment on lines
+3
to
+4
|
||||||||||
| ## Why use secure-ws | ||||||||||
|
|
||||||||||
| - Run middleware during the WebSocket upgrade handshake | ||||||||||
| - Reuse Express Middlewares for authentication, validation and more | ||||||||||
| - Abstract away connection, upgrade, and messaging with Express-style routes, middleware, and controllers. | ||||||||||
|
|
||||||||||
| ## Installation | ||||||||||
|
|
||||||||||
| ``` | ||||||||||
| npm install --save secure-ws | ||||||||||
| ``` | ||||||||||
|
|
||||||||||
| ## Usage | ||||||||||
|
|
||||||||||
| ```typescript | ||||||||||
| import express from "express"; | ||||||||||
| import { WebSocketProvider } from 'secure-ws'; | ||||||||||
|
|
||||||||||
| const wsApp = new WebSocketProvider(); | ||||||||||
|
||||||||||
| const wsApp = new WebSocketProvider(); | |
| const wsApp = new WebSocketProvider({ | |
| allowedOrigins: "https://your-app.example.com" | |
| }); |
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| @@ -0,0 +1,138 @@ | ||||||||||||||||||||||||||||||||||||||||||||
| import { WebSocketServer } from 'ws'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { IncomingMessage } from 'http'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { decode } from "@msgpack/msgpack"; | ||||||||||||||||||||||||||||||||||||||||||||
| import { randomUUID } from "crypto"; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import { WSProtocolCodec } from './ws-protocol-codec'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { runExpressMiddleware } from '../utils/middleware-adapter'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { injectHttpRequest } from '../utils/inject-http-request'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| import { WSController } from '../types/ws-controller'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { AddRouteParams } from '../types/add-route-params'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { ClientSocket } from '../types/client-socket'; | ||||||||||||||||||||||||||||||||||||||||||||
| import { Duplex } from 'stream'; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const FUNDWAVE_DOMAIN_PATTERNS = [ | ||||||||||||||||||||||||||||||||||||||||||||
| /^(https:\/\/([a-z0-9-]+[.])*(jcurve|fundwave|dealflow|investorportal))[.]app/, | ||||||||||||||||||||||||||||||||||||||||||||
| /^(https:\/\/[a-z0-9-]+[.](get)*fundwave)[.]com/ | ||||||||||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| export class WebSocketProvider { | ||||||||||||||||||||||||||||||||||||||||||||
| public server: WebSocketServer; | ||||||||||||||||||||||||||||||||||||||||||||
| public clientSockets: Map<string, ClientSocket>; | ||||||||||||||||||||||||||||||||||||||||||||
| private routes: Record<string, AddRouteParams>; | ||||||||||||||||||||||||||||||||||||||||||||
| private allowedOrigins: string; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| constructor({ allowedOrigins }: { allowedOrigins: string }) { | ||||||||||||||||||||||||||||||||||||||||||||
| this.allowedOrigins = allowedOrigins; | ||||||||||||||||||||||||||||||||||||||||||||
| this.server = new WebSocketServer({ noServer: true }); | ||||||||||||||||||||||||||||||||||||||||||||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. what if we set noServer to false? Then we are able to set our own upgrade logic in the specified http server? https://www.reddit.com/r/node/comments/sfgmum/can_someone_kindly_explain_what_noserver_mode/
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. If noServer is set to false (which it is by default), it spins up an http server on the specified port. In this case we won't have any control over the upgrade logic. In our case, we already have an http server provided by express and we want to just use the socket connection after upgrade happens. Hence The alternative is to pass the server to the constructor. But even in that case, this.server.handleUpgrade will be called automatically on all upgrades. We don't want this as we only want to upgrade if the enpoint matches a registered route [Ref lines 38-41 core/web-socket-provider.ts] From docs:
Refs:
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. So the idea is to run the WS server in parallel with regular REST API? I'd imagine having a separate server for WS only might help plan scaling up and rate limiting better.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think that depends on the number of ws endpoints one wants across services. If we want to have one endpoint for most of the services that would mean a common ws server would need access to all resources for those microservices. This ws server would become a monolith soon. For just a few endpoints, having a common ws makes sense for scalability but, in my opinion, the drawbacks would overcome the benefits as the number of endpoints increase. The other alternative would be to have ws microservices in parallel with the existing microservices. But that would nearly double the number of microservices we have right now, leading to additional overhead of managing these services. Another approach that was discussed previously was to have a common ws-service that only manages a single socket connection from the client and relays the request to the respective services. However it would still need ws endpoints on the services that it connects to. We can add rate limiting rules to this service and then later plug this between the client and server with the current architecture. |
||||||||||||||||||||||||||||||||||||||||||||
| this.server.on('connection', this.handleConnection); | ||||||||||||||||||||||||||||||||||||||||||||
| this.routes = {}; | ||||||||||||||||||||||||||||||||||||||||||||
| this.clientSockets = new Map(); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+20
to
+32
|
||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| public addRoute = ( | ||||||||||||||||||||||||||||||||||||||||||||
| path: string, | ||||||||||||||||||||||||||||||||||||||||||||
| { | ||||||||||||||||||||||||||||||||||||||||||||
| onConnect, | ||||||||||||||||||||||||||||||||||||||||||||
| onMessage, | ||||||||||||||||||||||||||||||||||||||||||||
| } : AddRouteParams | ||||||||||||||||||||||||||||||||||||||||||||
| ) => { | ||||||||||||||||||||||||||||||||||||||||||||
| this.routes[path] = { | ||||||||||||||||||||||||||||||||||||||||||||
| onConnect, | ||||||||||||||||||||||||||||||||||||||||||||
| onMessage | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| public handleUpgrade = (request: IncomingMessage, socket: Duplex, head: Buffer) => { | ||||||||||||||||||||||||||||||||||||||||||||
| const { pathname } = new URL(request.url!, 'wss://base.url'); | ||||||||||||||||||||||||||||||||||||||||||||
| const origin = request.headers.origin; | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| if (this.allowedOrigins) { | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| const origins = this.allowedOrigins | ||||||||||||||||||||||||||||||||||||||||||||
| .split(",") | ||||||||||||||||||||||||||||||||||||||||||||
| .filter(origin => origin.trim()) | ||||||||||||||||||||||||||||||||||||||||||||
| .map(origin => { | ||||||||||||||||||||||||||||||||||||||||||||
| if (/^\/.*\/$/.test(origin)) { | ||||||||||||||||||||||||||||||||||||||||||||
| return new RegExp(origin.replace(/^\/(.*)\/$/, "$1")); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| return origin; | ||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
| origins.push(...FUNDWAVE_DOMAIN_PATTERNS); | ||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+61
to
+63
|
||||||||||||||||||||||||||||||||||||||||||||
| const isOriginAllowed = origins.some(allowedOrigin => { | ||||||||||||||||||||||||||||||||||||||||||||
| if (allowedOrigin === '*') return true; | ||||||||||||||||||||||||||||||||||||||||||||
| if (allowedOrigin instanceof RegExp) { | ||||||||||||||||||||||||||||||||||||||||||||
| return allowedOrigin.test(origin || ''); | ||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||
| const regex = new RegExp(`^${allowedOrigin.replace(/\*/g, '.*')}$`); | ||||||||||||||||||||||||||||||||||||||||||||
| return regex.test(origin || ''); | ||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+65
to
+70
|
||||||||||||||||||||||||||||||||||||||||||||
| if (allowedOrigin === '*') return true; | |
| if (allowedOrigin instanceof RegExp) { | |
| return allowedOrigin.test(origin || ''); | |
| } | |
| const regex = new RegExp(`^${allowedOrigin.replace(/\*/g, '.*')}$`); | |
| return regex.test(origin || ''); | |
| const requestOrigin = origin || ''; | |
| if (allowedOrigin === '*') return true; | |
| if (allowedOrigin instanceof RegExp) { | |
| return allowedOrigin.test(requestOrigin); | |
| } | |
| if (!allowedOrigin.includes('*')) { | |
| return allowedOrigin === requestOrigin; | |
| } | |
| const escapedAllowedOrigin = allowedOrigin | |
| .replace(/[|\\{}()[\]^$+?.]/g, '\\$&') | |
| .replace(/\*/g, '.*'); | |
| const regex = new RegExp(`^${escapedAllowedOrigin}$`); | |
| return regex.test(requestOrigin); |
Copilot
AI
Apr 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The README/PR description claims middleware runs "during the upgrade handshake", but the current flow upgrades the connection in handleUpgrade() and only runs onConnect middleware later in the 'connection' handler. If the intent is to reject unauthenticated clients before the WebSocket is established, the middleware (or an equivalent auth step) needs to run before calling server.handleUpgrade() (or use verifyClient/custom handleUpgrade gating) and respond with an HTTP error instead of upgrading first.
Copilot
AI
Apr 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
injectedRequest is initialized as an empty object and only set when ws.protocol is non-empty. When no protocol is provided, runExpressMiddleware receives an empty request object instead of the actual IncomingMessage, which will break typical auth/validation middleware expecting headers/url/etc. Initialize injectedRequest to request and then mutate it when protocol data is present.
Copilot
AI
Apr 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
injectedRequest is initialized as an empty object, so when ws.protocol is empty the onConnect middleware receives a request without headers/url/etc. Initialize injectedRequest to the original request by default, and only mutate it when protocol data is provided.
Copilot
AI
Apr 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On middleware rejection, the code calls ws.send(...) and immediately ws.close(...). In ws, closing right after sending can result in the message being dropped if it hasn't been flushed yet. Consider closing in the send callback (or waiting for the socket to drain) to ensure the client reliably receives the error payload.
| ws.send(JSON.stringify({ error: res.error })); | |
| ws.close(1000, res.error); | |
| ws.send(JSON.stringify({ error: res.error }), () => { | |
| ws.close(1000, res.error); | |
| }); |
Copilot
AI
Apr 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When middleware rejects a connection, the socket is closed with code 1000 (normal closure). Use a more appropriate close code like 1008 (policy violation) so clients can reliably distinguish authentication/authorization failures from normal disconnects.
| ws.close(1000, res.error); | |
| ws.close(1008, res.error); |
Copilot
AI
Apr 9, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
decode(message) is cast to IncomingMessage, but a msgpack-decoded WebSocket message is not an HTTP IncomingMessage. This misleading type can hide real issues and encourages controllers to treat the payload like an HTTP request. Consider defining a dedicated message payload type (or unknown) and passing that through to controllers.
| return async (message) => { | |
| const request = decode(message) as IncomingMessage; | |
| for (const controller of onMessage) { | |
| await controller(request, socket); | |
| type ControllerMessage = Parameters<WSController>[0]; | |
| return async (message) => { | |
| const payload: unknown = decode(message); | |
| for (const controller of onMessage) { | |
| await controller(payload as ControllerMessage, socket); |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| export class WSProtocolCodec { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Rather Base64UrlSafeCodec if it isn't specific to WS
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It is specific to the websocket as the name of the websocket protocol ( |
||
| static encode(data: unknown): string { | ||
| const json = JSON.stringify(data); | ||
| return Buffer.from(json).toString('base64') | ||
| .replace(/\+/g, "-") | ||
| .replace(/\//g, "_") | ||
| .replace(/=+$/, ""); | ||
| } | ||
|
|
||
| static decode<T = unknown>(encoded: string): T { | ||
| let str = encoded.replace(/-/g, "+").replace(/_/g, "/"); | ||
| while (str.length % 4) str += "="; | ||
| const json = Buffer.from(str, 'base64').toString(); | ||
| return JSON.parse(json) as T; | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,4 @@ | ||
| export * from './core/web-socket-provider'; | ||
| export * from './core/ws-protocol-codec'; | ||
| export type { MockResponse } from './types/mock-response'; | ||
| export type { ExpressMiddleware } from './types/express-middleware'; |
Uh oh!
There was an error while loading. Please reload this page.