api-ape 3.0.2 → 4.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +59 -572
- package/client/README.md +73 -14
- package/client/auth/crypto/aead.js +214 -0
- package/client/auth/crypto/constants.js +32 -0
- package/client/auth/crypto/encoding.js +104 -0
- package/client/auth/crypto/files.md +27 -0
- package/client/auth/crypto/kdf.js +217 -0
- package/client/auth/crypto-utils.js +118 -0
- package/client/auth/files.md +52 -0
- package/client/auth/key-recovery.js +288 -0
- package/client/auth/recovery/constants.js +37 -0
- package/client/auth/recovery/files.md +23 -0
- package/client/auth/recovery/key-derivation.js +61 -0
- package/client/auth/recovery/sss-browser.js +189 -0
- package/client/auth/share-storage.js +205 -0
- package/client/auth/storage/constants.js +18 -0
- package/client/auth/storage/db.js +132 -0
- package/client/auth/storage/files.md +27 -0
- package/client/auth/storage/keys.js +173 -0
- package/client/auth/storage/shares.js +200 -0
- package/client/browser.js +190 -23
- package/client/connectSocket.js +418 -988
- package/client/connection/README.md +23 -0
- package/client/connection/fileDownload.js +256 -0
- package/client/connection/fileHandling.js +450 -0
- package/client/connection/fileUtils.js +346 -0
- package/client/connection/files.md +71 -0
- package/client/connection/messageHandler.js +105 -0
- package/client/connection/network.js +350 -0
- package/client/connection/proxy.js +233 -0
- package/client/connection/sender.js +333 -0
- package/client/connection/state.js +321 -0
- package/client/connection/subscriptions.js +151 -0
- package/client/files.md +53 -0
- package/client/index.js +298 -142
- package/client/transports/README.md +50 -0
- package/client/transports/files.md +41 -0
- package/client/transports/streamParser.js +195 -0
- package/client/transports/streaming.js +555 -203
- package/dist/ape.js +6 -1
- package/dist/ape.js.map +4 -4
- package/index.d.ts +38 -16
- package/package.json +31 -6
- package/server/README.md +272 -67
- package/server/adapters/README.md +23 -14
- package/server/adapters/files.md +68 -0
- package/server/adapters/firebase.js +543 -160
- package/server/adapters/index.js +362 -112
- package/server/adapters/mongo.js +530 -140
- package/server/adapters/postgres.js +534 -155
- package/server/adapters/redis.js +508 -143
- package/server/adapters/supabase.js +555 -186
- package/server/client/README.md +43 -0
- package/server/client/connection.js +586 -0
- package/server/client/files.md +40 -0
- package/server/client/index.js +342 -0
- package/server/files.md +54 -0
- package/server/index.js +322 -71
- package/server/lib/README.md +26 -0
- package/server/lib/broadcast/clients.js +219 -0
- package/server/lib/broadcast/files.md +58 -0
- package/server/lib/broadcast/index.js +57 -0
- package/server/lib/broadcast/publishProxy.js +110 -0
- package/server/lib/broadcast/pubsub.js +137 -0
- package/server/lib/broadcast/sendProxy.js +103 -0
- package/server/lib/bun.js +315 -99
- package/server/lib/fileTransfer/README.md +63 -0
- package/server/lib/fileTransfer/files.md +30 -0
- package/server/lib/fileTransfer/streaming.js +435 -0
- package/server/lib/fileTransfer.js +710 -326
- package/server/lib/files.md +111 -0
- package/server/lib/httpUtils.js +283 -0
- package/server/lib/loader.js +208 -7
- package/server/lib/longPolling/README.md +63 -0
- package/server/lib/longPolling/files.md +44 -0
- package/server/lib/longPolling/getHandler.js +365 -0
- package/server/lib/longPolling/postHandler.js +327 -0
- package/server/lib/longPolling.js +174 -219
- package/server/lib/main.js +369 -532
- package/server/lib/runtimes/README.md +42 -0
- package/server/lib/runtimes/bun.js +586 -0
- package/server/lib/runtimes/files.md +56 -0
- package/server/lib/runtimes/node.js +511 -0
- package/server/lib/wiring.js +539 -98
- package/server/lib/ws/README.md +35 -0
- package/server/lib/ws/adapters/README.md +54 -0
- package/server/lib/ws/adapters/bun.js +538 -170
- package/server/lib/ws/adapters/deno.js +623 -149
- package/server/lib/ws/adapters/files.md +42 -0
- package/server/lib/ws/files.md +74 -0
- package/server/lib/ws/frames.js +532 -154
- package/server/lib/ws/index.js +207 -10
- package/server/lib/ws/server.js +385 -92
- package/server/lib/ws/socket.js +549 -181
- package/server/lib/wsProvider.js +363 -89
- package/server/plugins/binary.js +282 -0
- package/server/security/README.md +92 -0
- package/server/security/auth/README.md +319 -0
- package/server/security/auth/adapters/files.md +95 -0
- package/server/security/auth/adapters/ldap/constants.js +37 -0
- package/server/security/auth/adapters/ldap/files.md +19 -0
- package/server/security/auth/adapters/ldap/helpers.js +111 -0
- package/server/security/auth/adapters/ldap.js +353 -0
- package/server/security/auth/adapters/oauth2/constants.js +41 -0
- package/server/security/auth/adapters/oauth2/files.md +19 -0
- package/server/security/auth/adapters/oauth2/helpers.js +123 -0
- package/server/security/auth/adapters/oauth2.js +273 -0
- package/server/security/auth/adapters/opaque-handlers.js +314 -0
- package/server/security/auth/adapters/opaque.js +205 -0
- package/server/security/auth/adapters/saml/constants.js +52 -0
- package/server/security/auth/adapters/saml/files.md +19 -0
- package/server/security/auth/adapters/saml/helpers.js +74 -0
- package/server/security/auth/adapters/saml.js +173 -0
- package/server/security/auth/adapters/totp.js +703 -0
- package/server/security/auth/adapters/webauthn.js +625 -0
- package/server/security/auth/files.md +61 -0
- package/server/security/auth/framework/constants.js +27 -0
- package/server/security/auth/framework/files.md +23 -0
- package/server/security/auth/framework/handlers.js +272 -0
- package/server/security/auth/framework/socket-auth.js +177 -0
- package/server/security/auth/handlers/auth-messages.js +143 -0
- package/server/security/auth/handlers/files.md +28 -0
- package/server/security/auth/index.js +290 -0
- package/server/security/auth/mfa/crypto/aead.js +148 -0
- package/server/security/auth/mfa/crypto/constants.js +35 -0
- package/server/security/auth/mfa/crypto/files.md +27 -0
- package/server/security/auth/mfa/crypto/kdf.js +120 -0
- package/server/security/auth/mfa/crypto/utils.js +68 -0
- package/server/security/auth/mfa/crypto-utils.js +80 -0
- package/server/security/auth/mfa/files.md +77 -0
- package/server/security/auth/mfa/ledger/constants.js +75 -0
- package/server/security/auth/mfa/ledger/errors.js +73 -0
- package/server/security/auth/mfa/ledger/files.md +23 -0
- package/server/security/auth/mfa/ledger/share-record.js +32 -0
- package/server/security/auth/mfa/ledger.js +255 -0
- package/server/security/auth/mfa/recovery/constants.js +67 -0
- package/server/security/auth/mfa/recovery/files.md +19 -0
- package/server/security/auth/mfa/recovery/handlers.js +216 -0
- package/server/security/auth/mfa/recovery.js +191 -0
- package/server/security/auth/mfa/sss/constants.js +21 -0
- package/server/security/auth/mfa/sss/files.md +23 -0
- package/server/security/auth/mfa/sss/gf256.js +103 -0
- package/server/security/auth/mfa/sss/serialization.js +82 -0
- package/server/security/auth/mfa/sss.js +161 -0
- package/server/security/auth/mfa/two-of-three/constants.js +58 -0
- package/server/security/auth/mfa/two-of-three/files.md +23 -0
- package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
- package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
- package/server/security/auth/mfa/two-of-three.js +136 -0
- package/server/security/auth/nonce-manager.js +89 -0
- package/server/security/auth/state-machine-mfa.js +269 -0
- package/server/security/auth/state-machine.js +257 -0
- package/server/security/extractRootDomain.js +144 -16
- package/server/security/files.md +51 -0
- package/server/security/origin.js +197 -15
- package/server/security/reply.js +274 -16
- package/server/socket/README.md +119 -0
- package/server/socket/authMiddleware.js +299 -0
- package/server/socket/files.md +86 -0
- package/server/socket/open.js +154 -8
- package/server/socket/pluginHooks.js +334 -0
- package/server/socket/receive.js +184 -224
- package/server/socket/receiveContext.js +117 -0
- package/server/socket/send.js +416 -78
- package/server/socket/tagUtils.js +402 -0
- package/server/utils/README.md +19 -0
- package/server/utils/deepRequire.js +255 -30
- package/server/utils/files.md +57 -0
- package/server/utils/genId.js +182 -20
- package/server/utils/parseUserAgent.js +313 -251
- package/server/utils/userAgent/README.md +65 -0
- package/server/utils/userAgent/files.md +46 -0
- package/server/utils/userAgent/patterns.js +545 -0
- package/utils/README.md +21 -0
- package/utils/files.md +66 -0
- package/utils/jss/README.md +21 -0
- package/utils/jss/decode.js +471 -0
- package/utils/jss/encode.js +312 -0
- package/utils/jss/files.md +68 -0
- package/utils/jss/plugins.js +210 -0
- package/utils/jss.js +219 -273
- package/utils/messageHash.js +238 -35
- package/dist/api-ape.min.js +0 -2
- package/dist/api-ape.min.js.map +0 -7
- package/server/client.js +0 -311
- package/server/lib/broadcast.js +0 -146
package/server/lib/ws/server.js
CHANGED
|
@@ -1,109 +1,402 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
3
|
-
*
|
|
2
|
+
* @fileoverview WebSocket Server Implementation - RFC 6455 Compliant
|
|
3
|
+
*
|
|
4
|
+
* This module provides a WebSocket server that handles HTTP upgrade requests
|
|
5
|
+
* and manages WebSocket connections. It's designed as a lightweight, zero-dependency
|
|
6
|
+
* alternative to the popular `ws` library with compatible API.
|
|
7
|
+
*
|
|
8
|
+
* The server operates in "noServer" mode, meaning it doesn't create its own
|
|
9
|
+
* HTTP server. Instead, it integrates with an existing HTTP server by handling
|
|
10
|
+
* upgrade requests forwarded to it.
|
|
11
|
+
*
|
|
12
|
+
* ## Connection Flow
|
|
13
|
+
*
|
|
14
|
+
* 1. HTTP server receives an upgrade request
|
|
15
|
+
* 2. Server validates the request headers (Upgrade, Sec-WebSocket-Key)
|
|
16
|
+
* 3. Server generates the accept key and sends 101 Switching Protocols
|
|
17
|
+
* 4. WebSocket connection is established
|
|
18
|
+
* 5. Server emits 'connection' event with the WebSocket instance
|
|
19
|
+
*
|
|
20
|
+
* ## Key Features
|
|
21
|
+
*
|
|
22
|
+
* - RFC 6455 compliant WebSocket handshake
|
|
23
|
+
* - Client tracking via `clients` Set
|
|
24
|
+
* - Compatible with `ws` library API patterns
|
|
25
|
+
* - Automatic cleanup on connection close
|
|
26
|
+
*
|
|
27
|
+
* @module server/lib/ws/server
|
|
28
|
+
* @see {@link module:server/lib/ws/socket} - WebSocket connection class
|
|
29
|
+
* @see {@link module:server/lib/ws/frames} - Frame encoding/decoding
|
|
30
|
+
* @see {@link https://tools.ietf.org/html/rfc6455#section-4.2} - RFC 6455 Handshake
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* // Basic setup with HTTP server
|
|
34
|
+
* const http = require('http')
|
|
35
|
+
* const { WebSocketServer } = require('./server')
|
|
36
|
+
*
|
|
37
|
+
* const server = http.createServer()
|
|
38
|
+
* const wss = new WebSocketServer({ noServer: true })
|
|
39
|
+
*
|
|
40
|
+
* server.on('upgrade', (req, socket, head) => {
|
|
41
|
+
* wss.handleUpgrade(req, socket, head, (ws) => {
|
|
42
|
+
* wss.emit('connection', ws, req)
|
|
43
|
+
* })
|
|
44
|
+
* })
|
|
45
|
+
*
|
|
46
|
+
* wss.on('connection', (ws, req) => {
|
|
47
|
+
* console.log('Client connected from:', req.socket.remoteAddress)
|
|
48
|
+
* ws.on('message', (data) => console.log('Received:', data))
|
|
49
|
+
* ws.send('Welcome!')
|
|
50
|
+
* })
|
|
51
|
+
*
|
|
52
|
+
* server.listen(8080)
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* // Broadcasting to all clients
|
|
56
|
+
* function broadcast(message) {
|
|
57
|
+
* for (const client of wss.clients) {
|
|
58
|
+
* if (client.readyState === WebSocket.OPEN) {
|
|
59
|
+
* client.send(message)
|
|
60
|
+
* }
|
|
61
|
+
* }
|
|
62
|
+
* }
|
|
63
|
+
*
|
|
64
|
+
* @example
|
|
65
|
+
* // Graceful shutdown
|
|
66
|
+
* process.on('SIGTERM', () => {
|
|
67
|
+
* wss.close(() => {
|
|
68
|
+
* console.log('All WebSocket connections closed')
|
|
69
|
+
* server.close()
|
|
70
|
+
* })
|
|
71
|
+
* })
|
|
4
72
|
*/
|
|
5
73
|
|
|
6
|
-
const { EventEmitter } = require(
|
|
7
|
-
const { generateAcceptKey } = require(
|
|
8
|
-
const { WebSocket } = require(
|
|
74
|
+
const { EventEmitter } = require("events");
|
|
75
|
+
const { generateAcceptKey } = require("./frames");
|
|
76
|
+
const { WebSocket } = require("./socket");
|
|
9
77
|
|
|
78
|
+
/**
|
|
79
|
+
* @typedef {Object} WebSocketServerOptions
|
|
80
|
+
* Configuration options for creating a WebSocketServer.
|
|
81
|
+
*
|
|
82
|
+
* @property {boolean} [noServer=false] - When true, the server doesn't create
|
|
83
|
+
* its own HTTP server. Upgrade requests must be manually forwarded via
|
|
84
|
+
* `handleUpgrade()`. This is the typical mode for api-ape integration.
|
|
85
|
+
*/
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* @typedef {function(WebSocket): void} UpgradeCallback
|
|
89
|
+
* Callback function invoked after successful WebSocket upgrade.
|
|
90
|
+
*
|
|
91
|
+
* @param {WebSocket} ws - The newly created WebSocket connection
|
|
92
|
+
*/
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* WebSocket server for handling HTTP upgrade requests and managing connections.
|
|
96
|
+
*
|
|
97
|
+
* This class extends EventEmitter and provides a ws-compatible API for
|
|
98
|
+
* accepting WebSocket connections. It validates upgrade requests according
|
|
99
|
+
* to RFC 6455 and manages the lifecycle of all connected clients.
|
|
100
|
+
*
|
|
101
|
+
* ## Events
|
|
102
|
+
*
|
|
103
|
+
* - **connection**: Emitted when a new WebSocket connection is established.
|
|
104
|
+
* Listeners receive `(ws: WebSocket, req: http.IncomingMessage)`.
|
|
105
|
+
*
|
|
106
|
+
* ## Client Management
|
|
107
|
+
*
|
|
108
|
+
* The server maintains a Set of all connected clients accessible via the
|
|
109
|
+
* `clients` property. Clients are automatically added on connection and
|
|
110
|
+
* removed when the connection closes.
|
|
111
|
+
*
|
|
112
|
+
* @class WebSocketServer
|
|
113
|
+
* @extends EventEmitter
|
|
114
|
+
*
|
|
115
|
+
* @param {WebSocketServerOptions} [options={}] - Server configuration
|
|
116
|
+
*
|
|
117
|
+
* @example
|
|
118
|
+
* // Create server in noServer mode (typical usage)
|
|
119
|
+
* const wss = new WebSocketServer({ noServer: true })
|
|
120
|
+
*
|
|
121
|
+
* wss.on('connection', (ws, req) => {
|
|
122
|
+
* console.log('New connection')
|
|
123
|
+
*
|
|
124
|
+
* ws.on('message', (data) => {
|
|
125
|
+
* // Echo back
|
|
126
|
+
* ws.send(data)
|
|
127
|
+
* })
|
|
128
|
+
*
|
|
129
|
+
* ws.on('close', () => {
|
|
130
|
+
* console.log('Client disconnected')
|
|
131
|
+
* })
|
|
132
|
+
* })
|
|
133
|
+
*
|
|
134
|
+
* @example
|
|
135
|
+
* // Access all connected clients
|
|
136
|
+
* console.log(`${wss.clients.size} clients connected`)
|
|
137
|
+
*
|
|
138
|
+
* // Iterate over clients
|
|
139
|
+
* for (const client of wss.clients) {
|
|
140
|
+
* client.send(JSON.stringify({ type: 'ping' }))
|
|
141
|
+
* }
|
|
142
|
+
*/
|
|
10
143
|
class WebSocketServer extends EventEmitter {
|
|
144
|
+
/**
|
|
145
|
+
* Create a new WebSocketServer instance.
|
|
146
|
+
*
|
|
147
|
+
* @param {WebSocketServerOptions} [options={}] - Configuration options
|
|
148
|
+
* @param {boolean} [options.noServer=false] - Run without creating HTTP server
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* // Standard noServer mode for integration
|
|
152
|
+
* const wss = new WebSocketServer({ noServer: true })
|
|
153
|
+
*
|
|
154
|
+
* @example
|
|
155
|
+
* // Default options
|
|
156
|
+
* const wss = new WebSocketServer()
|
|
157
|
+
*/
|
|
158
|
+
constructor(options = {}) {
|
|
159
|
+
super();
|
|
160
|
+
|
|
11
161
|
/**
|
|
12
|
-
*
|
|
13
|
-
* @
|
|
162
|
+
* Whether this server operates in noServer mode.
|
|
163
|
+
* @type {boolean}
|
|
164
|
+
* @private
|
|
14
165
|
*/
|
|
15
|
-
|
|
16
|
-
super()
|
|
17
|
-
this._noServer = options.noServer || false
|
|
18
|
-
this._clients = new Set()
|
|
19
|
-
}
|
|
166
|
+
this._noServer = options.noServer || false;
|
|
20
167
|
|
|
21
168
|
/**
|
|
22
|
-
*
|
|
23
|
-
*
|
|
169
|
+
* Set of all connected WebSocket clients.
|
|
170
|
+
* Clients are automatically added on connection and removed on close.
|
|
171
|
+
* @type {Set<WebSocket>}
|
|
172
|
+
* @private
|
|
24
173
|
*/
|
|
25
|
-
|
|
26
|
-
|
|
174
|
+
this._clients = new Set();
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Get all connected WebSocket clients.
|
|
179
|
+
*
|
|
180
|
+
* Returns a Set containing all active WebSocket connections managed by
|
|
181
|
+
* this server. Clients are automatically added when connections are
|
|
182
|
+
* established and removed when connections close.
|
|
183
|
+
*
|
|
184
|
+
* Use this to broadcast messages, count connections, or iterate over
|
|
185
|
+
* all clients for any purpose.
|
|
186
|
+
*
|
|
187
|
+
* @type {Set<WebSocket>}
|
|
188
|
+
* @readonly
|
|
189
|
+
*
|
|
190
|
+
* @example
|
|
191
|
+
* // Count connected clients
|
|
192
|
+
* console.log(`Active connections: ${wss.clients.size}`)
|
|
193
|
+
*
|
|
194
|
+
* @example
|
|
195
|
+
* // Broadcast to all clients
|
|
196
|
+
* const message = JSON.stringify({ type: 'announcement', text: 'Hello!' })
|
|
197
|
+
* for (const client of wss.clients) {
|
|
198
|
+
* if (client.readyState === 1) { // OPEN
|
|
199
|
+
* client.send(message)
|
|
200
|
+
* }
|
|
201
|
+
* }
|
|
202
|
+
*
|
|
203
|
+
* @example
|
|
204
|
+
* // Find specific client
|
|
205
|
+
* const targetClient = [...wss.clients].find(c => c.userId === targetId)
|
|
206
|
+
* if (targetClient) {
|
|
207
|
+
* targetClient.send('Direct message')
|
|
208
|
+
* }
|
|
209
|
+
*/
|
|
210
|
+
get clients() {
|
|
211
|
+
return this._clients;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Handle an HTTP upgrade request and establish a WebSocket connection.
|
|
216
|
+
*
|
|
217
|
+
* This method validates the upgrade request according to RFC 6455:
|
|
218
|
+
* 1. Checks for `Upgrade: websocket` header
|
|
219
|
+
* 2. Validates `Sec-WebSocket-Key` header (must be 16 bytes, base64 encoded)
|
|
220
|
+
* 3. Generates the accept key using SHA-1 hash
|
|
221
|
+
* 4. Sends HTTP 101 Switching Protocols response
|
|
222
|
+
* 5. Creates WebSocket wrapper and invokes callback
|
|
223
|
+
*
|
|
224
|
+
* If validation fails, the socket is destroyed without response.
|
|
225
|
+
*
|
|
226
|
+
* ## Security Note
|
|
227
|
+
*
|
|
228
|
+
* The Sec-WebSocket-Key validation ensures the request came from a
|
|
229
|
+
* WebSocket client and prevents cross-protocol attacks. The accept key
|
|
230
|
+
* is computed as: `base64(sha1(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"))`
|
|
231
|
+
*
|
|
232
|
+
* @param {http.IncomingMessage} req - The HTTP upgrade request
|
|
233
|
+
* @param {net.Socket} socket - The underlying TCP socket
|
|
234
|
+
* @param {Buffer} head - First packet of the upgraded stream (usually empty)
|
|
235
|
+
* @param {UpgradeCallback} [callback] - Called with the WebSocket instance
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* // Basic upgrade handling
|
|
239
|
+
* server.on('upgrade', (req, socket, head) => {
|
|
240
|
+
* if (req.url === '/ws') {
|
|
241
|
+
* wss.handleUpgrade(req, socket, head, (ws) => {
|
|
242
|
+
* wss.emit('connection', ws, req)
|
|
243
|
+
* })
|
|
244
|
+
* } else {
|
|
245
|
+
* socket.destroy()
|
|
246
|
+
* }
|
|
247
|
+
* })
|
|
248
|
+
*
|
|
249
|
+
* @example
|
|
250
|
+
* // With authentication check
|
|
251
|
+
* server.on('upgrade', (req, socket, head) => {
|
|
252
|
+
* const token = new URL(req.url, 'http://localhost').searchParams.get('token')
|
|
253
|
+
*
|
|
254
|
+
* if (!isValidToken(token)) {
|
|
255
|
+
* socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n')
|
|
256
|
+
* socket.destroy()
|
|
257
|
+
* return
|
|
258
|
+
* }
|
|
259
|
+
*
|
|
260
|
+
* wss.handleUpgrade(req, socket, head, (ws) => {
|
|
261
|
+
* ws.userId = getUserIdFromToken(token)
|
|
262
|
+
* wss.emit('connection', ws, req)
|
|
263
|
+
* })
|
|
264
|
+
* })
|
|
265
|
+
*
|
|
266
|
+
* @example
|
|
267
|
+
* // Multiple WebSocket endpoints
|
|
268
|
+
* server.on('upgrade', (req, socket, head) => {
|
|
269
|
+
* const pathname = new URL(req.url, 'http://localhost').pathname
|
|
270
|
+
*
|
|
271
|
+
* if (pathname === '/chat') {
|
|
272
|
+
* chatServer.handleUpgrade(req, socket, head, (ws) => {
|
|
273
|
+
* chatServer.emit('connection', ws, req)
|
|
274
|
+
* })
|
|
275
|
+
* } else if (pathname === '/notifications') {
|
|
276
|
+
* notifyServer.handleUpgrade(req, socket, head, (ws) => {
|
|
277
|
+
* notifyServer.emit('connection', ws, req)
|
|
278
|
+
* })
|
|
279
|
+
* } else {
|
|
280
|
+
* socket.destroy()
|
|
281
|
+
* }
|
|
282
|
+
* })
|
|
283
|
+
*/
|
|
284
|
+
handleUpgrade(req, socket, head, callback) {
|
|
285
|
+
// Validate WebSocket upgrade request
|
|
286
|
+
const upgrade = req.headers["upgrade"];
|
|
287
|
+
if (!upgrade || upgrade.toLowerCase() !== "websocket") {
|
|
288
|
+
socket.destroy();
|
|
289
|
+
return;
|
|
27
290
|
}
|
|
28
291
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
* @param {Buffer} head - First packet of upgraded stream
|
|
34
|
-
* @param {function} callback - Called with WebSocket instance
|
|
35
|
-
*/
|
|
36
|
-
handleUpgrade(req, socket, head, callback) {
|
|
37
|
-
// Validate WebSocket upgrade request
|
|
38
|
-
const upgrade = req.headers['upgrade']
|
|
39
|
-
if (!upgrade || upgrade.toLowerCase() !== 'websocket') {
|
|
40
|
-
socket.destroy()
|
|
41
|
-
return
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
const key = req.headers['sec-websocket-key']
|
|
45
|
-
if (!key) {
|
|
46
|
-
socket.destroy()
|
|
47
|
-
return
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// Validate key is valid base64 (16 bytes = 24 chars base64)
|
|
51
|
-
if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) {
|
|
52
|
-
socket.destroy()
|
|
53
|
-
return
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Generate accept key
|
|
57
|
-
const acceptKey = generateAcceptKey(key)
|
|
58
|
-
|
|
59
|
-
// Build HTTP 101 response
|
|
60
|
-
const headers = [
|
|
61
|
-
'HTTP/1.1 101 Switching Protocols',
|
|
62
|
-
'Upgrade: websocket',
|
|
63
|
-
'Connection: Upgrade',
|
|
64
|
-
`Sec-WebSocket-Accept: ${acceptKey}`,
|
|
65
|
-
'', '' // Empty line to end headers
|
|
66
|
-
].join('\r\n')
|
|
67
|
-
|
|
68
|
-
// Send handshake response
|
|
69
|
-
socket.write(headers)
|
|
70
|
-
|
|
71
|
-
// If there's buffered data after upgrade, process it
|
|
72
|
-
// The 'head' contains any data received after the upgrade request
|
|
73
|
-
// We'll handle this in the WebSocket's buffer
|
|
74
|
-
|
|
75
|
-
// Create WebSocket wrapper
|
|
76
|
-
const ws = new WebSocket(socket)
|
|
77
|
-
this._clients.add(ws)
|
|
78
|
-
|
|
79
|
-
// Remove from clients on close
|
|
80
|
-
ws.on('close', () => {
|
|
81
|
-
this._clients.delete(ws)
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
// Handle any buffered data
|
|
85
|
-
if (head && head.length > 0) {
|
|
86
|
-
socket.unshift(head)
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// Call callback with the WebSocket
|
|
90
|
-
if (callback) {
|
|
91
|
-
callback(ws)
|
|
92
|
-
}
|
|
292
|
+
const key = req.headers["sec-websocket-key"];
|
|
293
|
+
if (!key) {
|
|
294
|
+
socket.destroy();
|
|
295
|
+
return;
|
|
93
296
|
}
|
|
94
297
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
298
|
+
// Validate key is valid base64 (16 bytes = 24 chars base64)
|
|
299
|
+
if (!/^[A-Za-z0-9+/]{22}==$/.test(key)) {
|
|
300
|
+
socket.destroy();
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Generate accept key per RFC 6455
|
|
305
|
+
const acceptKey = generateAcceptKey(key);
|
|
306
|
+
|
|
307
|
+
// Build HTTP 101 Switching Protocols response
|
|
308
|
+
const headers = [
|
|
309
|
+
"HTTP/1.1 101 Switching Protocols",
|
|
310
|
+
"Upgrade: websocket",
|
|
311
|
+
"Connection: Upgrade",
|
|
312
|
+
`Sec-WebSocket-Accept: ${acceptKey}`,
|
|
313
|
+
"",
|
|
314
|
+
"", // Empty line to end headers
|
|
315
|
+
].join("\r\n");
|
|
316
|
+
|
|
317
|
+
// Send handshake response
|
|
318
|
+
socket.write(headers);
|
|
319
|
+
|
|
320
|
+
// If there's buffered data after upgrade, process it
|
|
321
|
+
// The 'head' contains any data received after the upgrade request
|
|
322
|
+
// We'll handle this in the WebSocket's buffer
|
|
323
|
+
|
|
324
|
+
// Create WebSocket wrapper around the TCP socket
|
|
325
|
+
const ws = new WebSocket(socket);
|
|
326
|
+
this._clients.add(ws);
|
|
327
|
+
|
|
328
|
+
// Remove from clients set when connection closes
|
|
329
|
+
ws.on("close", () => {
|
|
330
|
+
this._clients.delete(ws);
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
// Handle any buffered data from the upgrade request
|
|
334
|
+
/* istanbul ignore next 3 - buffered upgrade data rare in practice */
|
|
335
|
+
if (head && head.length > 0) {
|
|
336
|
+
socket.unshift(head);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Invoke callback with the new WebSocket
|
|
340
|
+
if (callback) {
|
|
341
|
+
callback(ws);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
/**
|
|
346
|
+
* Close the server and all active connections.
|
|
347
|
+
*
|
|
348
|
+
* Iterates through all connected clients and sends a close frame with
|
|
349
|
+
* code 1001 (Going Away) and reason "Server shutting down". All clients
|
|
350
|
+
* are then removed from the clients set.
|
|
351
|
+
*
|
|
352
|
+
* This method should be called during graceful shutdown to properly
|
|
353
|
+
* terminate all WebSocket connections.
|
|
354
|
+
*
|
|
355
|
+
* @param {function(): void} [callback] - Called after all connections are closed
|
|
356
|
+
*
|
|
357
|
+
* @example
|
|
358
|
+
* // Graceful shutdown on process termination
|
|
359
|
+
* process.on('SIGTERM', () => {
|
|
360
|
+
* console.log('Shutting down...')
|
|
361
|
+
*
|
|
362
|
+
* wss.close(() => {
|
|
363
|
+
* console.log('All WebSocket connections closed')
|
|
364
|
+
* httpServer.close(() => {
|
|
365
|
+
* console.log('HTTP server closed')
|
|
366
|
+
* process.exit(0)
|
|
367
|
+
* })
|
|
368
|
+
* })
|
|
369
|
+
* })
|
|
370
|
+
*
|
|
371
|
+
* @example
|
|
372
|
+
* // Close with custom message broadcast first
|
|
373
|
+
* function shutdown() {
|
|
374
|
+
* // Notify clients before closing
|
|
375
|
+
* for (const client of wss.clients) {
|
|
376
|
+
* client.send(JSON.stringify({ type: 'shutdown', message: 'Server restarting' }))
|
|
377
|
+
* }
|
|
378
|
+
*
|
|
379
|
+
* // Give clients time to receive the message
|
|
380
|
+
* setTimeout(() => {
|
|
381
|
+
* wss.close(() => {
|
|
382
|
+
* console.log('Shutdown complete')
|
|
383
|
+
* })
|
|
384
|
+
* }, 100)
|
|
385
|
+
* }
|
|
386
|
+
*
|
|
387
|
+
* @example
|
|
388
|
+
* // Simple close without callback
|
|
389
|
+
* wss.close()
|
|
390
|
+
*/
|
|
391
|
+
close(callback) {
|
|
392
|
+
for (const client of this._clients) {
|
|
393
|
+
client.close(1001, "Server shutting down");
|
|
394
|
+
}
|
|
395
|
+
this._clients.clear();
|
|
396
|
+
if (callback) {
|
|
397
|
+
callback();
|
|
106
398
|
}
|
|
399
|
+
}
|
|
107
400
|
}
|
|
108
401
|
|
|
109
|
-
module.exports = { WebSocketServer }
|
|
402
|
+
module.exports = { WebSocketServer };
|