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.
Files changed (186) hide show
  1. package/README.md +59 -572
  2. package/client/README.md +73 -14
  3. package/client/auth/crypto/aead.js +214 -0
  4. package/client/auth/crypto/constants.js +32 -0
  5. package/client/auth/crypto/encoding.js +104 -0
  6. package/client/auth/crypto/files.md +27 -0
  7. package/client/auth/crypto/kdf.js +217 -0
  8. package/client/auth/crypto-utils.js +118 -0
  9. package/client/auth/files.md +52 -0
  10. package/client/auth/key-recovery.js +288 -0
  11. package/client/auth/recovery/constants.js +37 -0
  12. package/client/auth/recovery/files.md +23 -0
  13. package/client/auth/recovery/key-derivation.js +61 -0
  14. package/client/auth/recovery/sss-browser.js +189 -0
  15. package/client/auth/share-storage.js +205 -0
  16. package/client/auth/storage/constants.js +18 -0
  17. package/client/auth/storage/db.js +132 -0
  18. package/client/auth/storage/files.md +27 -0
  19. package/client/auth/storage/keys.js +173 -0
  20. package/client/auth/storage/shares.js +200 -0
  21. package/client/browser.js +190 -23
  22. package/client/connectSocket.js +418 -988
  23. package/client/connection/README.md +23 -0
  24. package/client/connection/fileDownload.js +256 -0
  25. package/client/connection/fileHandling.js +450 -0
  26. package/client/connection/fileUtils.js +346 -0
  27. package/client/connection/files.md +71 -0
  28. package/client/connection/messageHandler.js +105 -0
  29. package/client/connection/network.js +350 -0
  30. package/client/connection/proxy.js +233 -0
  31. package/client/connection/sender.js +333 -0
  32. package/client/connection/state.js +321 -0
  33. package/client/connection/subscriptions.js +151 -0
  34. package/client/files.md +53 -0
  35. package/client/index.js +298 -142
  36. package/client/transports/README.md +50 -0
  37. package/client/transports/files.md +41 -0
  38. package/client/transports/streamParser.js +195 -0
  39. package/client/transports/streaming.js +555 -203
  40. package/dist/ape.js +6 -1
  41. package/dist/ape.js.map +4 -4
  42. package/index.d.ts +38 -16
  43. package/package.json +31 -6
  44. package/server/README.md +272 -67
  45. package/server/adapters/README.md +23 -14
  46. package/server/adapters/files.md +68 -0
  47. package/server/adapters/firebase.js +543 -160
  48. package/server/adapters/index.js +362 -112
  49. package/server/adapters/mongo.js +530 -140
  50. package/server/adapters/postgres.js +534 -155
  51. package/server/adapters/redis.js +508 -143
  52. package/server/adapters/supabase.js +555 -186
  53. package/server/client/README.md +43 -0
  54. package/server/client/connection.js +586 -0
  55. package/server/client/files.md +40 -0
  56. package/server/client/index.js +342 -0
  57. package/server/files.md +54 -0
  58. package/server/index.js +322 -71
  59. package/server/lib/README.md +26 -0
  60. package/server/lib/broadcast/clients.js +219 -0
  61. package/server/lib/broadcast/files.md +58 -0
  62. package/server/lib/broadcast/index.js +57 -0
  63. package/server/lib/broadcast/publishProxy.js +110 -0
  64. package/server/lib/broadcast/pubsub.js +137 -0
  65. package/server/lib/broadcast/sendProxy.js +103 -0
  66. package/server/lib/bun.js +315 -99
  67. package/server/lib/fileTransfer/README.md +63 -0
  68. package/server/lib/fileTransfer/files.md +30 -0
  69. package/server/lib/fileTransfer/streaming.js +435 -0
  70. package/server/lib/fileTransfer.js +710 -326
  71. package/server/lib/files.md +111 -0
  72. package/server/lib/httpUtils.js +283 -0
  73. package/server/lib/loader.js +208 -7
  74. package/server/lib/longPolling/README.md +63 -0
  75. package/server/lib/longPolling/files.md +44 -0
  76. package/server/lib/longPolling/getHandler.js +365 -0
  77. package/server/lib/longPolling/postHandler.js +327 -0
  78. package/server/lib/longPolling.js +174 -219
  79. package/server/lib/main.js +369 -532
  80. package/server/lib/runtimes/README.md +42 -0
  81. package/server/lib/runtimes/bun.js +586 -0
  82. package/server/lib/runtimes/files.md +56 -0
  83. package/server/lib/runtimes/node.js +511 -0
  84. package/server/lib/wiring.js +539 -98
  85. package/server/lib/ws/README.md +35 -0
  86. package/server/lib/ws/adapters/README.md +54 -0
  87. package/server/lib/ws/adapters/bun.js +538 -170
  88. package/server/lib/ws/adapters/deno.js +623 -149
  89. package/server/lib/ws/adapters/files.md +42 -0
  90. package/server/lib/ws/files.md +74 -0
  91. package/server/lib/ws/frames.js +532 -154
  92. package/server/lib/ws/index.js +207 -10
  93. package/server/lib/ws/server.js +385 -92
  94. package/server/lib/ws/socket.js +549 -181
  95. package/server/lib/wsProvider.js +363 -89
  96. package/server/plugins/binary.js +282 -0
  97. package/server/security/README.md +92 -0
  98. package/server/security/auth/README.md +319 -0
  99. package/server/security/auth/adapters/files.md +95 -0
  100. package/server/security/auth/adapters/ldap/constants.js +37 -0
  101. package/server/security/auth/adapters/ldap/files.md +19 -0
  102. package/server/security/auth/adapters/ldap/helpers.js +111 -0
  103. package/server/security/auth/adapters/ldap.js +353 -0
  104. package/server/security/auth/adapters/oauth2/constants.js +41 -0
  105. package/server/security/auth/adapters/oauth2/files.md +19 -0
  106. package/server/security/auth/adapters/oauth2/helpers.js +123 -0
  107. package/server/security/auth/adapters/oauth2.js +273 -0
  108. package/server/security/auth/adapters/opaque-handlers.js +314 -0
  109. package/server/security/auth/adapters/opaque.js +205 -0
  110. package/server/security/auth/adapters/saml/constants.js +52 -0
  111. package/server/security/auth/adapters/saml/files.md +19 -0
  112. package/server/security/auth/adapters/saml/helpers.js +74 -0
  113. package/server/security/auth/adapters/saml.js +173 -0
  114. package/server/security/auth/adapters/totp.js +703 -0
  115. package/server/security/auth/adapters/webauthn.js +625 -0
  116. package/server/security/auth/files.md +61 -0
  117. package/server/security/auth/framework/constants.js +27 -0
  118. package/server/security/auth/framework/files.md +23 -0
  119. package/server/security/auth/framework/handlers.js +272 -0
  120. package/server/security/auth/framework/socket-auth.js +177 -0
  121. package/server/security/auth/handlers/auth-messages.js +143 -0
  122. package/server/security/auth/handlers/files.md +28 -0
  123. package/server/security/auth/index.js +290 -0
  124. package/server/security/auth/mfa/crypto/aead.js +148 -0
  125. package/server/security/auth/mfa/crypto/constants.js +35 -0
  126. package/server/security/auth/mfa/crypto/files.md +27 -0
  127. package/server/security/auth/mfa/crypto/kdf.js +120 -0
  128. package/server/security/auth/mfa/crypto/utils.js +68 -0
  129. package/server/security/auth/mfa/crypto-utils.js +80 -0
  130. package/server/security/auth/mfa/files.md +77 -0
  131. package/server/security/auth/mfa/ledger/constants.js +75 -0
  132. package/server/security/auth/mfa/ledger/errors.js +73 -0
  133. package/server/security/auth/mfa/ledger/files.md +23 -0
  134. package/server/security/auth/mfa/ledger/share-record.js +32 -0
  135. package/server/security/auth/mfa/ledger.js +255 -0
  136. package/server/security/auth/mfa/recovery/constants.js +67 -0
  137. package/server/security/auth/mfa/recovery/files.md +19 -0
  138. package/server/security/auth/mfa/recovery/handlers.js +216 -0
  139. package/server/security/auth/mfa/recovery.js +191 -0
  140. package/server/security/auth/mfa/sss/constants.js +21 -0
  141. package/server/security/auth/mfa/sss/files.md +23 -0
  142. package/server/security/auth/mfa/sss/gf256.js +103 -0
  143. package/server/security/auth/mfa/sss/serialization.js +82 -0
  144. package/server/security/auth/mfa/sss.js +161 -0
  145. package/server/security/auth/mfa/two-of-three/constants.js +58 -0
  146. package/server/security/auth/mfa/two-of-three/files.md +23 -0
  147. package/server/security/auth/mfa/two-of-three/handlers.js +241 -0
  148. package/server/security/auth/mfa/two-of-three/helpers.js +71 -0
  149. package/server/security/auth/mfa/two-of-three.js +136 -0
  150. package/server/security/auth/nonce-manager.js +89 -0
  151. package/server/security/auth/state-machine-mfa.js +269 -0
  152. package/server/security/auth/state-machine.js +257 -0
  153. package/server/security/extractRootDomain.js +144 -16
  154. package/server/security/files.md +51 -0
  155. package/server/security/origin.js +197 -15
  156. package/server/security/reply.js +274 -16
  157. package/server/socket/README.md +119 -0
  158. package/server/socket/authMiddleware.js +299 -0
  159. package/server/socket/files.md +86 -0
  160. package/server/socket/open.js +154 -8
  161. package/server/socket/pluginHooks.js +334 -0
  162. package/server/socket/receive.js +184 -224
  163. package/server/socket/receiveContext.js +117 -0
  164. package/server/socket/send.js +416 -78
  165. package/server/socket/tagUtils.js +402 -0
  166. package/server/utils/README.md +19 -0
  167. package/server/utils/deepRequire.js +255 -30
  168. package/server/utils/files.md +57 -0
  169. package/server/utils/genId.js +182 -20
  170. package/server/utils/parseUserAgent.js +313 -251
  171. package/server/utils/userAgent/README.md +65 -0
  172. package/server/utils/userAgent/files.md +46 -0
  173. package/server/utils/userAgent/patterns.js +545 -0
  174. package/utils/README.md +21 -0
  175. package/utils/files.md +66 -0
  176. package/utils/jss/README.md +21 -0
  177. package/utils/jss/decode.js +471 -0
  178. package/utils/jss/encode.js +312 -0
  179. package/utils/jss/files.md +68 -0
  180. package/utils/jss/plugins.js +210 -0
  181. package/utils/jss.js +219 -273
  182. package/utils/messageHash.js +238 -35
  183. package/dist/api-ape.min.js +0 -2
  184. package/dist/api-ape.min.js.map +0 -7
  185. package/server/client.js +0 -311
  186. package/server/lib/broadcast.js +0 -146
@@ -1,109 +1,402 @@
1
1
  /**
2
- * WebSocketServer class
3
- * Handles HTTP upgrade requests and creates WebSocket connections
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('events')
7
- const { generateAcceptKey } = require('./frames')
8
- const { WebSocket } = require('./socket')
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
- * Create WebSocket server
13
- * @param {{ noServer?: boolean }} options
162
+ * Whether this server operates in noServer mode.
163
+ * @type {boolean}
164
+ * @private
14
165
  */
15
- constructor(options = {}) {
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
- * Get all connected clients
23
- * @returns {Set<WebSocket>}
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
- get clients() {
26
- return this._clients
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
- * Handle HTTP upgrade request
31
- * @param {http.IncomingMessage} req - HTTP request
32
- * @param {net.Socket} socket - TCP socket
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
- * Close server and all connections
97
- */
98
- close(callback) {
99
- for (const client of this._clients) {
100
- client.close(1001, 'Server shutting down')
101
- }
102
- this._clients.clear()
103
- if (callback) {
104
- callback()
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 };