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 ADDED
@@ -0,0 +1,4 @@
1
+ import WebSocket from './lib/socket'
2
+ import WebSocketServer from './lib/server'
3
+
4
+ export { WebSocket as Socket, WebSocketServer as Server }
@@ -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
@@ -1,6 +1,5 @@
1
- const EOL = (exports.EOL = '\r\n')
2
-
3
- exports.EOF = EOL.repeat(2)
1
+ exports.EOL = '\r\n'
2
+ exports.EOF = exports.EOL.repeat(2)
4
3
 
5
4
  exports.GUID = Buffer.from('258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
6
5
 
@@ -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.setUint16(i, high, false)
107
+ v.setUint32(i, high, false)
108
108
 
109
109
  i += 4
110
110
 
111
111
  const low = length & 0xffffffff
112
112
 
113
- v.setUint16(i, low, false)
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 = state.start
142
- let n = b.length
142
+ let i = s
143
+ let n = b.byteLength
143
144
 
144
- if (n < 2) return null
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) return null
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) return null
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) return null
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) return null
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
 
@@ -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
- const handshake = (exports.handshake = function handshake(
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
+ }
@@ -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 = null
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
- if (this._buffer === null) this._buffer = data
105
- else this._buffer = Buffer.concat([this._buffer, data])
106
+ this._buffer.push(data)
107
+ this._buffered += data.byteLength
106
108
 
107
- while (this._buffer !== null) {
108
- const state = { start: 0, end: this._buffer.length, buffer: this._buffer }
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
- let frame
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
- this._buffer =
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(frame)
120
+ this._onframe(Frame.decode(state))
124
121
  } catch (err) {
125
- return this.destroy(err)
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) throw errors.UNEXPECTED_CONTROL()
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
- const handshake = (exports.handshake = function handshake(req, cb) {
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.1",
3
+ "version": "2.0.3",
4
4
  "description": "WebSocket library for JavaScript",
5
5
  "exports": {
6
- ".": "./index.js",
6
+ ".": {
7
+ "types": "./index.d.ts",
8
+ "default": "./index.js"
9
+ },
7
10
  "./package": "./package.json",
8
- "./constants": "./lib/constants.js",
9
- "./errors": "./lib/errors.js"
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
  }