Comparing version 3.0.0-alpha.4 to 3.0.0-alpha.5
@@ -5,2 +5,11 @@ # Changelog | ||
## [3.0.0-alpha.5][] - 2022-12-23 | ||
- Move `serveStatic` to impress | ||
- Fix path separaton in url for windows | ||
- Fix serving static files | ||
- Use `fetch` polyfill from metautil | ||
- Optimize abstractions | ||
- Update dependencies | ||
## [3.0.0-alpha.4][] - 2022-07-30 | ||
@@ -223,3 +232,4 @@ | ||
[unreleased]: https://github.com/metarhia/metacom/compare/v3.0.0-alpha.4...HEAD | ||
[unreleased]: https://github.com/metarhia/metacom/compare/v3.0.0-alpha.5...HEAD | ||
[3.0.0-alpha.5]: https://github.com/metarhia/metacom/compare/v3.0.0-alpha.4...v3.0.0-alpha.5 | ||
[3.0.0-alpha.4]: https://github.com/metarhia/metacom/compare/v3.0.0-alpha.3...v3.0.0-alpha.4 | ||
@@ -226,0 +236,0 @@ [3.0.0-alpha.3]: https://github.com/metarhia/metacom/compare/v3.0.0-alpha.2...v3.0.0-alpha.3 |
@@ -14,3 +14,2 @@ const warnAboutMemoryLeak = (eventName, count) => | ||
} | ||
getMaxListeners() { | ||
@@ -46,7 +45,16 @@ return this.maxListenersCount; | ||
emit(name, ...args) { | ||
if (name === '*') { | ||
throw new Error('Cannot emit reserved "*" global listeners.'); | ||
} | ||
const event = this.events.get(name); | ||
if (!event) return; | ||
for (const fn of event.values()) { | ||
fn(...args); | ||
if (event) { | ||
for (const fn of event.values()) { | ||
fn(...args); | ||
} | ||
} | ||
const globalListeners = this.events.get('*'); | ||
if (!globalListeners) return; | ||
for (const fn of globalListeners.values()) { | ||
fn(name, ...args); | ||
} | ||
} | ||
@@ -63,5 +71,11 @@ | ||
clear(name) { | ||
if (name) this.events.delete(name); | ||
else this.events.clear(); | ||
const globalListeners = this.events.get('*'); | ||
if (!name) { | ||
this.events.clear(); | ||
globalListeners.clear(); | ||
return; | ||
} | ||
if (name === '*') globalListeners.clear(); | ||
else this.events.delete(name); | ||
} | ||
} |
import EventEmitter from './events.js'; | ||
import { MetacomChunk, MetacomReadable, MetacomWritable } from './streams.js'; | ||
import { Chunk, MetaReadable, MetaWritable } from './streams.js'; | ||
@@ -39,2 +39,3 @@ const CALL_TIMEOUT = 7 * 1000; | ||
this.streamId = 0; | ||
this.eventId = 0; | ||
this.active = false; | ||
@@ -66,3 +67,3 @@ this.connected = false; | ||
const transport = this; | ||
return new MetacomWritable(transport, initData); | ||
return new MetaWritable(transport, initData); | ||
} | ||
@@ -122,3 +123,3 @@ | ||
const streamData = { streamId, name, size }; | ||
const stream = new MetacomReadable(streamData); | ||
const stream = new MetaReadable(streamData); | ||
this.streams.set(streamId, stream); | ||
@@ -144,3 +145,3 @@ } | ||
const byteView = new Uint8Array(buffer); | ||
const { streamId, payload } = MetacomChunk.decode(byteView); | ||
const { streamId, payload } = Chunk.decode(byteView); | ||
const stream = this.streams.get(streamId); | ||
@@ -164,2 +165,7 @@ if (stream) await stream.push(payload); | ||
} | ||
methods.on('*', (eventName, data) => { | ||
const target = `${interfaceName}/${eventName}`; | ||
const packet = { event: ++this.eventId, [target]: data }; | ||
this.send(JSON.stringify(packet)); | ||
}); | ||
this.api[interfaceName] = methods; | ||
@@ -166,0 +172,0 @@ } |
@@ -17,3 +17,3 @@ import EventEmitter from './events.js'; | ||
class MetacomChunk { | ||
class Chunk { | ||
static encode(streamId, payload) { | ||
@@ -39,3 +39,3 @@ const streamIdView = new Uint8Array(createStreamIdBuffer(streamId)); | ||
class MetacomReadable extends EventEmitter { | ||
class MetaReadable extends EventEmitter { | ||
constructor(initData, options = {}) { | ||
@@ -149,3 +149,3 @@ super(); | ||
class MetacomWritable extends EventEmitter { | ||
class MetaWritable extends EventEmitter { | ||
constructor(transport, initData) { | ||
@@ -170,3 +170,3 @@ super(); | ||
write(data) { | ||
const chunk = MetacomChunk.encode(this.streamId, data); | ||
const chunk = Chunk.encode(this.streamId, data); | ||
this.transport.send(chunk); | ||
@@ -186,2 +186,2 @@ } | ||
export { MetacomChunk, MetacomReadable, MetacomWritable }; | ||
export { Chunk, MetaReadable, MetaWritable }; |
'use strict'; | ||
const http = require('http'); | ||
const { EventEmitter } = require('events'); | ||
const metautil = require('metautil'); | ||
const { | ||
MetacomReadable, | ||
MetacomWritable, | ||
MetacomChunk, | ||
} = require('./streams.js'); | ||
const { MetaReadable, MetaWritable, Chunk } = require('./streams.js'); | ||
@@ -16,3 +13,3 @@ const EMPTY_PACKET = Buffer.from('{}'); | ||
this.token = token; | ||
this.channel = channel; | ||
this.channels = new Set([channel]); | ||
this.data = data; | ||
@@ -27,3 +24,3 @@ this.context = new Proxy(data, { | ||
const res = Reflect.set(data, key, value); | ||
channel.application.auth.saveSession(token, data); | ||
channel.auth.saveSession(token, data); | ||
return res; | ||
@@ -36,7 +33,9 @@ }, | ||
const sessions = new Map(); // token: Session | ||
const channels = new Map(); // Client: Channel | ||
class Client { | ||
constructor() { | ||
this.events = { close: [] }; | ||
class Client extends EventEmitter { | ||
#channel; | ||
constructor(channel) { | ||
super(); | ||
this.#channel = channel; | ||
this.eventId = 0; | ||
@@ -48,28 +47,23 @@ this.streams = new Map(); | ||
redirect(location) { | ||
const channel = channels.get(this); | ||
if (channel) channel.redirect(location); | ||
if (this.#channel) this.#channel.redirect(location); | ||
} | ||
get ip() { | ||
const channel = channels.get(this); | ||
return channel ? channel.ip : undefined; | ||
return this.#channel ? this.#channel.ip : undefined; | ||
} | ||
on(name, callback) { | ||
if (name !== 'close') return; | ||
this.events.close.push(callback); | ||
} | ||
emit(name, data) { | ||
if (name === 'close') { | ||
super.emit(name, data); | ||
return; | ||
} | ||
const packet = { event: --this.eventId, [name]: data }; | ||
const channel = channels.get(this); | ||
if (!channel || !channel.connection) { | ||
if (!this.#channel || !this.#channel.connection) { | ||
throw new Error(`Can't send metacom events to http transport`); | ||
} | ||
channel.send(packet); | ||
this.#channel.send(packet); | ||
} | ||
getStream(streamId) { | ||
const channel = channels.get(this); | ||
if (!channel.connection) { | ||
if (!this.#channel.connection) { | ||
throw new Error(`Can't receive stream from http transport`); | ||
@@ -83,4 +77,3 @@ } | ||
createStream(name, size) { | ||
const channel = channels.get(this); | ||
if (!channel.connection) { | ||
if (!this.#channel.connection) { | ||
throw new Error(`Can't send metacom streams to http transport`); | ||
@@ -92,12 +85,11 @@ } | ||
const initData = { streamId, name, size }; | ||
const transport = channel.connection; | ||
return new MetacomWritable(transport, initData); | ||
const transport = this.#channel.connection; | ||
return new MetaWritable(transport, initData); | ||
} | ||
startSession(token, data = {}) { | ||
const channel = channels.get(this); | ||
if (!channel) return false; | ||
if (channel.session) sessions.delete(channel.session.token); | ||
const session = new Session(token, channel, data); | ||
channel.session = session; | ||
if (!this.#channel) return false; | ||
if (this.#channel.session) sessions.delete(this.#channel.session.token); | ||
const session = new Session(token, this.#channel, data); | ||
this.#channel.session = session; | ||
sessions.set(token, session); | ||
@@ -110,5 +102,4 @@ return true; | ||
if (!session) return false; | ||
const channel = channels.get(this); | ||
if (!channel) return false; | ||
channel.session = session; | ||
if (!this.#channel) return false; | ||
this.#channel.session = session; | ||
return true; | ||
@@ -120,6 +111,5 @@ } | ||
if (!session) return false; | ||
const channel = channels.get(this); | ||
if (!channel.session) return false; | ||
sessions.delete(channel.session.token); | ||
channel.session = null; | ||
if (!this.#channel.session) return false; | ||
sessions.delete(this.#channel.session.token); | ||
this.#channel.session = null; | ||
return true; | ||
@@ -130,9 +120,10 @@ } | ||
class Channel { | ||
constructor(application, req, res) { | ||
this.application = application; | ||
constructor(server, req, res) { | ||
this.server = server; | ||
this.auth = server.application.auth; | ||
this.console = server.application.console; | ||
this.req = req; | ||
this.res = res; | ||
this.ip = req.socket.remoteAddress; | ||
this.client = new Client(); | ||
channels.set(this.client, this); | ||
this.client = new Client(this); | ||
this.session = null; | ||
@@ -172,3 +163,3 @@ this.restoreSession(); | ||
try { | ||
const { streamId, payload } = MetacomChunk.decode(data); | ||
const { streamId, payload } = Chunk.decode(data); | ||
const upstream = this.client.streams.get(streamId); | ||
@@ -208,3 +199,3 @@ if (upstream) { | ||
const streamData = { streamId, name, size }; | ||
const stream = new MetacomReadable(streamData); | ||
const stream = new MetaReadable(streamData); | ||
this.client.streams.set(streamId, stream); | ||
@@ -228,5 +219,5 @@ } | ||
async rpc(callId, interfaceName, methodName, args) { | ||
const { application, session, client } = this; | ||
const { server, console, session, client } = this; | ||
const [iname, ver = '*'] = interfaceName.split('.'); | ||
const proc = application.getMethod(iname, ver, methodName); | ||
const proc = server.application.getMethod(iname, ver, methodName); | ||
if (!proc) { | ||
@@ -265,8 +256,7 @@ this.error(404, { callId }); | ||
this.send({ callback: callId, result }); | ||
const record = `${this.ip}\t${interfaceName}/${methodName}`; | ||
application.console.log(record); | ||
console.log(`${this.ip}\t${interfaceName}/${methodName}`); | ||
} | ||
error(code = 500, { callId, error = null, httpCode = null } = {}) { | ||
const { req, ip, application } = this; | ||
const { console, req, ip } = this; | ||
const { url, method } = req; | ||
@@ -278,3 +268,3 @@ if (!httpCode) httpCode = (error && error.httpCode) || code; | ||
const reason = `${httpCode}\t${code}\t${error ? error.stack : status}`; | ||
application.console.error(`${ip}\t${method}\t${url}\t${reason}`); | ||
console.error(`${ip}\t${method}\t${url}\t${reason}`); | ||
const packet = { callback: callId, error: { message, code } }; | ||
@@ -290,3 +280,3 @@ this.send(packet, httpCode); | ||
if (!token) return null; | ||
const session = await this.application.auth.restoreSession(token); | ||
const session = await this.auth.restoreSession(token); | ||
if (!session) return null; | ||
@@ -297,4 +287,3 @@ return session; | ||
destroy() { | ||
channels.delete(this.client); | ||
for (const callback of this.client.events.close) callback(); | ||
this.emit('close'); | ||
if (!this.session) return; | ||
@@ -305,2 +294,2 @@ sessions.delete(this.session.token); | ||
module.exports = { Channel, channels }; | ||
module.exports = { Channel }; |
'use strict'; | ||
const http = require('http'); | ||
const https = require('https'); | ||
const { EventEmitter } = require('events'); | ||
const transport = { http, https }; | ||
const WebSocket = require('ws'); | ||
const { MetacomWritable, MetacomReadable, MetacomChunk } = require('./streams'); | ||
const metautil = require('metautil'); | ||
const { MetaWritable, MetaReadable, Chunk } = require('./streams.js'); | ||
@@ -17,18 +15,3 @@ class MetacomError extends Error { | ||
const fetch = (url, options) => { | ||
const dest = new URL(url); | ||
return new Promise((resolve, reject) => { | ||
const protocol = transport[dest.protocol.slice(0, -1)]; | ||
const req = protocol.request(url, options, async (res) => { | ||
const buffers = []; | ||
for await (const chunk of res) { | ||
buffers.push(chunk); | ||
} | ||
resolve(Buffer.concat(buffers).toString()); | ||
}); | ||
req.on('error', reject); | ||
req.write(options.body); | ||
req.end(); | ||
}); | ||
}; | ||
class MetacomInterface extends EventEmitter {} | ||
@@ -99,3 +82,3 @@ class Metacom extends EventEmitter { | ||
const streamData = { streamId, name, size }; | ||
const stream = new MetacomReadable(streamData); | ||
const stream = new MetaReadable(streamData); | ||
this.streams.set(streamId, stream); | ||
@@ -120,3 +103,3 @@ } | ||
const byteView = new Uint8Array(buffer); | ||
const { streamId, payload } = MetacomChunk.decode(byteView); | ||
const { streamId, payload } = Chunk.decode(byteView); | ||
const stream = this.streams.get(streamId); | ||
@@ -144,3 +127,3 @@ if (stream) await stream.push(payload); | ||
if (!available.includes(interfaceName)) continue; | ||
const methods = {}; | ||
const methods = new MetacomInterface(); | ||
const iface = introspection[interfaceName]; | ||
@@ -168,3 +151,3 @@ const request = this.socketCall(interfaceName); | ||
const transport = this; | ||
return new MetacomWritable(transport, initData); | ||
return new MetaWritable(transport, initData); | ||
} | ||
@@ -191,3 +174,3 @@ | ||
return (methodName) => | ||
(args = {}) => { | ||
async (args = {}) => { | ||
const callId = ++this.callId; | ||
@@ -200,11 +183,10 @@ const interfaceName = ver ? `${iname}.${ver}` : iname; | ||
const url = `${protocol}://${dest.host}/api`; | ||
return fetch(url, { | ||
const res = metautil.fetch(url, { | ||
method: 'POST', | ||
headers: { 'Content-Type': 'application/json' }, | ||
body: JSON.stringify(packet), | ||
}).then((json) => { | ||
const packet = JSON.parse(json); | ||
if (packet.error) throw new MetacomError(packet.error); | ||
return packet.result; | ||
}); | ||
const data = await res.json(); | ||
if (data.error) throw new MetacomError(data.error); | ||
return data.result; | ||
}; | ||
@@ -211,0 +193,0 @@ } |
@@ -87,4 +87,4 @@ 'use strict'; | ||
class HttpChannel extends Channel { | ||
constructor(application, req, res) { | ||
super(application, req, res); | ||
constructor(server, req, res) { | ||
super(server, req, res); | ||
res.on('close', () => { | ||
@@ -123,3 +123,3 @@ this.destroy(); | ||
async hook(proc, interfaceName, methodName, args, headers) { | ||
const { application, client, req } = this; | ||
const { console, client, req } = this; | ||
const verb = req.method; | ||
@@ -141,11 +141,10 @@ const callId = -1; | ||
this.send(result); | ||
const record = `${this.ip}\t${interfaceName}/${methodName}`; | ||
application.console.log(record); | ||
console.log(`${this.ip}\t${interfaceName}/${methodName}`); | ||
} | ||
startSession() { | ||
const token = this.application.auth.generateToken(); | ||
const token = this.auth.generateToken(); | ||
const host = metautil.parseHost(this.req.headers.host); | ||
const cookie = `${TOKEN}=${token}; ${COOKIE_HOST}=${host}`; | ||
const session = this.application.auth.startSession(); | ||
const session = this.auth.startSession(); | ||
if (this.res) this.res.setHeader('Set-Cookie', cookie); | ||
@@ -160,8 +159,7 @@ return session; | ||
this.res.setHeader('Set-Cookie', COOKIE_DELETE + host); | ||
this.application.auth.deleteSession(token); | ||
this.auth.deleteSession(token); | ||
} | ||
} | ||
const createChannel = (application, req, res) => | ||
new HttpChannel(application, req, res); | ||
const createChannel = (server, req, res) => new HttpChannel(server, req, res); | ||
@@ -168,0 +166,0 @@ const addHeaders = (headers) => { |
@@ -8,5 +8,6 @@ 'use strict'; | ||
const ws = require('ws'); | ||
const { channels } = require('./channel.js'); | ||
const { serveStatic } = require('./static.js'); | ||
const transport = require('./transport.js'); | ||
const transport = { | ||
http: require('./http.js'), | ||
ws: require('./ws.js'), | ||
}; | ||
@@ -20,2 +21,3 @@ const SHORT_TIMEOUT = 500; | ||
this.application = application; | ||
this.console = application.console; | ||
if (cors) transport.http.addHeaders(cors); | ||
@@ -26,3 +28,3 @@ const concurrency = queue.concurrency || options.concurrency; | ||
this.ws = null; | ||
this.channels = channels; | ||
this.channels = new Set(); | ||
this.bind(); | ||
@@ -32,3 +34,3 @@ } | ||
bind() { | ||
const { options, application } = this; | ||
const { options, application, console } = this; | ||
const { host, port, kind, protocol, timeouts, nagle = true } = options; | ||
@@ -44,11 +46,12 @@ const proto = protocol === 'http' || kind === 'balancer' ? http : https; | ||
this.server.on('listening', () => { | ||
application.console.info(`Listen port ${port}`); | ||
console.info(`Listen port ${port}`); | ||
}); | ||
this.ws = new ws.Server({ server: this.server }); | ||
this.ws.on('connection', (connection, req) => { | ||
transport.ws.createChannel(application, req, connection); | ||
const channel = transport.ws.createChannel(this, req, connection); | ||
this.channels.add(channel); | ||
}); | ||
this.ws.on('error', (err) => { | ||
if (err.code !== 'EADDRINUSE') return; | ||
application.console.warn(`Address in use: ${host}:${port}, retry...`); | ||
console.warn(`Address in use: ${host}:${port}, retry...`); | ||
setTimeout(() => { | ||
@@ -63,3 +66,4 @@ this.bind(); | ||
const { url } = req; | ||
const channel = transport.http.createChannel(this.application, req, res); | ||
const channel = transport.http.createChannel(this, req, res); | ||
this.channels.add(channel); | ||
if (this.options.kind === 'balancer') { | ||
@@ -73,3 +77,3 @@ const host = metautil.parseHost(req.headers.host); | ||
if (url.startsWith('/api')) this.request(channel); | ||
else serveStatic(channel); | ||
else this.application.serveStatic(channel); | ||
} | ||
@@ -131,3 +135,3 @@ | ||
this.server.close((err) => { | ||
if (err) this.application.console.error(err); | ||
if (err) this.console.error(err); | ||
}); | ||
@@ -134,0 +138,0 @@ if (this.channels.size === 0) { |
@@ -20,3 +20,3 @@ 'use strict'; | ||
class MetacomChunk { | ||
class Chunk { | ||
static encode(streamId, payload) { | ||
@@ -42,3 +42,3 @@ const streamIdView = new Uint8Array(createStreamIdBuffer(streamId)); | ||
class MetacomReadable extends EventEmitter { | ||
class MetaReadable extends EventEmitter { | ||
constructor(initData, options = {}) { | ||
@@ -153,3 +153,3 @@ super(); | ||
class MetacomWritable extends EventEmitter { | ||
class MetaWritable extends EventEmitter { | ||
constructor(transport, initData) { | ||
@@ -175,3 +175,3 @@ super(); | ||
write(data) { | ||
const chunk = MetacomChunk.encode(this.streamId, data); | ||
const chunk = Chunk.encode(this.streamId, data); | ||
this.transport.send(chunk); | ||
@@ -193,6 +193,2 @@ return true; | ||
module.exports = { | ||
MetacomChunk, | ||
MetacomReadable, | ||
MetacomWritable, | ||
}; | ||
module.exports = { Chunk, MetaReadable, MetaWritable }; |
@@ -6,4 +6,4 @@ 'use strict'; | ||
class WsChannel extends Channel { | ||
constructor(application, req, connection) { | ||
super(application, req); | ||
constructor(server, req, connection) { | ||
super(server, req); | ||
this.connection = connection; | ||
@@ -28,5 +28,5 @@ connection.on('message', (data, isBinary) => { | ||
const createChannel = (application, req, connection) => | ||
new WsChannel(application, req, connection); | ||
const createChannel = (server, req, connection) => | ||
new WsChannel(server, req, connection); | ||
module.exports = { createChannel }; |
@@ -11,3 +11,3 @@ import { EventEmitter } from 'events'; | ||
export class MetacomReadable extends EventEmitter { | ||
export class MetaReadable extends EventEmitter { | ||
streamId: number; | ||
@@ -22,3 +22,3 @@ name: string; | ||
export class MetacomWritable extends EventEmitter { | ||
export class MetaWritable extends EventEmitter { | ||
streamId: number; | ||
@@ -55,4 +55,4 @@ name: string; | ||
): (methodName: string) => (args: object) => Promise<void>; | ||
getStream(streamId: number): MetacomReadable; | ||
createStream(name: string, size: number): MetacomWritable; | ||
getStream(streamId: number): MetaReadable; | ||
createStream(name: string, size: number): MetaWritable; | ||
createBlobUploader(blob: Blob): BlobUploader; | ||
@@ -81,2 +81,12 @@ } | ||
export interface Auth { | ||
generateToken(): string; | ||
saveSession(token: string, data: object): void; | ||
startSession(token: string, data: object, fields?: object): void; | ||
restoreSession(token: string): Promise<object | null>; | ||
deleteSession(token: string): void; | ||
registerUser(login: string, password: string): Promise<object>; | ||
getUser(login: string): Promise<object>; | ||
} | ||
export class Client extends EventEmitter { | ||
@@ -87,8 +97,8 @@ ip: string | undefined; | ||
events: { close: Array<Function> }; | ||
streams: Map<number, MetacomReadable>; | ||
streams: Map<number, MetaReadable>; | ||
redirect(location: string): void; | ||
startSession(token: string, data: object): boolean; | ||
restoreSession(token: string): boolean; | ||
getStream(streamId: number): MetacomReadable; | ||
createStream(name: string, size: number): MetacomWritable; | ||
getStream(streamId: number): MetaReadable; | ||
createStream(name: string, size: number): MetaWritable; | ||
} | ||
@@ -98,2 +108,4 @@ | ||
application: object; | ||
console: Console; | ||
auth: Auth; | ||
req: ClientRequest; | ||
@@ -152,2 +164,3 @@ res: ServerResponse; | ||
application: object; | ||
console: Console; | ||
semaphore: Semaphore; | ||
@@ -154,0 +167,0 @@ server?: any; |
{ | ||
"name": "metacom", | ||
"version": "3.0.0-alpha.4", | ||
"version": "3.0.0-alpha.5", | ||
"author": "Timur Shemsedinov <timur.shemsedinov@gmail.com>", | ||
@@ -53,12 +53,12 @@ "description": "Communication protocol for Metarhia stack with rpc, events, binary streams, memory and db access", | ||
"engines": { | ||
"node": "14 || 16 || 18" | ||
"node": "14 || 16 || 18 || 19" | ||
}, | ||
"dependencies": { | ||
"metautil": "^3.5.22", | ||
"ws": "^8.8.1" | ||
"metautil": "^3.6.0", | ||
"ws": "^8.11.0" | ||
}, | ||
"devDependencies": { | ||
"@types/node": "^18.6.2", | ||
"@types/node": "^18.11.17", | ||
"@types/ws": "^8.5.3", | ||
"eslint": "^8.20.0", | ||
"eslint": "^8.30.0", | ||
"eslint-config-metarhia": "^8.1.0", | ||
@@ -69,5 +69,5 @@ "eslint-config-prettier": "^8.5.0", | ||
"metatests": "^0.8.2", | ||
"prettier": "^2.6.2", | ||
"typescript": "^4.6.4" | ||
"prettier": "^2.8.1", | ||
"typescript": "^4.9.4" | ||
} | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
4
63144
16
1563
Updatedmetautil@^3.6.0
Updatedws@^8.11.0