api-ape 3.0.1 → 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 +58 -570
- 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 -202
- package/dist/ape.js +6 -1
- package/dist/ape.js.map +4 -4
- package/index.d.ts +38 -16
- package/package.json +32 -7
- package/server/README.md +287 -53
- package/server/adapters/README.md +28 -19
- 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 +332 -27
- 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 -221
- 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 -225
- 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 -308
- package/server/lib/broadcast.js +0 -146
|
@@ -1,186 +1,660 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Deno Native WebSocket Adapter
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
2
|
+
* @fileoverview Deno Native WebSocket Adapter
|
|
3
|
+
*
|
|
4
|
+
* This module provides adapter classes that wrap Deno's native WebSocket
|
|
5
|
+
* implementation to be compatible with the `ws` library API. This enables
|
|
6
|
+
* api-ape to work seamlessly with Deno's runtime.
|
|
7
|
+
*
|
|
8
|
+
* ## Why an Adapter?
|
|
9
|
+
*
|
|
10
|
+
* Deno has a unique WebSocket API compared to Node.js:
|
|
11
|
+
* - Uses `Deno.upgradeWebSocket(req)` which returns `{ socket, response }`
|
|
12
|
+
* - WebSocket uses `onmessage`, `onclose`, `onerror` properties instead of EventEmitter
|
|
13
|
+
* - The upgrade process returns a Response that must be returned from the handler
|
|
14
|
+
*
|
|
15
|
+
* This adapter bridges these differences by:
|
|
16
|
+
* - Wrapping Deno's WebSocket in an EventEmitter-based interface
|
|
17
|
+
* - Providing `ws`-compatible methods (`send`, `close`, `readyState`)
|
|
18
|
+
* - Handling the upgrade response flow that Deno requires
|
|
19
|
+
*
|
|
20
|
+
* ## Architecture
|
|
21
|
+
*
|
|
22
|
+
* ```
|
|
23
|
+
* Deno.serve() ──> DenoWebSocketServer ──> DenoWebSocket
|
|
24
|
+
* │ │ │
|
|
25
|
+
* │ Request │ handleUpgrade() │ EventEmitter
|
|
26
|
+
* │ Response │ clients Set │ send(), close()
|
|
27
|
+
* │ │ │ readyState
|
|
28
|
+
* ```
|
|
29
|
+
*
|
|
30
|
+
* ## Usage with Deno
|
|
31
|
+
*
|
|
32
|
+
* ```javascript
|
|
33
|
+
* const { DenoWebSocketServer } = require('./ws/adapters/deno')
|
|
34
|
+
*
|
|
35
|
+
* const wss = new DenoWebSocketServer({ noServer: true })
|
|
36
|
+
*
|
|
37
|
+
* wss.on('connection', (ws, req) => {
|
|
38
|
+
* ws.on('message', (data) => console.log('Received:', data))
|
|
39
|
+
* ws.send('Hello from Deno!')
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* Deno.serve((req) => {
|
|
43
|
+
* if (new URL(req.url).pathname === '/ws') {
|
|
44
|
+
* const result = wss.handleUpgrade(req)
|
|
45
|
+
* return result?.response || new Response('Upgrade failed', { status: 500 })
|
|
46
|
+
* }
|
|
47
|
+
* return new Response('Hello')
|
|
48
|
+
* })
|
|
49
|
+
* ```
|
|
50
|
+
*
|
|
51
|
+
* @module server/lib/ws/adapters/deno
|
|
52
|
+
* @see {@link module:server/lib/ws} - Polyfill WebSocket implementation
|
|
53
|
+
* @see {@link module:server/lib/ws/adapters/bun} - Similar adapter for Bun runtime
|
|
54
|
+
*
|
|
55
|
+
* @example
|
|
56
|
+
* // Basic Deno server with WebSocket
|
|
57
|
+
* const { DenoWebSocketServer } = require('./ws/adapters/deno')
|
|
58
|
+
*
|
|
59
|
+
* const wss = new DenoWebSocketServer({ noServer: true })
|
|
60
|
+
*
|
|
61
|
+
* wss.on('connection', (ws) => {
|
|
62
|
+
* console.log('Client connected')
|
|
63
|
+
*
|
|
64
|
+
* ws.on('message', (data) => {
|
|
65
|
+
* console.log('Message:', data.toString())
|
|
66
|
+
* ws.send('Echo: ' + data.toString())
|
|
67
|
+
* })
|
|
68
|
+
*
|
|
69
|
+
* ws.on('close', () => {
|
|
70
|
+
* console.log('Client disconnected')
|
|
71
|
+
* })
|
|
72
|
+
* })
|
|
73
|
+
*
|
|
74
|
+
* @example
|
|
75
|
+
* // Broadcasting to all connected clients
|
|
76
|
+
* function broadcast(message) {
|
|
77
|
+
* for (const client of wss.clients) {
|
|
78
|
+
* if (client.readyState === client.OPEN) {
|
|
79
|
+
* client.send(message)
|
|
80
|
+
* }
|
|
81
|
+
* }
|
|
82
|
+
* }
|
|
11
83
|
*/
|
|
12
84
|
|
|
13
|
-
const { EventEmitter } = require(
|
|
85
|
+
const { EventEmitter } = require("events");
|
|
14
86
|
|
|
15
|
-
|
|
87
|
+
/**
|
|
88
|
+
* WebSocket ready state constants matching the W3C WebSocket API.
|
|
89
|
+
*
|
|
90
|
+
* These match the standard WebSocket readyState values and are
|
|
91
|
+
* compatible with both the browser WebSocket API and the ws library.
|
|
92
|
+
*
|
|
93
|
+
* @readonly
|
|
94
|
+
* @enum {number}
|
|
95
|
+
* @private
|
|
96
|
+
*/
|
|
16
97
|
const READY_STATES = {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
98
|
+
/** Connection is being established (0) */
|
|
99
|
+
CONNECTING: 0,
|
|
100
|
+
/** Connection is open and ready for communication (1) */
|
|
101
|
+
OPEN: 1,
|
|
102
|
+
/** Connection is in the process of closing (2) */
|
|
103
|
+
CLOSING: 2,
|
|
104
|
+
/** Connection has been closed (3) */
|
|
105
|
+
CLOSED: 3,
|
|
106
|
+
};
|
|
22
107
|
|
|
23
108
|
/**
|
|
24
|
-
* Wrapper around Deno's native WebSocket to provide ws-compatible API
|
|
25
|
-
*
|
|
109
|
+
* Wrapper around Deno's native WebSocket to provide ws-compatible API.
|
|
110
|
+
*
|
|
111
|
+
* Deno's WebSocket uses property-based event handlers (`onmessage`, `onclose`,
|
|
112
|
+
* `onerror`) instead of Node.js EventEmitter pattern. This class bridges
|
|
113
|
+
* that difference by:
|
|
114
|
+
* - Extending EventEmitter for event-based API
|
|
115
|
+
* - Wiring Deno's property handlers to emit events
|
|
116
|
+
* - Converting message data to Buffer for consistency
|
|
117
|
+
*
|
|
118
|
+
* ## Events
|
|
119
|
+
*
|
|
120
|
+
* | Event | Arguments | Description |
|
|
121
|
+
* |-----------|---------------------|--------------------------------|
|
|
122
|
+
* | `message` | `(data: Buffer)` | Received a message |
|
|
123
|
+
* | `close` | `(code?, reason?)` | Connection was closed |
|
|
124
|
+
* | `error` | `(event)` | An error occurred |
|
|
125
|
+
*
|
|
126
|
+
* ## Data Handling
|
|
127
|
+
*
|
|
128
|
+
* All incoming messages are converted to Buffer for consistency with
|
|
129
|
+
* the ws library. String messages are encoded as UTF-8 Buffers.
|
|
130
|
+
*
|
|
131
|
+
* When sending, Buffers are converted to ArrayBuffer (which Deno's
|
|
132
|
+
* WebSocket expects), while strings are passed through directly.
|
|
133
|
+
*
|
|
134
|
+
* @class DenoWebSocket
|
|
135
|
+
* @extends EventEmitter
|
|
136
|
+
*
|
|
137
|
+
* @param {WebSocket} denoSocket - Deno's native WebSocket instance
|
|
138
|
+
*
|
|
139
|
+
* @example
|
|
140
|
+
* // Creating a wrapper (done by DenoWebSocketServer)
|
|
141
|
+
* const { socket } = Deno.upgradeWebSocket(req)
|
|
142
|
+
* const ws = new DenoWebSocket(socket)
|
|
143
|
+
*
|
|
144
|
+
* ws.on('message', (data) => {
|
|
145
|
+
* console.log('Received:', data.toString())
|
|
146
|
+
* })
|
|
147
|
+
*
|
|
148
|
+
* ws.send('Hello!')
|
|
149
|
+
*
|
|
150
|
+
* @example
|
|
151
|
+
* // Check ready state before sending
|
|
152
|
+
* if (ws.readyState === ws.OPEN) {
|
|
153
|
+
* ws.send(JSON.stringify({ type: 'ping' }))
|
|
154
|
+
* }
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* // Handle connection close
|
|
158
|
+
* ws.on('close', (code, reason) => {
|
|
159
|
+
* console.log(`Connection closed: ${code} - ${reason}`)
|
|
160
|
+
* })
|
|
26
161
|
*/
|
|
27
162
|
class DenoWebSocket extends EventEmitter {
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
// Wire up Deno's event properties to our EventEmitter
|
|
40
|
-
this._setupDenoEvents()
|
|
41
|
-
}
|
|
163
|
+
/**
|
|
164
|
+
* Create a new DenoWebSocket wrapper.
|
|
165
|
+
*
|
|
166
|
+
* Sets up the wrapper around Deno's native WebSocket and wires
|
|
167
|
+
* up the event handlers to emit EventEmitter events.
|
|
168
|
+
*
|
|
169
|
+
* @param {WebSocket} denoSocket - Deno's native WebSocket instance
|
|
170
|
+
* (from Deno.upgradeWebSocket())
|
|
171
|
+
*/
|
|
172
|
+
constructor(denoSocket) {
|
|
173
|
+
super();
|
|
42
174
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
175
|
+
/**
|
|
176
|
+
* The underlying Deno WebSocket instance.
|
|
177
|
+
* @type {WebSocket}
|
|
178
|
+
* @private
|
|
179
|
+
*/
|
|
180
|
+
this._socket = denoSocket;
|
|
46
181
|
|
|
47
182
|
/**
|
|
48
|
-
*
|
|
49
|
-
* @
|
|
183
|
+
* Current connection state.
|
|
184
|
+
* @type {number}
|
|
185
|
+
* @private
|
|
50
186
|
*/
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const buffer = typeof data === 'string'
|
|
56
|
-
? Buffer.from(data)
|
|
57
|
-
: Buffer.from(data)
|
|
58
|
-
this.emit('message', buffer)
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
this._socket.onclose = (event) => {
|
|
62
|
-
this._readyState = READY_STATES.CLOSED
|
|
63
|
-
this.emit('close', event.code, event.reason)
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
this._socket.onerror = (event) => {
|
|
67
|
-
this.emit('error', event)
|
|
68
|
-
}
|
|
69
|
-
}
|
|
187
|
+
this._readyState = READY_STATES.OPEN;
|
|
188
|
+
|
|
189
|
+
// Expose ready states as instance properties for convenience
|
|
190
|
+
// (matching ws library API)
|
|
70
191
|
|
|
71
192
|
/**
|
|
72
|
-
*
|
|
73
|
-
* @
|
|
193
|
+
* CONNECTING ready state constant (0).
|
|
194
|
+
* @type {number}
|
|
195
|
+
* @readonly
|
|
74
196
|
*/
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
197
|
+
this.CONNECTING = READY_STATES.CONNECTING;
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* OPEN ready state constant (1).
|
|
201
|
+
* @type {number}
|
|
202
|
+
* @readonly
|
|
203
|
+
*/
|
|
204
|
+
this.OPEN = READY_STATES.OPEN;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* CLOSING ready state constant (2).
|
|
208
|
+
* @type {number}
|
|
209
|
+
* @readonly
|
|
210
|
+
*/
|
|
211
|
+
this.CLOSING = READY_STATES.CLOSING;
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* CLOSED ready state constant (3).
|
|
215
|
+
* @type {number}
|
|
216
|
+
* @readonly
|
|
217
|
+
*/
|
|
218
|
+
this.CLOSED = READY_STATES.CLOSED;
|
|
219
|
+
|
|
220
|
+
// Wire up Deno's event properties to our EventEmitter
|
|
221
|
+
this._setupDenoEvents();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Get the current ready state of the WebSocket connection.
|
|
226
|
+
*
|
|
227
|
+
* @type {number}
|
|
228
|
+
* @readonly
|
|
229
|
+
*
|
|
230
|
+
* @example
|
|
231
|
+
* switch (ws.readyState) {
|
|
232
|
+
* case ws.CONNECTING:
|
|
233
|
+
* console.log('Connecting...')
|
|
234
|
+
* break
|
|
235
|
+
* case ws.OPEN:
|
|
236
|
+
* console.log('Ready to send')
|
|
237
|
+
* ws.send('Hello!')
|
|
238
|
+
* break
|
|
239
|
+
* case ws.CLOSING:
|
|
240
|
+
* console.log('Closing...')
|
|
241
|
+
* break
|
|
242
|
+
* case ws.CLOSED:
|
|
243
|
+
* console.log('Closed')
|
|
244
|
+
* break
|
|
245
|
+
* }
|
|
246
|
+
*/
|
|
247
|
+
get readyState() {
|
|
248
|
+
return this._readyState;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Setup Deno WebSocket event handlers.
|
|
253
|
+
*
|
|
254
|
+
* Wires Deno's property-based event handlers (`onmessage`, `onclose`,
|
|
255
|
+
* `onerror`) to emit EventEmitter events for ws library compatibility.
|
|
256
|
+
*
|
|
257
|
+
* Message data is converted to Buffer for consistency with the ws
|
|
258
|
+
* library, which always provides Buffer instances for message data.
|
|
259
|
+
*
|
|
260
|
+
* @private
|
|
261
|
+
*/
|
|
262
|
+
_setupDenoEvents() {
|
|
263
|
+
/**
|
|
264
|
+
* Handle incoming messages from Deno's WebSocket.
|
|
265
|
+
* Converts all data to Buffer for consistency with ws library.
|
|
266
|
+
*/
|
|
267
|
+
this._socket.onmessage = (event) => {
|
|
268
|
+
// Convert to Buffer for consistency with ws library
|
|
269
|
+
const data = event.data;
|
|
270
|
+
const buffer =
|
|
271
|
+
typeof data === "string" ? Buffer.from(data) : Buffer.from(data);
|
|
272
|
+
this.emit("message", buffer);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
/**
|
|
276
|
+
* Handle connection close from Deno's WebSocket.
|
|
277
|
+
* Updates ready state and emits close event with code and reason.
|
|
278
|
+
*/
|
|
279
|
+
this._socket.onclose = (event) => {
|
|
280
|
+
this._readyState = READY_STATES.CLOSED;
|
|
281
|
+
this.emit("close", event.code, event.reason);
|
|
282
|
+
};
|
|
86
283
|
|
|
87
284
|
/**
|
|
88
|
-
*
|
|
89
|
-
*
|
|
90
|
-
* @param {string} reason - Close reason
|
|
285
|
+
* Handle errors from Deno's WebSocket.
|
|
286
|
+
* Emits the error event with the event object.
|
|
91
287
|
*/
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
288
|
+
this._socket.onerror = (event) => {
|
|
289
|
+
this.emit("error", event);
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
/**
|
|
294
|
+
* Send data to the remote endpoint.
|
|
295
|
+
*
|
|
296
|
+
* Deno's WebSocket.send() accepts string, ArrayBuffer, or Blob.
|
|
297
|
+
* This method handles the conversion from Buffer (Node.js style)
|
|
298
|
+
* to ArrayBuffer (Deno style).
|
|
299
|
+
*
|
|
300
|
+
* @param {string|Buffer|ArrayBuffer} data - Data to send
|
|
301
|
+
* @throws {Error} If the WebSocket is not in the OPEN state
|
|
302
|
+
*
|
|
303
|
+
* @example
|
|
304
|
+
* // Send text
|
|
305
|
+
* ws.send('Hello, World!')
|
|
306
|
+
*
|
|
307
|
+
* @example
|
|
308
|
+
* // Send JSON
|
|
309
|
+
* ws.send(JSON.stringify({ type: 'message', text: 'Hi!' }))
|
|
310
|
+
*
|
|
311
|
+
* @example
|
|
312
|
+
* // Send binary data
|
|
313
|
+
* ws.send(Buffer.from([0x01, 0x02, 0x03, 0x04]))
|
|
314
|
+
*
|
|
315
|
+
* @example
|
|
316
|
+
* // Safe send with state check
|
|
317
|
+
* if (ws.readyState === ws.OPEN) {
|
|
318
|
+
* ws.send('Safe message')
|
|
319
|
+
* }
|
|
320
|
+
*/
|
|
321
|
+
send(data) {
|
|
322
|
+
if (this._readyState !== READY_STATES.OPEN) {
|
|
323
|
+
throw new Error("WebSocket is not open");
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Deno's WebSocket.send() accepts string, ArrayBuffer, or Blob
|
|
327
|
+
// Convert Buffer to ArrayBuffer for Deno compatibility
|
|
328
|
+
if (Buffer.isBuffer(data)) {
|
|
329
|
+
// Extract the underlying ArrayBuffer, accounting for Buffer offset
|
|
330
|
+
this._socket.send(
|
|
331
|
+
data.buffer.slice(data.byteOffset, data.byteOffset + data.byteLength),
|
|
332
|
+
);
|
|
333
|
+
} else {
|
|
334
|
+
// String or ArrayBuffer - pass through directly
|
|
335
|
+
this._socket.send(data);
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
/**
|
|
340
|
+
* Close the WebSocket connection.
|
|
341
|
+
*
|
|
342
|
+
* Initiates a graceful close by sending a close frame to the
|
|
343
|
+
* remote endpoint with the specified code and reason.
|
|
344
|
+
*
|
|
345
|
+
* Standard close codes (RFC 6455):
|
|
346
|
+
* - `1000` - Normal closure
|
|
347
|
+
* - `1001` - Going away (e.g., server shutdown)
|
|
348
|
+
* - `1002` - Protocol error
|
|
349
|
+
* - `1003` - Unsupported data type
|
|
350
|
+
* - `1008` - Policy violation
|
|
351
|
+
* - `1011` - Unexpected server error
|
|
352
|
+
*
|
|
353
|
+
* @param {number} [code=1000] - Close status code (1000-4999)
|
|
354
|
+
* @param {string} [reason=''] - Human-readable close reason (max 123 bytes)
|
|
355
|
+
*
|
|
356
|
+
* @example
|
|
357
|
+
* // Normal close
|
|
358
|
+
* ws.close()
|
|
359
|
+
*
|
|
360
|
+
* @example
|
|
361
|
+
* // Close with code and reason
|
|
362
|
+
* ws.close(1000, 'Session ended')
|
|
363
|
+
*
|
|
364
|
+
* @example
|
|
365
|
+
* // Close due to error
|
|
366
|
+
* ws.close(1008, 'Policy violation')
|
|
367
|
+
*
|
|
368
|
+
* @example
|
|
369
|
+
* // Server shutdown
|
|
370
|
+
* ws.close(1001, 'Server restarting')
|
|
371
|
+
*/
|
|
372
|
+
close(code = 1000, reason = "") {
|
|
373
|
+
// Don't close if already closing or closed
|
|
374
|
+
if (
|
|
375
|
+
this._readyState === READY_STATES.CLOSING ||
|
|
376
|
+
this._readyState === READY_STATES.CLOSED
|
|
377
|
+
) {
|
|
378
|
+
return;
|
|
99
379
|
}
|
|
380
|
+
|
|
381
|
+
this._readyState = READY_STATES.CLOSING;
|
|
382
|
+
this._socket.close(code, reason);
|
|
383
|
+
}
|
|
100
384
|
}
|
|
101
385
|
|
|
102
386
|
/**
|
|
103
|
-
*
|
|
104
|
-
*
|
|
105
|
-
*
|
|
106
|
-
*
|
|
107
|
-
*
|
|
108
|
-
*
|
|
387
|
+
* WebSocket server adapter for Deno's native WebSocket implementation.
|
|
388
|
+
*
|
|
389
|
+
* This class provides a `ws.WebSocketServer`-compatible interface for use
|
|
390
|
+
* with Deno's unique server architecture. It handles the upgrade process
|
|
391
|
+
* using `Deno.upgradeWebSocket()` and manages client connections.
|
|
392
|
+
*
|
|
393
|
+
* ## Key Differences from ws.WebSocketServer
|
|
394
|
+
*
|
|
395
|
+
* 1. **handleUpgrade Return Value**: Returns `{ response }` which must be
|
|
396
|
+
* returned from the Deno request handler.
|
|
397
|
+
*
|
|
398
|
+
* 2. **Upgrade Mechanism**: Uses `Deno.upgradeWebSocket()` internally
|
|
399
|
+
* instead of raw socket manipulation.
|
|
400
|
+
*
|
|
401
|
+
* 3. **No HTTP Server Integration**: Designed for noServer mode only,
|
|
402
|
+
* integrates with `Deno.serve()`.
|
|
403
|
+
*
|
|
404
|
+
* ## Events
|
|
405
|
+
*
|
|
406
|
+
* | Event | Arguments | Description |
|
|
407
|
+
* |--------------|------------------------------|--------------------------|
|
|
408
|
+
* | `connection` | `(ws: DenoWebSocket, req)` | New client connected |
|
|
409
|
+
*
|
|
410
|
+
* @class DenoWebSocketServer
|
|
411
|
+
* @extends EventEmitter
|
|
412
|
+
*
|
|
413
|
+
* @param {Object} [options={}] - Server configuration
|
|
414
|
+
* @param {boolean} [options.noServer=false] - Run without creating HTTP server
|
|
415
|
+
*
|
|
416
|
+
* @example
|
|
417
|
+
* // Basic setup with Deno.serve()
|
|
418
|
+
* const wss = new DenoWebSocketServer({ noServer: true })
|
|
419
|
+
*
|
|
420
|
+
* wss.on('connection', (ws, req) => {
|
|
421
|
+
* console.log('Client connected')
|
|
422
|
+
* ws.on('message', (data) => console.log('Received:', data))
|
|
423
|
+
* ws.send('Welcome!')
|
|
424
|
+
* })
|
|
425
|
+
*
|
|
426
|
+
* Deno.serve((req) => {
|
|
427
|
+
* const url = new URL(req.url)
|
|
428
|
+
*
|
|
429
|
+
* if (url.pathname === '/ws') {
|
|
430
|
+
* const result = wss.handleUpgrade(req)
|
|
431
|
+
* if (result) {
|
|
432
|
+
* return result.response
|
|
433
|
+
* }
|
|
434
|
+
* return new Response('Upgrade failed', { status: 500 })
|
|
435
|
+
* }
|
|
436
|
+
*
|
|
437
|
+
* return new Response('Hello from Deno!')
|
|
438
|
+
* })
|
|
439
|
+
*
|
|
440
|
+
* @example
|
|
441
|
+
* // Broadcasting to all clients
|
|
442
|
+
* function broadcast(message) {
|
|
443
|
+
* for (const client of wss.clients) {
|
|
444
|
+
* if (client.readyState === client.OPEN) {
|
|
445
|
+
* client.send(message)
|
|
446
|
+
* }
|
|
447
|
+
* }
|
|
448
|
+
* }
|
|
449
|
+
*
|
|
450
|
+
* @example
|
|
451
|
+
* // Graceful shutdown
|
|
452
|
+
* async function shutdown() {
|
|
453
|
+
* wss.close(() => {
|
|
454
|
+
* console.log('All WebSocket connections closed')
|
|
455
|
+
* })
|
|
456
|
+
* }
|
|
109
457
|
*/
|
|
110
458
|
class DenoWebSocketServer extends EventEmitter {
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
459
|
+
/**
|
|
460
|
+
* Create a new DenoWebSocketServer instance.
|
|
461
|
+
*
|
|
462
|
+
* @param {Object} [options={}] - Configuration options
|
|
463
|
+
* @param {boolean} [options.noServer=false] - Run in noServer mode
|
|
464
|
+
* (this is the typical mode for Deno integration)
|
|
465
|
+
*/
|
|
466
|
+
constructor(options = {}) {
|
|
467
|
+
super();
|
|
116
468
|
|
|
117
469
|
/**
|
|
118
|
-
*
|
|
119
|
-
* @
|
|
470
|
+
* Whether this server operates in noServer mode.
|
|
471
|
+
* @type {boolean}
|
|
472
|
+
* @private
|
|
120
473
|
*/
|
|
121
|
-
|
|
122
|
-
return this._clients
|
|
123
|
-
}
|
|
474
|
+
this._noServer = options.noServer || false;
|
|
124
475
|
|
|
125
476
|
/**
|
|
126
|
-
*
|
|
127
|
-
* @
|
|
128
|
-
* @
|
|
129
|
-
* @param {*} _head - Not used in Deno (placeholder for API compat)
|
|
130
|
-
* @param {function} callback - Called with wrapped WebSocket
|
|
131
|
-
* @returns {{ response: Response } | null} - Response to return from handler
|
|
477
|
+
* Set of all connected DenoWebSocket clients.
|
|
478
|
+
* @type {Set<DenoWebSocket>}
|
|
479
|
+
* @private
|
|
132
480
|
*/
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
481
|
+
this._clients = new Set();
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
/**
|
|
485
|
+
* Get all connected WebSocket clients.
|
|
486
|
+
*
|
|
487
|
+
* Returns a Set of DenoWebSocket wrappers for all active connections.
|
|
488
|
+
* Clients are automatically added when connections are established
|
|
489
|
+
* and removed when connections close.
|
|
490
|
+
*
|
|
491
|
+
* @type {Set<DenoWebSocket>}
|
|
492
|
+
* @readonly
|
|
493
|
+
*
|
|
494
|
+
* @example
|
|
495
|
+
* // Count connected clients
|
|
496
|
+
* console.log(`${wss.clients.size} clients connected`)
|
|
497
|
+
*
|
|
498
|
+
* @example
|
|
499
|
+
* // Broadcast to all clients
|
|
500
|
+
* for (const client of wss.clients) {
|
|
501
|
+
* if (client.readyState === client.OPEN) {
|
|
502
|
+
* client.send('Hello everyone!')
|
|
503
|
+
* }
|
|
504
|
+
* }
|
|
505
|
+
*
|
|
506
|
+
* @example
|
|
507
|
+
* // Convert to array for filtering
|
|
508
|
+
* const activeClients = [...wss.clients].filter(c =>
|
|
509
|
+
* c.readyState === c.OPEN
|
|
510
|
+
* )
|
|
511
|
+
*/
|
|
512
|
+
get clients() {
|
|
513
|
+
return this._clients;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* Handle an HTTP upgrade request using Deno.upgradeWebSocket.
|
|
518
|
+
*
|
|
519
|
+
* This method uses Deno's built-in `Deno.upgradeWebSocket()` function
|
|
520
|
+
* to upgrade the HTTP connection to a WebSocket. The returned object
|
|
521
|
+
* contains a `response` property that MUST be returned from the Deno
|
|
522
|
+
* request handler.
|
|
523
|
+
*
|
|
524
|
+
* ## Deno Upgrade Flow
|
|
525
|
+
*
|
|
526
|
+
* 1. Check for 'Upgrade: websocket' header
|
|
527
|
+
* 2. Call `Deno.upgradeWebSocket(req)` to get socket and response
|
|
528
|
+
* 3. Wrap the socket in DenoWebSocket adapter
|
|
529
|
+
* 4. Add to clients set and emit 'connection' event
|
|
530
|
+
* 5. Return `{ response }` for the caller to return to Deno
|
|
531
|
+
*
|
|
532
|
+
* @param {Request} req - Deno Request object
|
|
533
|
+
* @param {*} _socket - Not used in Deno (placeholder for API compatibility)
|
|
534
|
+
* @param {*} _head - Not used in Deno (placeholder for API compatibility)
|
|
535
|
+
* @param {Function} [callback] - Called with the DenoWebSocket wrapper
|
|
536
|
+
* @returns {{ response: Response } | null} Object with Response to return,
|
|
537
|
+
* or null if upgrade failed
|
|
538
|
+
*
|
|
539
|
+
* @example
|
|
540
|
+
* // In Deno.serve() handler
|
|
541
|
+
* Deno.serve((req) => {
|
|
542
|
+
* if (new URL(req.url).pathname === '/ws') {
|
|
543
|
+
* const result = wss.handleUpgrade(req, null, null, (ws) => {
|
|
544
|
+
* console.log('WebSocket ready')
|
|
545
|
+
* })
|
|
546
|
+
*
|
|
547
|
+
* if (result) {
|
|
548
|
+
* return result.response
|
|
549
|
+
* }
|
|
550
|
+
* return new Response('Upgrade failed', { status: 500 })
|
|
551
|
+
* }
|
|
552
|
+
*
|
|
553
|
+
* return new Response('Not found', { status: 404 })
|
|
554
|
+
* })
|
|
555
|
+
*
|
|
556
|
+
* @example
|
|
557
|
+
* // Without callback (use 'connection' event instead)
|
|
558
|
+
* wss.on('connection', (ws, req) => {
|
|
559
|
+
* console.log('Client connected from:', req.url)
|
|
560
|
+
* })
|
|
561
|
+
*
|
|
562
|
+
* Deno.serve((req) => {
|
|
563
|
+
* if (shouldUpgrade(req)) {
|
|
564
|
+
* const result = wss.handleUpgrade(req)
|
|
565
|
+
* return result?.response || new Response('Failed', { status: 500 })
|
|
566
|
+
* }
|
|
567
|
+
* return new Response('Hello')
|
|
568
|
+
* })
|
|
569
|
+
*/
|
|
570
|
+
handleUpgrade(req, _socket, _head, callback) {
|
|
571
|
+
// Check for upgrade header
|
|
572
|
+
const upgrade = req.headers.get("upgrade");
|
|
573
|
+
if (!upgrade || upgrade.toLowerCase() !== "websocket") {
|
|
574
|
+
return null;
|
|
167
575
|
}
|
|
168
576
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
577
|
+
try {
|
|
578
|
+
// Use Deno's built-in upgrade mechanism
|
|
579
|
+
const { socket: denoSocket, response } = Deno.upgradeWebSocket(req);
|
|
580
|
+
|
|
581
|
+
// Wrap with our adapter for ws-compatible API
|
|
582
|
+
const wrapper = new DenoWebSocket(denoSocket);
|
|
583
|
+
this._clients.add(wrapper);
|
|
584
|
+
|
|
585
|
+
// Remove from clients set when connection closes
|
|
586
|
+
wrapper.on("close", () => {
|
|
587
|
+
this._clients.delete(wrapper);
|
|
588
|
+
});
|
|
589
|
+
|
|
590
|
+
// Invoke callback if provided
|
|
591
|
+
if (callback) {
|
|
592
|
+
callback(wrapper);
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
// Emit connection event for ws library compatibility
|
|
596
|
+
this.emit("connection", wrapper, req);
|
|
597
|
+
|
|
598
|
+
// Return the response for Deno's handler to return
|
|
599
|
+
return { response };
|
|
600
|
+
} catch (err) {
|
|
601
|
+
console.error("[api-ape] Deno WebSocket upgrade failed:", err);
|
|
602
|
+
return null;
|
|
180
603
|
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Close the server and all active connections.
|
|
608
|
+
*
|
|
609
|
+
* Sends a close frame with code 1001 (Going Away) and reason
|
|
610
|
+
* "Server shutting down" to all connected clients, then clears
|
|
611
|
+
* the clients set.
|
|
612
|
+
*
|
|
613
|
+
* @param {Function} [callback] - Called after all connections are closed
|
|
614
|
+
*
|
|
615
|
+
* @example
|
|
616
|
+
* // Graceful shutdown
|
|
617
|
+
* wss.close(() => {
|
|
618
|
+
* console.log('All WebSocket connections closed')
|
|
619
|
+
* Deno.exit(0)
|
|
620
|
+
* })
|
|
621
|
+
*
|
|
622
|
+
* @example
|
|
623
|
+
* // Shutdown with notification
|
|
624
|
+
* for (const client of wss.clients) {
|
|
625
|
+
* client.send(JSON.stringify({ type: 'shutdown', message: 'Server restarting' }))
|
|
626
|
+
* }
|
|
627
|
+
*
|
|
628
|
+
* setTimeout(() => {
|
|
629
|
+
* wss.close(() => console.log('Shutdown complete'))
|
|
630
|
+
* }, 1000)
|
|
631
|
+
*
|
|
632
|
+
* @example
|
|
633
|
+
* // Simple close without callback
|
|
634
|
+
* wss.close()
|
|
635
|
+
*/
|
|
636
|
+
close(callback) {
|
|
637
|
+
for (const client of this._clients) {
|
|
638
|
+
client.close(1001, "Server shutting down");
|
|
639
|
+
}
|
|
640
|
+
this._clients.clear();
|
|
641
|
+
|
|
642
|
+
if (callback) {
|
|
643
|
+
callback();
|
|
644
|
+
}
|
|
645
|
+
}
|
|
181
646
|
}
|
|
182
647
|
|
|
183
648
|
module.exports = {
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
649
|
+
/**
|
|
650
|
+
* DenoWebSocket class - ws-compatible wrapper for Deno WebSocket.
|
|
651
|
+
* @type {typeof DenoWebSocket}
|
|
652
|
+
*/
|
|
653
|
+
DenoWebSocket,
|
|
654
|
+
|
|
655
|
+
/**
|
|
656
|
+
* DenoWebSocketServer class - ws-compatible server for Deno.
|
|
657
|
+
* @type {typeof DenoWebSocketServer}
|
|
658
|
+
*/
|
|
659
|
+
DenoWebSocketServer,
|
|
660
|
+
};
|