Compare commits

...

4 Commits

Author SHA1 Message Date
louistiti ee3ff8e38e feat: Socket.IO to native websockets (WIP, binaries do not work) 2023-04-10 00:06:58 +08:00
louistiti f667924a91 refactor(web app): client to server new WS communication (WIP) 2023-04-05 23:54:16 +08:00
louistiti cbd6e8bf3e Merge branch 'develop' into socket.io-to-ws 2023-04-04 23:13:14 +08:00
louistiti 4c47725998 refactor: Socket.IO to WS (WIP) 2023-04-04 22:59:24 +08:00
11 changed files with 420 additions and 664 deletions
+127 -84
View File
@@ -1,14 +1,25 @@
import { io } from 'socket.io-client'
import Chatbot from './chatbot'
const WS_READY_STATES = {
CONNECTING: 0,
OPEN: 1,
CLOSING: 2,
CLOSED: 3
}
function createNewWebSocket(serverUrl) {
return window.location.protocol === 'http:'
? new WebSocket(`ws://${serverUrl.split('://')[1]}/ws`)
: new WebSocket(`wss://${serverUrl.split('://')[1]}/ws`)
}
export default class Client {
constructor(client, serverUrl, input, res) {
this.client = client
this._input = input
this._suggestionContainer = document.querySelector('#suggestions-container')
this.serverUrl = serverUrl
this.socket = io(this.serverUrl)
this.socket = createNewWebSocket(serverUrl)
this.history = localStorage.getItem('history')
this.parsedHistory = []
this.info = res
@@ -31,98 +42,130 @@ export default class Client {
return this._recorder
}
sendSocketMessage(event, data) {
if (this.socket.readyState !== WS_READY_STATES.OPEN) {
console.error('Socket not ready')
/**
* Auto reconnect WebSocket and send the message again
*/
setTimeout(() => {
this.socket = createNewWebSocket(this.serverUrl)
this.initSocket()
}, 250)
setTimeout(() => {
this.sendSocketMessage(event, data)
}, 500)
return
}
this.socket.send(
JSON.stringify({
event,
data,
sentAt: Date.now(),
client: this.client
})
)
}
init(loader) {
this.chatbot.init()
this.socket.on('connect', () => {
this.socket.emit('init', this.client)
})
this.socket.on('ready', () => {
loader.stop()
})
this.socket.on('answer', (data) => {
this.chatbot.receivedFrom('leon', data)
})
this.socket.on('suggest', (data) => {
data?.forEach((suggestionText) => {
this.addSuggestion(suggestionText)
})
})
this.socket.on('is-typing', (data) => {
this.chatbot.isTyping('leon', data)
})
this.socket.on('recognized', (data, cb) => {
this._input.value = data
this.send('utterance')
cb('string-received')
})
this.socket.on('audio-forwarded', (data, cb) => {
const ctx = new AudioContext()
const source = ctx.createBufferSource()
ctx.decodeAudioData(data.buffer, (buffer) => {
source.buffer = buffer
source.connect(ctx.destination)
source.start(0)
/**
* When the after speech option is enabled and
* the answer is a final one
*/
if (this.info.after_speech && data.is_final_answer) {
// Enable recording after the speech + 500ms
setTimeout(() => {
this._recorder.start()
this._recorder.enabled = true
// Check every second if the recorder is enabled to stop it
const id = setInterval(() => {
if (this._recorder.enabled) {
if (this._recorder.countSilenceAfterTalk <= 8) {
// Stop recording if there was no noise for 8 seconds
if (this._recorder.countSilenceAfterTalk === 8) {
this._recorder.stop()
this._recorder.enabled = false
this._recorder.countSilenceAfterTalk = 0
clearInterval(id)
} else if (!this._recorder.noiseDetected) {
this._recorder.countSilenceAfterTalk += 1
} else {
clearInterval(id)
}
}
}
}, 1_000)
}, data.duration + 500)
}
})
cb('audio-received')
})
this.socket.on('download', (data) => {
window.location = `${this.serverUrl}/api/v1/downloads?domain=${data.domain}&skill=${data.skill}`
})
this.initSocket(loader)
if (this.history !== null) {
this.parsedHistory = JSON.parse(this.history)
}
}
initSocket(loader) {
const eventHandlers = {
ready: () => {
loader.stop()
},
answer: (data) => {
this.chatbot.receivedFrom('leon', data)
},
suggest: (data) => {
data?.forEach((suggestionText) => {
this.addSuggestion(suggestionText)
})
},
'is-typing': (data) => {
this.chatbot.isTyping('leon', data)
},
recognized: (data, cb) => {
this._input.value = data
this.send('utterance')
cb('string-received')
},
'audio-forwarded': (data, cb) => {
const ctx = new AudioContext()
const source = ctx.createBufferSource()
ctx.decodeAudioData(data.buffer, (buffer) => {
source.buffer = buffer
source.connect(ctx.destination)
source.start(0)
/**
* When the after speech option is enabled and
* the answer is a final one
*/
if (this.info.after_speech && data.is_final_answer) {
// Enable recording after the speech + 500ms
setTimeout(() => {
this._recorder.start()
this._recorder.enabled = true
// Check every second if the recorder is enabled to stop it
const id = setInterval(() => {
if (this._recorder.enabled) {
if (this._recorder.countSilenceAfterTalk <= 8) {
// Stop recording if there was no noise for 8 seconds
if (this._recorder.countSilenceAfterTalk === 8) {
this._recorder.stop()
this._recorder.enabled = false
this._recorder.countSilenceAfterTalk = 0
clearInterval(id)
} else if (!this._recorder.noiseDetected) {
this._recorder.countSilenceAfterTalk += 1
} else {
clearInterval(id)
}
}
}
}, 1_000)
}, data.duration + 500)
}
})
cb('audio-received')
},
download: (data) => {
window.location = `${this.serverUrl}/api/v1/downloads?domain=${data.domain}&skill=${data.skill}`
}
}
this.socket.addEventListener('open', () => {
this.socket.addEventListener('message', (message) => {
const { event, data } = JSON.parse(message.data)
eventHandlers[event](data)
})
})
}
send(keyword) {
if (this._input.value !== '') {
this.socket.emit(keyword, {
client: this.client,
value: this._input.value.trim()
})
this.sendSocketMessage(keyword, this._input.value.trim())
this.chatbot.sendTo('leon', this._input.value)
this._suggestions.forEach((suggestion) => {
+12 -6
View File
@@ -13,10 +13,8 @@ const config = {
min_decibels: -40, // Noise detection sensitivity
max_blank_time: 1_000 // Maximum time to consider a blank (ms)
}
const serverUrl =
import.meta.env.VITE_LEON_NODE_ENV === 'production'
? ''
: `${config.server_host}:${config.server_port}`
const serverUrl = `${config.server_host}:${config.server_port}`
document.addEventListener('DOMContentLoaded', async () => {
const loader = new Loader()
@@ -59,7 +57,15 @@ document.addEventListener('DOMContentLoaded', async () => {
// Ensure there are some data
if (blob.size >= 1_000) {
client.socket.emit('recognize', blob)
// convert blob to string
const reader = new FileReader()
reader.readAsDataURL(blob)
reader.onloadend = () => {
const base64data = reader.result
client.sendSocketMessage('recognize', base64data)
}
// client.sendSocketMessage('recognize', blob)
}
})
@@ -84,7 +90,7 @@ document.addEventListener('DOMContentLoaded', async () => {
}
)
client.socket.on('enable-record', () => {
client.socket.addEventListener('enable-record', () => {
rec.hotwordTriggered = true
rec.start()
setTimeout(() => {
+18 -5
View File
@@ -6,17 +6,22 @@
const axios = require('axios')
const record = require('node-record-lpcm16')
const { Detector, Models } = require('@bugsounet/snowboy')
const { io } = require('socket.io-client')
const WebSocket = require('ws')
process.env.LEON_HOST = process.env.LEON_HOST || 'http://localhost'
process.env.LEON_PORT = process.env.LEON_PORT || 1337
const url = `${process.env.LEON_HOST}:${process.env.LEON_PORT}`
const socket = io(url)
const socket = url.startsWith('http://')
? new WebSocket(`ws://${url.split('://')[1]}/ws`)
: new WebSocket(`wss://${url.split('://')[1]}/ws`)
const { argv } = process
const lang = argv[2] || 'en'
socket.on('connect', () => {
socket.emit('init', 'hotword-node')
socket.on('error', (error) => {
console.error('Socket error:', error)
})
socket.on('open', () => {
console.log('Language:', lang)
console.log('Connected to the server')
console.log('Waiting for hotword...')
@@ -64,7 +69,15 @@ socket.on('connect', () => {
const obj = { hotword, buffer }
console.log('Hotword detected', obj)
socket.emit('hotword-detected', obj)
socket.send(
JSON.stringify({
event: 'hotword-detected',
data: obj,
sentAt: Date.now(),
client: 'hotword-node'
})
)
})
const mic = record.start({
+57 -438
View File
@@ -11,9 +11,9 @@
"dependencies": {
"@bugsounet/snowboy": "^2.2.5",
"@mapbox/node-pre-gyp": "^1.0.10",
"axios": "^1.3.5",
"node-record-lpcm16": "^0.3.1",
"socket.io-client": "^4.5.2",
"superagent": "^8.0.0"
"ws": "^8.13.0"
}
},
"node_modules/@bugsounet/node-lpcm16": {
@@ -58,11 +58,6 @@
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"node_modules/@types/node": {
"version": "16.11.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.21.tgz",
@@ -109,16 +104,21 @@
"node": ">=10"
}
},
"node_modules/asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
},
"node_modules/asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.5.tgz",
"integrity": "sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -133,18 +133,6 @@
"concat-map": "0.0.1"
}
},
"node_modules/call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"dependencies": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/child_process": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz",
@@ -177,11 +165,6 @@
"node": ">= 0.8"
}
},
"node_modules/component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -192,11 +175,6 @@
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
},
"node_modules/cookiejar": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz",
"integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ=="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -234,45 +212,30 @@
"node": ">=8"
}
},
"node_modules/dezalgo": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
"integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==",
"dependencies": {
"asap": "^2.0.0",
"wrappy": "1"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"node_modules/engine.io-client": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz",
"integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.0.3",
"ws": "~8.2.3",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"node_modules/engine.io-parser": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz",
"integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==",
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=10.0.0"
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/fast-safe-stringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
},
"node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
@@ -286,31 +249,6 @@
"node": ">= 6"
}
},
"node_modules/formidable": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz",
"integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==",
"dependencies": {
"dezalgo": "1.0.3",
"hexoid": "1.0.0",
"once": "1.4.0",
"qs": "6.9.3"
},
"funding": {
"url": "https://ko-fi.com/tunnckoCore/commissions"
}
},
"node_modules/formidable/node_modules/qs": {
"version": "6.9.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
"integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw==",
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
@@ -327,11 +265,6 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"node_modules/function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
@@ -351,19 +284,6 @@
"node": ">=10"
}
},
"node_modules/get-intrinsic": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
"integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
"dependencies": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.3"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@@ -383,41 +303,11 @@
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"dependencies": {
"function-bind": "^1.1.1"
},
"engines": {
"node": ">= 0.4.0"
}
},
"node_modules/has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
"engines": {
"node": ">= 0.4"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
},
"node_modules/hexoid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g==",
"engines": {
"node": ">=8"
}
},
"node_modules/https-proxy-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
@@ -485,25 +375,6 @@
"semver": "bin/semver.js"
}
},
"node_modules/methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==",
"bin": {
"mime": "cli.js"
},
"engines": {
"node": ">=4.0.0"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -635,14 +506,6 @@
"node": ">=0.10.0"
}
},
"node_modules/object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -689,19 +552,10 @@
"node": ">= 0.6.0"
}
},
"node_modules/qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"dependencies": {
"side-channel": "^1.0.4"
},
"engines": {
"node": ">=0.6"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
"node_modules/proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"node_modules/readable-stream": {
"version": "3.6.0",
@@ -754,50 +608,11 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"node_modules/side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"dependencies": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
},
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/signal-exit": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
"integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ=="
},
"node_modules/socket.io-client": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.2.tgz",
"integrity": "sha512-naqYfFu7CLDiQ1B7AlLhRXKX3gdeaIMfgigwavDzgJoIUYulc1qHH5+2XflTsXTPY7BlPH5rppJyUjhjrKQKLg==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.2.1",
"socket.io-parser": "~4.2.0"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/socket.io-parser": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz",
"integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==",
"dependencies": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -830,27 +645,6 @@
"node": ">=8"
}
},
"node_modules/superagent": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.0.tgz",
"integrity": "sha512-iudipXEel+SzlP9y29UBWGDjB+Zzag+eeA1iLosaR2YHBRr1Q1kC29iBrF2zIVD9fqVbpZnXkN/VJmwFMVyNWg==",
"dependencies": {
"component-emitter": "^1.3.0",
"cookiejar": "^2.1.3",
"debug": "^4.3.4",
"fast-safe-stringify": "^2.1.1",
"form-data": "^4.0.0",
"formidable": "^2.0.1",
"methods": "^1.1.2",
"mime": "2.6.0",
"qs": "^6.10.3",
"readable-stream": "^3.6.0",
"semver": "^7.3.7"
},
"engines": {
"node": ">=6.4.0 <13 || >=14"
}
},
"node_modules/tar": {
"version": "6.1.11",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz",
@@ -917,15 +711,15 @@
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"node_modules/ws": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"engines": {
"node": ">=10.0.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
"utf-8-validate": ">=5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
@@ -936,14 +730,6 @@
}
}
},
"node_modules/xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A==",
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
@@ -989,11 +775,6 @@
"tar": "^6.1.11"
}
},
"@socket.io/component-emitter": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
},
"@types/node": {
"version": "16.11.21",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.21.tgz",
@@ -1031,16 +812,21 @@
"readable-stream": "^3.6.0"
}
},
"asap": {
"version": "2.0.6",
"resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz",
"integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA=="
},
"asynckit": {
"version": "0.4.0",
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"axios": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.5.tgz",
"integrity": "sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==",
"requires": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -1055,15 +841,6 @@
"concat-map": "0.0.1"
}
},
"call-bind": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
"integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
"requires": {
"function-bind": "^1.1.1",
"get-intrinsic": "^1.0.2"
}
},
"child_process": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/child_process/-/child_process-1.0.2.tgz",
@@ -1087,11 +864,6 @@
"delayed-stream": "~1.0.0"
}
},
"component-emitter": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
@@ -1102,11 +874,6 @@
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4="
},
"cookiejar": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/cookiejar/-/cookiejar-2.1.3.tgz",
"integrity": "sha512-JxbCBUdrfr6AQjOXrxoTvAMJO4HBTUIlBzslcJPAz+/KT8yk53fXun51u+RenNYvad/+Vc2DIz5o9UxlCDymFQ=="
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
@@ -1130,41 +897,15 @@
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.1.tgz",
"integrity": "sha512-463v3ZeIrcWtdgIg6vI6XUncguvr2TnGl4SzDXinkt9mSLpBJKXT3mW6xT3VQdDN11+WVs29pgvivTc4Lp8v+w=="
},
"dezalgo": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.3.tgz",
"integrity": "sha512-K7i4zNfT2kgQz3GylDw40ot9GAE47sFZ9EXHFSPP6zONLgH6kWXE0KWJchkbQJLBkRazq4APwZ4OwiFFlT95OQ==",
"requires": {
"asap": "^2.0.0",
"wrappy": "1"
}
},
"emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
},
"engine.io-client": {
"version": "6.2.2",
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz",
"integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1",
"engine.io-parser": "~5.0.3",
"ws": "~8.2.3",
"xmlhttprequest-ssl": "~2.0.0"
}
},
"engine.io-parser": {
"version": "5.0.4",
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz",
"integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg=="
},
"fast-safe-stringify": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA=="
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
},
"form-data": {
"version": "4.0.0",
@@ -1176,24 +917,6 @@
"mime-types": "^2.1.12"
}
},
"formidable": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/formidable/-/formidable-2.0.1.tgz",
"integrity": "sha512-rjTMNbp2BpfQShhFbR3Ruk3qk2y9jKpvMW78nJgx8QKtxjDVrwbZG+wvDOmVbifHyOUOQJXxqEy6r0faRrPzTQ==",
"requires": {
"dezalgo": "1.0.3",
"hexoid": "1.0.0",
"once": "1.4.0",
"qs": "6.9.3"
},
"dependencies": {
"qs": {
"version": "6.9.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.9.3.tgz",
"integrity": "sha512-EbZYNarm6138UKKq46tdx08Yo/q9ZhFoAXAI1meAFd2GtbRDhbZY2WQSICskT0c5q99aFzLG1D4nvTk9tqfXIw=="
}
}
},
"fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
@@ -1207,11 +930,6 @@
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
},
"function-bind": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
"integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A=="
},
"gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
@@ -1228,16 +946,6 @@
"wide-align": "^1.1.2"
}
},
"get-intrinsic": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.2.tgz",
"integrity": "sha512-Jfm3OyCxHh9DJyc28qGk+JmfkpO41A4XkneDSujN9MDXrm4oDKdHvndhZ2dN94+ERNfkYJWDclW6k2L/ZGHjXA==",
"requires": {
"function-bind": "^1.1.1",
"has": "^1.0.3",
"has-symbols": "^1.0.3"
}
},
"glob": {
"version": "7.1.6",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
@@ -1251,29 +959,11 @@
"path-is-absolute": "^1.0.0"
}
},
"has": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
"integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
"requires": {
"function-bind": "^1.1.1"
}
},
"has-symbols": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
"integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A=="
},
"has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk="
},
"hexoid": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/hexoid/-/hexoid-1.0.0.tgz",
"integrity": "sha512-QFLV0taWQOZtvIRIAdBChesmogZrtuXvVWsFHZTk2SU+anspqZ2vMnoLg7IE1+Uk16N19APic1BuF8bC8c2m5g=="
},
"https-proxy-agent": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.0.tgz",
@@ -1325,16 +1015,6 @@
}
}
},
"methods": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz",
"integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4="
},
"mime": {
"version": "2.6.0",
"resolved": "https://registry.npmjs.org/mime/-/mime-2.6.0.tgz",
"integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg=="
},
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -1425,11 +1105,6 @@
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM="
},
"object-inspect": {
"version": "1.12.2",
"resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
"integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ=="
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
@@ -1472,13 +1147,10 @@
"resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz",
"integrity": "sha1-czIwDoQBYb2j5podHZGn1LwW8YI="
},
"qs": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.11.0.tgz",
"integrity": "sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==",
"requires": {
"side-channel": "^1.0.4"
}
"proxy-from-env": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
"integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
},
"readable-stream": {
"version": "3.6.0",
@@ -1516,41 +1188,11 @@
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
},
"side-channel": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
"integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
"requires": {
"call-bind": "^1.0.0",
"get-intrinsic": "^1.0.2",
"object-inspect": "^1.9.0"
}
},
"signal-exit": {
"version": "3.0.6",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz",
"integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ=="
},
"socket.io-client": {
"version": "4.5.2",
"resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.5.2.tgz",
"integrity": "sha512-naqYfFu7CLDiQ1B7AlLhRXKX3gdeaIMfgigwavDzgJoIUYulc1qHH5+2XflTsXTPY7BlPH5rppJyUjhjrKQKLg==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.2",
"engine.io-client": "~6.2.1",
"socket.io-parser": "~4.2.0"
}
},
"socket.io-parser": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.1.tgz",
"integrity": "sha512-V4GrkLy+HeF1F/en3SpUaM+7XxYXpuMUWLGde1kSSh5nQMN4hLrbPIkD+otwh6q9R6NOQBN4AMaOZ2zVjui82g==",
"requires": {
"@socket.io/component-emitter": "~3.1.0",
"debug": "~4.3.1"
}
},
"string_decoder": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
@@ -1577,24 +1219,6 @@
"ansi-regex": "^5.0.1"
}
},
"superagent": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/superagent/-/superagent-8.0.0.tgz",
"integrity": "sha512-iudipXEel+SzlP9y29UBWGDjB+Zzag+eeA1iLosaR2YHBRr1Q1kC29iBrF2zIVD9fqVbpZnXkN/VJmwFMVyNWg==",
"requires": {
"component-emitter": "^1.3.0",
"cookiejar": "^2.1.3",
"debug": "^4.3.4",
"fast-safe-stringify": "^2.1.1",
"form-data": "^4.0.0",
"formidable": "^2.0.1",
"methods": "^1.1.2",
"mime": "2.6.0",
"qs": "^6.10.3",
"readable-stream": "^3.6.0",
"semver": "^7.3.7"
}
},
"tar": {
"version": "6.1.11",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.1.11.tgz",
@@ -1651,16 +1275,11 @@
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
},
"ws": {
"version": "8.2.3",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"requires": {}
},
"xmlhttprequest-ssl": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A=="
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+2 -2
View File
@@ -15,8 +15,8 @@
"dependencies": {
"@bugsounet/snowboy": "^2.2.5",
"@mapbox/node-pre-gyp": "^1.0.10",
"axios": "^1.3.5",
"node-record-lpcm16": "^0.3.1",
"socket.io-client": "^4.5.2",
"superagent": "^8.0.0"
"ws": "^8.13.0"
}
}
+3 -3
View File
@@ -97,10 +97,9 @@
"os-name": "4.0.1",
"pretty-bytes": "5.6.0",
"pretty-ms": "7.0.1",
"socket.io": "4.5.2",
"socket.io-client": "4.5.2",
"stt": "1.4.0",
"tree-kill": "1.2.2"
"tree-kill": "1.2.2",
"ws": "8.13.0"
},
"devDependencies": {
"@nlpjs/utils": "4.24.1",
@@ -111,6 +110,7 @@
"@types/fluent-ffmpeg": "2.1.20",
"@types/node": "18.7.13",
"@types/node-wav": "0.0.0",
"@types/ws": "8.5.4",
"@typescript-eslint/eslint-plugin": "5.55.0",
"@typescript-eslint/parser": "5.55.0",
"cli-spinner": "0.2.10",
+28 -38
View File
@@ -33,45 +33,35 @@ export default class ASR {
return new Promise((resolve, reject) => {
LogHelper.title('ASR')
fs.writeFile(
this.audioPaths.webm,
Buffer.from(blob),
'binary',
async (err) => {
if (err) {
reject(new Error(`${err}`))
return
fs.createWriteStream(this.audioPaths.webm).write(blob)
ffmpeg.setFfmpegPath(ffmpegPath)
/**
* Encode WebM file to WAVE file
* ffmpeg -i speech.webm -acodec pcm_s16le -ar 16000 -ac 1 speech.wav
*/
ffmpeg()
.addInput(this.audioPaths.webm)
.on('start', () => {
LogHelper.info('Encoding WebM file to WAVE file...')
})
.on('end', () => {
LogHelper.success('Encoding done')
if (!STT.isParserReady) {
reject(new Error('The speech recognition is not ready yet'))
} else {
STT.transcribe(this.audioPaths.wav)
resolve()
}
ffmpeg.setFfmpegPath(ffmpegPath)
/**
* Encode WebM file to WAVE file
* ffmpeg -i speech.webm -acodec pcm_s16le -ar 16000 -ac 1 speech.wav
*/
ffmpeg()
.addInput(this.audioPaths.webm)
.on('start', () => {
LogHelper.info('Encoding WebM file to WAVE file...')
})
.on('end', () => {
LogHelper.success('Encoding done')
if (!STT.isParserReady) {
reject(new Error('The speech recognition is not ready yet'))
} else {
STT.transcribe(this.audioPaths.wav)
resolve()
}
})
.on('error', (err) => {
reject(new Error(`Encoding error ${err}`))
})
.outputOptions(['-acodec pcm_s16le', '-ar 16000', '-ac 1'])
.output(this.audioPaths.wav)
.run()
}
)
})
.on('error', (err) => {
reject(new Error(`Encoding error ${err}`))
})
.outputOptions(['-acodec pcm_s16le', '-ar 16000', '-ac 1'])
.output(this.audioPaths.wav)
.run()
})
}
}
+8 -8
View File
@@ -104,7 +104,7 @@ export default class Brain {
TTS.add(speech, end)
}
SOCKET_SERVER.socket?.emit('answer', rawSpeech)
SOCKET_SERVER.sendSocketMessage('answer', rawSpeech)
}
}
@@ -153,7 +153,7 @@ export default class Brain {
const speech = `${this.wernicke('random_not_sure')}.`
this.talk(speech, true)
SOCKET_SERVER.socket?.emit('ask-to-repeat', nluResult)
SOCKET_SERVER.sendSocketMessage('ask-to-repeat', nluResult)
}
}
@@ -227,7 +227,7 @@ export default class Brain {
})}!`
if (!this.isMuted) {
this.talk(speech)
SOCKET_SERVER.socket?.emit('is-typing', false)
SOCKET_SERVER.sendSocketMessage('is-typing', false)
}
this.speeches.push(speech)
}
@@ -417,7 +417,7 @@ export default class Brain {
Brain.deleteIntentObjFile(intentObjectPath)
if (!this.isMuted) {
SOCKET_SERVER.socket?.emit('is-typing', false)
SOCKET_SERVER.sendSocketMessage('is-typing', false)
}
const executionTimeEnd = Date.now()
@@ -428,13 +428,13 @@ export default class Brain {
nextAction?.suggestions &&
skillResult?.output.core?.showNextActionSuggestions
) {
SOCKET_SERVER.socket?.emit('suggest', nextAction.suggestions)
SOCKET_SERVER.sendSocketMessage('suggest', nextAction.suggestions)
}
if (
action?.suggestions &&
skillResult?.output.core?.showSuggestions
) {
SOCKET_SERVER.socket?.emit('suggest', action.suggestions)
SOCKET_SERVER.sendSocketMessage('suggest', action.suggestions)
}
resolve({
@@ -558,12 +558,12 @@ export default class Brain {
if (!this.isMuted) {
this.talk(answer as string, true)
SOCKET_SERVER.socket?.emit('is-typing', false)
SOCKET_SERVER.sendSocketMessage('is-typing', false)
}
// Send suggestions to the client
if (nextAction?.suggestions) {
SOCKET_SERVER.socket?.emit('suggest', nextAction.suggestions)
SOCKET_SERVER.sendSocketMessage('suggest', nextAction.suggestions)
}
resolve({
+4 -4
View File
@@ -101,7 +101,7 @@ export default class NLU {
if (!MODEL_LOADER.hasNlpModels()) {
if (!BRAIN.isMuted) {
BRAIN.talk(`${BRAIN.wernicke('random_errors')}!`)
SOCKET_SERVER.socket?.emit('is-typing', false)
SOCKET_SERVER.sendSocketMessage('is-typing', false)
}
const msg =
@@ -175,7 +175,7 @@ export default class NLU {
const isSupportedLanguage = LangHelper.getShortCodes().includes(locale)
if (!isSupportedLanguage) {
BRAIN.talk(`${BRAIN.wernicke('random_language_not_supported')}.`, true)
SOCKET_SERVER.socket?.emit('is-typing', false)
SOCKET_SERVER.sendSocketMessage('is-typing', false)
return resolve({})
}
@@ -195,7 +195,7 @@ export default class NLU {
if (!fallback) {
if (!BRAIN.isMuted) {
BRAIN.talk(`${BRAIN.wernicke('random_unknown_intents')}.`, true)
SOCKET_SERVER.socket?.emit('is-typing', false)
SOCKET_SERVER.sendSocketMessage('is-typing', false)
}
LogHelper.title('NLU')
@@ -307,7 +307,7 @@ export default class NLU {
LogHelper.error(errorMessage)
if (!BRAIN.isMuted) {
SOCKET_SERVER.socket?.emit('is-typing', false)
SOCKET_SERVER.sendSocketMessage('is-typing', false)
}
return reject(new Error(errorMessage))
+3 -3
View File
@@ -98,7 +98,7 @@ export class SlotFilling {
notFilledSlot = NLU.conversation.getNotFilledSlot()
if (notFilledSlot) {
BRAIN.talk(notFilledSlot.pickedQuestion)
SOCKET_SERVER.socket?.emit('is-typing', false)
SOCKET_SERVER.sendSocketMessage('is-typing', false)
return {}
}
@@ -170,9 +170,9 @@ export class SlotFilling {
({ name }) => name === notFilledSlot.name
) ?? []
SOCKET_SERVER.socket?.emit('suggest', currentSlot?.suggestions)
SOCKET_SERVER.sendSocketMessage('suggest', currentSlot?.suggestions)
BRAIN.talk(notFilledSlot.pickedQuestion)
SOCKET_SERVER.socket?.emit('is-typing', false)
SOCKET_SERVER.sendSocketMessage('is-typing', false)
return true
}
+158 -73
View File
@@ -1,7 +1,7 @@
import type { DefaultEventsMap } from 'socket.io/dist/typed-events'
import { Server as SocketIOServer, Socket } from 'socket.io'
import type { WebSocket } from 'ws'
import { WebSocketServer } from 'ws'
import { LANG, HAS_STT, HAS_TTS, IS_DEVELOPMENT_ENV } from '@/constants'
import { LANG, HAS_STT, HAS_TTS } from '@/constants'
import {
HTTP_SERVER,
TCP_CLIENT,
@@ -15,21 +15,40 @@ import {
import { LogHelper } from '@/helpers/log-helper'
import { LangHelper } from '@/helpers/lang-helper'
interface HotwordDataEvent {
hotword: string
buffer: Buffer
interface SocketMessage {
event: string
sentAt: number // Timestamp
client: string
data: unknown
}
interface UtteranceDataEvent {
client: string
value: string
interface HotwordDetectedMessage extends SocketMessage {
data: {
hotword: string
buffer: Buffer
}
}
interface UtteranceMessage extends SocketMessage {
data: string
}
interface RecognizeMessage extends SocketMessage {
data: Buffer
}
enum WSReadyState {
CONNECTING = 0,
OPEN = 1,
CLOSING = 2,
CLOSED = 3
}
export default class SocketServer {
private static instance: SocketServer
private wsServer: WebSocketServer | undefined = undefined
public socket: Socket<DefaultEventsMap, DefaultEventsMap> | undefined =
undefined
public socket: WebSocket | undefined = undefined
constructor() {
if (!SocketServer.instance) {
@@ -40,12 +59,107 @@ export default class SocketServer {
}
}
public async init(): Promise<void> {
const io = IS_DEVELOPMENT_ENV
? new SocketIOServer(HTTP_SERVER.httpServer, {
cors: { origin: `${HTTP_SERVER.host}:3000` }
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public sendSocketMessage(event: string, data: any = null): void {
if (!this.socket || this.socket.readyState !== WSReadyState.OPEN) {
LogHelper.title('Socket Server')
LogHelper.error('Socket not ready')
return
}
this.socket.send(
JSON.stringify({
event,
data,
sentAt: Date.now()
})
)
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any
public broadcastSocketMessage(event: string, data: any = null): void {
if (!this.wsServer) {
LogHelper.title('Socket Server')
LogHelper.error('WebSocket server not ready')
return
}
this.wsServer.clients.forEach((client) => {
client.send(
JSON.stringify({
event,
data,
sentAt: Date.now()
})
: new SocketIOServer(HTTP_SERVER.httpServer)
)
})
}
/**
* Handle on hotward detected
*/
private onHotwordDetectedMessage(message: HotwordDetectedMessage): void {
if (message.event === 'hotword-detected') {
// Hotword triggered
LogHelper.title('Socket')
LogHelper.success(`Hotword ${message.data.hotword} detected`)
this.broadcastSocketMessage('enable-record')
}
}
/**
* Handle on utterance
*/
private async onUtteranceMessage(message: UtteranceMessage): Promise<void> {
// Listen for new utterance
if (message.event === 'utterance') {
const { data: utterance, client } = message
LogHelper.title('Socket')
LogHelper.info(`${client} emitted: ${utterance}`)
this.sendSocketMessage('is-typing', true)
try {
LogHelper.time('Utterance processed in')
BRAIN.isMuted = false
await NLU.process(utterance)
LogHelper.title('Execution Time')
LogHelper.timeEnd('Utterance processed in')
} catch (e) {
LogHelper.error(`Failed to process utterance: ${e}`)
}
}
}
/**
* Handle on automatic speech recognition
*/
private async onRecognizeMessage(message: RecognizeMessage): Promise<void> {
if (message.event === 'recognize') {
const { data: string } = message
console.log('string', string)
try {
await ASR.encode(Buffer.from(string, 'base64'))
} catch (e) {
LogHelper.error(`ASR - Failed to encode audio blob to WAVE file: ${e}`)
}
}
}
public async init(): Promise<void> {
/**
* @see https://github.com/websockets/ws/blob/HEAD/doc/ws.md#class-websocketserver
*/
this.wsServer = new WebSocketServer({
server: HTTP_SERVER.httpServer,
path: '/ws'
})
let sttState = 'disabled'
let ttsState = 'disabled'
@@ -71,73 +185,44 @@ export default class SocketServer {
LogHelper.error(`Failed to load NLP models: ${e}`)
}
io.on('connection', (socket) => {
this.wsServer.on('connection', (socket) => {
LogHelper.title('Client')
LogHelper.success('Connected')
/**
* @see https://github.com/websockets/ws/blob/HEAD/doc/ws.md#class-websocket
*/
this.socket = socket
// Init
this.socket.on('init', (data: string) => {
LogHelper.info(`Type: ${data}`)
LogHelper.info(`Socket ID: ${this.socket?.id}`)
// Check whether the TCP client is connected to the TCP server
if (TCP_CLIENT.isConnected) {
this.sendSocketMessage('ready')
} else {
TCP_CLIENT.ee.on('connected', () => {
this.sendSocketMessage('ready')
})
}
// TODO
// const provider = await addProvider(socket.id)
this.socket.on('message', (socketMessage: Buffer) => {
try {
const message = JSON.parse(socketMessage.toString())
// Check whether the TCP client is connected to the TCP server
if (TCP_CLIENT.isConnected) {
this.socket?.emit('ready')
} else {
TCP_CLIENT.ee.on('connected', () => {
this.socket?.emit('ready')
})
}
if (data === 'hotword-node') {
// Hotword triggered
this.socket?.on('hotword-detected', (data: HotwordDataEvent) => {
LogHelper.title('Socket')
LogHelper.success(`Hotword ${data.hotword} detected`)
this.socket?.broadcast.emit('enable-record')
})
} else {
// Listen for new utterance
this.socket?.on('utterance', async (data: UtteranceDataEvent) => {
LogHelper.title('Socket')
LogHelper.info(`${data.client} emitted: ${data.value}`)
this.socket?.emit('is-typing', true)
const { value: utterance } = data
try {
LogHelper.time('Utterance processed in')
BRAIN.isMuted = false
await NLU.process(utterance)
LogHelper.title('Execution Time')
LogHelper.timeEnd('Utterance processed in')
} catch (e) {
LogHelper.error(`Failed to process utterance: ${e}`)
}
})
// Handle automatic speech recognition
this.socket?.on('recognize', async (data: Buffer) => {
try {
await ASR.encode(data)
} catch (e) {
LogHelper.error(
`ASR - Failed to encode audio blob to WAVE file: ${e}`
)
}
})
/**
* Listen for socket messages
*/
this.onHotwordDetectedMessage(message)
this.onUtteranceMessage(message)
this.onRecognizeMessage(message)
} catch (e) {
LogHelper.error(
`Failed to process socket message as it does not respect the format: ${e}`
)
}
})
this.socket.once('disconnect', () => {
this.socket?.once('close', () => {
LogHelper.title('Client')
LogHelper.success('Disconnected')
// TODO
// deleteProvider(this.socket.id)
})