bare-ws 1.2.1 → 1.3.1

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/lib/constants.js CHANGED
@@ -9,7 +9,9 @@ exports.opcode = {
9
9
  CONTINUATION: 0x0,
10
10
  TEXT: 0x1,
11
11
  BINARY: 0x2,
12
- CLOSE: 0x8
12
+ CLOSE: 0x8,
13
+ PING: 0x9,
14
+ PONG: 0xa
13
15
  }
14
16
 
15
17
  // https://datatracker.ietf.org/doc/html/rfc6455#section-7.4.1
package/lib/errors.js CHANGED
@@ -15,6 +15,22 @@ module.exports = class WebSocketError extends Error {
15
15
  return 'WebSocketError'
16
16
  }
17
17
 
18
+ static NOT_CONNECTED (msg = 'Socket is not connected') {
19
+ return new WebSocketError(msg, 'NOT_CONNECTED', 0, WebSocketError.NOT_CONNECTED)
20
+ }
21
+
22
+ static UNEXPECTED_RSV1 (msg = 'RSV1 must be unset') {
23
+ return new WebSocketError(msg, 'UNEXPECTED_RSV1', status.PROTOCOL_ERROR, WebSocketError.UNEXPECTED_RSV1)
24
+ }
25
+
26
+ static UNEXPECTED_RSV2 (msg = 'RSV2 must be unset') {
27
+ return new WebSocketError(msg, 'UNEXPECTED_RSV2', status.PROTOCOL_ERROR, WebSocketError.UNEXPECTED_RSV2)
28
+ }
29
+
30
+ static UNEXPECTED_RSV3 (msg = 'RSV3 must be unset') {
31
+ return new WebSocketError(msg, 'UNEXPECTED_RSV3', status.PROTOCOL_ERROR, WebSocketError.UNEXPECTED_RSV3)
32
+ }
33
+
18
34
  static EXPECTED_MASK (msg = 'MASK must be set') {
19
35
  return new WebSocketError(msg, 'EXPECTED_MASK', status.PROTOCOL_ERROR, WebSocketError.EXPECTED_MASK)
20
36
  }
package/lib/frame.js CHANGED
@@ -58,7 +58,7 @@ exports.preencode = function preencode (state, f) {
58
58
 
59
59
  if (length <= 0x7d) i++
60
60
  else {
61
- if (length < 2 ** 16) i += 3
61
+ if (length <= 0xffff) i += 3
62
62
  else i += 9
63
63
  }
64
64
 
@@ -91,7 +91,7 @@ exports.encode = function encode (state, f) {
91
91
 
92
92
  if (length <= 0x7d) b[i++] |= length
93
93
  else {
94
- if (length < 2 ** 16) {
94
+ if (length <= 0xffff) {
95
95
  b[i++] |= 0x7e
96
96
 
97
97
  v.setUint16(i, length, false)
package/lib/socket.js CHANGED
@@ -12,7 +12,7 @@ module.exports = exports = class WebSocket extends Duplex {
12
12
  constructor (url, opts = {}) {
13
13
  if (typeof url === 'string') url = new URL(url)
14
14
 
15
- if (URL.isURL(url)) {
15
+ if (isURL(url)) {
16
16
  opts = opts ? { ...opts } : {}
17
17
 
18
18
  opts.host = url.hostname
@@ -21,6 +21,10 @@ module.exports = exports = class WebSocket extends Duplex {
21
21
  opts.secure = url.protocol === 'https:' || url.protocol === 'wss:'
22
22
  } else {
23
23
  opts = url ? { ...url } : {}
24
+
25
+ // For Node.js compatibility
26
+ opts.host = opts.hostname || opts.host
27
+ opts.port = typeof opts.port === 'string' ? parseInt(opts.port, 10) : opts.port
24
28
  }
25
29
 
26
30
  const {
@@ -28,7 +32,7 @@ module.exports = exports = class WebSocket extends Duplex {
28
32
  socket = null
29
33
  } = opts
30
34
 
31
- super()
35
+ super({ eagerOpen: true })
32
36
 
33
37
  this._socket = null
34
38
  this._isServer = isServer
@@ -44,6 +48,22 @@ module.exports = exports = class WebSocket extends Duplex {
44
48
  else this._connect(opts)
45
49
  }
46
50
 
51
+ ping (data) {
52
+ if (this._socket === null) throw errors.NOT_CONNECTED()
53
+
54
+ if (typeof data === 'string') data = Buffer.from(data)
55
+
56
+ this._socket.write(new Frame(opcode.PING, data, { mask: this._mask }).toBuffer())
57
+ }
58
+
59
+ pong (data) {
60
+ if (this._socket === null) throw errors.NOT_CONNECTED()
61
+
62
+ if (typeof data === 'string') data = Buffer.from(data)
63
+
64
+ this._socket.write(new Frame(opcode.PONG, data, { mask: this._mask }).toBuffer())
65
+ }
66
+
47
67
  _attach (socket) {
48
68
  this._socket = socket
49
69
 
@@ -85,31 +105,46 @@ module.exports = exports = class WebSocket extends Duplex {
85
105
  while (this._buffer !== null) {
86
106
  const state = { start: 0, end: this._buffer.length, buffer: this._buffer }
87
107
 
88
- const frame = Frame.decode(state)
108
+ let frame
109
+ try {
110
+ frame = Frame.decode(state)
111
+ } catch (err) {
112
+ return this.destroy(err)
113
+ }
89
114
 
90
115
  if (frame === null) return
91
116
 
92
117
  this._buffer = state.start === state.end ? null : this._buffer.subarray(state.start)
93
118
 
94
- this._onframe(frame)
119
+ try {
120
+ this._onframe(frame)
121
+ } catch (err) {
122
+ return this.destroy(err)
123
+ }
95
124
  }
96
125
  }
97
126
 
98
127
  _onframe (frame) {
128
+ if (frame.rsv1) throw errors.UNEXPECTED_RSV1()
129
+
130
+ if (frame.rsv2) throw errors.UNEXPECTED_RSV2()
131
+
132
+ if (frame.rsv3) throw errors.UNEXPECTED_RSV3()
133
+
99
134
  if (frame.payload.length > 0 && !frame.mask === this._isServer) {
100
- return this.destroy(this._isServer ? errors.EXPECTED_MASK() : errors.UNEXPECTED_MASK())
135
+ throw this._isServer ? errors.EXPECTED_MASK() : errors.UNEXPECTED_MASK()
101
136
  }
102
137
 
103
138
  if (frame.fin === false) {
104
139
  if (this._fragments.push(frame) === 1) { // First frame
105
- if (frame.opcode === opcode.CONTINUATION) return this.destroy(errors.UNEXPECTED_CONTINUATION())
140
+ if (frame.opcode === opcode.CONTINUATION) throw errors.UNEXPECTED_CONTINUATION()
106
141
 
107
- if (frame.opcode >= opcode.CLOSE) return this.destroy(errors.UNEXPECTED_CONTROL())
142
+ if (frame.opcode >= opcode.CLOSE) throw errors.UNEXPECTED_CONTROL()
108
143
 
109
144
  return
110
145
  }
111
146
 
112
- if (frame.opcode !== opcode.CONTINUATION) return this.destroy(errors.EXPECTED_CONTINUATION())
147
+ if (frame.opcode !== opcode.CONTINUATION) throw errors.EXPECTED_CONTINUATION()
113
148
 
114
149
  return
115
150
  }
@@ -120,8 +155,17 @@ module.exports = exports = class WebSocket extends Duplex {
120
155
  this.end()
121
156
  return
122
157
 
158
+ case opcode.PING:
159
+ this.pong(frame.payload)
160
+ this.emit('ping', frame.payload)
161
+ return
162
+
163
+ case opcode.PONG:
164
+ this.emit('pong', frame.payload)
165
+ return
166
+
123
167
  case opcode.CONTINUATION: {
124
- if (this._fragments.length === 0) return this.destroy(errors.UNEXPECTED_CONTINUATION())
168
+ if (this._fragments.length === 0) throw errors.UNEXPECTED_CONTINUATION()
125
169
 
126
170
  frame.opcode = this._fragments[0].opcode
127
171
 
@@ -137,7 +181,7 @@ module.exports = exports = class WebSocket extends Duplex {
137
181
  }
138
182
 
139
183
  default:
140
- if (this._fragments.length > 0) return this.destroy(errors.EXPECTED_CONTINUATION())
184
+ if (this._fragments.length > 0) throw errors.EXPECTED_CONTINUATION()
141
185
  }
142
186
 
143
187
  switch (frame.opcode) {
@@ -147,7 +191,7 @@ module.exports = exports = class WebSocket extends Duplex {
147
191
  break
148
192
 
149
193
  default:
150
- this.destroy(errors.INVALID_OPCODE())
194
+ throw errors.INVALID_OPCODE()
151
195
  }
152
196
  }
153
197
 
@@ -230,3 +274,15 @@ function defaultPort (url) {
230
274
 
231
275
  return null
232
276
  }
277
+
278
+ // https://url.spec.whatwg.org/#api
279
+ function isURL (url) {
280
+ return (
281
+ url !== null &&
282
+ typeof url === 'object' &&
283
+ typeof url.protocol === 'string' &&
284
+ typeof url.hostname === 'string' &&
285
+ typeof url.pathname === 'string' &&
286
+ typeof url.search === 'string'
287
+ )
288
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-ws",
3
- "version": "1.2.1",
3
+ "version": "1.3.1",
4
4
  "description": "WebSocket library for JavaScript",
5
5
  "exports": {
6
6
  ".": "./index.js",
@@ -28,8 +28,8 @@
28
28
  "dependencies": {
29
29
  "bare-crypto": "^1.2.0",
30
30
  "bare-events": "^2.3.1",
31
- "bare-http1": "^3.5.2",
32
- "bare-https": "^1.1.0",
31
+ "bare-http1": "^3.8.0",
32
+ "bare-https": "^1.3.0",
33
33
  "bare-stream": "^2.1.2"
34
34
  },
35
35
  "devDependencies": {