Merge remote-tracking branch 'tuya/webrtc' into tuya

This commit is contained in:
Koushik Dutta
2022-09-03 15:42:33 -05:00
9 changed files with 1402 additions and 40 deletions

View File

@@ -4,7 +4,14 @@ This is a Tuya controller that integrates Tuya devices, specifically cameras, in
The plugin will discover all the cameras within Tuya Cloud IoT project and report them to Scrypted, including motion events, for the ones that are supported.
## Features
- Supports Tuya Cameras Streaming.
- Supports Tuya Doorbells with ring notifications.
- Supports 2-Way communication (for devices that support WebRTC).
## Requirements
### Access Id, Access Key, and User Id
In order to retrieve `Access Id` and `Access Key`, you must follow the guide below:
- [Using Smart Home PaaS (TuyaSmart, SmartLife, ect...)](https://developer.tuya.com/en/docs/iot/Platform_Configuration_smarthome?id=Kamcgamwoevrx&_source=6435717a3be1bc67fdd1f6699a1a59ac)
@@ -12,12 +19,8 @@ In order to retrieve `Access Id` and `Access Key`, you must follow the guide bel
Once you have retreived both the `Access Id` and `Access Key` from the project, you can get the `User Id` by going to Tuya Cloud IoT -> Select the Project -> Devices -> Link Tuya App Account -> and then get the UID.
You also need to enable Messages Service in your project in order to receive real time notifications to Scrypted. (motion events, online/offline, light switch ect...) The way this is achieved is by following this [guide](https://developer.tuya.com/en/docs/iot/subscribe-mq?id=Kavqcrvckbh9h).
### Tuya Pulsar
You need to enable Messages Service in your project in order to receive real time notifications to Scrypted. (motion events, online/offline, light switch ect...) The way this is achieved is by following this [guide](https://developer.tuya.com/en/docs/iot/subscribe-mq?id=Kavqcrvckbh9h).
- You do not need to set an alert notification of your phone.
- This might not be necessary in the future if I believe MQTT is the way to go, but in the mean time, TuyaPulse is required for this project.
## TODOs
- Fix 2-way talk for supported platforms (Can only work with WebRTC since we only get one stream with RTSPS)
- Add support for camera doorbells (Just need to implement doorbell notification)

View File

@@ -1,17 +1,19 @@
{
"name": "@scrypted/tuya",
"version": "0.0.6",
"version": "0.0.7-beta.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/tuya",
"version": "0.0.6",
"version": "0.0.7-beta.2",
"dependencies": {
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"axios": "^0.27.2",
"crypto-js": "^4.1.1",
"mqtt": "^4.3.7",
"mqtt-packet": "^8.1.2",
"ws": "^8.8.1"
},
"devDependencies": {
@@ -36,6 +38,49 @@
"@types/node": "^16.9.0"
}
},
"../../external/werift/packages/webrtc": {
"name": "werift",
"version": "0.15.11",
"extraneous": true,
"license": "MIT",
"dependencies": {
"@fidm/x509": "^1.2.1",
"@minhducsun2002/leb128": "^0.2.0",
"@peculiar/webcrypto": "^1.4.0",
"@peculiar/x509": "^1.7.2",
"@shinyoshiaki/ebml-builder": "^0.0.1",
"aes-js": "^3.1.2",
"binary-data": "^0.6.0",
"buffer-crc32": "^0.2.13",
"date-fns": "^2.28.0",
"debug": "^4.3.4",
"elliptic": "^6.5.3",
"int64-buffer": "^1.0.1",
"ip": "^1.1.8",
"jspack": "^0.0.4",
"lodash": "^4.17.20",
"nano-time": "^1.0.0",
"p-cancelable": "^2.1.1",
"rx.mini": "^1.1.0",
"turbo-crc32": "^1.0.1",
"tweetnacl": "^1.0.3",
"uuid": "^8.3.2"
},
"devDependencies": {
"@types/aes-js": "^3.1.1",
"@types/buffer-crc32": "^0.2.0",
"@types/debug": "^4.1.7",
"@types/elliptic": "^6.4.14",
"@types/ip": "^1.1.0",
"@types/jest": "^28.1.3",
"@types/lodash": "^4.14.178",
"@types/node": "^18.0.0",
"@types/uuid": "^8.3.3"
},
"engines": {
"node": ">=16"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.0.206",
@@ -124,6 +169,77 @@
"form-data": "^4.0.0"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/bl": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-5.0.0.tgz",
"integrity": "sha512-8vxFNZ0pflFfi0WXA3WQXlj6CaMEwsmh63I1CNp0q+wWv8sD0ARx1KovSQd0l2GkwrMIOyedq0EF1FxI+RCZLQ==",
"dependencies": {
"buffer": "^6.0.3",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -135,11 +251,55 @@
"node": ">= 0.8"
}
},
"node_modules/commist": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz",
"integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==",
"dependencies": {
"leven": "^2.1.0",
"minimist": "^1.1.0"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"node_modules/concat-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
"engines": [
"node >= 6.0"
],
"dependencies": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.0.2",
"typedarray": "^0.0.6"
}
},
"node_modules/crypto-js": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
@@ -148,6 +308,25 @@
"node": ">=0.4.0"
}
},
"node_modules/duplexify": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
"integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==",
"dependencies": {
"end-of-stream": "^1.4.1",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1",
"stream-shift": "^1.0.0"
}
},
"node_modules/end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"dependencies": {
"once": "^1.4.0"
}
},
"node_modules/follow-redirects": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
@@ -180,6 +359,96 @@
"node": ">= 6"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/help-me": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-3.0.0.tgz",
"integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==",
"dependencies": {
"glob": "^7.1.6",
"readable-stream": "^3.6.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/js-sdsl": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.3.tgz",
"integrity": "sha512-p6umEbgMJq1OL+2z6eYFj8/yHlsx+0gX2nNoSqnu0V5KZaFGBaUfvktdbm5BGrlojadQ+Hjir0rdsaTmzoyd5Q=="
},
"node_modules/leven": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
"integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -199,6 +468,249 @@
"node": ">= 0.6"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"node_modules/mqtt": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.3.7.tgz",
"integrity": "sha512-ew3qwG/TJRorTz47eW46vZ5oBw5MEYbQZVaEji44j5lAUSQSqIEoul7Kua/BatBW0H0kKQcC9kwUHa1qzaWHSw==",
"dependencies": {
"commist": "^1.0.0",
"concat-stream": "^2.0.0",
"debug": "^4.1.1",
"duplexify": "^4.1.1",
"help-me": "^3.0.0",
"inherits": "^2.0.3",
"lru-cache": "^6.0.0",
"minimist": "^1.2.5",
"mqtt-packet": "^6.8.0",
"number-allocator": "^1.0.9",
"pump": "^3.0.0",
"readable-stream": "^3.6.0",
"reinterval": "^1.1.0",
"rfdc": "^1.3.0",
"split2": "^3.1.0",
"ws": "^7.5.5",
"xtend": "^4.0.2"
},
"bin": {
"mqtt": "bin/mqtt.js",
"mqtt_pub": "bin/pub.js",
"mqtt_sub": "bin/sub.js"
},
"engines": {
"node": ">=10.0.0"
}
},
"node_modules/mqtt-packet": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-8.1.2.tgz",
"integrity": "sha512-vL1YTct+TAy0PqX3Jv8jM3JMzObH6vC/lyA0I5LtD4xvydOdIdmofrSp12PE3jajiIOUaW3XxmQekbyToXpsSw==",
"dependencies": {
"bl": "^5.0.0",
"debug": "^4.1.1",
"process-nextick-args": "^2.0.1"
}
},
"node_modules/mqtt/node_modules/bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"dependencies": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"node_modules/mqtt/node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/mqtt/node_modules/mqtt-packet": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-6.10.0.tgz",
"integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==",
"dependencies": {
"bl": "^4.0.2",
"debug": "^4.1.1",
"process-nextick-args": "^2.0.1"
}
},
"node_modules/mqtt/node_modules/ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"engines": {
"node": ">=8.3.0"
},
"peerDependencies": {
"bufferutil": "^4.0.1",
"utf-8-validate": "^5.0.2"
},
"peerDependenciesMeta": {
"bufferutil": {
"optional": true
},
"utf-8-validate": {
"optional": true
}
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"node_modules/number-allocator": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.11.tgz",
"integrity": "sha512-ykOuVG+oGw67qwt0eW0sPaIR+ANtB58QCpVaaGLxt0QekRXDA5Q/eG7sJmFEZpIcSVdjdevmO72Z6mH258y7Hw==",
"dependencies": {
"debug": "^4.3.1",
"js-sdsl": "^4.1.3"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"dependencies": {
"wrappy": "1"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"node_modules/pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"dependencies": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"node_modules/readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/reinterval": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz",
"integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ=="
},
"node_modules/rfdc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
"integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/split2": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
"integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
"dependencies": {
"readable-stream": "^3.0.0"
}
},
"node_modules/stream-shift": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"node_modules/ws": {
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz",
@@ -218,6 +730,19 @@
"optional": true
}
}
},
"node_modules/xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
"engines": {
"node": ">=0.4"
}
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
},
"dependencies": {
@@ -296,6 +821,49 @@
"form-data": "^4.0.0"
}
},
"balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
},
"base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
},
"bl": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-5.0.0.tgz",
"integrity": "sha512-8vxFNZ0pflFfi0WXA3WQXlj6CaMEwsmh63I1CNp0q+wWv8sD0ARx1KovSQd0l2GkwrMIOyedq0EF1FxI+RCZLQ==",
"requires": {
"buffer": "^6.0.3",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"requires": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
"integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ=="
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -304,16 +872,68 @@
"delayed-stream": "~1.0.0"
}
},
"commist": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/commist/-/commist-1.1.0.tgz",
"integrity": "sha512-rraC8NXWOEjhADbZe9QBNzLAN5Q3fsTPQtBV+fEVj6xKIgDgNiEVE6ZNfHpZOqfQ21YUzfVNUXLOEZquYvQPPg==",
"requires": {
"leven": "^2.1.0",
"minimist": "^1.1.0"
}
},
"concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
},
"concat-stream": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-2.0.0.tgz",
"integrity": "sha512-MWufYdFw53ccGjCA+Ol7XJYpAlW6/prSMzuPOTRnJGcGzuhLn4Scrz7qf6o8bROZ514ltazcIFJZevcfbo0x7A==",
"requires": {
"buffer-from": "^1.0.0",
"inherits": "^2.0.3",
"readable-stream": "^3.0.2",
"typedarray": "^0.0.6"
}
},
"crypto-js": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.1.1.tgz",
"integrity": "sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw=="
},
"debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"requires": {
"ms": "2.1.2"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"duplexify": {
"version": "4.1.2",
"resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.2.tgz",
"integrity": "sha512-fz3OjcNCHmRP12MJoZMPglx8m4rrFP8rovnk4vT8Fs+aonZoCwGg10dSsQsfP/E62eZcPTMSMP6686fu9Qlqtw==",
"requires": {
"end-of-stream": "^1.4.1",
"inherits": "^2.0.3",
"readable-stream": "^3.1.1",
"stream-shift": "^1.0.0"
}
},
"end-of-stream": {
"version": "1.4.4",
"resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
"integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
"requires": {
"once": "^1.4.0"
}
},
"follow-redirects": {
"version": "1.15.1",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.1.tgz",
@@ -329,6 +949,70 @@
"mime-types": "^2.1.12"
}
},
"fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
},
"glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"requires": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
}
},
"help-me": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/help-me/-/help-me-3.0.0.tgz",
"integrity": "sha512-hx73jClhyk910sidBB7ERlnhMlFsJJIBqSVMFDwPN8o2v9nmp5KgLq1Xz1Bf1fCMMZ6mPrX159iG0VLy/fPMtQ==",
"requires": {
"glob": "^7.1.6",
"readable-stream": "^3.6.0"
}
},
"ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
},
"inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"requires": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"js-sdsl": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.3.tgz",
"integrity": "sha512-p6umEbgMJq1OL+2z6eYFj8/yHlsx+0gX2nNoSqnu0V5KZaFGBaUfvktdbm5BGrlojadQ+Hjir0rdsaTmzoyd5Q=="
},
"leven": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/leven/-/leven-2.1.0.tgz",
"integrity": "sha512-nvVPLpIHUxCUoRLrFqTgSxXJ614d8AgQoWl7zPe/2VadE8+1dpU3LBhowRuBAcuwruWtOdD8oYC9jDNJjXDPyA=="
},
"lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"requires": {
"yallist": "^4.0.0"
}
},
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -342,11 +1026,207 @@
"mime-db": "1.52.0"
}
},
"minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"requires": {
"brace-expansion": "^1.1.7"
}
},
"minimist": {
"version": "1.2.6",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.6.tgz",
"integrity": "sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q=="
},
"mqtt": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/mqtt/-/mqtt-4.3.7.tgz",
"integrity": "sha512-ew3qwG/TJRorTz47eW46vZ5oBw5MEYbQZVaEji44j5lAUSQSqIEoul7Kua/BatBW0H0kKQcC9kwUHa1qzaWHSw==",
"requires": {
"commist": "^1.0.0",
"concat-stream": "^2.0.0",
"debug": "^4.1.1",
"duplexify": "^4.1.1",
"help-me": "^3.0.0",
"inherits": "^2.0.3",
"lru-cache": "^6.0.0",
"minimist": "^1.2.5",
"mqtt-packet": "^6.8.0",
"number-allocator": "^1.0.9",
"pump": "^3.0.0",
"readable-stream": "^3.6.0",
"reinterval": "^1.1.0",
"rfdc": "^1.3.0",
"split2": "^3.1.0",
"ws": "^7.5.5",
"xtend": "^4.0.2"
},
"dependencies": {
"bl": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz",
"integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==",
"requires": {
"buffer": "^5.5.0",
"inherits": "^2.0.4",
"readable-stream": "^3.4.0"
}
},
"buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"requires": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"mqtt-packet": {
"version": "6.10.0",
"resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-6.10.0.tgz",
"integrity": "sha512-ja8+mFKIHdB1Tpl6vac+sktqy3gA8t9Mduom1BA75cI+R9AHnZOiaBQwpGiWnaVJLDGRdNhQmFaAqd7tkKSMGA==",
"requires": {
"bl": "^4.0.2",
"debug": "^4.1.1",
"process-nextick-args": "^2.0.1"
}
},
"ws": {
"version": "7.5.9",
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz",
"integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==",
"requires": {}
}
}
},
"mqtt-packet": {
"version": "8.1.2",
"resolved": "https://registry.npmjs.org/mqtt-packet/-/mqtt-packet-8.1.2.tgz",
"integrity": "sha512-vL1YTct+TAy0PqX3Jv8jM3JMzObH6vC/lyA0I5LtD4xvydOdIdmofrSp12PE3jajiIOUaW3XxmQekbyToXpsSw==",
"requires": {
"bl": "^5.0.0",
"debug": "^4.1.1",
"process-nextick-args": "^2.0.1"
}
},
"ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
},
"number-allocator": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/number-allocator/-/number-allocator-1.0.11.tgz",
"integrity": "sha512-ykOuVG+oGw67qwt0eW0sPaIR+ANtB58QCpVaaGLxt0QekRXDA5Q/eG7sJmFEZpIcSVdjdevmO72Z6mH258y7Hw==",
"requires": {
"debug": "^4.3.1",
"js-sdsl": "^4.1.3"
}
},
"once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"requires": {
"wrappy": "1"
}
},
"path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
},
"process-nextick-args": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
"integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag=="
},
"pump": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
"integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
"requires": {
"end-of-stream": "^1.1.0",
"once": "^1.3.1"
}
},
"readable-stream": {
"version": "3.6.0",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
"integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
"requires": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
}
},
"reinterval": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/reinterval/-/reinterval-1.1.0.tgz",
"integrity": "sha512-QIRet3SYrGp0HUHO88jVskiG6seqUGC5iAG7AwI/BV4ypGcuqk9Du6YQBUOUqm9c8pw1eyLoIaONifRua1lsEQ=="
},
"rfdc": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.3.0.tgz",
"integrity": "sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA=="
},
"safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
},
"split2": {
"version": "3.2.2",
"resolved": "https://registry.npmjs.org/split2/-/split2-3.2.2.tgz",
"integrity": "sha512-9NThjpgZnifTkJpzTZ7Eue85S49QwpNhZTq6GRJwObb6jnLFNGB7Qm73V5HewTROPyxD0C29xqmaI68bQtV+hg==",
"requires": {
"readable-stream": "^3.0.0"
}
},
"stream-shift": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.1.tgz",
"integrity": "sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ=="
},
"string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"requires": {
"safe-buffer": "~5.2.0"
}
},
"typedarray": {
"version": "0.0.6",
"resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz",
"integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA=="
},
"util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw=="
},
"wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
},
"ws": {
"version": "8.8.1",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.8.1.tgz",
"integrity": "sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA==",
"requires": {}
},
"xtend": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
"integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
},
"yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
}
}
}

View File

@@ -27,7 +27,8 @@
],
"pluginDependencies": [
"@scrypted/prebuffer-mixin",
"@scrypted/snapshot"
"@scrypted/snapshot",
"@scrypted/webrtc"
]
},
"dependencies": {
@@ -35,6 +36,8 @@
"@scrypted/sdk": "file:../../sdk",
"axios": "^0.27.2",
"crypto-js": "^4.1.1",
"mqtt": "^4.3.7",
"mqtt-packet": "^8.1.2",
"ws": "^8.8.1"
},
"devDependencies": {
@@ -43,5 +46,5 @@
"@types/uuid": "^8.3.4",
"@types/ws": "^8.5.3"
},
"version": "0.0.6"
"version": "0.0.7-beta.2"
}

View File

@@ -1,8 +1,10 @@
import { ScryptedDeviceBase, VideoCamera, MotionSensor, BinarySensor, MediaObject, ScryptedInterface, MediaStreamOptions, MediaStreamUrl, ScryptedMimeTypes, ResponseMediaStreamOptions, OnOff, DeviceProvider, Online, Logger, Intercom } from "@scrypted/sdk";
import sdk from '@scrypted/sdk';
import sdk, { ScryptedDeviceBase, VideoCamera, MotionSensor, BinarySensor, MediaObject, MediaStreamOptions, MediaStreamUrl, ScryptedMimeTypes, ResponseMediaStreamOptions, OnOff, DeviceProvider, Online, Logger, Intercom, RTCSignalingClient, RTCSignalingSession, RTCAVSignalingSetup, RTCSignalingOptions, RTCSignalingSendIceCandidate, RTCSignalingChannel, RTCSessionControl } from "@scrypted/sdk";
import { connectRTCSignalingClients } from '@scrypted/common/src/rtc-signaling';
import { TuyaController } from "./main";
import { TuyaDeviceConfig } from "./tuya/const";
import { MQTTConfig, TuyaDeviceConfig, DeviceWebRTConfig, WebRTCMQMessage, OfferMessage, CandidateMessage, AnswerMessage } from "./tuya/const";
import { TuyaDevice } from "./tuya/device";
import { TuyaMQ } from "./tuya/mq";
import { randomUUID } from "crypto";
const { deviceManager } = sdk;
export class TuyaCameraLight extends ScryptedDeviceBase implements OnOff, Online {
@@ -51,7 +53,174 @@ export class TuyaCameraLight extends ScryptedDeviceBase implements OnOff, Online
}
}
export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, VideoCamera, BinarySensor, MotionSensor, OnOff, Online {
class TuyaRTCSessionControl implements RTCSessionControl {
constructor(
private readonly sessionId: string,
private mqtt: TuyaMQ,
private readonly mqttWebRTConfig: MQTTConfig,
private readonly deviceWebRTConfig: DeviceWebRTConfig,
) {
}
async getRefreshAt(): Promise<number | void> {}
async extendSession(): Promise<void> {}
async setPlayback(options: { audio: boolean; video: boolean; }): Promise<void> {}
async endSession(): Promise<void> {
let webRTCMessage: WebRTCMQMessage = {
protocol: 302,
pv: "2.2",
t: Date.now(),
data: {
header: {
type: 'disconnect',
from: this.mqttWebRTConfig.source_topic.split('/')[3],
to: this.deviceWebRTConfig.id,
sub_dev_id: '',
sessionid: this.sessionId,
moto_id: this.deviceWebRTConfig.moto_id,
tid: ''
},
msg: {
mode: 'webrtc'
}
}
};
this.mqtt.publish(JSON.stringify(webRTCMessage));
}
}
class TuyaRTCSignalingSesion implements RTCSignalingSession {
private readonly sessionId: string;
constructor(
private mqtt: TuyaMQ,
private readonly mqttWebRTConfig: MQTTConfig,
private readonly deviceWebRTConfig: DeviceWebRTConfig,
private readonly log: Logger
) {
this.sessionId = randomUUID();
}
async createLocalDescription(type: "offer" | "answer", setup: RTCAVSignalingSetup, sendIceCandidate: RTCSignalingSendIceCandidate): Promise<RTCSessionDescriptionInit> {
return new Promise((resolve, reject) => {
if (type !== 'answer')
reject(Error('[WebRTC] - Can only create answer value.'));
const messageHandler = (_client: any, message: any) => {
const webRTCMessage = JSON.parse(message) as WebRTCMQMessage;
this.log.i(`[WebRTC] - TuyaMQ message received: ${JSON.stringify(webRTCMessage)}`);
if (webRTCMessage.data.header.type == 'answer') {
const answer = webRTCMessage.data.msg as AnswerMessage;
resolve({
sdp: answer.sdp,
type: 'answer'
});
} else if (webRTCMessage.data.header.type == 'candidate') {
const candidate = webRTCMessage.data.msg as CandidateMessage;
if (!candidate?.candidate || candidate.candidate == '') {
return;
}
sendIceCandidate({
candidate: candidate.candidate,
sdpMid: '0',
sdpMLineIndex: 0
});
} else {
this.log.e('[WebRTC] - TuyaMQ: There was an error trying to get an answer or candidate from TuyaMQ.');
this.mqtt.removeMessageListener(messageHandler);
this.mqtt.stop();
reject(new Error('[WebRTC] - TuyaMQ: There was an error trying to get an answer or candidate from TuyaMQ.'));
}
}
this.mqtt.message(messageHandler);
});
}
async setRemoteDescription(description: RTCSessionDescriptionInit, setup: RTCAVSignalingSetup): Promise<void> {
if (description.type !== 'offer')
throw new Error("This only accepts offer request.");
let offerMessage: OfferMessage = {
mode: 'webrtc',
sdp: description.sdp,
auth: this.deviceWebRTConfig.auth,
stream_type: 1
}
let webRTCMessage: WebRTCMQMessage = {
protocol: 302,
pv: "2.2",
t: Date.now(),
data: {
header: {
type: 'offer',
from: this.mqttWebRTConfig.source_topic.split('/')[3],
to: this.deviceWebRTConfig.id,
sub_dev_id: '',
sessionid: this.sessionId,
moto_id: this.deviceWebRTConfig.moto_id,
tid: ''
},
msg: offerMessage
}
};
this.mqtt.publish(JSON.stringify(webRTCMessage));
this.log.i(`[WebRTC] - TuyaMQ: Sent Offer w/ sdp.`);
}
async addIceCandidate(candidate: RTCIceCandidateInit): Promise<void> {
const acandidate = candidate.candidate ? `a=${candidate.candidate}` : '';
let candidateMessage: CandidateMessage = {
mode: 'webrtc',
candidate: acandidate
}
let webRTCMQMessage: WebRTCMQMessage = {
protocol: 302,
pv: '2.2',
t: Date.now(),
data: {
header: {
type: 'candidate',
from: this.mqttWebRTConfig.source_topic.split('/')[3],
to: this.deviceWebRTConfig.id,
sub_dev_id: '',
sessionid: this.sessionId,
moto_id: this.deviceWebRTConfig.moto_id,
tid: ''
},
msg: candidateMessage
}
};
this.mqtt.publish(JSON.stringify(webRTCMQMessage));
this.log.i(`[WebRTC] - TuyaMQ: Sent candidate.`);
}
async getOptions(): Promise<RTCSignalingOptions> {
return {
requiresOffer: true,
disableTrickle: false
};
}
get id(): string {
return this.sessionId;
}
}
export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, VideoCamera, BinarySensor, MotionSensor, OnOff, Online, RTCSignalingChannel {
private cameraLightSwitch?: TuyaCameraLight
private previousMotion?: any;
private previousDoorbellRing?: any;
@@ -124,19 +293,19 @@ export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, Vi
const camera = this.findCamera();
if (!camera) {
this.logger.w(`Could not find camera for ${this.name} to show stream.`);
this.logger.e(`Could not find camera for ${this.name} to show stream.`);
throw new Error(`Failed to stream ${this.name}: Camera not found.`);
}
if (!camera.online) {
this.logger.w(`${this.name} is currently offline. Will not be able to stream until device is back online.`);
this.logger.e(`${this.name} is currently offline. Will not be able to stream until device is back online.`);
throw new Error(`Failed to stream ${this.name}: Camera is offline.`);
}
const rtsps = await this.controller.cloud?.getRTSPS(camera);
if (!rtsps) {
this.logger.w("There was an error retreiving camera's rtsps for streamimg.");
this.logger.e("There was an error retreiving camera's rtsps for streamimg.");
throw new Error(`Failed to capture stream for ${this.name}: RTSPS link not found.`);
}
@@ -148,10 +317,80 @@ export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, Vi
return this.createMediaObject(mediaStreamUrl, ScryptedMimeTypes.MediaStreamUrl);
}
async startRTCSignalingSession(session: RTCSignalingSession): Promise<RTCSessionControl> {
const camera = this.findCamera();
if (!camera) {
this.logger.e(`Could not find camera for ${this.name} to create rtc signal session.`);
throw new Error(`Failed to create rtc config for ${this.name}: Camera not found.`);
}
const deviceWebRTConfigResponse = await this.controller.cloud?.getDeviceWebRTConfig(camera);
if (!deviceWebRTConfigResponse?.success) {
this.logger.e(`[${this.name}] There was an error retrieving WebRTConfig.`);
throw new Error(`Failed to create device rtc config for ${this.name}: request failed: ${deviceWebRTConfigResponse?.result}.`);
}
const deviceWebRTConfig = deviceWebRTConfigResponse.result;
let mqResponse = await this.controller.cloud?.getWebRTCMQConfig(deviceWebRTConfig);
if (!mqResponse?.success) {
this.logger.e(`[${this.name}] There was an error retrieving WebRTC MQTT RTC Config.`);
throw new Error(`Failed to create rtc mqtt config for ${this.name}: request failed: ${mqResponse?.result}.`);
}
const mqttWebRTConfig = mqResponse.result;
const mqtt = new TuyaMQ(mqttWebRTConfig);
await mqtt.connect();
const tuyaSignalingSession = new TuyaRTCSignalingSesion(mqtt, mqttWebRTConfig, deviceWebRTConfig, this.logger);
const iceServers = deviceWebRTConfig.p2p_config.ices.map((ice): RTCIceServer => {
return {
credential: ice.credential,
urls: ice.urls,
username: ice.username
}
});
const offerSetup: RTCAVSignalingSetup = {
type: "offer",
configuration: {
iceServers: iceServers
},
audio: {
direction: 'sendrecv',
},
video: {
direction: 'recvonly',
},
}
const tuyaAnswerSetup: Partial<RTCAVSignalingSetup> = {}
await connectRTCSignalingClients(
this.console,
session,
offerSetup,
tuyaSignalingSession,
tuyaAnswerSetup
);
return new TuyaRTCSessionControl(
tuyaSignalingSession.id,
mqtt,
mqttWebRTConfig,
deviceWebRTConfig
);
}
async getVideoStreamOptions(): Promise<ResponseMediaStreamOptions[]> {
return [
{
id: 'default',
id: 'cloud-rtsp',
name: 'Cloud RTSP',
container: 'rtsp',
video: {
codec: 'h264',
@@ -221,7 +460,7 @@ export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, Vi
} else if (this.previousMotion !== motionDetectedStatus.value) {
this.previousMotion = motionDetectedStatus.value;
this.triggerMotion();
}
}
}
}
@@ -238,7 +477,8 @@ export class TuyaCamera extends ScryptedDeviceBase implements DeviceProvider, Vi
}
// By the time this is called, scrypted would have already reported the device
// Only set light switch on cameras that have a status light indicator.
// Only set light switch on cameras that have a light switch.
if (TuyaDevice.hasLightSwitch(camera)) {
this.getDevice(this.nativeLightSwitchId)?.updateState(camera);
}

View File

@@ -1,6 +1,5 @@
import { Device, DeviceDiscovery, DeviceProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, Settings, SettingValue } from '@scrypted/sdk';
import { Device, DeviceDiscovery, DeviceProvider, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, Settings } from '@scrypted/sdk';
import sdk from '@scrypted/sdk';
import { StorageSettings } from '../../../common/src/settings';
import { TuyaCloud } from './tuya/cloud';
import { TuyaDevice } from './tuya/device';
import { createInstanceableProviderPlugin } from '@scrypted/common/src/provider-plugin';
@@ -193,7 +192,7 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
let deviceInfo: string[] = [`Creating camera device for: \n- ${camera.name}`];
if (TuyaDevice.isDoorbell(camera)) {
deviceInfo.push(`- Doorbell Notification`);
deviceInfo.push(`- Doorbell Notification Supported`);
device.interfaces.push(ScryptedInterface.BinarySensor);
}
@@ -203,14 +202,19 @@ export class TuyaController extends ScryptedDeviceBase implements DeviceProvider
}
if (TuyaDevice.hasMotionDetection(camera)) {
deviceInfo.push(`- Motion Detection`);
deviceInfo.push(`- Motion Detection Supported`);
device.interfaces.push(ScryptedInterface.MotionSensor);
}
if (await TuyaDevice.supportsWebRTC(camera, this.cloud)) {
deviceInfo.push(`- WebRTC Supported with Intercom`);
device.interfaces.push(ScryptedInterface.RTCSignalingChannel);
}
// Device Provider
if (TuyaDevice.hasLightSwitch(camera)) {
deviceInfo.push(`- Light Switch`);
deviceInfo.push(`- Has Light Switch`);
device.interfaces.push(ScryptedInterface.DeviceProvider);
}

View File

@@ -1,7 +1,8 @@
import { Axios, Method } from "axios";
import { HmacSHA256, SHA256, lib } from 'crypto-js';
import { getTuyaCloudEndpoint, TuyaSupportedCountry } from "./utils";
import { DeviceFunction, TuyaDeviceStatus, RTSPToken, TuyaDeviceConfig, TuyaResponse } from "./const";
import { TuyaDeviceStatus, RTSPToken, TuyaDeviceConfig, TuyaResponse, MQTTConfig, DeviceWebRTConfig as DeviceWebRTConfig } from "./const";
import { randomBytes } from "crypto";
interface Session {
accessToken: string;
@@ -97,7 +98,9 @@ export class TuyaCloud {
// Camera Functions
public async getRTSPS(camera: TuyaDeviceConfig): Promise<RTSPToken | undefined> {
public async getRTSPS(
camera: TuyaDeviceConfig
): Promise<RTSPToken | undefined> {
interface RTSPResponse {
url: string
}
@@ -117,6 +120,39 @@ export class TuyaCloud {
}
}
public async getDeviceWebRTConfig(camera: TuyaDeviceConfig) : Promise<TuyaResponse<DeviceWebRTConfig>> {
const response = await this.get<DeviceWebRTConfig>(
`/v1.0/users/${this.userId}/devices/${camera.id}/webrtc-configs`
);
return response;
}
public async getWebRTCMQConfig(webRTCDeviceConfig: DeviceWebRTConfig) : Promise<TuyaResponse<MQTTConfig>> {
const response = await this.post<any>(
`/v1.0/open-hub/access/config`,
{
link_id: randomBytes(8).readUInt8(),
uid: this.userId,
link_type: 'mqtt',
topics: 'ipc'
}
);
if (response.success) {
response.result = {
...response.result,
sink_topic: (response.result.sink_topic.ipc as string)
.replace('{device_id}', webRTCDeviceConfig.id)
.replace('moto_id', webRTCDeviceConfig.moto_id),
source_topic: response.result.source_topic.ipc as string
}
return response
}
return response;
}
public getSessionUserId(): string | undefined {
return this.session?.uid;
}

View File

@@ -51,22 +51,79 @@ export interface RTSPToken {
export interface MQTTConfig {
url: string;
client_id: string;
username: string;
password: string;
client_id: string;
source_topic: string;
sink_topic: string;
expire_topic: string;
expire_time: number;
}
// From Unify Protect Api:
// This type declaration make all properties optional recursively including nested objects. This should
// only be used on JSON objects only. Otherwise...you're going to end up with class methods marked as
// optional as well. Credit for this belongs to: https://github.com/joonhocho/tsdef. #Grateful
// export type DeepPartial<T> = {
// [P in keyof T]?: T[P] extends Array<infer I> ? Array<DeepPartial<I>> : DeepPartial<T[P]>
// };
export interface DeviceWebRTConfig {
audio_attributes: AudioAttributes;
auth: string;
id: string;
moto_id: string;
p2p_config: P2PConfig;
skill: string;
supports_webrtc: boolean;
vedio_clarity: number;
}
// export type ProtectTuyaDeviceConfig = Readonly<TuyaDeviceInterface>;
// export type ProtectTuyaDeviceConfigPartial = DeepPartial<TuyaDeviceInterface>;
// export type ProtectTuyaDeviceStatus = Readonly<TuyaDeviceStatus>;
interface AudioAttributes {
call_mode: number[];
hardware_capability: number[];
}
interface P2PConfig {
ices: Ice[];
}
interface Ice {
urls: string;
credential?: string;
ttl?: number;
username?: string;
}
export interface WebRTCMQMessage {
protocol: number;
pv: string;
t: number;
data: {
header: {
type: 'offer' | 'answer' | 'candidate' | 'disconnect';
from: string;
to: string;
sub_dev_id: string;
sessionid: string;
moto_id: string;
tid: string;
},
msg: OfferMessage | AnswerMessage | CandidateMessage | DisconnectMessage
}
}
export interface OfferMessage {
mode: string;
sdp: string;
auth: string;
stream_type: number;
}
export interface AnswerMessage {
mode: string;
sdp: string;
stream_type: number;
token: Ice[];
replay: any;
}
export interface CandidateMessage {
mode: string;
candidate: string;
}
export interface DisconnectMessage {
mode: string;
}

View File

@@ -1,3 +1,4 @@
import { TuyaCloud } from "./cloud";
import { TuyaDeviceStatus, TuyaDeviceConfig as TuyaDeviceConfig } from "./const";
export namespace TuyaDevice {
@@ -70,6 +71,15 @@ export namespace TuyaDevice {
return getStatus(camera, motionDetectionCodes);
}
// Supports WebRTC
export async function supportsWebRTC(camera: TuyaDeviceConfig, cloud: TuyaCloud) {
const webRTConfig = await cloud.getDeviceWebRTConfig(camera);
return webRTConfig.success && webRTConfig.result.supports_webrtc;
}
// Device Status
function getStatus(camera: TuyaDeviceConfig, statusCode: string[]) : TuyaDeviceStatus | undefined {
return camera.status.find(value => statusCode.includes(value.code));
}

129
plugins/tuya/src/tuya/mq.ts Normal file
View File

@@ -0,0 +1,129 @@
import Event from 'events';
import * as mqtt from "mqtt";
import { IClientPublishOptions } from 'mqtt';
import { IPublishPacket } from 'mqtt-packet'
import { MQTTConfig } from "./const";
export class TuyaMQ {
static connected = "TUYA_CONNECTED";
static message = "TUYA_MESSAGE";
static error = "TUYA_ERROR";
static close = "TUYA_CLOSE";
private client?: mqtt.MqttClient;
private config: MQTTConfig;
private event: Event;
constructor(
config: MQTTConfig
) {
this.config = Object.assign({}, config);
this.event = new Event();
}
public stop() {
this.client?.end();
}
public async connect(): Promise<mqtt.Client> {
return new Promise((resolve, reject) => {
this.event.on(
TuyaMQ.connected,
(client: mqtt.MqttClient) => {
if (client.connected) {
resolve(client);
} else {
reject(new Error('Client did not connect successfully.'));
}
}
);
this.client = this._connect();
});
}
public message(
cb: (client: mqtt.MqttClient, message: any) => void
) {
this.event.on(TuyaMQ.message, cb);
}
public error(
cb: (client: mqtt.MqttClient, error: Error) => void
) {
this.event.on(TuyaMQ.error, cb);
}
public close(
cb: (client: mqtt.MqttClient) => void
) {
this.event.on(TuyaMQ.close, cb);
}
public publish(message: string) {
const properties: IClientPublishOptions = {
qos: 1,
retain: false
}
this.client?.publish(this.config.sink_topic, message, properties);
}
public removeMessageListener(
cb: (client: mqtt.MqttClient, message: any) => void
) {
this.event.removeListener(TuyaMQ.message, cb);
}
private _connect() {
this.client = mqtt.connect(this.config.url, {
clientId: this.config.client_id,
username: this.config.username,
password: this.config.password
});
this.subConnect(this.client);
this.subMessage(this.client);
this.subError(this.client);
this.subClose(this.client);
return this.client;
}
private subConnect(client: mqtt.MqttClient) {
client.on('connect', () => {
client.subscribe(this.config.source_topic);
this.event.emit(
TuyaMQ.connected,
client
)
});
}
private subMessage(client: mqtt.MqttClient) {
client.on('message', (topic: string, payload: Buffer, packet: IPublishPacket) => {
this.event.emit(
TuyaMQ.message,
client,
payload
)
});
}
private subError(client: mqtt.MqttClient) {
client.on('error', (error: Error) => {
this.event.emit(
TuyaMQ.error,
error
)
});
}
private subClose(client: mqtt.MqttClient) {
client.on('close', () => {
this.event.emit(
TuyaMQ.close,
this.client
);
});
}
}