bare-ws 2.0.1 → 2.0.3
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/index.d.ts +4 -0
- package/lib/constants.d.ts +17 -0
- package/lib/constants.js +2 -3
- package/lib/errors.d.ts +29 -0
- package/lib/errors.js +9 -0
- package/lib/frame.js +12 -9
- package/lib/server.d.ts +63 -0
- package/lib/server.js +3 -3
- package/lib/socket.d.ts +41 -0
- package/lib/socket.js +30 -23
- package/package.json +28 -4
package/index.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Buffer from 'bare-buffer'
|
|
2
|
+
|
|
3
|
+
export const EOL: string
|
|
4
|
+
export const EOF: string
|
|
5
|
+
|
|
6
|
+
export const GUID: Buffer
|
|
7
|
+
|
|
8
|
+
export const opcode: {
|
|
9
|
+
CONTINUATION: number
|
|
10
|
+
TEXT: number
|
|
11
|
+
BINARY: number
|
|
12
|
+
CLOSE: number
|
|
13
|
+
PING: number
|
|
14
|
+
PONG: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const status: { PROTOCOL_ERROR: number; MESSAGE_TOO_LARGE: number }
|
package/lib/constants.js
CHANGED
package/lib/errors.d.ts
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
declare class WebSocketError extends Error {
|
|
2
|
+
constructor(
|
|
3
|
+
msg: string,
|
|
4
|
+
code: string,
|
|
5
|
+
status: number,
|
|
6
|
+
fn?: WebSocketError,
|
|
7
|
+
cause?: unknown
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
static NETWORK_ERROR(msg: string, cause?: unknown): WebSocketError
|
|
11
|
+
static NOT_CONNECTED(msg?: string): WebSocketError
|
|
12
|
+
static UNEXPECTED_RSV1(msg?: string): WebSocketError
|
|
13
|
+
static UNEXPECTED_RSV2(msg?: string): WebSocketError
|
|
14
|
+
static UNEXPECTED_RSV3(msg?: string): WebSocketError
|
|
15
|
+
static EXPECTED_MASK(msg?: string): WebSocketError
|
|
16
|
+
static EXPECTED_CONTINUATION(msg?: string): WebSocketError
|
|
17
|
+
static UNEXPECTED_CONTINUATION(msg?: string): WebSocketError
|
|
18
|
+
static UNEXPECTED_CONTROL(msg?: string): WebSocketError
|
|
19
|
+
static INVALID_ENCODING(msg?: string): WebSocketError
|
|
20
|
+
static INVALID_UPGRADE_HEADER(msg?: string): WebSocketError
|
|
21
|
+
static INVALID_VERSION_HEADER(msg?: string): WebSocketError
|
|
22
|
+
static INVALID_KEY_HEADER(msg?: string): WebSocketError
|
|
23
|
+
static INVALID_ACCEPT_HEADER(msg?: string): WebSocketError
|
|
24
|
+
static INVALID_OPCODE(msg?: string): WebSocketError
|
|
25
|
+
static INVALID_PAYLOAD_LENGTH(msg?: string): WebSocketError
|
|
26
|
+
static INCOMPLETE_FRAME(msg?: string, length?: number): WebSocketError
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export = WebSocketError
|
package/lib/errors.js
CHANGED
|
@@ -168,4 +168,13 @@ module.exports = class WebSocketError extends Error {
|
|
|
168
168
|
WebSocketError.INVALID_PAYLOAD_LENGTH
|
|
169
169
|
)
|
|
170
170
|
}
|
|
171
|
+
|
|
172
|
+
static INCOMPLETE_FRAME(msg = 'Incomplete frame', length = -1) {
|
|
173
|
+
return new WebSocketError(
|
|
174
|
+
msg,
|
|
175
|
+
'INCOMPLETE_FRAME',
|
|
176
|
+
length,
|
|
177
|
+
WebSocketError.INCOMPLETE_FRAME
|
|
178
|
+
)
|
|
179
|
+
}
|
|
171
180
|
}
|
package/lib/frame.js
CHANGED
|
@@ -104,13 +104,13 @@ exports.encode = function encode(state, f) {
|
|
|
104
104
|
|
|
105
105
|
const high = Math.floor(length / 0x100000000)
|
|
106
106
|
|
|
107
|
-
v.
|
|
107
|
+
v.setUint32(i, high, false)
|
|
108
108
|
|
|
109
109
|
i += 4
|
|
110
110
|
|
|
111
111
|
const low = length & 0xffffffff
|
|
112
112
|
|
|
113
|
-
v.
|
|
113
|
+
v.setUint32(i, low, false)
|
|
114
114
|
|
|
115
115
|
i += 4
|
|
116
116
|
}
|
|
@@ -136,12 +136,13 @@ exports.encode = function encode(state, f) {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
exports.decode = function decode(state) {
|
|
139
|
+
const s = state.start
|
|
139
140
|
const b = state.buffer
|
|
140
141
|
|
|
141
|
-
let i =
|
|
142
|
-
let n = b.
|
|
142
|
+
let i = s
|
|
143
|
+
let n = b.byteLength
|
|
143
144
|
|
|
144
|
-
if (n < 2)
|
|
145
|
+
if (n < 2) throw errors.INCOMPLETE_FRAME()
|
|
145
146
|
|
|
146
147
|
const view = new DataView(b.buffer, b.byteOffset, b.byteLength)
|
|
147
148
|
|
|
@@ -164,14 +165,14 @@ exports.decode = function decode(state) {
|
|
|
164
165
|
n--
|
|
165
166
|
|
|
166
167
|
if (length === 0x7e) {
|
|
167
|
-
if (n < 2)
|
|
168
|
+
if (n < 2) throw errors.INCOMPLETE_FRAME()
|
|
168
169
|
|
|
169
170
|
length = view.getUint16(i, false)
|
|
170
171
|
|
|
171
172
|
i += 2
|
|
172
173
|
n -= 2
|
|
173
174
|
} else if (length === 0x7f) {
|
|
174
|
-
if (n < 8)
|
|
175
|
+
if (n < 8) throw errors.INCOMPLETE_FRAME()
|
|
175
176
|
|
|
176
177
|
const high = view.getUint32(i, false)
|
|
177
178
|
|
|
@@ -191,7 +192,7 @@ exports.decode = function decode(state) {
|
|
|
191
192
|
let mask = null
|
|
192
193
|
|
|
193
194
|
if (masked) {
|
|
194
|
-
if (n < 4)
|
|
195
|
+
if (n < 4) throw errors.INCOMPLETE_FRAME()
|
|
195
196
|
|
|
196
197
|
mask = b.subarray(i, i + 4)
|
|
197
198
|
|
|
@@ -199,7 +200,9 @@ exports.decode = function decode(state) {
|
|
|
199
200
|
n -= 4
|
|
200
201
|
}
|
|
201
202
|
|
|
202
|
-
if (n < length)
|
|
203
|
+
if (n < length) {
|
|
204
|
+
throw errors.INCOMPLETE_FRAME('Incomplete frame', i - s + length)
|
|
205
|
+
}
|
|
203
206
|
|
|
204
207
|
const payload = b.subarray(i, i + length)
|
|
205
208
|
|
package/lib/server.d.ts
ADDED
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { type HTTPSServerConnectionOptions } from 'bare-https'
|
|
2
|
+
import { HTTPClientRequest, type HTTPServerConnectionOptions } from 'bare-http1'
|
|
3
|
+
import { Socket as TCPSocket, type TCPSocketAddress } from 'bare-tcp'
|
|
4
|
+
import { type DuplexEvents } from 'bare-stream'
|
|
5
|
+
import EventEmitter from 'bare-events'
|
|
6
|
+
import Buffer from 'bare-buffer'
|
|
7
|
+
import WebSocket from './socket'
|
|
8
|
+
import WebSocketError from './errors'
|
|
9
|
+
|
|
10
|
+
interface WebSocketServerOptions
|
|
11
|
+
extends HTTPServerConnectionOptions,
|
|
12
|
+
HTTPSServerConnectionOptions {
|
|
13
|
+
secure?: boolean
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface WebSocketServerEvents extends DuplexEvents {
|
|
17
|
+
connection: [socket: WebSocket, req: HTTPClientRequest]
|
|
18
|
+
listening: []
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface WebSocketServer<
|
|
22
|
+
M extends WebSocketServerEvents = WebSocketServerEvents
|
|
23
|
+
> extends EventEmitter<M> {
|
|
24
|
+
readonly listening: boolean
|
|
25
|
+
|
|
26
|
+
address(): TCPSocketAddress
|
|
27
|
+
close(cb?: (err: WebSocketError | null) => void): void
|
|
28
|
+
ref(): void
|
|
29
|
+
unref(): void
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare class WebSocketServer {
|
|
33
|
+
constructor(onconnection: (socket: WebSocket, req: HTTPClientRequest) => void)
|
|
34
|
+
|
|
35
|
+
constructor(
|
|
36
|
+
opts?: WebSocketServerOptions,
|
|
37
|
+
onconnection?: (socket: WebSocket, req: HTTPClientRequest) => void
|
|
38
|
+
)
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
declare namespace WebSocketServer {
|
|
42
|
+
export { type WebSocketServerOptions, type WebSocketServerEvents }
|
|
43
|
+
|
|
44
|
+
export function handshake(
|
|
45
|
+
req: HTTPClientRequest,
|
|
46
|
+
cb: (err: WebSocketError | null) => void
|
|
47
|
+
): void
|
|
48
|
+
|
|
49
|
+
export function handshake(
|
|
50
|
+
req: HTTPClientRequest,
|
|
51
|
+
socket: TCPSocket,
|
|
52
|
+
cb?: (err: WebSocketError | null) => void
|
|
53
|
+
): void
|
|
54
|
+
|
|
55
|
+
export function handshake(
|
|
56
|
+
req: HTTPClientRequest,
|
|
57
|
+
socket?: TCPSocket,
|
|
58
|
+
head?: Buffer,
|
|
59
|
+
cb?: (err: WebSocketError | null) => void
|
|
60
|
+
): void
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export = WebSocketServer
|
package/lib/server.js
CHANGED
|
@@ -71,7 +71,7 @@ module.exports = exports = class WebSocketServer extends EventEmitter {
|
|
|
71
71
|
}
|
|
72
72
|
|
|
73
73
|
_onupgrade(req, socket, head) {
|
|
74
|
-
handshake(req, socket, head, (err) => {
|
|
74
|
+
exports.handshake(req, socket, head, (err) => {
|
|
75
75
|
if (err) return socket.destroy(err)
|
|
76
76
|
|
|
77
77
|
this.emit('connection', new WebSocket({ socket, isServer: true }), req)
|
|
@@ -80,7 +80,7 @@ module.exports = exports = class WebSocketServer extends EventEmitter {
|
|
|
80
80
|
}
|
|
81
81
|
|
|
82
82
|
// https://datatracker.ietf.org/doc/html/rfc6455#section-4.2
|
|
83
|
-
|
|
83
|
+
exports.handshake = function handshake(
|
|
84
84
|
req,
|
|
85
85
|
socket = req.socket,
|
|
86
86
|
head = EMPTY,
|
|
@@ -131,4 +131,4 @@ const handshake = (exports.handshake = function handshake(
|
|
|
131
131
|
if (head.byteLength) socket.unshift(head)
|
|
132
132
|
|
|
133
133
|
cb(null)
|
|
134
|
-
}
|
|
134
|
+
}
|
package/lib/socket.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { HTTPClientRequest } from 'bare-http1'
|
|
2
|
+
import { Socket as TCPSocket } from 'bare-tcp'
|
|
3
|
+
import { Duplex, type DuplexEvents } from 'bare-stream'
|
|
4
|
+
import URL from 'bare-url'
|
|
5
|
+
import WebSocketError from './errors'
|
|
6
|
+
|
|
7
|
+
interface WebSocketOptions {
|
|
8
|
+
host?: string
|
|
9
|
+
hostname?: string
|
|
10
|
+
path?: string
|
|
11
|
+
port?: string | number
|
|
12
|
+
secure?: boolean
|
|
13
|
+
socket?: TCPSocket
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface WebSocketEvents extends DuplexEvents {
|
|
17
|
+
ping: [payload: unknown]
|
|
18
|
+
pong: [payload: unknown]
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface WebSocket<M extends WebSocketEvents = WebSocketEvents>
|
|
22
|
+
extends Duplex<M> {
|
|
23
|
+
ping(data: unknown): void
|
|
24
|
+
pong(data: unknown): void
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
declare class WebSocket {
|
|
28
|
+
constructor(opts: WebSocketOptions)
|
|
29
|
+
constructor(url: URL | string, opts?: WebSocketOptions)
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
declare namespace WebSocket {
|
|
33
|
+
export { type WebSocketOptions, type WebSocketEvents }
|
|
34
|
+
|
|
35
|
+
export function handshake(
|
|
36
|
+
req: HTTPClientRequest,
|
|
37
|
+
cb: (error: WebSocketError | null) => void
|
|
38
|
+
): void
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export = WebSocket
|
package/lib/socket.js
CHANGED
|
@@ -40,7 +40,9 @@ module.exports = exports = class WebSocket extends Duplex {
|
|
|
40
40
|
this._pendingOpen = null
|
|
41
41
|
this._pendingWrite = null
|
|
42
42
|
|
|
43
|
-
this._buffer =
|
|
43
|
+
this._buffer = []
|
|
44
|
+
this._buffered = 0
|
|
45
|
+
this._frame = -1
|
|
44
46
|
|
|
45
47
|
if (socket !== null) this._attach(socket)
|
|
46
48
|
else this._connect(opts)
|
|
@@ -81,7 +83,7 @@ module.exports = exports = class WebSocket extends Duplex {
|
|
|
81
83
|
|
|
82
84
|
const req = request(opts)
|
|
83
85
|
|
|
84
|
-
handshake(req, (err) => {
|
|
86
|
+
exports.handshake(req, (err) => {
|
|
85
87
|
const cb = this._pendingOpen
|
|
86
88
|
this._pendingOpen = null
|
|
87
89
|
|
|
@@ -101,29 +103,30 @@ module.exports = exports = class WebSocket extends Duplex {
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
_ondata(data) {
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
this._buffer.push(data)
|
|
107
|
+
this._buffered += data.byteLength
|
|
106
108
|
|
|
107
|
-
while (this.
|
|
108
|
-
const
|
|
109
|
+
while (this._frame === -1 || this._frame <= this._buffered) {
|
|
110
|
+
const buffer =
|
|
111
|
+
this._buffer.length === 1
|
|
112
|
+
? this._buffer[0]
|
|
113
|
+
: Buffer.concat(this._buffer)
|
|
109
114
|
|
|
110
|
-
|
|
111
|
-
try {
|
|
112
|
-
frame = Frame.decode(state)
|
|
113
|
-
} catch (err) {
|
|
114
|
-
return this.destroy(err)
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
if (frame === null) return
|
|
115
|
+
this._buffer = [buffer]
|
|
118
116
|
|
|
119
|
-
|
|
120
|
-
state.start === state.end ? null : this._buffer.subarray(state.start)
|
|
117
|
+
const state = { start: 0, end: buffer.length, buffer }
|
|
121
118
|
|
|
122
119
|
try {
|
|
123
|
-
this._onframe(
|
|
120
|
+
this._onframe(Frame.decode(state))
|
|
124
121
|
} catch (err) {
|
|
125
|
-
|
|
122
|
+
if (err.code === 'INCOMPLETE_FRAME') this._frame = err.status
|
|
123
|
+
else this.destroy(err)
|
|
124
|
+
return
|
|
126
125
|
}
|
|
126
|
+
|
|
127
|
+
this._buffered -= state.start
|
|
128
|
+
this._buffer = this._buffered > 0 ? [buffer.subarray(state.start)] : []
|
|
129
|
+
this._frame = -1
|
|
127
130
|
}
|
|
128
131
|
}
|
|
129
132
|
|
|
@@ -141,16 +144,20 @@ module.exports = exports = class WebSocket extends Duplex {
|
|
|
141
144
|
if (frame.fin === false) {
|
|
142
145
|
if (this._fragments.push(frame) === 1) {
|
|
143
146
|
// First frame
|
|
144
|
-
if (frame.opcode === opcode.CONTINUATION)
|
|
147
|
+
if (frame.opcode === opcode.CONTINUATION) {
|
|
145
148
|
throw errors.UNEXPECTED_CONTINUATION()
|
|
149
|
+
}
|
|
146
150
|
|
|
147
|
-
if (frame.opcode >= opcode.CLOSE)
|
|
151
|
+
if (frame.opcode >= opcode.CLOSE) {
|
|
152
|
+
throw errors.UNEXPECTED_CONTROL()
|
|
153
|
+
}
|
|
148
154
|
|
|
149
155
|
return
|
|
150
156
|
}
|
|
151
157
|
|
|
152
|
-
if (frame.opcode !== opcode.CONTINUATION)
|
|
158
|
+
if (frame.opcode !== opcode.CONTINUATION) {
|
|
153
159
|
throw errors.EXPECTED_CONTINUATION()
|
|
160
|
+
}
|
|
154
161
|
|
|
155
162
|
return
|
|
156
163
|
}
|
|
@@ -240,7 +247,7 @@ module.exports = exports = class WebSocket extends Duplex {
|
|
|
240
247
|
}
|
|
241
248
|
|
|
242
249
|
// https://datatracker.ietf.org/doc/html/rfc6455#section-4.1
|
|
243
|
-
|
|
250
|
+
exports.handshake = function handshake(req, cb) {
|
|
244
251
|
const key = crypto.randomBytes(16).toString('base64')
|
|
245
252
|
|
|
246
253
|
req.headers = {
|
|
@@ -276,7 +283,7 @@ const handshake = (exports.handshake = function handshake(req, cb) {
|
|
|
276
283
|
})
|
|
277
284
|
|
|
278
285
|
req.end()
|
|
279
|
-
}
|
|
286
|
+
}
|
|
280
287
|
|
|
281
288
|
// https://url.spec.whatwg.org/#default-port
|
|
282
289
|
function defaultPort(url) {
|
package/package.json
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "bare-ws",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "WebSocket library for JavaScript",
|
|
5
5
|
"exports": {
|
|
6
|
-
".":
|
|
6
|
+
".": {
|
|
7
|
+
"types": "./index.d.ts",
|
|
8
|
+
"default": "./index.js"
|
|
9
|
+
},
|
|
7
10
|
"./package": "./package.json",
|
|
8
|
-
"./constants":
|
|
9
|
-
|
|
11
|
+
"./constants": {
|
|
12
|
+
"types": "./lib/constants.d.ts",
|
|
13
|
+
"default": "./lib/constants.js"
|
|
14
|
+
},
|
|
15
|
+
"./errors": {
|
|
16
|
+
"types": "./lib/errors.d.ts",
|
|
17
|
+
"default": "./lib/errors.js"
|
|
18
|
+
}
|
|
10
19
|
},
|
|
11
20
|
"files": [
|
|
12
21
|
"index.js",
|
|
22
|
+
"index.d.ts",
|
|
13
23
|
"lib"
|
|
14
24
|
],
|
|
15
25
|
"scripts": {
|
|
@@ -33,9 +43,23 @@
|
|
|
33
43
|
"bare-stream": "^2.1.2"
|
|
34
44
|
},
|
|
35
45
|
"devDependencies": {
|
|
46
|
+
"bare-buffer": "^3.0.2",
|
|
36
47
|
"bare-fs": "^4.0.0",
|
|
48
|
+
"bare-url": "^2.1.3",
|
|
37
49
|
"brittle": "^3.3.0",
|
|
38
50
|
"prettier": "^3.4.1",
|
|
39
51
|
"prettier-config-standard": "^7.0.0"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"bare-buffer": "*",
|
|
55
|
+
"bare-url": "*"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"bare-buffer": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"bare-url": {
|
|
62
|
+
"optional": true
|
|
63
|
+
}
|
|
40
64
|
}
|
|
41
65
|
}
|