From f0da9d60854968811693c175167b8309a269e223 Mon Sep 17 00:00:00 2001 From: Nick Berardi Date: Mon, 27 Feb 2023 20:57:44 -0500 Subject: [PATCH] alexa: refactor code structure --- plugins/alexa/.vscode/settings.json | 2 +- plugins/alexa/package-lock.json | 3159 +++++++++++++++-- plugins/alexa/package.json | 14 +- plugins/alexa/src/alexa.ts | 221 ++ plugins/alexa/src/common.ts | 131 + plugins/alexa/src/handlers.ts | 34 + plugins/alexa/src/main.ts | 546 +-- plugins/alexa/src/message.ts | 5 - plugins/alexa/src/types/camera.ts | 188 +- .../alexa/src/types/camera/capabilities.ts | 194 + plugins/alexa/src/types/camera/handlers.ts | 152 + plugins/alexa/src/types/common.ts | 180 - plugins/alexa/src/types/doorbell.ts | 112 +- plugins/alexa/src/types/garagedoor.ts | 58 +- .../alexa/src/types/garagedoor/handlers.ts | 32 + plugins/alexa/src/types/index.ts | 21 +- plugins/alexa/src/types/securitysystem.ts | 179 + plugins/alexa/src/types/sensor.ts | 196 + plugins/alexa/src/types/switch.ts | 71 + plugins/alexa/src/types/switch/handlers.ts | 55 + 20 files changed, 4621 insertions(+), 929 deletions(-) create mode 100644 plugins/alexa/src/alexa.ts create mode 100644 plugins/alexa/src/common.ts create mode 100644 plugins/alexa/src/handlers.ts delete mode 100644 plugins/alexa/src/message.ts create mode 100644 plugins/alexa/src/types/camera/capabilities.ts create mode 100644 plugins/alexa/src/types/camera/handlers.ts delete mode 100644 plugins/alexa/src/types/common.ts create mode 100644 plugins/alexa/src/types/garagedoor/handlers.ts create mode 100644 plugins/alexa/src/types/securitysystem.ts create mode 100644 plugins/alexa/src/types/sensor.ts create mode 100644 plugins/alexa/src/types/switch.ts create mode 100644 plugins/alexa/src/types/switch/handlers.ts diff --git a/plugins/alexa/.vscode/settings.json b/plugins/alexa/.vscode/settings.json index 77ccdbd6db..43fcd8fbd4 100644 --- a/plugins/alexa/.vscode/settings.json +++ b/plugins/alexa/.vscode/settings.json @@ -1,4 +1,4 @@ { - "scrypted.debugHost": "127.0.0.1", + "scrypted.debugHost": "10.10.0.50", } \ No newline at end of file diff --git a/plugins/alexa/package-lock.json b/plugins/alexa/package-lock.json index a43ab55eec..8c936e765a 100644 --- a/plugins/alexa/package-lock.json +++ b/plugins/alexa/package-lock.json @@ -1,188 +1,2705 @@ { "name": "@scrypted/alexa", - "version": "0.0.20", - "lockfileVersion": 2, + "version": "0.2.0", + "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@scrypted/alexa", - "version": "0.0.20", + "version": "0.2.0", "dependencies": { - "@types/node": "^16.6.1", - "alexa-smarthome-ts": "^0.0.1", - "axios": "^0.24.0", + "axios": "^1.3.4", "uuid": "^9.0.0" }, "devDependencies": { - "@scrypted/common": "file:../../common", - "@scrypted/sdk": "file:../../sdk", - "@scrypted/server": "file:../../server" + "@scrypted/sdk": "^0.2.70", + "@types/node": "^18.4.2" } }, - "../../common": { - "name": "@scrypted/common", + "node_modules/@ampproject/remapping": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz", + "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.1.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz", + "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.21.0.tgz", + "integrity": "sha512-gMuZsmsgxk/ENC3O/fRw5QY8A9/uxQbbCEypnLIiYYc/qVJtEV7ouxC3EllIIwNzMqAQee5tanFabWsUOutS7g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.21.0.tgz", + "integrity": "sha512-PuxUbxcW6ZYe656yL3EAhpy7qXKq0DmYsrJLpbB8XrsCP9Nm+XCg9XFMb5vIDliPD7+U/+M+QJlH17XOcB7eXA==", + "dev": true, + "peer": true, + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.0", + "@babel/helper-compilation-targets": "^7.20.7", + "@babel/helper-module-transforms": "^7.21.0", + "@babel/helpers": "^7.21.0", + "@babel/parser": "^7.21.0", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.2", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.21.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.21.1.tgz", + "integrity": "sha512-1lT45bAYlQhFn/BHivJs43AiW2rg3/UbLyShGfF3C0KmHvO5fSghWd5kBJy30kpRRucGzXStvnnCFniCR2kXAA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.0", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/helper-annotate-as-pure": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.18.6.tgz", + "integrity": "sha512-duORpUiYrEpzKIop6iNbjnwKLAKnJ47csTyRACyEmWj0QdUrm5aqNJGHSSEQSUAvNW0ojX0dOmK9dZduvkfeXA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.7.tgz", + "integrity": "sha512-4tGORmfQcrc+bvrjb5y3dG9Mx1IOZjsHqQVUz7XCNHO+iTmqxWnVg3KRygjGmpRLJGdQSKuvFinbIb0CnZwHAQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/compat-data": "^7.20.5", + "@babel/helper-validator-option": "^7.18.6", + "browserslist": "^4.21.3", + "lru-cache": "^5.1.1", + "semver": "^6.3.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-create-class-features-plugin": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.21.0.tgz", + "integrity": "sha512-Q8wNiMIdwsv5la5SPxNYzzkPnjgC0Sy0i7jLkVOCdllu/xcVNkr3TeZzbHBJrj+XXRqzX5uCyCoV9eu6xUG7KQ==", + "dev": true, + "dependencies": { + "@babel/helper-annotate-as-pure": "^7.18.6", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-member-expression-to-functions": "^7.21.0", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/helper-replace-supers": "^7.20.7", + "@babel/helper-skip-transparent-expression-wrappers": "^7.20.0", + "@babel/helper-split-export-declaration": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.18.9", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz", + "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.21.0.tgz", + "integrity": "sha512-HfK1aMRanKHpxemaY2gqBmL04iAPOPRj7DxtNbiDOrJK+gdwkiNRVpCpUJYbUT+aZyemKN8brqTOxzCaG6ExRg==", + "dev": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz", + "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-member-expression-to-functions": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.21.0.tgz", + "integrity": "sha512-Muu8cdZwNN6mRRNG6lAYErJ5X3bRevgYR2O8wN0yn7jJSnGDu6eG59RfT29JHxGUovyfrh6Pj0XzmR7drNVL3Q==", + "dev": true, + "dependencies": { + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz", + "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.21.2.tgz", + "integrity": "sha512-79yj2AR4U/Oqq/WOV7Lx6hUjau1Zfo4cI+JLAVYeMV5XIlbOhmjEk5ulbTc9fMpmlojzZHkUUxAiK+UKn+hNQQ==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-module-imports": "^7.18.6", + "@babel/helper-simple-access": "^7.20.2", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/helper-validator-identifier": "^7.19.1", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.2", + "@babel/types": "^7.21.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-optimise-call-expression": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.18.6.tgz", + "integrity": "sha512-HP59oD9/fEHQkdcbgFCnbmgH5vIQTJbxh2yf+CdM89/glUNnuzr87Q8GIjGEnOktTROemO0Pe0iPAYbqZuOUiA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.20.2.tgz", + "integrity": "sha512-8RvlJG2mj4huQ4pZ+rU9lqKi9ZKiRmuvGuM2HlWmkmgOhbs6zEAw6IEiJ5cQqGbDzGZOhwuOQNtZMi/ENLjZoQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-replace-supers": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.20.7.tgz", + "integrity": "sha512-vujDMtB6LVfNW13jhlCrp48QNslK6JXi7lQG736HVbHz/mbf4Dc7tIRh1Xf5C0rF7BP8iiSxGMCmY6Ci1ven3A==", + "dev": true, + "dependencies": { + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-member-expression-to-functions": "^7.20.7", + "@babel/helper-optimise-call-expression": "^7.18.6", + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.20.2", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz", + "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/types": "^7.20.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.20.0.tgz", + "integrity": "sha512-5y1JYeNKfvnT8sZcK9DVRtpTbGiomYIHviSP3OQWmDPU3DeH4a1ZlT/N2lyQ5P8egjcRaT/Y9aNqUxK0WsnIIg==", + "dev": true, + "dependencies": { + "@babel/types": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz", + "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==", + "dev": true, + "dependencies": { + "@babel/types": "^7.18.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.19.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz", + "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.19.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz", + "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.21.0.tgz", + "integrity": "sha512-rmL/B8/f0mKS2baE9ZpyTcTavvEuWhTTW8amjzXNvYG4AwBsqTLikfXsEofsJEfKHf+HQVQbFOHy6o+4cnC/fQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.21.0.tgz", + "integrity": "sha512-XXve0CBtOW0pd7MRzzmoyuSj0e3SEzj8pgyFxnTT1NJZL38BD1MK7yYrm8yefRPIDvNNe14xR4FdbHwpInD4rA==", + "dev": true, + "peer": true, + "dependencies": { + "@babel/template": "^7.20.7", + "@babel/traverse": "^7.21.0", + "@babel/types": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.18.6", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz", + "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.18.6", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.21.2.tgz", + "integrity": "sha512-URpaIJQwEkEC2T9Kn+Ai6Xe/02iNaVCuT/PtoRz3GPVJVDpPd7mLo+VddTbhCRU9TXqW5mSrQfXZyi8kDKOVpQ==", + "dev": true, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-syntax-typescript": { + "version": "7.20.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.20.0.tgz", + "integrity": "sha512-rd9TkG+u1CExzS4SM1BlMEhMXwFLKVjOAFFCDx9PbX5ycJWDoWMcwdJH9RhkPu1dOgn5TrxLot/Gx6lWFuAUNQ==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.19.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-typescript": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typescript/-/plugin-transform-typescript-7.21.0.tgz", + "integrity": "sha512-xo///XTPp3mDzTtrqXoBlK9eiAYW3wv9JXglcn/u1bi60RW11dEUxIgA8cbnDhutS1zacjMRmAwxE0gMklLnZg==", + "dev": true, + "dependencies": { + "@babel/helper-create-class-features-plugin": "^7.21.0", + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/plugin-syntax-typescript": "^7.20.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/preset-typescript": { + "version": "7.21.0", + "resolved": "https://registry.npmjs.org/@babel/preset-typescript/-/preset-typescript-7.21.0.tgz", + "integrity": "sha512-myc9mpoVA5m1rF8K8DgLEatOYFDpwC+RkMkjZ0Du6uI62YvDe8uxIEYVs/VCdSJ097nlALiU/yBC7//3nI+hNg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.20.2", + "@babel/helper-validator-option": "^7.21.0", + "@babel/plugin-transform-typescript": "^7.21.0" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.20.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.20.7.tgz", + "integrity": "sha512-8SegXApWe6VoNw0r9JHpSteLKTpTiLZ4rMlGIm9JQ18KiCtyQiAMEazujAHrUS5flrcqYZa75ukev3P6QmUwUw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.21.2.tgz", + "integrity": "sha512-ts5FFU/dSUPS13tv8XiEObDu9K+iagEKME9kAbaP7r0Y9KtZJZ+NGndDvWoRAYNpeWafbpFeki3q9QoMD6gxyw==", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.18.6", + "@babel/generator": "^7.21.1", + "@babel/helper-environment-visitor": "^7.18.9", + "@babel/helper-function-name": "^7.21.0", + "@babel/helper-hoist-variables": "^7.18.6", + "@babel/helper-split-export-declaration": "^7.18.6", + "@babel/parser": "^7.21.2", + "@babel/types": "^7.21.2", + "debug": "^4.1.0", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.21.2", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.21.2.tgz", + "integrity": "sha512-3wRZSs7jiFaB8AjxiiD+VqN5DTG2iRvJGQ+qYFrs/654lg6kGTQWIOFjlBo5RaXuAZjBmP3+OQH4dmhqiiyYxw==", + "dev": true, + "dependencies": { + "@babel/helper-string-parser": "^7.19.4", + "@babel/helper-validator-identifier": "^7.19.1", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@discoveryjs/json-ext": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", + "integrity": "sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw==", + "dev": true, + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.15.18.tgz", + "integrity": "sha512-5GT+kcs2WVGjVs7+boataCkO5Fg0y4kCjzkB5bAip7H4jfnOS3dA6KPiww9W1OEKTKeAcUVhdZGvgI65OXmUnw==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.15.18.tgz", + "integrity": "sha512-L4jVKS82XVhw2nvzLg/19ClLWg0y27ulRwuP7lcyL6AbUWB5aPglXY3M21mauDQMDfRLs8cQmeT03r/+X3cZYQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz", + "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==", + "dev": true, + "peer": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.0", + "@jridgewell/sourcemap-codec": "^1.4.10" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/source-map": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/source-map/-/source-map-0.3.2.tgz", + "integrity": "sha512-m7O9o2uR8k2ObDysZYzdfhb08VuEml5oWGiosa1VdaPZ/A6QyPkAJuwN0Q1lhULOf6B7MtQmHENS743hWtCrgw==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "node_modules/@jridgewell/source-map/node_modules/@jridgewell/gen-mapping": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz", + "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz", + "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@polka/url": { + "version": "1.0.0-next.21", + "resolved": "https://registry.npmjs.org/@polka/url/-/url-1.0.0-next.21.tgz", + "integrity": "sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==", + "dev": true + }, + "node_modules/@scrypted/sdk": { + "version": "0.2.70", + "resolved": "https://registry.npmjs.org/@scrypted/sdk/-/sdk-0.2.70.tgz", + "integrity": "sha512-OOtfNY+12CzynQbncInc3L78eOm1gxQt+f8ewscwdkUzt62uG3Xy2DzdIozhhr2EgRoJ/m0BtBZSKKv//TQXJw==", + "dev": true, + "dependencies": { + "@babel/preset-typescript": "^7.18.6", + "adm-zip": "^0.4.13", + "axios": "^0.21.4", + "babel-loader": "^9.1.0", + "babel-plugin-const-enum": "^1.1.0", + "esbuild": "^0.15.9", + "ncp": "^2.0.0", + "raw-loader": "^4.0.2", + "rimraf": "^3.0.2", + "tmp": "^0.2.1", + "ts-loader": "^9.4.2", + "typescript": "^4.9.4", + "webpack": "^5.75.0", + "webpack-bundle-analyzer": "^4.5.0" + }, + "bin": { + "scrypted-changelog": "bin/scrypted-changelog.js", + "scrypted-debug": "bin/scrypted-debug.js", + "scrypted-deploy": "bin/scrypted-deploy.js", + "scrypted-deploy-debug": "bin/scrypted-deploy-debug.js", + "scrypted-package-json": "bin/scrypted-package-json.js", + "scrypted-setup-project": "bin/scrypted-setup-project.js", + "scrypted-webpack": "bin/scrypted-webpack.js" + } + }, + "node_modules/@scrypted/sdk/node_modules/axios": { + "version": "0.21.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz", + "integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==", + "dev": true, + "dependencies": { + "follow-redirects": "^1.14.0" + } + }, + "node_modules/@types/eslint": { + "version": "8.21.1", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-8.21.1.tgz", + "integrity": "sha512-rc9K8ZpVjNcLs8Fp0dkozd5Pt2Apk1glO4Vgz8ix1u6yFByxfqo5Yavpy65o+93TAe24jr7v+eSBtFLvOQtCRQ==", + "dev": true, + "dependencies": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "node_modules/@types/eslint-scope": { + "version": "3.7.4", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.4.tgz", + "integrity": "sha512-9K4zoImiZc3HlIp6AVUDE4CWYx22a+lhSZMYNpbjW04+YF0KWj4pJXnEMjdnFTiQibFFmElcsasJXDbdI/EPhA==", + "dev": true, + "dependencies": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "node_modules/@types/estree": { + "version": "0.0.51", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.51.tgz", + "integrity": "sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==", + "dev": true + }, + "node_modules/@types/json-schema": { + "version": "7.0.11", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz", + "integrity": "sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ==", + "dev": true + }, + "node_modules/@types/node": { + "version": "18.14.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz", + "integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==", + "dev": true + }, + "node_modules/@webassemblyjs/ast": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.1.tgz", + "integrity": "sha512-ukBh14qFLjxTQNTXocdyksN5QdM28S1CxHt2rdskFyL+xFV7VremuBLVbmCePj+URalXBENx/9Lm7lnhihtCSw==", + "dev": true, + "dependencies": { + "@webassemblyjs/helper-numbers": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1" + } + }, + "node_modules/@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.1.tgz", + "integrity": "sha512-iGRfyc5Bq+NnNuX8b5hwBrRjzf0ocrJPI6GWFodBFzmFnyvrQ83SHKhmilCU/8Jv67i4GJZBMhEzltxzcNagtQ==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-api-error": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.1.tgz", + "integrity": "sha512-RlhS8CBCXfRUR/cwo2ho9bkheSXG0+NwooXcc3PAILALf2QLdFyj7KGsKRbVc95hZnhnERon4kW/D3SZpp6Tcg==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-buffer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.1.tgz", + "integrity": "sha512-gwikF65aDNeeXa8JxXa2BAk+REjSyhrNC9ZwdT0f8jc4dQQeDQ7G4m0f2QCLPJiMTTO6wfDmRmj/pW0PsUvIcA==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-numbers": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.1.tgz", + "integrity": "sha512-vDkbxiB8zfnPdNK9Rajcey5C0w+QJugEglN0of+kmO8l7lDb77AnlKYQF7aarZuCrv+l0UvqL+68gSDr3k9LPQ==", + "dev": true, + "dependencies": { + "@webassemblyjs/floating-point-hex-parser": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.1.tgz", + "integrity": "sha512-PvpoOGiJwXeTrSf/qfudJhwlvDQxFgelbMqtq52WWiXC6Xgg1IREdngmPN3bs4RoO83PnL/nFrxucXj1+BX62Q==", + "dev": true + }, + "node_modules/@webassemblyjs/helper-wasm-section": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.1.tgz", + "integrity": "sha512-10P9No29rYX1j7F3EVPX3JvGPQPae+AomuSTPiF9eBQeChHI6iqjMIwR9JmOJXwpnn/oVGDk7I5IlskuMwU/pg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1" + } + }, + "node_modules/@webassemblyjs/ieee754": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.1.tgz", + "integrity": "sha512-hJ87QIPtAMKbFq6CGTkZYJivEwZDbQUgYd3qKSadTNOhVY7p+gfP6Sr0lLRVTaG1JjFj+r3YchoqRYxNH3M0GQ==", + "dev": true, + "dependencies": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "node_modules/@webassemblyjs/leb128": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.1.tgz", + "integrity": "sha512-BJ2P0hNZ0u+Th1YZXJpzW6miwqQUGcIHT1G/sf72gLVD9DZ5AdYTqPNbHZh6K1M5VmKvFXwGSWZADz+qBWxeRw==", + "dev": true, + "dependencies": { + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@webassemblyjs/utf8": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.1.tgz", + "integrity": "sha512-9kqcxAEdMhiwQkHpkNiorZzqpGrodQQ2IGrHHxCy+Ozng0ofyMA0lTqiLkVs1uzTRejX+/O0EOT7KxqVPuXosQ==", + "dev": true + }, + "node_modules/@webassemblyjs/wasm-edit": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.1.tgz", + "integrity": "sha512-g+RsupUC1aTHfR8CDgnsVRVZFJqdkFHpsHMfJuWQzWU3tvnLC07UqHICfP+4XyL2tnr1amvl1Sdp06TnYCmVkA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/helper-wasm-section": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-opt": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "@webassemblyjs/wast-printer": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-gen": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.1.tgz", + "integrity": "sha512-F7QqKXwwNlMmsulj6+O7r4mmtAlCWfO/0HdgOxSklZfQcDu0TpLiD1mRt/zF25Bk59FIjEuGAIyn5ei4yMfLhA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-opt": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.1.tgz", + "integrity": "sha512-VqnkNqnZlU5EB64pp1l7hdm3hmQw7Vgqa0KF/KCNO9sIpI6Fk6brDEiX+iCOYrvMuBWDws0NkTOxYEb85XQHHw==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-buffer": "1.11.1", + "@webassemblyjs/wasm-gen": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wasm-parser": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.1.tgz", + "integrity": "sha512-rrBujw+dJu32gYB7/Lup6UhdkPx9S9SnobZzRVL7VcBH9Bt9bCBLEuX/YXOOtBsOZ4NQrRykKhffRWHvigQvOA==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/helper-api-error": "1.11.1", + "@webassemblyjs/helper-wasm-bytecode": "1.11.1", + "@webassemblyjs/ieee754": "1.11.1", + "@webassemblyjs/leb128": "1.11.1", + "@webassemblyjs/utf8": "1.11.1" + } + }, + "node_modules/@webassemblyjs/wast-printer": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.1.tgz", + "integrity": "sha512-IQboUWM4eKzWW+N/jij2sRatKMh99QEelo3Eb2q0qXkvPRISAj8Qxtmw5itwqK+TTkBuUIE45AxYPToqPtL5gg==", + "dev": true, + "dependencies": { + "@webassemblyjs/ast": "1.11.1", + "@xtuc/long": "4.2.2" + } + }, + "node_modules/@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "node_modules/@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "node_modules/acorn": { + "version": "8.8.2", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.2.tgz", + "integrity": "sha512-xjIYgE8HBrkpd/sJqOGNspf8uHG+NOHGOw6a/Urj8taM2EXfdNAH2oFcPeIFfsv3+kz/mJrS5VuMqbNLjCa2vw==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-import-assertions": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/acorn-import-assertions/-/acorn-import-assertions-1.8.0.tgz", + "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", + "dev": true, + "peerDependencies": { + "acorn": "^8" + } + }, + "node_modules/acorn-walk": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.2.0.tgz", + "integrity": "sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==", + "dev": true, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/adm-zip": { + "version": "0.4.16", + "resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.4.16.tgz", + "integrity": "sha512-TFi4HBKSGfIKsK5YCkKaaFG2m4PEDyViZmEwof3MTIgzimHLto6muaHVpbrljdIvIrFZzEq/p4nafOeLcYegrg==", + "dev": true, + "engines": { + "node": ">=0.3.0" + } + }, + "node_modules/ajv": { + "version": "8.12.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.12.0.tgz", + "integrity": "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ajv-formats": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-2.1.1.tgz", + "integrity": "sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==", + "dev": true, + "dependencies": { + "ajv": "^8.0.0" + }, + "peerDependencies": { + "ajv": "^8.0.0" + }, + "peerDependenciesMeta": { + "ajv": { + "optional": true + } + } + }, + "node_modules/ajv-keywords": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-5.1.0.tgz", + "integrity": "sha512-YCS/JNFAUyr5vAuhk1DWm1CBxRHW9LbJ2ozWeemrIqpbsqKjHVxYPyi5GC0rjZIT5JxJ3virVTS8wk4i/Z+krw==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.3" + }, + "peerDependencies": { + "ajv": "^8.8.2" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "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.4", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz", + "integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "node_modules/babel-loader": { + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-9.1.2.tgz", + "integrity": "sha512-mN14niXW43tddohGl8HPu5yfQq70iUThvFL/4QzESA7GcZoC0eVOhvWdQ8+3UlSjaDE9MVtsW9mxDY07W7VpVA==", + "dev": true, + "dependencies": { + "find-cache-dir": "^3.3.2", + "schema-utils": "^4.0.0" + }, + "engines": { + "node": ">= 14.15.0" + }, + "peerDependencies": { + "@babel/core": "^7.12.0", + "webpack": ">=5" + } + }, + "node_modules/babel-plugin-const-enum": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/babel-plugin-const-enum/-/babel-plugin-const-enum-1.2.0.tgz", + "integrity": "sha512-o1m/6iyyFnp9MRsK1dHF3bneqyf3AlM2q3A/YbgQr2pCat6B6XJVDv2TXqzfY2RYUi4mak6WAksSBPlyYGx9dg==", + "dev": true, + "dependencies": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-syntax-typescript": "^7.3.3", + "@babel/traverse": "^7.16.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.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==", + "dev": true + }, + "node_modules/big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true, + "engines": { + "node": "*" + } + }, + "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==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.21.5", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.5.tgz", + "integrity": "sha512-tUkiguQGW7S3IhB7N+c2MV/HZPSCPAAiYBZXLsBhFB/PCy6ZKKsZrmBayHV9fdGV/ARIfJ14NkxKzRDjvp7L6w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001449", + "electron-to-chromium": "^1.4.284", + "node-releases": "^2.0.8", + "update-browserslist-db": "^1.0.10" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "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==", + "dev": true + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001458", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001458.tgz", + "integrity": "sha512-lQ1VlUUq5q9ro9X+5gOEyH7i3vm+AYVT1WDCVB69XOZ17KZRhnZ9J0Sqz7wTHQaLBJccNCHq8/Ww5LlOIZbB0w==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true, + "engines": { + "node": ">=6.0" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==", + "dev": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "node_modules/convert-source-map": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", + "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", + "dev": true, + "peer": true + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "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", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/duplexer": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/duplexer/-/duplexer-0.1.2.tgz", + "integrity": "sha512-jtD6YG370ZCIi/9GTaJKQxWTZD045+4R4hTk/x1UyoqadyJ9x9CgSi1RlVDQF8U2sxLLSnFkCaMihqljHIWgMg==", + "dev": true + }, + "node_modules/electron-to-chromium": { + "version": "1.4.313", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.313.tgz", + "integrity": "sha512-QckB9OVqr2oybjIrbMI99uF+b9+iTja5weFe0ePbqLb5BHqXOJUO1SG6kDj/1WtWPRIBr51N153AEq8m7HuIaA==", + "dev": true + }, + "node_modules/emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/enhanced-resolve": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.12.0.tgz", + "integrity": "sha512-QHTXI/sZQmko1cbDoNAa3mJ5qhWUUNAq3vR0/YiD379fWQrcfuoX1+HW2S0MTt7XmoPLapdaDKUtelUSPic7hQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/es-module-lexer": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.9.3.tgz", + "integrity": "sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==", + "dev": true + }, + "node_modules/esbuild": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.15.18.tgz", + "integrity": "sha512-x/R72SmW3sSFRm5zrrIjAhCeQSAWoni3CmHEqfQrZIQTM3lVCdehdwuIqaOtfC2slvpdlLa62GYoN8SxT23m6Q==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.15.18", + "@esbuild/linux-loong64": "0.15.18", + "esbuild-android-64": "0.15.18", + "esbuild-android-arm64": "0.15.18", + "esbuild-darwin-64": "0.15.18", + "esbuild-darwin-arm64": "0.15.18", + "esbuild-freebsd-64": "0.15.18", + "esbuild-freebsd-arm64": "0.15.18", + "esbuild-linux-32": "0.15.18", + "esbuild-linux-64": "0.15.18", + "esbuild-linux-arm": "0.15.18", + "esbuild-linux-arm64": "0.15.18", + "esbuild-linux-mips64le": "0.15.18", + "esbuild-linux-ppc64le": "0.15.18", + "esbuild-linux-riscv64": "0.15.18", + "esbuild-linux-s390x": "0.15.18", + "esbuild-netbsd-64": "0.15.18", + "esbuild-openbsd-64": "0.15.18", + "esbuild-sunos-64": "0.15.18", + "esbuild-windows-32": "0.15.18", + "esbuild-windows-64": "0.15.18", + "esbuild-windows-arm64": "0.15.18" + } + }, + "node_modules/esbuild-android-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.15.18.tgz", + "integrity": "sha512-wnpt3OXRhcjfIDSZu9bnzT4/TNTDsOUvip0foZOUBG7QbSt//w3QV4FInVJxNhKc/ErhUxc5z4QjHtMi7/TbgA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.15.18.tgz", + "integrity": "sha512-G4xu89B8FCzav9XU8EjsXacCKSG2FT7wW9J6hOc18soEHJdtWu03L3TQDGf0geNxfLTtxENKBzMSq9LlbjS8OQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.15.18.tgz", + "integrity": "sha512-2WAvs95uPnVJPuYKP0Eqx+Dl/jaYseZEUUT1sjg97TJa4oBtbAKnPnl3b5M9l51/nbx7+QAEtuummJZW0sBEmg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.15.18.tgz", + "integrity": "sha512-tKPSxcTJ5OmNb1btVikATJ8NftlyNlc8BVNtyT/UAr62JFOhwHlnoPrhYWz09akBLHI9nElFVfWSTSRsrZiDUA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.15.18.tgz", + "integrity": "sha512-TT3uBUxkteAjR1QbsmvSsjpKjOX6UkCstr8nMr+q7zi3NuZ1oIpa8U41Y8I8dJH2fJgdC3Dj3CXO5biLQpfdZA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.15.18.tgz", + "integrity": "sha512-R/oVr+X3Tkh+S0+tL41wRMbdWtpWB8hEAMsOXDumSSa6qJR89U0S/PpLXrGF7Wk/JykfpWNokERUpCeHDl47wA==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.15.18.tgz", + "integrity": "sha512-lphF3HiCSYtaa9p1DtXndiQEeQDKPl9eN/XNoBf2amEghugNuqXNZA/ZovthNE2aa4EN43WroO0B85xVSjYkbg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.15.18.tgz", + "integrity": "sha512-hNSeP97IviD7oxLKFuii5sDPJ+QHeiFTFLoLm7NZQligur8poNOWGIgpQ7Qf8Balb69hptMZzyOBIPtY09GZYw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.15.18.tgz", + "integrity": "sha512-UH779gstRblS4aoS2qpMl3wjg7U0j+ygu3GjIeTonCcN79ZvpPee12Qun3vcdxX+37O5LFxz39XeW2I9bybMVA==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.15.18.tgz", + "integrity": "sha512-54qr8kg/6ilcxd+0V3h9rjT4qmjc0CccMVWrjOEM/pEcUzt8X62HfBSeZfT2ECpM7104mk4yfQXkosY8Quptug==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.15.18.tgz", + "integrity": "sha512-Mk6Ppwzzz3YbMl/ZZL2P0q1tnYqh/trYZ1VfNP47C31yT0K8t9s7Z077QrDA/guU60tGNp2GOwCQnp+DYv7bxQ==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.15.18.tgz", + "integrity": "sha512-b0XkN4pL9WUulPTa/VKHx2wLCgvIAbgwABGnKMY19WhKZPT+8BxhZdqz6EgkqCLld7X5qiCY2F/bfpUUlnFZ9w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.15.18.tgz", + "integrity": "sha512-ba2COaoF5wL6VLZWn04k+ACZjZ6NYniMSQStodFKH/Pu6RxzQqzsmjR1t9QC89VYJxBeyVPTaHuBMCejl3O/xg==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.15.18.tgz", + "integrity": "sha512-VbpGuXEl5FCs1wDVp93O8UIzl3ZrglgnSQ+Hu79g7hZu6te6/YHgVJxCM2SqfIila0J3k0csfnf8VD2W7u2kzQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.15.18.tgz", + "integrity": "sha512-98ukeCdvdX7wr1vUYQzKo4kQ0N2p27H7I11maINv73fVEXt2kyh4K4m9f35U1K43Xc2QGXlzAw0K9yoU7JUjOg==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.15.18.tgz", + "integrity": "sha512-yK5NCcH31Uae076AyQAXeJzt/vxIo9+omZRKj1pauhk3ITuADzuOx5N2fdHrAKPxN+zH3w96uFKlY7yIn490xQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-sunos-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.15.18.tgz", + "integrity": "sha512-On22LLFlBeLNj/YF3FT+cXcyKPEI263nflYlAhz5crxtp3yRG1Ugfr7ITyxmCmjm4vbN/dGrb/B7w7U8yJR9yw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.15.18.tgz", + "integrity": "sha512-o+eyLu2MjVny/nt+E0uPnBxYuJHBvho8vWsC2lV61A7wwTWC3jkN2w36jtA+yv1UgYkHRihPuQsL23hsCYGcOQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.15.18.tgz", + "integrity": "sha512-qinug1iTTaIIrCorAUjR0fcBk24fjzEedFYhhispP8Oc7SFvs+XeW3YpAKiKp8dRpizl4YYAhxMjlftAMJiaUw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.15.18", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.15.18.tgz", + "integrity": "sha512-q9bsYzegpZcLziq0zgUi5KqGVtfhjxGbnksaBFYmWLxeV/S1fK4OLdq2DFYnXcLMjlZw2L0jLsk1eGoB522WXQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esrecurse/node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true, + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-cache-dir": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.2.tgz", + "integrity": "sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==", + "dev": true, + "dependencies": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/avajs/find-cache-dir?sponsor=1" + } + }, + "node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "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==", + "dev": true + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "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/glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/gzip-size": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/gzip-size/-/gzip-size-6.0.0.tgz", + "integrity": "sha512-ax7ZYomf6jqPTQ4+XCpUGyXKHk5WweS+e05MBO4/y3WJ5RkmPXNKvX+bx1behVILVwr6JSQvZAku021CHPXG3Q==", + "dev": true, + "dependencies": { + "duplexer": "^0.1.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "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==", + "dev": true, + "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==", + "dev": true + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jest-worker": { + "version": "27.5.1", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.5.1.tgz", + "integrity": "sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==", + "dev": true, + "dependencies": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/jest-worker/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/jest-worker/node_modules/supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true, + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json-parse-even-better-errors": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", + "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", + "dev": true + }, + "node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/loader-runner": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.3.0.tgz", + "integrity": "sha512-3R/1M+yS3j5ou80Me59j7F9IMs4PXs3VqRrm0TU3AbKPxlmpoY1TNscJV/oGJXo8qCatFGTfDbY6W6ipGOYXfg==", + "dev": true, + "engines": { + "node": ">=6.11.5" + } + }, + "node_modules/loader-utils": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.4.tgz", + "integrity": "sha512-xXqpXoINfFhgua9xiqD8fPFHgkoq1mmmpE92WlDbm9rNRd/EbRb+Gqf908T2DMfuHjjJlksiK2RbHVOdD/MqSw==", + "dev": true, + "dependencies": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + }, + "engines": { + "node": ">=8.9.0" + } + }, + "node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "dev": true + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dev": true, + "peer": true, + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "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==", + "dev": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/mrmime": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/mrmime/-/mrmime-1.0.1.tgz", + "integrity": "sha512-hzzEagAgDyoU1Q6yg5uI+AorQgdvMCur3FcKf7NhMKWsaYg+RnbTyHRa/9IlLF9rf455MOCtcqqrQQ83pPP7Uw==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "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==", + "dev": true + }, + "node_modules/ncp": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ncp/-/ncp-2.0.0.tgz", + "integrity": "sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==", + "dev": true, + "bin": { + "ncp": "bin/ncp" + } + }, + "node_modules/neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node_modules/node-releases": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.10.tgz", + "integrity": "sha512-5GFldHPXVG/YZmFzJvKK2zDSzPKhEp0+ZR5SVaoSag9fsL5YgHbUHDfnG5494ISANDcK4KwPXAx2xqVEydmd7w==", + "dev": true + }, + "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==", + "dev": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/opener": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/opener/-/opener-1.5.2.tgz", + "integrity": "sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==", + "dev": true, + "bin": { + "opener": "bin/opener-bin.js" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "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==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "dependencies": { + "find-up": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "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/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, + "node_modules/raw-loader": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/raw-loader/-/raw-loader-4.0.2.tgz", + "integrity": "sha512-ZnScIV3ag9A4wPX/ZayxL/jZH+euYb6FcUinPcgiQW0+UBtEv0O6Q3lGd3cqJ+GHH+rksEv3Pj99oxJ3u3VIKA==", + "dev": true, + "dependencies": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependencies": { + "webpack": "^4.0.0 || ^5.0.0" + } + }, + "node_modules/raw-loader/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/raw-loader/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/raw-loader/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/raw-loader/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/require-from-string": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", + "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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==", + "dev": true, + "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/schema-utils": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.0.0.tgz", + "integrity": "sha512-1edyXKgh6XnJsJSQ8mKWXnN/BVaIbFMLpouRUrXgVq7WYne5kw3MW7UPhO44uRXQSIpTSXoJbmrR2X0w9kUTyg==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.9", + "ajv": "^8.8.0", + "ajv-formats": "^2.1.1", + "ajv-keywords": "^5.0.0" + }, + "engines": { + "node": ">= 12.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "node_modules/semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true, + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/serialize-javascript": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.1.tgz", + "integrity": "sha512-owoXEFjWRllis8/M1Q+Cw5k8ZH40e3zhp/ovX+Xr/vi1qj6QesbyXXViFbpNvWvPNAD62SutwEXavefrLJWj7w==", + "dev": true, + "dependencies": { + "randombytes": "^2.1.0" + } + }, + "node_modules/sirv": { + "version": "1.0.19", + "resolved": "https://registry.npmjs.org/sirv/-/sirv-1.0.19.tgz", + "integrity": "sha512-JuLThK3TnZG1TAKDwNIqNq6QA2afLOCcm+iE8D1Kj3GA40pSPsxQjjJl0J8X3tsR7T+CP1GavpzLwYkgVLWrZQ==", + "dev": true, + "dependencies": { + "@polka/url": "^1.0.0-next.20", + "mrmime": "^1.0.0", + "totalist": "^1.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/source-map-support": { + "version": "0.5.21", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", + "integrity": "sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==", + "dev": true, + "dependencies": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "license": "ISC", "dependencies": { - "@scrypted/sdk": "file:../sdk", - "@scrypted/server": "file:../server", - "http-auth-utils": "^3.0.2", - "node-fetch-commonjs": "^3.1.1", - "typescript": "^4.4.3" + "has-flag": "^3.0.0" }, - "devDependencies": { - "@types/node": "^16.9.0" + "engines": { + "node": ">=4" + } + }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "dev": true, + "engines": { + "node": ">=6" } }, - "../../sdk": { - "name": "@scrypted/sdk", - "version": "0.2.39", + "node_modules/terser": { + "version": "5.16.5", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.16.5.tgz", + "integrity": "sha512-qcwfg4+RZa3YvlFh0qjifnzBHjKGNbtDo9yivMqMFDy9Q6FSaQWSB/j1xKhsoUFJIqDOM3TsN6D5xbrMrFcHbg==", "dev": true, - "license": "ISC", "dependencies": { - "@babel/preset-typescript": "^7.16.7", - "adm-zip": "^0.4.13", - "axios": "^0.21.4", - "babel-loader": "^8.2.3", - "babel-plugin-const-enum": "^1.1.0", - "esbuild": "^0.15.9", - "ncp": "^2.0.0", - "raw-loader": "^4.0.2", - "rimraf": "^3.0.2", - "tmp": "^0.2.1", - "typescript": "^4.9.3", - "webpack": "^5.74.0", - "webpack-bundle-analyzer": "^4.5.0" + "@jridgewell/source-map": "^0.3.2", + "acorn": "^8.5.0", + "commander": "^2.20.0", + "source-map-support": "~0.5.20" }, "bin": { - "scrypted-debug": "bin/scrypted-debug.js", - "scrypted-deploy": "bin/scrypted-deploy.js", - "scrypted-deploy-debug": "bin/scrypted-deploy-debug.js", - "scrypted-package-json": "bin/scrypted-package-json.js", - "scrypted-readme": "bin/scrypted-readme.js", - "scrypted-setup-project": "bin/scrypted-setup-project.js", - "scrypted-webpack": "bin/scrypted-webpack.js" + "terser": "bin/terser" }, - "devDependencies": { - "@types/node": "^18.11.9", - "@types/stringify-object": "^4.0.0", - "stringify-object": "^3.3.0", - "ts-node": "^10.4.0", - "typedoc": "^0.23.21" + "engines": { + "node": ">=10" } }, - "../../server": { - "version": "0.4.9", + "node_modules/terser-webpack-plugin": { + "version": "5.3.6", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.3.6.tgz", + "integrity": "sha512-kfLFk+PoLUQIbLmB1+PZDMRSZS99Mp+/MHqDNmMA6tOItzRt+Npe3E+fsMs5mfcM0wCtrrdU387UnV+vnSffXQ==", "dev": true, - "license": "ISC", "dependencies": { - "@ffmpeg-installer/ffmpeg": "^1.1.0", - "@mapbox/node-pre-gyp": "^1.0.10", - "@scrypted/types": "^0.2.36", - "adm-zip": "^0.5.9", - "axios": "^0.21.4", - "body-parser": "^1.19.0", - "cookie-parser": "^1.4.6", - "debug": "^4.3.4", - "engine.io": "^6.2.0", - "express": "^4.18.2", - "ffmpeg-static": "^5.1.0", - "http-auth": "^4.2.0", - "ip": "^1.1.8", - "level": "^6.0.1", - "linkfs": "^2.1.0", - "lodash": "^4.17.21", - "memfs": "^3.4.7", - "mime": "^3.0.0", - "mkdirp": "^1.0.4", - "nan": "^2.17.0", - "node-dijkstra": "^2.5.0", - "node-forge": "^1.3.1", - "node-gyp": "^8.4.1", - "router": "^1.3.7", - "semver": "^7.3.8", - "source-map-support": "^0.5.21", - "tar": "^6.1.11", - "tslib": "^2.4.0", - "typescript": "^4.8.4", - "whatwg-mimetype": "^2.3.0", - "ws": "^8.9.0" + "@jridgewell/trace-mapping": "^0.3.14", + "jest-worker": "^27.4.5", + "schema-utils": "^3.1.1", + "serialize-javascript": "^6.0.0", + "terser": "^5.14.1" }, - "bin": { - "scrypted-serve": "bin/scrypted-serve" + "engines": { + "node": ">= 10.13.0" }, - "devDependencies": { - "@types/adm-zip": "^0.4.34", - "@types/cookie-parser": "^1.4.3", - "@types/debug": "^4.1.7", - "@types/express": "^4.17.14", - "@types/http-auth": "^4.1.1", - "@types/ip": "^1.1.0", - "@types/lodash": "^4.14.186", - "@types/mime": "^3.0.1", - "@types/mkdirp": "^1.0.2", - "@types/node-dijkstra": "^2.5.3", - "@types/node-forge": "^1.3.0", - "@types/pem": "^1.9.6", - "@types/rimraf": "^3.0.2", - "@types/semver": "^7.3.12", - "@types/source-map-support": "^0.5.6", - "@types/tar": "^4.0.5", - "@types/whatwg-mimetype": "^2.1.1", - "@types/ws": "^7.4.7" + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" }, - "optionalDependencies": { - "node-pty-prebuilt-multiarch": "^0.10.1-pre.5" + "peerDependencies": { + "webpack": "^5.1.0" + }, + "peerDependenciesMeta": { + "@swc/core": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "uglify-js": { + "optional": true + } } }, - "node_modules/@scrypted/common": { - "resolved": "../../common", - "link": true + "node_modules/terser-webpack-plugin/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } }, - "node_modules/@scrypted/sdk": { - "resolved": "../../sdk", - "link": true + "node_modules/terser-webpack-plugin/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } }, - "node_modules/@scrypted/server": { - "resolved": "../../server", - "link": true + "node_modules/terser-webpack-plugin/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true }, - "node_modules/@types/node": { - "version": "16.11.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz", - "integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==" + "node_modules/terser-webpack-plugin/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } }, - "node_modules/alexa-smarthome-ts": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/alexa-smarthome-ts/-/alexa-smarthome-ts-0.0.1.tgz", - "integrity": "sha512-Pbbs/fJ/2P/AN6f6/5UCH6WhW+HP3z9FtXpcuRgBI+WpT9dru9kYt/HiBeihmTPvvwmHMqKSCp0yodMqRJ2Zhw==" + "node_modules/tmp": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", + "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", + "dev": true, + "dependencies": { + "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" + } }, - "node_modules/axios": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", - "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, "dependencies": { - "follow-redirects": "^1.14.4" + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" } }, - "node_modules/follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==", + "node_modules/totalist": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/totalist/-/totalist-1.1.0.tgz", + "integrity": "sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/ts-loader": { + "version": "9.4.2", + "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.4.2.tgz", + "integrity": "sha512-OmlC4WVmFv5I0PpaxYb+qGeGOdm5giHU7HwDDUjw59emP2UYMHy9fFSDcYgSNoH8sXcj4hGCSEhlDZ9ULeDraA==", + "dev": true, + "dependencies": { + "chalk": "^4.1.0", + "enhanced-resolve": "^5.0.0", + "micromatch": "^4.0.0", + "semver": "^7.3.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "typescript": "*", + "webpack": "^5.0.0" + } + }, + "node_modules/ts-loader/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/ts-loader/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/ts-loader/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/ts-loader/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-loader/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==", + "dev": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-loader/node_modules/semver": { + "version": "7.3.8", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz", + "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/ts-loader/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ts-loader/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz", + "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==", + "dev": true, "funding": [ { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" } ], - "engines": { - "node": ">=4.0" + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } + "bin": { + "browserslist-lint": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "dependencies": { + "punycode": "^2.1.0" } }, "node_modules/uuid": { @@ -192,125 +2709,261 @@ "bin": { "uuid": "dist/bin/uuid" } - } - }, - "dependencies": { - "@scrypted/common": { - "version": "file:../../common", - "requires": { - "@scrypted/sdk": "file:../sdk", - "@scrypted/server": "file:../server", - "@types/node": "^16.9.0", - "http-auth-utils": "^3.0.2", - "node-fetch-commonjs": "^3.1.1", - "typescript": "^4.4.3" - } - }, - "@scrypted/sdk": { - "version": "file:../../sdk", - "requires": { - "@babel/preset-typescript": "^7.16.7", - "@types/node": "^18.11.9", - "@types/stringify-object": "^4.0.0", - "adm-zip": "^0.4.13", - "axios": "^0.21.4", - "babel-loader": "^8.2.3", - "babel-plugin-const-enum": "^1.1.0", - "esbuild": "^0.15.9", - "ncp": "^2.0.0", - "raw-loader": "^4.0.2", - "rimraf": "^3.0.2", - "stringify-object": "^3.3.0", - "tmp": "^0.2.1", - "ts-node": "^10.4.0", - "typedoc": "^0.23.21", - "typescript": "^4.9.3", - "webpack": "^5.74.0", - "webpack-bundle-analyzer": "^4.5.0" + }, + "node_modules/watchpack": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.4.0.tgz", + "integrity": "sha512-Lcvm7MGST/4fup+ifyKi2hjyIAwcdI4HRgtvTpIUxBRhB+RFtUh8XtDOxUfctVCnhVi+QQj49i91OyvzkJl6cg==", + "dev": true, + "dependencies": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + }, + "engines": { + "node": ">=10.13.0" } }, - "@scrypted/server": { - "version": "file:../../server", - "requires": { - "@ffmpeg-installer/ffmpeg": "^1.1.0", - "@mapbox/node-pre-gyp": "^1.0.10", - "@scrypted/types": "^0.2.36", - "@types/adm-zip": "^0.4.34", - "@types/cookie-parser": "^1.4.3", - "@types/debug": "^4.1.7", - "@types/express": "^4.17.14", - "@types/http-auth": "^4.1.1", - "@types/ip": "^1.1.0", - "@types/lodash": "^4.14.186", - "@types/mime": "^3.0.1", - "@types/mkdirp": "^1.0.2", - "@types/node-dijkstra": "^2.5.3", - "@types/node-forge": "^1.3.0", - "@types/pem": "^1.9.6", - "@types/rimraf": "^3.0.2", - "@types/semver": "^7.3.12", - "@types/source-map-support": "^0.5.6", - "@types/tar": "^4.0.5", - "@types/whatwg-mimetype": "^2.1.1", - "@types/ws": "^7.4.7", - "adm-zip": "^0.5.9", - "axios": "^0.21.4", - "body-parser": "^1.19.0", - "cookie-parser": "^1.4.6", - "debug": "^4.3.4", - "engine.io": "^6.2.0", - "express": "^4.18.2", - "ffmpeg-static": "^5.1.0", - "http-auth": "^4.2.0", - "ip": "^1.1.8", - "level": "^6.0.1", - "linkfs": "^2.1.0", - "lodash": "^4.17.21", - "memfs": "^3.4.7", - "mime": "^3.0.0", - "mkdirp": "^1.0.4", - "nan": "^2.17.0", - "node-dijkstra": "^2.5.0", - "node-forge": "^1.3.1", - "node-gyp": "^8.4.1", - "node-pty-prebuilt-multiarch": "^0.10.1-pre.5", - "router": "^1.3.7", - "semver": "^7.3.8", - "source-map-support": "^0.5.21", - "tar": "^6.1.11", - "tslib": "^2.4.0", - "typescript": "^4.8.4", - "whatwg-mimetype": "^2.3.0", - "ws": "^8.9.0" - } - }, - "@types/node": { - "version": "16.11.1", - "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz", - "integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==" - }, - "alexa-smarthome-ts": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/alexa-smarthome-ts/-/alexa-smarthome-ts-0.0.1.tgz", - "integrity": "sha512-Pbbs/fJ/2P/AN6f6/5UCH6WhW+HP3z9FtXpcuRgBI+WpT9dru9kYt/HiBeihmTPvvwmHMqKSCp0yodMqRJ2Zhw==" + "node_modules/webpack": { + "version": "5.75.0", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.75.0.tgz", + "integrity": "sha512-piaIaoVJlqMsPtX/+3KTTO6jfvrSYgauFVdt8cr9LTHKmcq/AMd4mhzsiP7ZF/PGRNPGA8336jldh9l2Kt2ogQ==", + "dev": true, + "dependencies": { + "@types/eslint-scope": "^3.7.3", + "@types/estree": "^0.0.51", + "@webassemblyjs/ast": "1.11.1", + "@webassemblyjs/wasm-edit": "1.11.1", + "@webassemblyjs/wasm-parser": "1.11.1", + "acorn": "^8.7.1", + "acorn-import-assertions": "^1.7.6", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.10.0", + "es-module-lexer": "^0.9.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.9", + "json-parse-even-better-errors": "^2.3.1", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.1.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.3", + "watchpack": "^2.4.0", + "webpack-sources": "^3.2.3" + }, + "bin": { + "webpack": "bin/webpack.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + }, + "peerDependenciesMeta": { + "webpack-cli": { + "optional": true + } + } + }, + "node_modules/webpack-bundle-analyzer": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/webpack-bundle-analyzer/-/webpack-bundle-analyzer-4.8.0.tgz", + "integrity": "sha512-ZzoSBePshOKhr+hd8u6oCkZVwpVaXgpw23ScGLFpR6SjYI7+7iIWYarjN6OEYOfRt8o7ZyZZQk0DuMizJ+LEIg==", + "dev": true, + "dependencies": { + "@discoveryjs/json-ext": "0.5.7", + "acorn": "^8.0.4", + "acorn-walk": "^8.0.0", + "chalk": "^4.1.0", + "commander": "^7.2.0", + "gzip-size": "^6.0.0", + "lodash": "^4.17.20", + "opener": "^1.5.2", + "sirv": "^1.0.7", + "ws": "^7.3.1" + }, + "bin": { + "webpack-bundle-analyzer": "lib/bin/analyzer.js" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } }, - "axios": { - "version": "0.24.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz", - "integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==", - "requires": { - "follow-redirects": "^1.14.4" + "node_modules/webpack-bundle-analyzer/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" } }, - "follow-redirects": { - "version": "1.14.9", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", - "integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==" + "node_modules/webpack-bundle-analyzer/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } }, - "uuid": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz", - "integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==" + "node_modules/webpack-bundle-analyzer/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/webpack-bundle-analyzer/node_modules/commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-bundle-analyzer/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/webpack-sources": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-3.2.3.tgz", + "integrity": "sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==", + "dev": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/webpack/node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/webpack/node_modules/ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true, + "peerDependencies": { + "ajv": "^6.9.1" + } + }, + "node_modules/webpack/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "node_modules/webpack/node_modules/schema-utils": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.1.1.tgz", + "integrity": "sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==", + "dev": true, + "dependencies": { + "@types/json-schema": "^7.0.8", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + }, + "engines": { + "node": ">= 10.13.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/webpack" + } + }, + "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==", + "dev": true + }, + "node_modules/ws": { + "version": "7.5.9", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.9.tgz", + "integrity": "sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==", + "dev": true, + "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/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true, + "peer": true } } } diff --git a/plugins/alexa/package.json b/plugins/alexa/package.json index 938084322e..670cf9f975 100644 --- a/plugins/alexa/package.json +++ b/plugins/alexa/package.json @@ -1,6 +1,6 @@ { "name": "@scrypted/alexa", - "version": "0.1.0", + "version": "0.2.0", "scripts": { "scrypted-setup-project": "scrypted-setup-project", "prescrypted-setup-project": "scrypted-package-json", @@ -21,11 +21,12 @@ "amazon" ], "scrypted": { - "name": "Alexa Plugin", + "name": "Alexa", "type": "API", "interfaces": [ "HttpRequestHandler", - "MixinProvider" + "MixinProvider", + "Settings" ], "pluginDependencies": [ "@scrypted/cloud", @@ -33,14 +34,11 @@ ] }, "dependencies": { - "@types/node": "^16.6.1", - "alexa-smarthome-ts": "^0.0.1", "axios": "^1.3.4", "uuid": "^9.0.0" }, "devDependencies": { - "@scrypted/common": "file:../../common", - "@scrypted/sdk": "file:../../sdk", - "@scrypted/server": "file:../../server" + "@types/node": "^18.4.2", + "@scrypted/sdk": "^0.2.70" } } diff --git a/plugins/alexa/src/alexa.ts b/plugins/alexa/src/alexa.ts new file mode 100644 index 0000000000..2edc180ab8 --- /dev/null +++ b/plugins/alexa/src/alexa.ts @@ -0,0 +1,221 @@ +export declare type DisplayCategory = 'ACTIVITY_TRIGGER' | 'CAMERA' | 'CONTACT_SENSOR' | 'DOOR' | 'DOORBELL' | 'GARAGE_DOOR' | 'LIGHT' | 'MICROWAVE' | 'MOTION_SENSOR' | 'OTHER' | 'SCENE_TRIGGER' | 'SECURITY_PANEL' | 'SMARTLOCK' | 'SMARTPLUG' | 'SPEAKER' | 'SWITCH' | 'TEMPERATURE_SENSOR' | 'THERMOSTAT' | 'TV'; + +/* +COMMON DIRECTIVES AND RESPONSES +*/ + +export interface AddOrUpdateReport { + event: { + header: Header<"Alexa.Discovery", "AddOrUpdateReport">; + payload: AddOrUpdateReportPayload; + } +} + +export interface DeleteReport { + event: { + header: Header<"Alexa.Discovery", "DeleteReport">; + payload: DeleteReportPayload; + } +} + +export interface StateReport extends Report<"Alexa", "StateReport"> { } + +export interface ChangeReport extends Report<"Alexa", "ChangeReport", ChangePayload> { } + +export interface Response { + event: Event<"Alexa", "Response">; + context?: Context; +} + +export interface DeferredResponse { + event: Event<"Alexa", "DeferredResponse", DeferredPayload>; +} + +export interface ErrorResponse { + event: Event<"Alexa", "ErrorResponse", ErrorPayload>; +} + +/* +DEVICE EVENTS +*/ + +export interface WebRTCAnswerGeneratedForSessionEvent extends Report<"Alexa.RTCSessionController", "AnswerGeneratedForSession", WebRTCAnswerGeneratedForSessionPayload> { } + +export interface WebRTCSessionConnectedEvent extends Report<"Alexa.RTCSessionController", "SessionConnected", WebRTCSessionPayload> { } + +export interface WebRTCSessionDisconnectedEvent extends Report<"Alexa.RTCSessionController", "SessionDisconnected", WebRTCSessionPayload> { } + +export interface ObjectDetectionEvent extends Report<"Alexa.SmartVision.ObjectDetectionSensor", "ObjectDetection", ObjectDetectionPayload> { } + +export interface DoorbellPressEvent extends Report<"Alexa.DoorbellEventSource", "DoorbellPress", DoorbellPressPayload> { } + +/* +IMPLIMENTATION TYPES +*/ + + +export interface Header { + namespace: NS; + name: N; + messageId: string; + correlationToken?: string; + payloadVersion: string; +} + +export interface Scope { + type: string; + token: string; + partition?: string; + userId?: string; +} + +export interface Endpoint { + endpointId: string; + scope?: Scope; + cookie?: any; +} + +export interface Payload { } + +export interface Directive { + header: Header; + endpoint: Endpoint; + payload: P; +} + +export interface Event { + header: Header; + endpoint: Endpoint; + payload: P; +} + +export interface Property { + namespace: string; + instance?: string; + name: string; + value: any; + timeOfSample: string; + uncertaintyInMilliseconds?: number; +} + +export interface Context { + properties: Property[]; +} + +export interface Report { + event: Event; + context: Context; +} + +export interface DeferredPayload { + estimatedDeferralInSeconds: number; +} + +export interface ErrorPayload { + type: string; + message: string; +} + +export interface ChangePayload { + change: { + cause: { + type: "APP_INTERACTION" | "PERIODIC_POLL" | "PHYSICAL_INTERACTION" | "VOICE_INTERACTION" | "RULE_TRIGGER"; + }, + properties: Property[]; + } +} + +export interface WebRTCSessionPayload { + sessionId: string; +} + +export interface WebRTCAnswerGeneratedForSessionPayload { + answer: { + format: string; + value: string; + } +} + +export interface ObjectDetectionPayloadEvent { + eventIdenifier: string; + imageNetClass: string; + timeOfSample: string; + uncertaintyInMilliseconds: number; + objectIdentifier: string; + frameImageUri: string; + croppedImageUri: string; +} + +export interface ObjectDetectionPayload { + events: ObjectDetectionPayloadEvent[] +} + + +export interface DoorbellPressPayload { + cause: { + type: "APP_INTERACTION" | "PERIODIC_POLL" | "PHYSICAL_INTERACTION" | "VOICE_INTERACTION"; + }, + timestamp: string; +} + +export interface DiscoveryProperty { + supported: any[]; + proactivelyReported: boolean; + retrievable: boolean; +} + +export interface DiscoveryCapability { + type: string; + interface: string; + instance?: string; + version: string; + properties?: DiscoveryProperty; + capabilityResources?: any; + configuration?: any; + semantics?: any; +} + +export interface DiscoveryEndpoint { + endpointId: string; + manufacturerName: string; + description: string; + friendlyName: string; + displayCategories: DisplayCategory[]; + additionalAttributes?: { + "manufacturer"?: string; + "model"?: string; + "serialNumber"?: string; + "firmwareVersion"? : string; + "softwareVersion"?: string; + "customIdentifier"?: string; + }; + capabilities?: DiscoveryCapability[]; + connections?: any[]; + relationships?: any; + cookie?: any; +} + +export interface DiscoverPayload { + endpoints: DiscoveryEndpoint[] +} + +export interface Discovery { + event: { + header: Header<"Alexa.Discovery", "Discover.Response">; + payload: DiscoverPayload; + } +} + +export interface AddOrUpdateReportPayload { + endpoints: DiscoveryEndpoint[] + scope: Scope; +} + +export interface DeleteReportEndpoint { + endpointId: string; +} + +export interface DeleteReportPayload { + endpoints: DeleteReportEndpoint[] + scope: Scope; +} \ No newline at end of file diff --git a/plugins/alexa/src/common.ts b/plugins/alexa/src/common.ts new file mode 100644 index 0000000000..a4a1d64fda --- /dev/null +++ b/plugins/alexa/src/common.ts @@ -0,0 +1,131 @@ +import { Battery, Online, PowerSensor, ScryptedDevice, ScryptedInterface, HttpResponse } from "@scrypted/sdk"; +import { v4 as createMessageId } from 'uuid'; + +export interface AlexaHttpResponse extends HttpResponse { + send(body: any, options?: any): void; +} + +export function addOnline(data: any, device: ScryptedDevice & Online) : any { + if (!device.interfaces.includes(ScryptedInterface.Online)) + return data; + + if (data.context === undefined) + data.context = {}; + + if (data.context.properties === undefined) + data.context.properties = []; + + data.context.properties.push( + { + "namespace": "Alexa.EndpointHealth", + "name": "connectivity", + "value": { + "value": device.online ? "OK" : "UNREACHABLE", + "reason": device.online ? undefined : "INTERNET_UNREACHABLE" + }, + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + } + ); + + return data; +} + +export function addBattery(data: any, device: ScryptedDevice & Battery) : any { + if (!device.interfaces.includes(ScryptedInterface.Battery)) + return data; + + if (data.context === undefined) + data.context = {}; + + if (data.context.properties === undefined) + data.context.properties = []; + + const lowPower = device.batteryLevel < 20; + let health = undefined; + + if (lowPower) { + health = { + "state": "WARNING", + "reasons": [ + "LOW_CHARGE" + ] + }; + } + + data.context.properties.push( + { + "namespace": "Alexa.EndpointHealth", + "name": "battery", + "value": { + health, + "levelPercentage": device.batteryLevel, + }, + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + } + ); + + return data; +} + +export function authErrorResponse(errorType: string, errorMessage: string, directive: any): any { + const { header } = directive; + const data = { + "event": { + header, + "payload": { + "type": errorType, + "message": errorMessage + } + } + }; + + data.event.header.name = "ErrorResponse"; + data.event.header.messageId = createMessageId(); + + return data; +} + +// https://developer.amazon.com/en-US/docs/alexa/device-apis/alexa-errorresponse.html#error-types +export function deviceErrorResponse (errorType: string, errorMessage: string, directive: any): any{ + const { header, endpoint } = directive; + const data = { + "event": { + header, + endpoint, + "payload": { + "type": errorType, + "message": errorMessage + } + } + }; + + data.event.header.name = "ErrorResponse"; + data.event.header.messageId = createMessageId(); + + return data; +} + +export function mirroredResponse (directive: any): any { + const { header, endpoint, payload } = directive; + const data = { + "event": { + header, + endpoint, + payload + } + }; + + data.event.header.name = "Response"; + data.event.header.messageId = createMessageId(); + + return data; +} + +export function sendDeviceResponse(data: any, response: any, device: ScryptedDevice) { + data = addBattery(data, device); + data = addOnline(data, device); + + response.send(data); +} \ No newline at end of file diff --git a/plugins/alexa/src/handlers.ts b/plugins/alexa/src/handlers.ts new file mode 100644 index 0000000000..f3e98c5db1 --- /dev/null +++ b/plugins/alexa/src/handlers.ts @@ -0,0 +1,34 @@ +import { HttpRequest, ScryptedDevice } from "@scrypted/sdk"; +import { AlexaHttpResponse, sendDeviceResponse } from "./common"; +import { supportedTypes } from "./types"; +import { v4 as createMessageId } from 'uuid'; +import { Directive, StateReport } from "./alexa"; + +export type AlexaHandler = (request: HttpRequest, response: AlexaHttpResponse, directive: Directive) => Promise +export type AlexaDeviceHandler = (request: HttpRequest, response: AlexaHttpResponse, directive: Directive, device: ScryptedDevice & T) => Promise + +export const alexaDeviceHandlers = new Map>(); +export const alexaHandlers = new Map(); + +alexaDeviceHandlers.set('Alexa/ReportState', async (request, response, directive: any, device: ScryptedDevice) => { + const supportedType = supportedTypes.get(device.type); + if (!supportedType) + return; + + const { header, endpoint, payload } = directive; + const report = await supportedType.sendReport(device); + + let data = { + "event": { + header, + endpoint, + payload + }, + context: report?.context + } as StateReport; + + data.event.header.name = "StateReport"; + data.event.header.messageId = createMessageId(); + + sendDeviceResponse(data, response, device); +}); \ No newline at end of file diff --git a/plugins/alexa/src/main.ts b/plugins/alexa/src/main.ts index 28ee2b7119..d73b2da98e 100644 --- a/plugins/alexa/src/main.ts +++ b/plugins/alexa/src/main.ts @@ -1,18 +1,21 @@ import axios from 'axios'; -import sdk, { HttpRequest, HttpRequestHandler, HttpResponse, MixinProvider, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, Setting, SettingValue, Settings } from '@scrypted/sdk'; +import sdk, { HttpRequest, HttpRequestHandler, MixinProvider, ScryptedDevice, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, EventDetails, Setting, SettingValue, Settings, HttpResponseOptions, HttpResponse } from '@scrypted/sdk'; import { StorageSettings } from '@scrypted/sdk/storage-settings'; -import { AutoenableMixinProvider } from '@scrypted/common/src/autoenable-mixin-provider'; -import { isSupported } from './types'; -import { DiscoveryEndpoint, DiscoverEvent } from 'alexa-smarthome-ts'; -import { AlexaHandler, addBattery, addOnline, addPowerSensor, capabilityHandlers, supportedTypes } from './types/common'; -import { createMessageId } from './message'; +import { addBattery, addOnline, deviceErrorResponse, mirroredResponse, authErrorResponse, AlexaHttpResponse } from './common'; +import { supportedTypes } from './types'; +import { v4 as createMessageId } from 'uuid'; +import { ChangeReport, Discovery, DiscoveryEndpoint } from './alexa'; +import { alexaHandlers, alexaDeviceHandlers } from './handlers'; const { systemManager, deviceManager } = sdk; const client_id = "amzn1.application-oa2-client.3283807e04d8408eb44a698c10f9dd13"; const client_secret = "bed445e2b26730acd818b90e175b275f6b67b18ff8645e571c5b3e311fa75ee9"; +const includeToken = 4; -class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, MixinProvider, Settings { +export let DEBUG = false; + +class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, MixinProvider, Settings { storageSettings = new StorageSettings(this, { tokenInfo: { hide: true, @@ -22,6 +25,10 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, multiple: true, hide: true }, + defaultIncluded: { + hide: true, + json: true + }, apiEndpoint: { title: 'Alexa Endpoint', description: 'This is the endpoint Alexa will use to send events to. This is set after you login.', @@ -30,86 +37,167 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, } }); - handlers = new Map(); accessToken: Promise; validAuths = new Set(); + devices = new Map(); constructor(nativeId?: string) { super(nativeId); - this.handlers.set('Alexa.Authorization', this.alexaAuthorization); - this.handlers.set('Alexa.Discovery', this.alexaDiscovery); + alexaHandlers.set('Alexa.Authorization/AcceptGrant', this.onAlexaAuthorization); + alexaHandlers.set('Alexa.Discovery/Discover', this.onDiscoverEndpoints); - this.syncDevices(); + this.start(); + } - systemManager.listen(async (eventSource, eventDetails, eventData) => { - if (!eventSource) - return; + async start() { - if (!this.storageSettings.values.syncedDevices.includes(eventSource.id)) - return; + for (const id of Object.keys(systemManager.getSystemState())) { + const device = systemManager.getDeviceById(id); + await this.tryEnableMixin(device); + } - const supportedType = supportedTypes.get(eventSource.type); - if (!supportedType) { - this.console.warn(`${eventSource.name} no longer supported type?`); - return; - } + systemManager.listen((async (eventSource: ScryptedDevice | undefined, eventDetails: EventDetails, eventData: any) => { + const status = await this.tryEnableMixin(eventSource); - const report = await supportedType.sendEvent(eventSource, eventDetails, eventData); - let data = { - "event": { - "header": { - "messageId": createMessageId(), - "namespace": report?.namespace ?? "Alexa", - "name": report?.name ?? "ChangeReport", - "payloadVersion": "3" - }, - "endpoint": { - "endpointId": eventSource.id, - "scope": undefined - }, - "payload": report?.payload, - }, - "context": report?.context - } + // sync new devices when added or removed + if (status === DeviceMixinStatus.Setup) + await this.syncEndpoints(); - data = addOnline(data, eventSource); - data = addBattery(data, eventSource); - data = addPowerSensor(data, eventSource); + if (status === DeviceMixinStatus.Setup || status === DeviceMixinStatus.AlreadySetup) { - // nothing to report - if (data.context === undefined && data.event.payload === undefined) - return; - - const accessToken = await this.getAccessToken(); - data.event.endpoint.scope = { - "type": "BearerToken", - "token": accessToken, - }; + if (!this.devices.has(eventSource.id)) { + this.devices.set(eventSource.id, eventSource); + eventSource.listen(ScryptedInterface.ObjectDetector, this.deviceListen.bind(this)); + } - await this.postEvent(data); - }); + this.deviceListen(eventSource, eventDetails, eventData); + } + }).bind(this)); + + await this.syncEndpoints(); } - getSettings(): Promise { - return this.storageSettings.getSettings(); + private async tryEnableMixin(device: ScryptedDevice): Promise { + if (!device) + return DeviceMixinStatus.NotSupported; + + const mixins = (device.mixins || []).slice(); + if (mixins.includes(this.id)) + return DeviceMixinStatus.AlreadySetup; + + const defaultIncluded = this.storageSettings.values.defaultIncluded || {}; + if (defaultIncluded[device.id] === includeToken) + return DeviceMixinStatus.AlreadySetup; + + if (!supportedTypes.has(device.type)) + return DeviceMixinStatus.NotSupported; + + mixins.push(this.id); + + const plugins = await systemManager.getComponent('plugins'); + await plugins.setMixins(device.id, mixins); + + defaultIncluded[device.id] = includeToken; + this.storageSettings.values.defaultIncluded = defaultIncluded; + + return DeviceMixinStatus.Setup; } - putSetting(key: string, value: SettingValue): Promise { - return this.storageSettings.putSetting(key, value); + + async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise { + const available = supportedTypes.has(type); + + if (available) + return []; + + return; } - async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any; }): Promise { - return mixinDevice; + async getMixin(device: ScryptedDevice, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }): Promise { + return device; } async releaseMixin(id: string, mixinDevice: any): Promise { const device = systemManager.getDeviceById(id); - if (device.mixins?.includes(this.id)) { + const mixins = (device.mixins || []).slice(); + if (mixins.includes(this.id)) + return; + + this.log.i(`Device removed from Alexa: ${device.name}. Requesting sync.`); + await this.syncEndpoints(); + } + + async deviceListen(eventSource: ScryptedDevice | undefined, eventDetails: EventDetails, eventData: any) : Promise { + if (!eventSource) + return; + + if (!this.storageSettings.values.syncedDevices.includes(eventSource.id)) + return; + + if (eventDetails.eventInterface === ScryptedInterface.ScryptedDevice) + return; + + const supportedType = supportedTypes.get(eventSource.type); + if (!supportedType) + return; + + const report = await supportedType.sendEvent(eventSource, eventDetails, eventData); + if (!report) { + this.console.warn(`${eventDetails.eventInterface}.${eventDetails.property} not supported for device ${eventSource.type}`); return; } - this.console.log('release mixin', id); - this.log.a(`${device.name} was removed. The Alexa plugin will reload momentarily.`); - deviceManager.requestRestart(); + + let data = { + "event": { + "header": { + "messageId": createMessageId(), + "namespace": report?.event?.header?.namespace ?? "Alexa", + "name": report?.event?.header?.name ?? "ChangeReport", + "payloadVersion": "3" + }, + "endpoint": { + "endpointId": eventSource.id, + }, + payload: report?.event?.payload + }, + context: report?.context + } as ChangeReport; + + data = addOnline(data, eventSource); + data = addBattery(data, eventSource); + + // nothing to report + if (data.context === undefined && data.event.payload === undefined) + return; + + data = await this.addAccessToken(data); + + await this.postEvent(data); + } + + private async addAccessToken(data: any) : Promise { + const accessToken = await this.getAccessToken(); + + if (data.event === undefined) + data.event = {}; + + if (data.event.endpoint === undefined) + data.event.endpoint = []; + + data.event.endpoint.scope = { + "type": "BearerToken", + "token": accessToken, + }; + + return data; + } + + getSettings(): Promise { + return this.storageSettings.getSettings(); + } + + putSetting(key: string, value: SettingValue): Promise { + return this.storageSettings.putSetting(key, value); } readonly endpoints: string[] = [ @@ -146,6 +234,8 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, const endpoint = await this.getAlexaEndpoint(); const self = this; + this.console.assert(!DEBUG, `event:`, data); + return axios.post(`https://${endpoint}/v3/events`, data, { headers: { 'Authorization': 'Bearer ' + accessToken, @@ -160,25 +250,59 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, }); } - async syncDevices() { - const endpoints = await this.addOrUpdateReport(); + async getEndpoints() : Promise { + const endpoints: DiscoveryEndpoint[] = []; + + for (const id of Object.keys(systemManager.getSystemState())) { + const device = systemManager.getDeviceById(id); + + if (!device.mixins?.includes(this.id)) + continue; + + const endpoint = await this.getEndpointForDevice(device); + if (endpoint) + endpoints.push(endpoint); + } + + return endpoints; + } + + async onDiscoverEndpoints(request: HttpRequest, response: AlexaHttpResponse, directive: any) { + const endpoints = await this.getEndpoints(); + + const data = { + "event": { + "header": { + "namespace": 'Alexa.Discovery', + "name": 'Discover.Response', + "payloadVersion": '3', + "messageId": createMessageId() + }, + "payload": { + endpoints + } + } + } as Discovery; + + response.send(data); + await this.saveEndpoints(endpoints); } - async addOrUpdateReport() { - const endpoints = this.getDiscoveryEndpoints(); + async syncEndpoints() { + const endpoints = await this.getEndpoints(); if (!endpoints.length) - return []; + return []; const accessToken = await this.getAccessToken(); - await this.postEvent({ + const data = { "event": { "header": { "namespace": "Alexa.Discovery", "name": "AddOrUpdateReport", "payloadVersion": "3", - "messageId": createMessageId(), + "messageId": createMessageId() }, "payload": { endpoints, @@ -188,12 +312,35 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, } } } - }); + }; - return endpoints; + await this.postEvent(data); + + await this.saveEndpoints(endpoints); } - async deleteReport(...ids: string[]) { + async saveEndpoints(endpoints: DiscoveryEndpoint[]) { + const existingEndpoints: string[] = this.storageSettings.values.syncedDevices; + const newEndpoints = endpoints.map(endpoint => endpoint.endpointId); + const deleted = new Set(existingEndpoints); + + for (const id of newEndpoints) { + deleted.delete(id); + } + + const all = new Set([...existingEndpoints, ...newEndpoints]); + + // save all the endpoints + this.storageSettings.values.syncedDevices = [...all]; + + // delete leftover endpoints + await this.deleteEndpoints(...deleted); + + // prune if the delete report completed successfully + this.storageSettings.values.syncedDevices = newEndpoints; + } + + async deleteEndpoints(...ids: string[]) { if (!ids.length) return; @@ -219,17 +366,6 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, }) } - async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise { - const discovery = isSupported({ - type, - interfaces, - } as any); - - if (!discovery) - return; - return []; - } - getAccessToken(): Promise { if (this.accessToken) return this.accessToken; @@ -306,9 +442,8 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, return this.accessToken; } - async alexaAuthorization(request: HttpRequest, response: HttpResponse) { - const json = JSON.parse(request.body); - const { grant } = json.directive.payload; + async onAlexaAuthorization(request: HttpRequest, response: AlexaHttpResponse, directive: any) { + const { grant } = directive.payload; this.storageSettings.values.tokenInfo = grant; this.storageSettings.values.apiEndpoint = undefined; this.accessToken = undefined; @@ -321,27 +456,14 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, this.storageSettings.values.apiEndpoint = undefined; this.accessToken = undefined; - response.send(JSON.stringify({ - "event": { - "header": { - "namespace": "Alexa.Authorization", - "name": "ErrorResponse", - "messageId": createMessageId(), - "payloadVersion": "3" - }, - "payload": { - "type": "ACCEPT_GRANT_FAILED", - "message": `Failed to handle the AcceptGrant directive because ${reason}` - } - } - })); + response.send(authErrorResponse("ACCEPT_GRANT_FAILED", `Failed to handle the AcceptGrant directive because ${reason}`, directive)); return undefined; }); if (accessToken !== undefined) { try { - response.send(JSON.stringify({ + response.send({ "event": { "header": { "namespace": "Alexa.Authorization", @@ -351,7 +473,7 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, }, "payload": {} } - })); + }); } catch (error) { this.console.error(`AcceptGrant.Response failed because ${error}`); @@ -363,14 +485,15 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, } } - createEndpoint(device: ScryptedDevice): DiscoveryEndpoint { + async getEndpointForDevice(device: ScryptedDevice) : Promise { if (!device) return; - const discovery = isSupported(device); + + const discovery = await supportedTypes.get(device.type)?.discover(device); if (!discovery) return; - const ret = Object.assign({ + const data: DiscoveryEndpoint = { endpointId: device.id, manufacturerName: "Scrypted", description: `${device.info?.manufacturer ?? 'Unknown'} ${device.info?.model ?? `device of type ${device.type}`}, connected via Scrypted`, @@ -380,13 +503,20 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, model: device.info?.model || undefined, serialNumber: device.info?.serialNumber || undefined, firmwareVersion: device.info?.firmware || undefined, - //softwareVersion: device.info?.version || undefined - } - }, discovery); + softwareVersion: device.info?.version || undefined + }, + displayCategories: discovery.displayCategories, + capabilities: discovery.capabilities + }; + + let supportedEndpointHealths: any[] = []; + + if (device.interfaces.includes(ScryptedInterface.Online)) { + supportedEndpointHealths.push({ + "name": "connectivity" + }); + } - let supportedEndpointHealths = [{ - "name": "connectivity" - }]; // { // "name": "radioDiagnostics" // }, @@ -400,17 +530,22 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, }) } - ret.capabilities.push( - { - "type": "AlexaInterface", - "interface": "Alexa.EndpointHealth", - "version": "3.2" as any, - "properties": { - "supported": supportedEndpointHealths, - "proactivelyReported": true, - "retrievable": true + if (supportedEndpointHealths.length > 0) { + data.capabilities.push( + { + "type": "AlexaInterface", + "interface": "Alexa.EndpointHealth", + "version": "3.2", + "properties": { + "supported": supportedEndpointHealths, + "proactivelyReported": true, + "retrievable": true + } } - }, + ); + } + + data.capabilities.push( { "type": "AlexaInterface", "interface": "Alexa", @@ -418,76 +553,20 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, } ); - //if (device.info?.mac !== undefined) - // ret.connections.push( - // { - // "type": "TCP_IP", - // "macAddress": device.info?.mac || undefined - // } - // ); - - return ret as any; - } - - async saveEndpoints(endpoints: DiscoveryEndpoint[]) { - const existingEndpoints: string[] = this.storageSettings.values.syncedDevices; - const newEndpoints = endpoints.map(endpoint => endpoint.endpointId); - const deleted = new Set(existingEndpoints); - - for (const id of newEndpoints) { - deleted.delete(id); - } - - const all = new Set([...existingEndpoints, ...newEndpoints]); - - // save all the endpoints - this.storageSettings.values.syncedDevices = [...all]; - - // delete leftover endpoints - await this.deleteReport(...deleted); - - // prune if the delete report completed successfully - this.storageSettings.values.syncedDevices = newEndpoints; - } - - getDiscoveryEndpoints() { - const endpoints: DiscoveryEndpoint[] = []; - - for (const id of Object.keys(systemManager.getSystemState())) { - const device = systemManager.getDeviceById(id); - - if (!device.mixins?.includes(this.id)) - continue; - const endpoint = this.createEndpoint(device); - if (endpoint) - endpoints.push(endpoint); - } - return endpoints; - } - - async alexaDiscovery(request: HttpRequest, response: HttpResponse) { - const endpoints = this.getDiscoveryEndpoints(); - - const ret: DiscoverEvent = { - event: { - header: { - namespace: 'Alexa.Discovery', - name: 'Discover.Response', - messageId: createMessageId(), - payloadVersion: '3', - }, - payload: { - endpoints, + if (device.info?.mac !== undefined) + data.connections = [ + { + "type": "TCP_IP", + "macAddress": device.info.mac } - } - } + ]; - response.send(JSON.stringify(ret)); - - this.saveEndpoints(endpoints); + return data as any; } - async onRequest(request: HttpRequest, response: HttpResponse) { + async onRequest(request: HttpRequest, rawResponse: HttpResponse) { + const response = new HttpResponseLoggingImpl(rawResponse, this.console); + const { authorization } = request.headers; if (!this.validAuths.has(authorization)) { try { @@ -501,42 +580,81 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler, catch (e) { this.console.error(`request failed due to invalid authorization`, e); response.send(e.message, { - code: 500 + code: 500, }); return; } } - try { - const body = JSON.parse(request.body); - const { directive } = body; - const { namespace } = directive.header; - const handler = this.handlers.get(namespace); - if (handler) - return handler.apply(this, arguments); - - const capHandler = capabilityHandlers.get(namespace); - if (capHandler) { - const device = systemManager.getDeviceById(directive.endpoint.endpointId); - if (!device) { - response.send('Not Found', { - code: 404, - }); - return; - } + const body = JSON.parse(request.body); + const { directive } = body; + const { namespace, name } = directive.header; + + this.console.assert(!DEBUG, `request: ${namespace}/${name}`); + + const mapName = `${namespace}/${name}`; + const handler = alexaHandlers.get(mapName); + + if (handler) + return handler.apply(this, [request, response, directive]); - return capHandler.apply(this, [request, response, directive, device]); + const deviceHandler = alexaDeviceHandlers.get(mapName); + + if (deviceHandler) { + const device = systemManager.getDeviceById(directive.endpoint.endpointId); + if (!device) { + response.send(deviceErrorResponse("NO_SUCH_ENDPOINT", "The device doesn't exist in Scrypted", directive)); + return; } - response.send('Not Found', { - code: 404, - }); - } - catch (e) { - response.send(e.message, { - code: 500, - }); + return deviceHandler.apply(this, [request, response, directive, device]); + } else { + this.console.error(`no handler for: ${mapName}`); } + + // it is better to send a non-specific response than an error, as the API might get rate throttled + response.send(mirroredResponse(directive)); + } +} + +enum DeviceMixinStatus { + NotSupported = 0, + Setup = 1, + AlreadySetup = 2 +} + +class HttpResponseLoggingImpl implements AlexaHttpResponse { + constructor(private response: HttpResponse, private console: Console) { + } + + send(body: string): void; + send(body: string, options: HttpResponseOptions): void; + send(body: Buffer): void; + send(body: Buffer, options: HttpResponseOptions): void; + send(body: any, options?: any): void { + if (!options) + options = {}; + + if (!options.code) + options.code = 200; + + if (options.code !== 200) + this.console.error(`response error ${options.code}:`, body); + else + this.console.assert(!DEBUG, `response ${options.code}:`, body); + + if (typeof body === 'object') + body = JSON.stringify(body); + + this.response.send(body, options); + } + sendFile(path: string): void; + sendFile(path: string, options: HttpResponseOptions): void; + sendFile(path: any, options?: any): void { + this.response.sendFile(path, options); + } + sendSocket(socket: any, options: HttpResponseOptions): void { + this.response.sendSocket(socket, options); } } diff --git a/plugins/alexa/src/message.ts b/plugins/alexa/src/message.ts deleted file mode 100644 index b8ee037d29..0000000000 --- a/plugins/alexa/src/message.ts +++ /dev/null @@ -1,5 +0,0 @@ -import {v4 as uuidv4} from 'uuid'; - -export function createMessageId() { - return uuidv4(); -} \ No newline at end of file diff --git a/plugins/alexa/src/types/camera.ts b/plugins/alexa/src/types/camera.ts index efad98d103..3cf8b5f65c 100644 --- a/plugins/alexa/src/types/camera.ts +++ b/plugins/alexa/src/types/camera.ts @@ -1,190 +1,24 @@ -import { HttpResponse, MotionSensor, RTCAVSignalingSetup, RTCSignalingChannel, RTCSignalingOptions, RTCSignalingSendIceCandidate, RTCSignalingSession, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, VideoCamera } from "@scrypted/sdk"; -import { addSupportedType, AlexaCapabilityHandler, capabilityHandlers, EventReport, StateReport } from "./common"; -import { createMessageId } from "../message"; -import { Capability } from "alexa-smarthome-ts/lib/skill/Capability"; -import { DisplayCategory } from "alexa-smarthome-ts"; +import { MotionSensor, ObjectDetector, ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk"; +import { DiscoveryEndpoint, Report } from "../alexa"; +import { getCameraCapabilities, reportCameraState, sendCameraEvent } from "./camera/capabilities"; +import { supportedTypes } from "."; -export function getCameraCapabilities(device: ScryptedDevice): Capability[] { - const capabilities: Capability[] = [ - { - "type": "AlexaInterface", - "interface": "Alexa.RTCSessionController", - "version": "3", - "configuration": { - isFullDuplexAudioSupported: true, - } - } as any, - ]; - - if (device.interfaces.includes(ScryptedInterface.MotionSensor)) { - capabilities.push( - { - "type": "AlexaInterface", - "interface": "Alexa.MotionSensor", - "version": "3", - "properties": { - "supported": [ - { - "name": "detectionState" - } - ], - "proactivelyReported": true, - "retrievable": true - } - }, - ) - } - - return capabilities; -} - -addSupportedType(ScryptedDeviceType.Camera, { - probe(device) { +supportedTypes.set(ScryptedDeviceType.Camera, { + async discover(device: ScryptedDevice): Promise> { if (!device.interfaces.includes(ScryptedInterface.RTCSignalingChannel)) return; - const capabilities = getCameraCapabilities(device); + const capabilities = await getCameraCapabilities(device); return { displayCategories: ['CAMERA'], capabilities } }, - async reportState(device: ScryptedDevice & MotionSensor): Promise { - return { - type: 'state', - namespace: 'Alexa', - name: 'StateReport', - context: { - "properties": [ - { - "namespace": "Alexa.MotionSensor", - "name": "detectionState", - "value": device.motionDetected ? "DETECTED" : "NOT_DETECTED", - "timeOfSample": new Date().toISOString(), - "uncertaintyInMilliseconds": 0 - } - ] - } - }; + sendReport(device: ScryptedDevice & MotionSensor & ObjectDetector): Promise>{ + return reportCameraState(device); }, - async sendEvent(eventSource: ScryptedDevice & MotionSensor, eventDetails, eventData): Promise { - if (eventDetails.eventInterface !== ScryptedInterface.MotionSensor) - return undefined; - - return { - type: 'event', - namespace: 'Alexa', - name: 'ChangeReport', - payload: { - change: { - cause: { - type: "PHYSICAL_INTERACTION" - }, - properties: [ - { - "namespace": "Alexa.MotionSensor", - "name": "detectionState", - "value": eventData ? "DETECTED" : "NOT_DETECTED", - "timeOfSample": new Date().toISOString(), - "uncertaintyInMilliseconds": 0 - } - ] - } - }, - }; - } -}); - -export const rtcHandlers = new Map>(); - -export class AlexaSignalingSession implements RTCSignalingSession { - constructor(public response: HttpResponse, public directive: any) { - } - - async getOptions(): Promise { - return { - proxy: true, - offer: { - type: 'offer', - sdp: this.directive.payload.offer.value, - }, - disableTrickle: true, - // this could be a low resolution screen, no way of knowing, so never send a - // 1080p+ stream. - screen: { - devicePixelRatio: 1, // TODO: get this from the device - width: 1280, - height: 720, - } - } + sendEvent(eventSource: ScryptedDevice & MotionSensor & ObjectDetector, eventDetails, eventData): Promise> { + return sendCameraEvent(eventSource, eventDetails, eventData); } - - async createLocalDescription(type: "offer" | "answer", setup: RTCAVSignalingSetup, sendIceCandidate: RTCSignalingSendIceCandidate): Promise { - if (type !== 'offer') - throw new Error('Alexa only supports RTC offer'); - if (sendIceCandidate) - throw new Error("Alexa does not support trickle ICE"); - return { - type: 'offer', - sdp: this.directive.payload.offer.value, - } - } - - async addIceCandidate(candidate: RTCIceCandidateInit): Promise { - throw new Error("Alexa does not support trickle ICE"); - } - - async setRemoteDescription(description: RTCSessionDescriptionInit, setup: RTCAVSignalingSetup): Promise { - this.response.send(JSON.stringify({ - "event": { - "header": { - "namespace": "Alexa.RTCSessionController", - "name": "AnswerGeneratedForSession", - "messageId": createMessageId(), - "payloadVersion": "3" - }, - "payload": { - "answer": { - "format": "SDP", - "value": description.sdp, - } - } - } - })); - } -} - -rtcHandlers.set('InitiateSessionWithOffer', async (request, response, directive: any, - device: ScryptedDevice & RTCSignalingChannel) => { - const session = new AlexaSignalingSession(response, directive); - const control = await device.startRTCSignalingSession(session); - control.setPlayback({ - audio: true, - video: false, - }) -}); - -capabilityHandlers.set('Alexa.RTCSessionController', async (request, response, directive: any, device: ScryptedDevice & VideoCamera) => { - const { name } = directive.header; - const handler = rtcHandlers.get(name); - if (handler) - return handler.apply(this, [request, response, directive, device]); - - const { sessionId } = directive.payload; - const body = { - "event": { - "header": { - "namespace": "Alexa.RTCSessionController", - name, - "messageId": createMessageId(), - "payloadVersion": "3" - }, - "payload": { - sessionId, - } - } - }; - - response.send(JSON.stringify(body)); }); diff --git a/plugins/alexa/src/types/camera/capabilities.ts b/plugins/alexa/src/types/camera/capabilities.ts new file mode 100644 index 0000000000..e014b008cd --- /dev/null +++ b/plugins/alexa/src/types/camera/capabilities.ts @@ -0,0 +1,194 @@ +import sdk, { MediaObject, MotionSensor, ObjectDetector, ScryptedDevice, ScryptedInterface } from "@scrypted/sdk"; +import { ChangeReport, DiscoveryCapability, ObjectDetectionEvent, Report, StateReport, Property } from "../../alexa"; + +const { mediaManager } = sdk; + +export async function reportCameraState(device: ScryptedDevice & MotionSensor & ObjectDetector): Promise>{ + let data = { + context: { + properties: [] + } + + } as Partial; + + if (device.interfaces.includes(ScryptedInterface.ObjectDetector)) { + const detectionTypes = await (device as any as ObjectDetector).getObjectTypes(); + const classNames = detectionTypes.classes.filter(t => t !== 'ring' && t !== 'motion').map(type => type.toLowerCase()); + + data.context.properties.push({ + "namespace": "Alexa.SmartVision.ObjectDetectionSensor", + "name": "objectDetectionClasses", + "value": classNames.map(type => ({ + "imageNetClass": type + })), + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + }); + } + + if (device.interfaces.includes(ScryptedInterface.MotionSensor)) { + data.context.properties.push({ + "namespace": "Alexa.MotionSensor", + "name": "detectionState", + "value": device.motionDetected ? "DETECTED" : "NOT_DETECTED", + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + }); + } + + return data; +}; + +export async function sendCameraEvent (eventSource: ScryptedDevice & MotionSensor & ObjectDetector, eventDetails, eventData): Promise> { + if (eventDetails.eventInterface === ScryptedInterface.ObjectDetector) { + + // ring and motion are not valid objects + if (eventData.detections.has('ring') || eventData.detections.has('motion')) + return undefined; + + console.debug('ObjectDetector event', eventData); + + let mediaObj: MediaObject = undefined; + let frameImageUri: string = undefined; + + try { + mediaObj = await eventSource.getDetectionInput(eventData.detectionId, eventData.eventId); + frameImageUri = await mediaManager.convertMediaObjectToUrl(mediaObj, 'image/jpeg'); + } catch (e) { } + + let data = { + event: { + header: { + namespace: 'Alexa.SmartVision.ObjectDetectionSensor', + name: 'ObjectDetection' + }, + payload: { + "events": [eventData.detections.map(detection => { + let event = { + "eventIdentifier": eventData.eventId, + "imageNetClass": detection.className, + "timeOfSample": new Date(eventData.timestamp).toISOString(), + "uncertaintyInMilliseconds": 500 + }; + + if (detection.id) { + event["objectIdentifier"] = detection.id; + } + + if (frameImageUri) { + event["frameImageUri"] = frameImageUri; + } + + return event; + })] + } + } + } as Partial; + + return data; + } + + if (eventDetails.eventInterface === ScryptedInterface.MotionSensor) + return { + event: { + payload: { + change: { + cause: { + type: "PHYSICAL_INTERACTION" + }, + properties: [ + { + "namespace": "Alexa.MotionSensor", + "name": "detectionState", + "value": eventData ? "DETECTED" : "NOT_DETECTED", + "timeOfSample": new Date(eventDetails.eventTime).toISOString(), + "uncertaintyInMilliseconds": 500 + } + ] + } + }, + } + } as Partial; + + return undefined; +}; + +export async function getCameraCapabilities(device: ScryptedDevice): Promise { + const capabilities = [ + { + "type": "AlexaInterface", + "interface": "Alexa.RTCSessionController", + "version": "3", + "configuration": { + isFullDuplexAudioSupported: true, + } + } as DiscoveryCapability + ]; + + if (device.interfaces.includes(ScryptedInterface.ObjectDetector)) { + const detectionTypes = await (device as any as ObjectDetector).getObjectTypes(); + const classNames = detectionTypes.classes.filter(t => t !== 'ring' && t !== 'motion').map(type => type.toLowerCase()); + + capabilities.push( + { + "type": "AlexaInterface", + "interface": "Alexa.SmartVision.ObjectDetectionSensor", + "version": "1.0", + "properties": { + "supported": [{ + "name": "objectDetectionClasses" + }], + "proactivelyReported": true, + "retrievable": true + }, + "configuration": { + "objectDetectionConfiguration": classNames.map(type => ({ + "imageNetClass": type + })) + } + } as DiscoveryCapability + ); + + capabilities.push( + { + "type": "AlexaInterface", + "interface": "Alexa.DataController", + "instance": "Camera.SmartVisionData", + "version": "1.0", + "properties": undefined, + "configuration": { + "targetCapability": { + "name": "Alexa.SmartVision.ObjectDetectionSensor", + "version": "1.0" + }, + "dataRetrievalSchema": { + "type": "JSON", + "schema": "SmartVisionData" + }, + "supportedAccess": ["BY_IDENTIFIER", "BY_TIMESTAMP_RANGE"] + } + } as DiscoveryCapability + ); + } + + if (device.interfaces.includes(ScryptedInterface.MotionSensor)) { + capabilities.push( + { + "type": "AlexaInterface", + "interface": "Alexa.MotionSensor", + "version": "3", + "properties": { + "supported": [ + { + "name": "detectionState" + } + ], + "proactivelyReported": true, + "retrievable": true + } + } as DiscoveryCapability + ); + } + + return capabilities; +}; \ No newline at end of file diff --git a/plugins/alexa/src/types/camera/handlers.ts b/plugins/alexa/src/types/camera/handlers.ts new file mode 100644 index 0000000000..37cab971ac --- /dev/null +++ b/plugins/alexa/src/types/camera/handlers.ts @@ -0,0 +1,152 @@ +import { ObjectDetector, RTCAVSignalingSetup, RTCSessionControl, RTCSignalingChannel, RTCSignalingOptions, RTCSignalingSendIceCandidate, RTCSignalingSession, ScryptedDevice } from "@scrypted/sdk"; +import { supportedTypes } from ".."; +import { v4 as createMessageId } from 'uuid'; +import { AlexaHttpResponse, sendDeviceResponse } from "../../common"; +import { alexaDeviceHandlers } from "../../handlers"; +import { Response, WebRTCAnswerGeneratedForSessionEvent, WebRTCSessionConnectedEvent, WebRTCSessionDisconnectedEvent } from '../../alexa' + +export class AlexaSignalingSession implements RTCSignalingSession { + constructor(public response: AlexaHttpResponse, public directive: any) { + } + + async getOptions(): Promise { + return { + proxy: true, + offer: { + type: 'offer', + sdp: this.directive.payload.offer.value, + }, + disableTrickle: true, + } + } + + async createLocalDescription(type: "offer" | "answer", setup: RTCAVSignalingSetup, sendIceCandidate: RTCSignalingSendIceCandidate): Promise { + if (type !== 'offer') + throw new Error('Alexa only supports RTC offer'); + + if (sendIceCandidate) + throw new Error("Alexa does not support trickle ICE"); + + return { + type: type, + sdp: this.directive.payload.offer.value, + } + } + + async addIceCandidate(candidate: RTCIceCandidateInit): Promise { + throw new Error("Alexa does not support trickle ICE"); + } + + async setRemoteDescription(description: RTCSessionDescriptionInit, setup: RTCAVSignalingSetup): Promise { + + const { header, endpoint, payload } = this.directive; + + const data: WebRTCAnswerGeneratedForSessionEvent = { + "event": { + header, + endpoint, + payload + }, + context: undefined + }; + + data.event.header.name = "AnswerGeneratedForSession"; + data.event.header.messageId = createMessageId(); + + data.event.payload.answer = { + format: 'SDP', + value: description.sdp, + }; + + this.response.send(data); + } +} + +const sessionCache = new Map(); + +alexaDeviceHandlers.set('Alexa.RTCSessionController/InitiateSessionWithOffer', async (request, response, directive: any, device: ScryptedDevice & RTCSignalingChannel) => { + const { header, endpoint, payload } = directive; + const { sessionId } = payload; + + const session = new AlexaSignalingSession(response, directive); + const control = await device.startRTCSignalingSession(session); + control.setPlayback({ + audio: true, + video: false, + }) + + sessionCache.set(sessionId, control); +}); + +alexaDeviceHandlers.set('Alexa.RTCSessionController/SessionConnected', async (request, response, directive: any, device: ScryptedDevice) => { + const { header, endpoint, payload } = directive; + const data: WebRTCSessionConnectedEvent = { + "event": { + header, + endpoint, + payload + }, + context: undefined + }; + + data.event.header.messageId = createMessageId(); + + response.send(data); +}); + +alexaDeviceHandlers.set('Alexa.RTCSessionController/SessionDisconnected', async (request, response, directive: any, device: ScryptedDevice) => { + const { header, endpoint, payload } = directive; + const { sessionId } = payload; + + const session = sessionCache.get(sessionId); + if (session) { + sessionCache.delete(sessionId); + await session.endSession(); + } + + const data: WebRTCSessionDisconnectedEvent = { + "event": { + header, + endpoint, + payload + }, + context: undefined + }; + + data.event.header.messageId = createMessageId(); + + response.send(data); +}); + +alexaDeviceHandlers.set('Alexa.SmartVision.ObjectDetectionSensor/SetObjectDetectionClasses', async (request, response, directive: any, device: ScryptedDevice & ObjectDetector) => { + const supportedType = supportedTypes.get(device.type); + if (!supportedType) + return; + + const { header, endpoint, payload } = directive; + const detectionTypes = await device.getObjectTypes(); + + const data: Response = { + "event": { + header, + endpoint, + payload: {} + }, + "context": { + "properties": [{ + "namespace": "Alexa.SmartVision.ObjectDetectionSensor", + "name": "objectDetectionClasses", + "value": detectionTypes.classes.map(type => ({ + "imageNetClass": type + })), + timeOfSample: new Date().toISOString(), + uncertaintyInMilliseconds: 0 + }] + } + }; + + data.event.header.name = "Response"; + data.event.header.messageId = createMessageId(); + + sendDeviceResponse(data, response, device); +}); \ No newline at end of file diff --git a/plugins/alexa/src/types/common.ts b/plugins/alexa/src/types/common.ts deleted file mode 100644 index 6dc306afa9..0000000000 --- a/plugins/alexa/src/types/common.ts +++ /dev/null @@ -1,180 +0,0 @@ -import { Battery, EventDetails, HttpRequest, HttpResponse, Online, PowerSensor, ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk"; -import {DiscoveryEndpoint, Directive} from 'alexa-smarthome-ts'; -import { createMessageId } from "../message"; - -export type AlexaHandler = (request: HttpRequest, response: HttpResponse, directive: Directive) => Promise -export type AlexaCapabilityHandler = (request: HttpRequest, response: HttpResponse, directive: Directive, device: ScryptedDevice & T) => Promise - -export const supportedTypes = new Map(); -export const capabilityHandlers = new Map>(); -export const alexaHandlers = new Map>(); - -export interface EventReport { - type: 'event'; - payload?: any; - context?: any; - namespace?: string; - name?: string; -} - -export interface StateReport { - type: 'state'; - payload?: any; - context?: any; - namespace?: string; - name?: string; -} - -export interface SupportedType { - probe(device: ScryptedDevice): Partial>; - sendEvent(eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any): Promise; - reportState(device: ScryptedDevice): Promise; -} - -export function addSupportedType(type: ScryptedDeviceType, supportedType: SupportedType) { - supportedTypes.set(type, supportedType); -} - -export function isSupported(device: ScryptedDevice) { - return supportedTypes.get(device.type)?.probe(device); -} - -export function addOnline(data: any, device: ScryptedDevice & Online) : any { - if (!device.interfaces.includes(ScryptedInterface.Online)) - return data; - - if (data.context === undefined) - data.context = {}; - - if (data.context.properties === undefined) - data.context.properties = []; - - data.context.properties.push( - { - "namespace": "Alexa.EndpointHealth", - "name": "connectivity", - "value": { - "value": device.online ? "OK" : "UNREACHABLE", - }, - "timeOfSample": new Date().toISOString(), - "uncertaintyInMilliseconds": 0 - } - ); - - return data; -} - -export function addPowerSensor(data: any, device: ScryptedDevice & PowerSensor) : any { - if (!device.interfaces.includes(ScryptedInterface.PowerSensor)) - return data; - - if (data.context === undefined) - data.context = {}; - - if (data.context.properties === undefined) - data.context.properties = []; - - data.context.properties.push( - { - "namespace": "Alexa.PowerController", - "name": "powerState", - "value": device.powerDetected ? "ON" : "OFF", - "timeOfSample": new Date().toISOString(), - "uncertaintyInMilliseconds": 0 - } - ); - - return data; -} - -export function addBattery(data: any, device: ScryptedDevice & Battery) : any { - if (!device.interfaces.includes(ScryptedInterface.Battery)) - return data; - - if (data.context === undefined) - data.context = {}; - - if (data.context.properties === undefined) - data.context.properties = []; - - const lowPower = device.batteryLevel < 20; - let health = undefined; - - if (lowPower) { - health = { - "state": "WARNING", - "reasons": [ - "LOW_CHARGE" - ] - }; - } - - data.context.properties.push( - { - "namespace": "Alexa.EndpointHealth", - "name": "battery", - "value": { - health, - "levelPercentage": device.batteryLevel, - }, - "timeOfSample": new Date().toISOString(), - "uncertaintyInMilliseconds": 0 - } - ); - - return data; -} - -function sendResponse(data: any, response: any, device: ScryptedDevice) { - data = addBattery(data, device); - data = addOnline(data, device); - data = addPowerSensor(data, device); - - response.send(JSON.stringify(data)); -} - -alexaHandlers.set('ReportState', async (request, response, directive: any, device: ScryptedDevice) => { - const supportedType = supportedTypes.get(device.type); - if (!supportedType) - return; - - const { header, endpoint } = directive; - - const report = await supportedType.reportState(device); - if (report.type === 'state') { - const data = { - "event": { - header, - endpoint, - payload: report.payload, - }, - "context": report.context - }; - - data.event.header.name = "StateReport"; - data.event.header.messageId = createMessageId(); - - sendResponse(data, response, device); - } -}); - -capabilityHandlers.set('Alexa', async (request, response, directive: any, device: ScryptedDevice) => { - const { name } = directive.header; - let handler = alexaHandlers.get(name); - if (handler) - return handler.apply(this, [request, response, directive, device]); - - const { header, endpoint, payload } = directive; - const data = { - "event": { - header, - endpoint, - payload - } - }; - - data.event.header.name = "Response"; - data.event.header.messageId = createMessageId(); - - sendResponse(data, response, device); -}); \ No newline at end of file diff --git a/plugins/alexa/src/types/doorbell.ts b/plugins/alexa/src/types/doorbell.ts index d1b7aa0508..891cc25989 100644 --- a/plugins/alexa/src/types/doorbell.ts +++ b/plugins/alexa/src/types/doorbell.ts @@ -1,81 +1,57 @@ -import { BinarySensor, MotionSensor, ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk"; -import { getCameraCapabilities } from "./camera"; -import { addSupportedType, EventReport, StateReport } from "./common"; -import { DisplayCategory } from "alexa-smarthome-ts"; +import { MotionSensor, ObjectDetector, ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk"; +import { getCameraCapabilities, reportCameraState, sendCameraEvent } from "./camera/capabilities"; +import { DiscoveryEndpoint, DisplayCategory, Report, DoorbellPressEvent } from "../alexa"; +import { supportedTypes } from "."; -addSupportedType(ScryptedDeviceType.Doorbell, { - probe(device) { - if (!device.interfaces.includes(ScryptedInterface.RTCSignalingChannel) || !device.interfaces.includes(ScryptedInterface.BinarySensor)) - return; +supportedTypes.set(ScryptedDeviceType.Doorbell, { + async discover(device: ScryptedDevice): Promise> { + let capabilities: any[] = []; + let category: DisplayCategory = 'DOORBELL'; - const capabilities = getCameraCapabilities(device); - capabilities.push( - { - "type": "AlexaInterface", - "interface": "Alexa.DoorbellEventSource", - "version": "3", - "proactivelyReported": true - } as any, - ); + if (device.interfaces.includes(ScryptedInterface.RTCSignalingChannel)) { + capabilities = await getCameraCapabilities(device); + category = 'CAMERA'; + } - return { - displayCategories: ['CAMERA'], - capabilities + if (device.interfaces.includes(ScryptedInterface.BinarySensor)) { + capabilities.push( + { + "type": "AlexaInterface", + "interface": "Alexa.DoorbellEventSource", + "version": "3", + "proactivelyReported": true + } as any, + ); } - }, - async reportState(device: ScryptedDevice & MotionSensor): Promise{ + return { - type: 'state', - namespace: 'Alexa', - name: 'StateReport', - context: { - "properties": [ - { - "namespace": "Alexa.MotionSensor", - "name": "detectionState", - "value": device.motionDetected ? "DETECTED" : "NOT_DETECTED", - "timeOfSample": new Date().toISOString(), - "uncertaintyInMilliseconds": 0 - } - ] - } + displayCategories: [category], + capabilities }; }, - async sendEvent(eventSource: ScryptedDevice, eventDetails, eventData): Promise { - if (eventDetails.eventInterface === ScryptedInterface.MotionSensor) - return { - type: 'event', - namespace: 'Alexa', - name: 'ChangeReport', - payload: { - change: { - cause: { - type: "PHYSICAL_INTERACTION" - }, - properties: [ - { - "namespace": "Alexa.MotionSensor", - "name": "detectionState", - "value": eventData ? "DETECTED" : "NOT_DETECTED", - "timeOfSample": new Date().toISOString(), - "uncertaintyInMilliseconds": 0 - } - ] - } - }, - }; + sendReport(device: ScryptedDevice & MotionSensor & ObjectDetector): Promise>{ + return reportCameraState(device); + }, + async sendEvent(eventSource: ScryptedDevice & MotionSensor & ObjectDetector, eventDetails, eventData): Promise> { + let response = await sendCameraEvent(eventSource, eventDetails, eventData); + + if (response) + return response; if (eventDetails.eventInterface === ScryptedInterface.BinarySensor && eventData === true) return { - type: 'event', - namespace: 'Alexa.DoorbellEventSource', - name: 'DoorbellPress', - payload: { - "cause": { - "type": "PHYSICAL_INTERACTION" + event: { + header: { + namespace: 'Alexa.DoorbellEventSource', + name: 'DoorbellPress' }, - "timestamp": new Date().toISOString(), - } - }; + payload: { + "cause": { + "type": "PHYSICAL_INTERACTION" + }, + "timestamp": new Date(eventDetails.eventTime).toISOString(), + } + } + } as Partial; } }); diff --git a/plugins/alexa/src/types/garagedoor.ts b/plugins/alexa/src/types/garagedoor.ts index 12b2840571..8cf8bc0624 100644 --- a/plugins/alexa/src/types/garagedoor.ts +++ b/plugins/alexa/src/types/garagedoor.ts @@ -1,14 +1,13 @@ -import { BinarySensor, Entry, EntrySensor, ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk"; -import { getCameraCapabilities } from "./camera"; -import { addSupportedType, EventReport, StateReport } from "./common"; -import { DisplayCategory } from "alexa-smarthome-ts"; +import { Entry, EntrySensor, ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk"; +import { DiscoveryEndpoint, DiscoveryCapability, ChangeReport, Report } from "../alexa"; +import { supportedTypes } from "."; -addSupportedType(ScryptedDeviceType.Garage, { - probe(device) { - if (!device.interfaces.includes(ScryptedInterface.EntrySensor)) +supportedTypes.set(ScryptedDeviceType.Garage, { + async discover(device: ScryptedDevice): Promise> { + if (!device.interfaces.includes(ScryptedInterface.EntrySensor)) return; - const capabilities = getCameraCapabilities(device); + const capabilities: DiscoveryCapability[] = []; capabilities.push( { "type": "AlexaInterface", @@ -115,19 +114,16 @@ addSupportedType(ScryptedDeviceType.Garage, { } ] } - } as any, + }, ); return { - displayCategories: ['GARAGE_DOOR'] as any, + displayCategories: ['GARAGE_DOOR'], capabilities } }, - async reportState(eventSource: ScryptedDevice & EntrySensor): Promise { + async sendReport(eventSource: ScryptedDevice & EntrySensor): Promise> { return { - type: 'state', - namespace: 'Alexa', - name: 'StateReport', context: { "properties": [ { @@ -142,14 +138,12 @@ addSupportedType(ScryptedDeviceType.Garage, { } }; }, - async sendEvent(eventSource: ScryptedDevice & EntrySensor, eventDetails, eventData): Promise { + async sendEvent(eventSource: ScryptedDevice & Entry & EntrySensor, eventDetails, eventData): Promise> { if (eventDetails.eventInterface !== ScryptedInterface.EntrySensor) return undefined; return { - type: 'event', - namespace: 'Alexa', - name: 'ChangeReport', + event: { payload: { change: { cause: { @@ -167,6 +161,30 @@ addSupportedType(ScryptedDeviceType.Garage, { ] } }, - }; - } + } + } as Partial; + }, + async setState(eventSource: ScryptedDevice & Entry & EntrySensor, payload: any): Promise> { + if (payload.mode === 'Position.Up') { + await eventSource.openEntry(); + } + else if (payload.mode === 'Position.Down') { + await eventSource.closeEntry(); + } + + return { + context: { + "properties": [ + { + "namespace": "Alexa.ModeController", + "instance": "GarageDoor.Position", + "name": "mode", + "value": payload.mode, + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + } + ] + } + }; + } }); diff --git a/plugins/alexa/src/types/garagedoor/handlers.ts b/plugins/alexa/src/types/garagedoor/handlers.ts new file mode 100644 index 0000000000..1606f9274f --- /dev/null +++ b/plugins/alexa/src/types/garagedoor/handlers.ts @@ -0,0 +1,32 @@ +import { ScryptedDevice } from "@scrypted/sdk"; +import { supportedTypes } from ".."; +import { sendDeviceResponse } from "../../common"; +import { alexaDeviceHandlers } from "../../handlers"; +import { v4 as createMessageId } from 'uuid'; +import { Response } from "../../alexa"; +import { send } from "process"; + +async function sendResponse (request, response, directive: any, device: ScryptedDevice) { + const supportedType = supportedTypes.get(device.type); + if (!supportedType) + return; + + const { header, endpoint, payload } = directive; + const report = await supportedType.setState(device, payload); + const data = { + "event": { + header, + endpoint, + payload + }, + context: report?.context + } as Response; + + data.event.header.name = "Response"; + data.event.header.messageId = createMessageId(); + + sendDeviceResponse(data, response, device); +} + +alexaDeviceHandlers.set('Alexa.ModeController/SetMode', sendResponse); +alexaDeviceHandlers.set('Alexa.ModeController/AdjustMode', sendResponse); \ No newline at end of file diff --git a/plugins/alexa/src/types/index.ts b/plugins/alexa/src/types/index.ts index 31227b86f5..6ff7d6845f 100644 --- a/plugins/alexa/src/types/index.ts +++ b/plugins/alexa/src/types/index.ts @@ -1,6 +1,21 @@ +import { ScryptedDeviceType, ScryptedDevice, EventDetails } from '@scrypted/sdk'; +import { DiscoveryEndpoint, Report } from '../alexa'; + +export interface SupportedType { + discover(device: ScryptedDevice): Promise>; + sendEvent(device: ScryptedDevice, eventDetails: EventDetails, eventData: any): Promise>; + sendReport(device: ScryptedDevice): Promise>; + setState?(device: ScryptedDevice, payload: any): Promise>; +} + +export const supportedTypes = new Map(); + +import '../handlers'; import './camera'; +import './camera/handlers'; import './doorbell'; import './garagedoor'; - -export { isSupported} from './common'; - +import './switch'; +import './switch/handlers'; +import './sensor'; +//import './securitysystem'; \ No newline at end of file diff --git a/plugins/alexa/src/types/securitysystem.ts b/plugins/alexa/src/types/securitysystem.ts new file mode 100644 index 0000000000..6dc463a2d8 --- /dev/null +++ b/plugins/alexa/src/types/securitysystem.ts @@ -0,0 +1,179 @@ +import { EventDetails, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, SecuritySystem, SecuritySystemMode } from "@scrypted/sdk"; +import { DiscoveryEndpoint, DiscoveryCapability, ChangeReport, Report, StateReport, DisplayCategory, ChangePayload, Property } from "../alexa"; +import { supportedTypes } from "."; + +function getArmState(mode: SecuritySystemMode): string { + switch(mode) { + case SecuritySystemMode.AwayArmed: + return 'ARMED_AWAY'; + case SecuritySystemMode.HomeArmed: + return 'ARMED_STAY'; + case SecuritySystemMode.NightArmed: + return 'ARMED_NIGHT'; + case SecuritySystemMode.Disarmed: + return 'DISARMED'; + } +} + +supportedTypes.set(ScryptedDeviceType.SecuritySystem, { + async discover(device: ScryptedDevice & SecuritySystem): Promise> { + const capabilities: DiscoveryCapability[] = []; + const displayCategories: DisplayCategory[] = []; + + if (device.interfaces.includes(ScryptedInterface.SecuritySystem)) { + const supportedModes = device.securitySystemState.supportedModes; + + capabilities.push( + { + "type": "AlexaInterface", + "interface": "Alexa.SecurityPanelController", + "version": "3", + "properties": { + "supported": [ + { + "name": "armState" + }, + { + "name": "burglaryAlarm" + }, + //{ + // "name": "waterAlarm" + //}, + //{ + // "name": "fireAlarm" + //}, + //{ + // "name": "carbonMonoxideAlarm" + //} + ], + "proactivelyReported": true, + "retrievable": true + }, + "configuration": { + "supportedArmStates": [supportedModes.map(mode => { + return { + "value": getArmState(mode) + } + })], + "supportedAuthorizationTypes": [ + { + "type": "FOUR_DIGIT_PIN" + } + ] + } + } as DiscoveryCapability + ); + + displayCategories.push('SECURITY_PANEL'); + } + + if (capabilities.length === 0) + return; + + return { + displayCategories, + capabilities + } + }, + async sendReport(eventSource: ScryptedDevice & SecuritySystem): Promise> { + let data = { + context: { + properties: [] + } + + } as Partial; + + if (eventSource.interfaces.includes(ScryptedInterface.SecuritySystem)) { + data.context.properties.push({ + "namespace": "Alexa.SecurityPanelController", + "name": "armState", + "value": getArmState(eventSource.securitySystemState.mode), + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + } as Property); + + data.context.properties.push({ + "namespace": "Alexa.SecurityPanelController", + "name": "burglaryAlarm", + "value": { + "value": eventSource.securitySystemState.triggered ? "ALARM" : "OK", + }, + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + } as Property); + } + + return data; + }, + async sendEvent(eventSource: ScryptedDevice & SecuritySystem, eventDetails: EventDetails, eventData): Promise> { + if (eventDetails.eventInterface === ScryptedInterface.SecuritySystem && eventDetails.property === "mode") { + return { + event: { + payload: { + change: { + cause: { + type: "PHYSICAL_INTERACTION" + }, + properties: [ + { + "namespace": "Alexa.SecurityPanelController", + "name": "armState", + "value": getArmState(eventData), + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + } as Property + ] + } + } as ChangePayload, + }, + context: { + properties: [{ + "namespace": "Alexa.SecurityPanelController", + "name": "burglaryAlarm", + "value": { + "value": eventSource.securitySystemState.triggered ? "ALARM" : "OK", + }, + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + } as Property] + } + } as Partial; + } + + if (eventDetails.eventInterface === ScryptedInterface.SecuritySystem && eventDetails.property === "triggered") { + return { + event: { + payload: { + change: { + cause: { + type: "RULE_TRIGGER" + }, + properties: [ + { + "namespace": "Alexa.SecurityPanelController", + "name": "burglaryAlarm", + "value": { + "value": eventData ? "ALARM" : "OK" + }, + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + } as Property + ] + } + } as ChangePayload, + }, + context: { + properties: [{ + "namespace": "Alexa.SecurityPanelController", + "name": "armState", + "value": getArmState(eventSource.securitySystemState.mode), + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + } as Property] + } + } as Partial; + } + + return undefined; + } +}); diff --git a/plugins/alexa/src/types/sensor.ts b/plugins/alexa/src/types/sensor.ts new file mode 100644 index 0000000000..e929826825 --- /dev/null +++ b/plugins/alexa/src/types/sensor.ts @@ -0,0 +1,196 @@ +import { EntrySensor, MotionSensor, ScryptedDevice, ScryptedDeviceType, ScryptedInterface, Thermometer } from "@scrypted/sdk"; +import { DiscoveryEndpoint, DiscoveryCapability, ChangeReport, Report, StateReport, DisplayCategory, ChangePayload, Property } from "../alexa"; +import { supportedTypes } from "."; + +supportedTypes.set(ScryptedDeviceType.Sensor, { + async discover(device: ScryptedDevice): Promise> { + const capabilities: DiscoveryCapability[] = []; + const displayCategories: DisplayCategory[] = []; + + if (device.interfaces.includes(ScryptedInterface.Thermometer)) { + capabilities.push( + { + "type": "AlexaInterface", + "interface": "Alexa.TemperatureSensor", + "version": "3", + "properties": { + "supported": [ + { + "name": "temperature" + } + ], + "proactivelyReported": true, + "retrievable": true + } + } as DiscoveryCapability + ); + + displayCategories.push('TEMPERATURE_SENSOR'); + } + + if (device.interfaces.includes(ScryptedInterface.EntrySensor)) { + capabilities.push( + { + "type": "AlexaInterface", + "interface": "Alexa.ContactSensor", + "version": "3", + "properties": { + "supported": [ + { + "name": "detectionState" + } + ], + "proactivelyReported": true, + "retrievable": true + } + } as DiscoveryCapability + ); + + displayCategories.push('CONTACT_SENSOR'); + } + + if (device.interfaces.includes(ScryptedInterface.MotionSensor)) { + capabilities.push( + { + "type": "AlexaInterface", + "interface": "Alexa.MotionSensor", + "version": "3", + "properties": { + "supported": [ + { + "name": "detectionState" + } + ], + "proactivelyReported": true, + "retrievable": true + } + } as DiscoveryCapability + ); + + displayCategories.push('MOTION_SENSOR'); + } + + if (capabilities.length === 0) + return; + + return { + displayCategories: displayCategories, + capabilities + } + }, + async sendReport(eventSource: ScryptedDevice & MotionSensor & EntrySensor & Thermometer): Promise> { + let data = { + context: { + properties: [] + } + + } as Partial; + + if (eventSource.interfaces.includes(ScryptedInterface.Thermometer)) { + data.context.properties.push({ + "namespace": "Alexa.TemperatureSensor", + "name": "temperature", + "value": { + "value": eventSource.temperature, + "scale": "CELSIUS" + }, + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + }); + } + + if (eventSource.interfaces.includes(ScryptedInterface.EntrySensor)) { + data.context.properties.push({ + "namespace": "Alexa.ContactSensor", + "name": "detectionState", + "value": eventSource.entryOpen ? "DETECTED" : "NOT_DETECTED", + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + }); + } + + if (eventSource.interfaces.includes(ScryptedInterface.MotionSensor)) { + data.context.properties.push({ + "namespace": "Alexa.MotionSensor", + "name": "detectionState", + "value": eventSource.motionDetected ? "DETECTED" : "NOT_DETECTED", + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + }); + } + + return data; + }, + async sendEvent(eventSource: ScryptedDevice & MotionSensor & EntrySensor & Thermometer, eventDetails, eventData): Promise> { + if (eventDetails.eventInterface === ScryptedInterface.MotionSensor) + return { + event: { + payload: { + change: { + cause: { + type: "PHYSICAL_INTERACTION" + }, + properties: [ + { + "namespace": "Alexa.MotionSensor", + "name": "detectionState", + "value": eventData ? "DETECTED" : "NOT_DETECTED", + "timeOfSample": new Date(eventDetails.eventTime).toISOString(), + "uncertaintyInMilliseconds": 0 + } as Property + ] + } + } as ChangePayload, + } + } as Partial; + + if (eventDetails.eventInterface === ScryptedInterface.EntrySensor) + return { + event: { + payload: { + change: { + cause: { + type: "PHYSICAL_INTERACTION" + }, + properties: [ + { + "namespace": "Alexa.ContactSensor", + "name": "detectionState", + "value": eventData ? "DETECTED" : "NOT_DETECTED", + "timeOfSample": new Date(eventDetails.eventTime).toISOString(), + "uncertaintyInMilliseconds": 0 + } as Property + ] + } + } as ChangePayload, + } + } as Partial; + + if (eventDetails.eventInterface === ScryptedInterface.Thermometer) + return { + event: { + payload: { + change: { + cause: { + type: "PERIODIC_POLL" + }, + properties: [ + { + "namespace": "Alexa.TemperatureSensor", + "name": "temperature", + "value": { + "value": eventSource.temperature, + "scale": "CELSIUS" + }, + "timeOfSample": new Date(eventDetails.eventTime).toISOString(), + "uncertaintyInMilliseconds": 0 + } as Property + ] + } + } as ChangePayload, + } + } as Partial; + + return undefined; + } +}); diff --git a/plugins/alexa/src/types/switch.ts b/plugins/alexa/src/types/switch.ts new file mode 100644 index 0000000000..76c0f7c073 --- /dev/null +++ b/plugins/alexa/src/types/switch.ts @@ -0,0 +1,71 @@ +import { OnOff, ScryptedDevice, ScryptedDeviceType, ScryptedInterface } from "@scrypted/sdk"; +import { DiscoveryEndpoint, ChangeReport, Report, Property, ChangePayload, DiscoveryCapability } from "../alexa"; +import { supportedTypes } from "."; + +supportedTypes.set(ScryptedDeviceType.Switch, { + async discover(device: ScryptedDevice): Promise> { + if (!device.interfaces.includes(ScryptedInterface.OnOff)) + return; + + const capabilities: DiscoveryCapability[] = []; + capabilities.push({ + "type": "AlexaInterface", + "interface": "Alexa.PowerController", + "version": "3", + "properties": { + "supported": [ + { + "name": "powerState" + } + ], + "proactivelyReported": true, + "retrievable": true + } + }); + + return { + displayCategories: ['SWITCH'], + capabilities + } + }, + async sendReport(eventSource: ScryptedDevice & OnOff): Promise> { + return { + context: { + "properties": [ + { + "namespace": "Alexa.PowerController", + "name": "powerState", + "value": eventSource.on ? "ON" : "OFF", + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + } as Property + ] + } + }; + }, + async sendEvent(eventSource: ScryptedDevice & OnOff, eventDetails, eventData): Promise> { + if (eventDetails.eventInterface !== ScryptedInterface.OnOff) + return undefined; + + return { + event: { + payload: { + change: { + cause: { + type: "PHYSICAL_INTERACTION" + }, + properties: [ + { + "namespace": "Alexa.PowerController", + "name": "powerState", + "value": eventData ? "ON" : "OFF", + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 0 + } as Property + ] + } + } as ChangePayload, + } + } as Partial; + } +}); diff --git a/plugins/alexa/src/types/switch/handlers.ts b/plugins/alexa/src/types/switch/handlers.ts new file mode 100644 index 0000000000..1a959ff24b --- /dev/null +++ b/plugins/alexa/src/types/switch/handlers.ts @@ -0,0 +1,55 @@ +import { OnOff, ScryptedDevice } from "@scrypted/sdk"; +import { supportedTypes } from ".."; +import { sendDeviceResponse } from "../../common"; +import { v4 as createMessageId } from 'uuid'; +import { alexaDeviceHandlers } from "../../handlers"; +import { Directive, Response } from "../../alexa"; + +function commonResponse(header, endpoint, payload, response, device: ScryptedDevice & OnOff) { + const data : Response = { + "event": { + header, + endpoint, + payload + }, + "context": { + "properties": [ + { + "namespace": "Alexa.PowerController", + "name": "powerState", + "value": device.on ? "ON" : "OFF", + "timeOfSample": new Date().toISOString(), + "uncertaintyInMilliseconds": 500 + } + ] + } + }; + + data.event.header.namespace = "Alexa"; + data.event.header.name = "Response"; + data.event.header.messageId = createMessageId(); + + sendDeviceResponse(data, response, device); +} + +alexaDeviceHandlers.set('Alexa.PowerController/TurnOn', async (request, response, directive: Directive, device: ScryptedDevice & OnOff) => { + const supportedType = supportedTypes.get(device.type); + if (!supportedType) + return; + + const { header, endpoint, payload } = directive; + await device.turnOn(); + + commonResponse(header, endpoint, payload, response, device); +}); + +alexaDeviceHandlers.set('Alexa.PowerController/TurnOff', async (request, response, directive: Directive, device: ScryptedDevice & OnOff) => { + const supportedType = supportedTypes.get(device.type); + if (!supportedType) + return; + + const { header, endpoint, payload } = directive; + await device.turnOff(); + + commonResponse(header, endpoint, payload, response, device); +}); \ No newline at end of file