isomorfeus-transport 2.5.5 → 22.9.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/lib/isomorfeus/core_ext/hash/deep_merge.rb +34 -0
  3. data/lib/isomorfeus/core_ext/kernel.rb +56 -0
  4. data/lib/isomorfeus/core_ext/object/deep_dup.rb +53 -0
  5. data/lib/isomorfeus/core_ext/object/duplicable.rb +60 -0
  6. data/lib/isomorfeus/transport/compressor_rack.rb +1 -1
  7. data/lib/isomorfeus/transport/config.rb +37 -0
  8. data/lib/isomorfeus/transport/rack_middleware.rb +16 -9
  9. data/lib/isomorfeus/transport/request_agent.rb +1 -0
  10. data/lib/isomorfeus/transport/server_socket_processor.rb +13 -1
  11. data/lib/isomorfeus/transport/version.rb +1 -1
  12. data/lib/isomorfeus/transport.rb +45 -38
  13. data/lib/isomorfeus-transport.rb +25 -16
  14. data/lib/lucid_channel.rb +103 -0
  15. data/lib/lucid_handler.rb +25 -0
  16. metadata +40 -90
  17. data/lib/isomorfeus/transport/handler/authentication_handler.rb +0 -55
  18. data/lib/isomorfeus/transport/imports.rb +0 -10
  19. data/lib/isomorfeus/transport/ssr_login.rb +0 -30
  20. data/lib/lucid_channel/base.rb +0 -8
  21. data/lib/lucid_channel/mixin.rb +0 -105
  22. data/lib/lucid_handler/base.rb +0 -8
  23. data/lib/lucid_handler/mixin.rb +0 -27
  24. data/node_modules/.package-lock.json +0 -27
  25. data/node_modules/ws/LICENSE +0 -19
  26. data/node_modules/ws/README.md +0 -489
  27. data/node_modules/ws/browser.js +0 -8
  28. data/node_modules/ws/index.js +0 -13
  29. data/node_modules/ws/lib/buffer-util.js +0 -126
  30. data/node_modules/ws/lib/constants.js +0 -12
  31. data/node_modules/ws/lib/event-target.js +0 -266
  32. data/node_modules/ws/lib/extension.js +0 -203
  33. data/node_modules/ws/lib/limiter.js +0 -55
  34. data/node_modules/ws/lib/permessage-deflate.js +0 -511
  35. data/node_modules/ws/lib/receiver.js +0 -618
  36. data/node_modules/ws/lib/sender.js +0 -478
  37. data/node_modules/ws/lib/stream.js +0 -159
  38. data/node_modules/ws/lib/subprotocol.js +0 -62
  39. data/node_modules/ws/lib/validation.js +0 -124
  40. data/node_modules/ws/lib/websocket-server.js +0 -488
  41. data/node_modules/ws/lib/websocket.js +0 -1264
  42. data/node_modules/ws/package.json +0 -61
  43. data/node_modules/ws/wrapper.mjs +0 -8
  44. data/package.json +0 -6
@@ -1,478 +0,0 @@
1
- /* eslint no-unused-vars: ["error", { "varsIgnorePattern": "^net|tls$" }] */
2
-
3
- 'use strict';
4
-
5
- const net = require('net');
6
- const tls = require('tls');
7
- const { randomFillSync } = require('crypto');
8
-
9
- const PerMessageDeflate = require('./permessage-deflate');
10
- const { EMPTY_BUFFER } = require('./constants');
11
- const { isValidStatusCode } = require('./validation');
12
- const { mask: applyMask, toBuffer } = require('./buffer-util');
13
-
14
- const kByteLength = Symbol('kByteLength');
15
- const maskBuffer = Buffer.alloc(4);
16
-
17
- /**
18
- * HyBi Sender implementation.
19
- */
20
- class Sender {
21
- /**
22
- * Creates a Sender instance.
23
- *
24
- * @param {(net.Socket|tls.Socket)} socket The connection socket
25
- * @param {Object} [extensions] An object containing the negotiated extensions
26
- * @param {Function} [generateMask] The function used to generate the masking
27
- * key
28
- */
29
- constructor(socket, extensions, generateMask) {
30
- this._extensions = extensions || {};
31
-
32
- if (generateMask) {
33
- this._generateMask = generateMask;
34
- this._maskBuffer = Buffer.alloc(4);
35
- }
36
-
37
- this._socket = socket;
38
-
39
- this._firstFragment = true;
40
- this._compress = false;
41
-
42
- this._bufferedBytes = 0;
43
- this._deflating = false;
44
- this._queue = [];
45
- }
46
-
47
- /**
48
- * Frames a piece of data according to the HyBi WebSocket protocol.
49
- *
50
- * @param {(Buffer|String)} data The data to frame
51
- * @param {Object} options Options object
52
- * @param {Boolean} [options.fin=false] Specifies whether or not to set the
53
- * FIN bit
54
- * @param {Function} [options.generateMask] The function used to generate the
55
- * masking key
56
- * @param {Boolean} [options.mask=false] Specifies whether or not to mask
57
- * `data`
58
- * @param {Buffer} [options.maskBuffer] The buffer used to store the masking
59
- * key
60
- * @param {Number} options.opcode The opcode
61
- * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
62
- * modified
63
- * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
64
- * RSV1 bit
65
- * @return {(Buffer|String)[]} The framed data
66
- * @public
67
- */
68
- static frame(data, options) {
69
- let mask;
70
- let merge = false;
71
- let offset = 2;
72
- let skipMasking = false;
73
-
74
- if (options.mask) {
75
- mask = options.maskBuffer || maskBuffer;
76
-
77
- if (options.generateMask) {
78
- options.generateMask(mask);
79
- } else {
80
- randomFillSync(mask, 0, 4);
81
- }
82
-
83
- skipMasking = (mask[0] | mask[1] | mask[2] | mask[3]) === 0;
84
- offset = 6;
85
- }
86
-
87
- let dataLength;
88
-
89
- if (typeof data === 'string') {
90
- if (
91
- (!options.mask || skipMasking) &&
92
- options[kByteLength] !== undefined
93
- ) {
94
- dataLength = options[kByteLength];
95
- } else {
96
- data = Buffer.from(data);
97
- dataLength = data.length;
98
- }
99
- } else {
100
- dataLength = data.length;
101
- merge = options.mask && options.readOnly && !skipMasking;
102
- }
103
-
104
- let payloadLength = dataLength;
105
-
106
- if (dataLength >= 65536) {
107
- offset += 8;
108
- payloadLength = 127;
109
- } else if (dataLength > 125) {
110
- offset += 2;
111
- payloadLength = 126;
112
- }
113
-
114
- const target = Buffer.allocUnsafe(merge ? dataLength + offset : offset);
115
-
116
- target[0] = options.fin ? options.opcode | 0x80 : options.opcode;
117
- if (options.rsv1) target[0] |= 0x40;
118
-
119
- target[1] = payloadLength;
120
-
121
- if (payloadLength === 126) {
122
- target.writeUInt16BE(dataLength, 2);
123
- } else if (payloadLength === 127) {
124
- target[2] = target[3] = 0;
125
- target.writeUIntBE(dataLength, 4, 6);
126
- }
127
-
128
- if (!options.mask) return [target, data];
129
-
130
- target[1] |= 0x80;
131
- target[offset - 4] = mask[0];
132
- target[offset - 3] = mask[1];
133
- target[offset - 2] = mask[2];
134
- target[offset - 1] = mask[3];
135
-
136
- if (skipMasking) return [target, data];
137
-
138
- if (merge) {
139
- applyMask(data, mask, target, offset, dataLength);
140
- return [target];
141
- }
142
-
143
- applyMask(data, mask, data, 0, dataLength);
144
- return [target, data];
145
- }
146
-
147
- /**
148
- * Sends a close message to the other peer.
149
- *
150
- * @param {Number} [code] The status code component of the body
151
- * @param {(String|Buffer)} [data] The message component of the body
152
- * @param {Boolean} [mask=false] Specifies whether or not to mask the message
153
- * @param {Function} [cb] Callback
154
- * @public
155
- */
156
- close(code, data, mask, cb) {
157
- let buf;
158
-
159
- if (code === undefined) {
160
- buf = EMPTY_BUFFER;
161
- } else if (typeof code !== 'number' || !isValidStatusCode(code)) {
162
- throw new TypeError('First argument must be a valid error code number');
163
- } else if (data === undefined || !data.length) {
164
- buf = Buffer.allocUnsafe(2);
165
- buf.writeUInt16BE(code, 0);
166
- } else {
167
- const length = Buffer.byteLength(data);
168
-
169
- if (length > 123) {
170
- throw new RangeError('The message must not be greater than 123 bytes');
171
- }
172
-
173
- buf = Buffer.allocUnsafe(2 + length);
174
- buf.writeUInt16BE(code, 0);
175
-
176
- if (typeof data === 'string') {
177
- buf.write(data, 2);
178
- } else {
179
- buf.set(data, 2);
180
- }
181
- }
182
-
183
- const options = {
184
- [kByteLength]: buf.length,
185
- fin: true,
186
- generateMask: this._generateMask,
187
- mask,
188
- maskBuffer: this._maskBuffer,
189
- opcode: 0x08,
190
- readOnly: false,
191
- rsv1: false
192
- };
193
-
194
- if (this._deflating) {
195
- this.enqueue([this.dispatch, buf, false, options, cb]);
196
- } else {
197
- this.sendFrame(Sender.frame(buf, options), cb);
198
- }
199
- }
200
-
201
- /**
202
- * Sends a ping message to the other peer.
203
- *
204
- * @param {*} data The message to send
205
- * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
206
- * @param {Function} [cb] Callback
207
- * @public
208
- */
209
- ping(data, mask, cb) {
210
- let byteLength;
211
- let readOnly;
212
-
213
- if (typeof data === 'string') {
214
- byteLength = Buffer.byteLength(data);
215
- readOnly = false;
216
- } else {
217
- data = toBuffer(data);
218
- byteLength = data.length;
219
- readOnly = toBuffer.readOnly;
220
- }
221
-
222
- if (byteLength > 125) {
223
- throw new RangeError('The data size must not be greater than 125 bytes');
224
- }
225
-
226
- const options = {
227
- [kByteLength]: byteLength,
228
- fin: true,
229
- generateMask: this._generateMask,
230
- mask,
231
- maskBuffer: this._maskBuffer,
232
- opcode: 0x09,
233
- readOnly,
234
- rsv1: false
235
- };
236
-
237
- if (this._deflating) {
238
- this.enqueue([this.dispatch, data, false, options, cb]);
239
- } else {
240
- this.sendFrame(Sender.frame(data, options), cb);
241
- }
242
- }
243
-
244
- /**
245
- * Sends a pong message to the other peer.
246
- *
247
- * @param {*} data The message to send
248
- * @param {Boolean} [mask=false] Specifies whether or not to mask `data`
249
- * @param {Function} [cb] Callback
250
- * @public
251
- */
252
- pong(data, mask, cb) {
253
- let byteLength;
254
- let readOnly;
255
-
256
- if (typeof data === 'string') {
257
- byteLength = Buffer.byteLength(data);
258
- readOnly = false;
259
- } else {
260
- data = toBuffer(data);
261
- byteLength = data.length;
262
- readOnly = toBuffer.readOnly;
263
- }
264
-
265
- if (byteLength > 125) {
266
- throw new RangeError('The data size must not be greater than 125 bytes');
267
- }
268
-
269
- const options = {
270
- [kByteLength]: byteLength,
271
- fin: true,
272
- generateMask: this._generateMask,
273
- mask,
274
- maskBuffer: this._maskBuffer,
275
- opcode: 0x0a,
276
- readOnly,
277
- rsv1: false
278
- };
279
-
280
- if (this._deflating) {
281
- this.enqueue([this.dispatch, data, false, options, cb]);
282
- } else {
283
- this.sendFrame(Sender.frame(data, options), cb);
284
- }
285
- }
286
-
287
- /**
288
- * Sends a data message to the other peer.
289
- *
290
- * @param {*} data The message to send
291
- * @param {Object} options Options object
292
- * @param {Boolean} [options.binary=false] Specifies whether `data` is binary
293
- * or text
294
- * @param {Boolean} [options.compress=false] Specifies whether or not to
295
- * compress `data`
296
- * @param {Boolean} [options.fin=false] Specifies whether the fragment is the
297
- * last one
298
- * @param {Boolean} [options.mask=false] Specifies whether or not to mask
299
- * `data`
300
- * @param {Function} [cb] Callback
301
- * @public
302
- */
303
- send(data, options, cb) {
304
- const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
305
- let opcode = options.binary ? 2 : 1;
306
- let rsv1 = options.compress;
307
-
308
- let byteLength;
309
- let readOnly;
310
-
311
- if (typeof data === 'string') {
312
- byteLength = Buffer.byteLength(data);
313
- readOnly = false;
314
- } else {
315
- data = toBuffer(data);
316
- byteLength = data.length;
317
- readOnly = toBuffer.readOnly;
318
- }
319
-
320
- if (this._firstFragment) {
321
- this._firstFragment = false;
322
- if (
323
- rsv1 &&
324
- perMessageDeflate &&
325
- perMessageDeflate.params[
326
- perMessageDeflate._isServer
327
- ? 'server_no_context_takeover'
328
- : 'client_no_context_takeover'
329
- ]
330
- ) {
331
- rsv1 = byteLength >= perMessageDeflate._threshold;
332
- }
333
- this._compress = rsv1;
334
- } else {
335
- rsv1 = false;
336
- opcode = 0;
337
- }
338
-
339
- if (options.fin) this._firstFragment = true;
340
-
341
- if (perMessageDeflate) {
342
- const opts = {
343
- [kByteLength]: byteLength,
344
- fin: options.fin,
345
- generateMask: this._generateMask,
346
- mask: options.mask,
347
- maskBuffer: this._maskBuffer,
348
- opcode,
349
- readOnly,
350
- rsv1
351
- };
352
-
353
- if (this._deflating) {
354
- this.enqueue([this.dispatch, data, this._compress, opts, cb]);
355
- } else {
356
- this.dispatch(data, this._compress, opts, cb);
357
- }
358
- } else {
359
- this.sendFrame(
360
- Sender.frame(data, {
361
- [kByteLength]: byteLength,
362
- fin: options.fin,
363
- generateMask: this._generateMask,
364
- mask: options.mask,
365
- maskBuffer: this._maskBuffer,
366
- opcode,
367
- readOnly,
368
- rsv1: false
369
- }),
370
- cb
371
- );
372
- }
373
- }
374
-
375
- /**
376
- * Dispatches a message.
377
- *
378
- * @param {(Buffer|String)} data The message to send
379
- * @param {Boolean} [compress=false] Specifies whether or not to compress
380
- * `data`
381
- * @param {Object} options Options object
382
- * @param {Boolean} [options.fin=false] Specifies whether or not to set the
383
- * FIN bit
384
- * @param {Function} [options.generateMask] The function used to generate the
385
- * masking key
386
- * @param {Boolean} [options.mask=false] Specifies whether or not to mask
387
- * `data`
388
- * @param {Buffer} [options.maskBuffer] The buffer used to store the masking
389
- * key
390
- * @param {Number} options.opcode The opcode
391
- * @param {Boolean} [options.readOnly=false] Specifies whether `data` can be
392
- * modified
393
- * @param {Boolean} [options.rsv1=false] Specifies whether or not to set the
394
- * RSV1 bit
395
- * @param {Function} [cb] Callback
396
- * @private
397
- */
398
- dispatch(data, compress, options, cb) {
399
- if (!compress) {
400
- this.sendFrame(Sender.frame(data, options), cb);
401
- return;
402
- }
403
-
404
- const perMessageDeflate = this._extensions[PerMessageDeflate.extensionName];
405
-
406
- this._bufferedBytes += options[kByteLength];
407
- this._deflating = true;
408
- perMessageDeflate.compress(data, options.fin, (_, buf) => {
409
- if (this._socket.destroyed) {
410
- const err = new Error(
411
- 'The socket was closed while data was being compressed'
412
- );
413
-
414
- if (typeof cb === 'function') cb(err);
415
-
416
- for (let i = 0; i < this._queue.length; i++) {
417
- const params = this._queue[i];
418
- const callback = params[params.length - 1];
419
-
420
- if (typeof callback === 'function') callback(err);
421
- }
422
-
423
- return;
424
- }
425
-
426
- this._bufferedBytes -= options[kByteLength];
427
- this._deflating = false;
428
- options.readOnly = false;
429
- this.sendFrame(Sender.frame(buf, options), cb);
430
- this.dequeue();
431
- });
432
- }
433
-
434
- /**
435
- * Executes queued send operations.
436
- *
437
- * @private
438
- */
439
- dequeue() {
440
- while (!this._deflating && this._queue.length) {
441
- const params = this._queue.shift();
442
-
443
- this._bufferedBytes -= params[3][kByteLength];
444
- Reflect.apply(params[0], this, params.slice(1));
445
- }
446
- }
447
-
448
- /**
449
- * Enqueues a send operation.
450
- *
451
- * @param {Array} params Send operation parameters.
452
- * @private
453
- */
454
- enqueue(params) {
455
- this._bufferedBytes += params[3][kByteLength];
456
- this._queue.push(params);
457
- }
458
-
459
- /**
460
- * Sends a frame.
461
- *
462
- * @param {Buffer[]} list The frame to send
463
- * @param {Function} [cb] Callback
464
- * @private
465
- */
466
- sendFrame(list, cb) {
467
- if (list.length === 2) {
468
- this._socket.cork();
469
- this._socket.write(list[0]);
470
- this._socket.write(list[1], cb);
471
- this._socket.uncork();
472
- } else {
473
- this._socket.write(list[0], cb);
474
- }
475
- }
476
- }
477
-
478
- module.exports = Sender;
@@ -1,159 +0,0 @@
1
- 'use strict';
2
-
3
- const { Duplex } = require('stream');
4
-
5
- /**
6
- * Emits the `'close'` event on a stream.
7
- *
8
- * @param {Duplex} stream The stream.
9
- * @private
10
- */
11
- function emitClose(stream) {
12
- stream.emit('close');
13
- }
14
-
15
- /**
16
- * The listener of the `'end'` event.
17
- *
18
- * @private
19
- */
20
- function duplexOnEnd() {
21
- if (!this.destroyed && this._writableState.finished) {
22
- this.destroy();
23
- }
24
- }
25
-
26
- /**
27
- * The listener of the `'error'` event.
28
- *
29
- * @param {Error} err The error
30
- * @private
31
- */
32
- function duplexOnError(err) {
33
- this.removeListener('error', duplexOnError);
34
- this.destroy();
35
- if (this.listenerCount('error') === 0) {
36
- // Do not suppress the throwing behavior.
37
- this.emit('error', err);
38
- }
39
- }
40
-
41
- /**
42
- * Wraps a `WebSocket` in a duplex stream.
43
- *
44
- * @param {WebSocket} ws The `WebSocket` to wrap
45
- * @param {Object} [options] The options for the `Duplex` constructor
46
- * @return {Duplex} The duplex stream
47
- * @public
48
- */
49
- function createWebSocketStream(ws, options) {
50
- let terminateOnDestroy = true;
51
-
52
- const duplex = new Duplex({
53
- ...options,
54
- autoDestroy: false,
55
- emitClose: false,
56
- objectMode: false,
57
- writableObjectMode: false
58
- });
59
-
60
- ws.on('message', function message(msg, isBinary) {
61
- const data =
62
- !isBinary && duplex._readableState.objectMode ? msg.toString() : msg;
63
-
64
- if (!duplex.push(data)) ws.pause();
65
- });
66
-
67
- ws.once('error', function error(err) {
68
- if (duplex.destroyed) return;
69
-
70
- // Prevent `ws.terminate()` from being called by `duplex._destroy()`.
71
- //
72
- // - If the `'error'` event is emitted before the `'open'` event, then
73
- // `ws.terminate()` is a noop as no socket is assigned.
74
- // - Otherwise, the error is re-emitted by the listener of the `'error'`
75
- // event of the `Receiver` object. The listener already closes the
76
- // connection by calling `ws.close()`. This allows a close frame to be
77
- // sent to the other peer. If `ws.terminate()` is called right after this,
78
- // then the close frame might not be sent.
79
- terminateOnDestroy = false;
80
- duplex.destroy(err);
81
- });
82
-
83
- ws.once('close', function close() {
84
- if (duplex.destroyed) return;
85
-
86
- duplex.push(null);
87
- });
88
-
89
- duplex._destroy = function (err, callback) {
90
- if (ws.readyState === ws.CLOSED) {
91
- callback(err);
92
- process.nextTick(emitClose, duplex);
93
- return;
94
- }
95
-
96
- let called = false;
97
-
98
- ws.once('error', function error(err) {
99
- called = true;
100
- callback(err);
101
- });
102
-
103
- ws.once('close', function close() {
104
- if (!called) callback(err);
105
- process.nextTick(emitClose, duplex);
106
- });
107
-
108
- if (terminateOnDestroy) ws.terminate();
109
- };
110
-
111
- duplex._final = function (callback) {
112
- if (ws.readyState === ws.CONNECTING) {
113
- ws.once('open', function open() {
114
- duplex._final(callback);
115
- });
116
- return;
117
- }
118
-
119
- // If the value of the `_socket` property is `null` it means that `ws` is a
120
- // client websocket and the handshake failed. In fact, when this happens, a
121
- // socket is never assigned to the websocket. Wait for the `'error'` event
122
- // that will be emitted by the websocket.
123
- if (ws._socket === null) return;
124
-
125
- if (ws._socket._writableState.finished) {
126
- callback();
127
- if (duplex._readableState.endEmitted) duplex.destroy();
128
- } else {
129
- ws._socket.once('finish', function finish() {
130
- // `duplex` is not destroyed here because the `'end'` event will be
131
- // emitted on `duplex` after this `'finish'` event. The EOF signaling
132
- // `null` chunk is, in fact, pushed when the websocket emits `'close'`.
133
- callback();
134
- });
135
- ws.close();
136
- }
137
- };
138
-
139
- duplex._read = function () {
140
- if (ws.isPaused) ws.resume();
141
- };
142
-
143
- duplex._write = function (chunk, encoding, callback) {
144
- if (ws.readyState === ws.CONNECTING) {
145
- ws.once('open', function open() {
146
- duplex._write(chunk, encoding, callback);
147
- });
148
- return;
149
- }
150
-
151
- ws.send(chunk, callback);
152
- };
153
-
154
- duplex.on('end', duplexOnEnd);
155
- duplex.on('error', duplexOnError);
156
- return duplex;
157
- }
158
-
159
- module.exports = createWebSocketStream;
@@ -1,62 +0,0 @@
1
- 'use strict';
2
-
3
- const { tokenChars } = require('./validation');
4
-
5
- /**
6
- * Parses the `Sec-WebSocket-Protocol` header into a set of subprotocol names.
7
- *
8
- * @param {String} header The field value of the header
9
- * @return {Set} The subprotocol names
10
- * @public
11
- */
12
- function parse(header) {
13
- const protocols = new Set();
14
- let start = -1;
15
- let end = -1;
16
- let i = 0;
17
-
18
- for (i; i < header.length; i++) {
19
- const code = header.charCodeAt(i);
20
-
21
- if (end === -1 && tokenChars[code] === 1) {
22
- if (start === -1) start = i;
23
- } else if (
24
- i !== 0 &&
25
- (code === 0x20 /* ' ' */ || code === 0x09) /* '\t' */
26
- ) {
27
- if (end === -1 && start !== -1) end = i;
28
- } else if (code === 0x2c /* ',' */) {
29
- if (start === -1) {
30
- throw new SyntaxError(`Unexpected character at index ${i}`);
31
- }
32
-
33
- if (end === -1) end = i;
34
-
35
- const protocol = header.slice(start, end);
36
-
37
- if (protocols.has(protocol)) {
38
- throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
39
- }
40
-
41
- protocols.add(protocol);
42
- start = end = -1;
43
- } else {
44
- throw new SyntaxError(`Unexpected character at index ${i}`);
45
- }
46
- }
47
-
48
- if (start === -1 || end !== -1) {
49
- throw new SyntaxError('Unexpected end of input');
50
- }
51
-
52
- const protocol = header.slice(start, i);
53
-
54
- if (protocols.has(protocol)) {
55
- throw new SyntaxError(`The "${protocol}" subprotocol is duplicated`);
56
- }
57
-
58
- protocols.add(protocol);
59
- return protocols;
60
- }
61
-
62
- module.exports = { parse };