Compare commits

...

1 Commits
libav ... unifi

Author SHA1 Message Date
Koushik Dutta
ea628a7130 wip: unifi 2024-11-28 08:47:11 -08:00
10 changed files with 348 additions and 220 deletions

View File

@@ -9,15 +9,18 @@
"version": "0.0.164",
"license": "Apache",
"dependencies": {
"@koush/unifi-protect": "file:../../external/unifi-protect",
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"axios": "^1.4.0",
"ws": "^8.13.0"
"axios": "^1.7.8",
"unifi-protect": "^4.16.0",
"ws": "^8.18.0"
},
"devDependencies": {
"@types/node": "^20.4.2",
"@types/ws": "^8.5.5"
"@types/node": "^22.9.4",
"@types/ws": "^8.5.13"
},
"optionalDependencies": {
"@adobe/fetch": "^4.1.9"
}
},
"../../common": {
@@ -26,58 +29,33 @@
"license": "ISC",
"dependencies": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/server": "file:../server",
"http-auth-utils": "^5.0.1",
"typescript": "^5.3.3"
"typescript": "^5.5.3"
},
"devDependencies": {
"@types/node": "^20.11.0",
"monaco-editor": "^0.50.0",
"ts-node": "^10.9.2"
}
},
"../../external/unifi-protect": {
"name": "@koush/unifi-protect",
"version": "3.0.4",
"license": "ISC",
"dependencies": {
"abort-controller": "^3.0.0",
"domexception": "^4.0.0",
"node-fetch": "^3.3.0",
"util": "^0.12.4",
"ws": "^8.5.0"
},
"devDependencies": {
"@types/node": "^17.0.18",
"@types/ws": "^8.2.3",
"@typescript-eslint/eslint-plugin": "^5.12.0",
"@typescript-eslint/parser": "^5.12.0",
"eslint": "^8.9.0",
"rimraf": "^3.0.2",
"typescript": "^4.5.5"
},
"engines": {
"node": ">=12"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.3.31",
"version": "0.3.88",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.26.0",
"adm-zip": "^0.5.16",
"axios": "^1.7.7",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"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"
"rimraf": "^6.0.1",
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"typescript": "^5.5.4",
"webpack": "^5.95.0",
"webpack-bundle-analyzer": "^4.10.2"
},
"bin": {
"scrypted-changelog": "bin/scrypted-changelog.js",
@@ -89,19 +67,28 @@
"scrypted-webpack": "bin/scrypted-webpack.js"
},
"devDependencies": {
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"@types/node": "^22.8.1",
"@types/stringify-object": "^4.0.5",
"stringify-object": "^3.3.0",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21"
"ts-node": "^10.9.2",
"typedoc": "^0.26.10"
}
},
"../sdk": {
"extraneous": true
},
"node_modules/@koush/unifi-protect": {
"resolved": "../../external/unifi-protect",
"link": true
"node_modules/@adobe/fetch": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.9.tgz",
"integrity": "sha512-FWIzm4vvl1OtCarTBgWDW6otj4gxrNmMf/DdriqBUw7DjjmckjT3ZQA43b4HzBkzkuAHhajwMy91btp9fWgTEw==",
"dependencies": {
"debug": "4.3.7",
"http-cache-semantics": "4.1.1",
"lru-cache": "7.18.3"
},
"engines": {
"node": ">=14.16"
}
},
"node_modules/@scrypted/common": {
"resolved": "../../common",
@@ -112,15 +99,18 @@
"link": true
},
"node_modules/@types/node": {
"version": "20.4.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz",
"integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==",
"dev": true
"version": "22.9.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.4.tgz",
"integrity": "sha512-d9RWfoR7JC/87vj7n+PVTzGg9hDyuFjir3RxUHbjFSKNd9mpxbxwMEyaCim/ddCmy4IuW7HjTzF3g9p3EtWEOg==",
"dev": true,
"dependencies": {
"undici-types": "~6.19.8"
}
},
"node_modules/@types/ws": {
"version": "8.5.5",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz",
"integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==",
"version": "8.5.13",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
"integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
"dev": true,
"dependencies": {
"@types/node": "*"
@@ -132,15 +122,28 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"version": "1.7.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
"integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
"dependencies": {
"follow-redirects": "^1.15.0",
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/bufferutil": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"node-gyp-build": "^4.3.0"
},
"engines": {
"node": ">=6.14.2"
}
},
"node_modules/combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -152,6 +155,22 @@
"node": ">= 0.8"
}
},
"node_modules/debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"dependencies": {
"ms": "^2.1.3"
},
"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",
@@ -161,9 +180,9 @@
}
},
"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==",
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ==",
"funding": [
{
"type": "individual",
@@ -192,6 +211,19 @@
"node": ">= 6"
}
},
"node_modules/http-cache-semantics": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="
},
"node_modules/lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
"engines": {
"node": ">=12"
}
},
"node_modules/mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -211,15 +243,55 @@
"node": ">= 0.6"
}
},
"node_modules/ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node_modules/node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"optional": true,
"bin": {
"node-gyp-build": "bin.js",
"node-gyp-build-optional": "optional.js",
"node-gyp-build-test": "build-test.js"
}
},
"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/undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
},
"node_modules/unifi-protect": {
"version": "4.16.0",
"resolved": "https://registry.npmjs.org/unifi-protect/-/unifi-protect-4.16.0.tgz",
"integrity": "sha512-M8/VUTKhPxlzagIQdpjvXbdUPp4a/3F051CghaLXWT9JfnVuJZGLYC3U1zYOXtKVIfqP+KcTmn6sSTawHTGADQ==",
"dependencies": {
"@adobe/fetch": "4.1.9",
"ws": "8.18.0"
},
"bin": {
"ufp": "dist/util/ufp.js"
},
"engines": {
"node": ">=18"
},
"optionalDependencies": {
"bufferutil": "4.0.8"
}
},
"node_modules/ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"engines": {
"node": ">=10.0.0"
},
@@ -238,68 +310,63 @@
}
},
"dependencies": {
"@koush/unifi-protect": {
"version": "file:../../external/unifi-protect",
"@adobe/fetch": {
"version": "4.1.9",
"resolved": "https://registry.npmjs.org/@adobe/fetch/-/fetch-4.1.9.tgz",
"integrity": "sha512-FWIzm4vvl1OtCarTBgWDW6otj4gxrNmMf/DdriqBUw7DjjmckjT3ZQA43b4HzBkzkuAHhajwMy91btp9fWgTEw==",
"requires": {
"@types/node": "^17.0.18",
"@types/ws": "^8.2.3",
"@typescript-eslint/eslint-plugin": "^5.12.0",
"@typescript-eslint/parser": "^5.12.0",
"abort-controller": "^3.0.0",
"domexception": "^4.0.0",
"eslint": "^8.9.0",
"node-fetch": "^3.3.0",
"rimraf": "^3.0.2",
"typescript": "^4.5.5",
"util": "^0.12.4",
"ws": "^8.5.0"
"debug": "4.3.7",
"http-cache-semantics": "4.1.1",
"lru-cache": "7.18.3"
}
},
"@scrypted/common": {
"version": "file:../../common",
"requires": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/server": "file:../server",
"@types/node": "^20.11.0",
"http-auth-utils": "^5.0.1",
"monaco-editor": "^0.50.0",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
"typescript": "^5.5.3"
}
},
"@scrypted/sdk": {
"version": "file:../../sdk",
"requires": {
"@babel/preset-typescript": "^7.18.6",
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"adm-zip": "^0.4.13",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
"@babel/preset-typescript": "^7.26.0",
"@types/node": "^22.8.1",
"@types/stringify-object": "^4.0.5",
"adm-zip": "^0.5.16",
"axios": "^1.7.7",
"babel-loader": "^9.2.1",
"babel-plugin-const-enum": "^1.2.0",
"ncp": "^2.0.0",
"raw-loader": "^4.0.2",
"rimraf": "^3.0.2",
"rimraf": "^6.0.1",
"stringify-object": "^3.3.0",
"tmp": "^0.2.1",
"ts-loader": "^9.4.2",
"ts-node": "^10.4.0",
"typedoc": "^0.23.21",
"typescript": "^4.9.4",
"webpack": "^5.75.0",
"webpack-bundle-analyzer": "^4.5.0"
"tmp": "^0.2.3",
"ts-loader": "^9.5.1",
"ts-node": "^10.9.2",
"typedoc": "^0.26.10",
"typescript": "^5.5.4",
"webpack": "^5.95.0",
"webpack-bundle-analyzer": "^4.10.2"
}
},
"@types/node": {
"version": "20.4.2",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.4.2.tgz",
"integrity": "sha512-Dd0BYtWgnWJKwO1jkmTrzofjK2QXXcai0dmtzvIBhcA+RsG5h8R3xlyta0kGOZRNfL9GuRtb1knmPEhQrePCEw==",
"dev": true
"version": "22.9.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-22.9.4.tgz",
"integrity": "sha512-d9RWfoR7JC/87vj7n+PVTzGg9hDyuFjir3RxUHbjFSKNd9mpxbxwMEyaCim/ddCmy4IuW7HjTzF3g9p3EtWEOg==",
"dev": true,
"requires": {
"undici-types": "~6.19.8"
}
},
"@types/ws": {
"version": "8.5.5",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.5.tgz",
"integrity": "sha512-lwhs8hktwxSjf9UaZ9tG5M03PGogvFaH8gUgLNbN9HKIg0dvv6q+gkSuJ8HN4/VbyxkuLzCjlN7GquQ0gUJfIg==",
"version": "8.5.13",
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.13.tgz",
"integrity": "sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==",
"dev": true,
"requires": {
"@types/node": "*"
@@ -311,15 +378,24 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"axios": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz",
"integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==",
"version": "1.7.8",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.8.tgz",
"integrity": "sha512-Uu0wb7KNqK2t5K+YQyVCLM76prD5sRFjKHbJYCP1J7JFGEQ6nN7HWn9+04LAeiJ3ji54lgS/gZCH1oxyrf1SPw==",
"requires": {
"follow-redirects": "^1.15.0",
"follow-redirects": "^1.15.6",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"bufferutil": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
"optional": true,
"requires": {
"node-gyp-build": "^4.3.0"
}
},
"combined-stream": {
"version": "1.0.8",
"resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
@@ -328,15 +404,23 @@
"delayed-stream": "~1.0.0"
}
},
"debug": {
"version": "4.3.7",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz",
"integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==",
"requires": {
"ms": "^2.1.3"
}
},
"delayed-stream": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
"integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
},
"follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA=="
"version": "1.15.9",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.9.tgz",
"integrity": "sha512-gew4GsXizNgdoRyqmyfMHyAmXsZDk6mHkSxZFCzW9gwlbtOW44CDtYavM+y+72qD/Vq2l550kMF52DT8fOLJqQ=="
},
"form-data": {
"version": "4.0.0",
@@ -348,6 +432,16 @@
"mime-types": "^2.1.12"
}
},
"http-cache-semantics": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
"integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ=="
},
"lru-cache": {
"version": "7.18.3",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
"integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA=="
},
"mime-db": {
"version": "1.52.0",
"resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
@@ -361,15 +455,42 @@
"mime-db": "1.52.0"
}
},
"ms": {
"version": "2.1.3",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="
},
"node-gyp-build": {
"version": "4.8.4",
"resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz",
"integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==",
"optional": true
},
"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=="
},
"undici-types": {
"version": "6.19.8",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz",
"integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==",
"dev": true
},
"unifi-protect": {
"version": "4.16.0",
"resolved": "https://registry.npmjs.org/unifi-protect/-/unifi-protect-4.16.0.tgz",
"integrity": "sha512-M8/VUTKhPxlzagIQdpjvXbdUPp4a/3F051CghaLXWT9JfnVuJZGLYC3U1zYOXtKVIfqP+KcTmn6sSTawHTGADQ==",
"requires": {
"@adobe/fetch": "4.1.9",
"bufferutil": "4.0.8",
"ws": "8.18.0"
}
},
"ws": {
"version": "8.13.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.13.0.tgz",
"integrity": "sha512-x9vcZYTrFPC7aSIbj7sRCYo7L/Xb8Iy+pW0ng0wt2vCJv7M9HOMy0UoN3rr+IFC7hb7vXoqS+P9ktyLLLhO+LA==",
"version": "8.18.0",
"resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz",
"integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==",
"requires": {}
}
}

View File

@@ -1,5 +1,6 @@
{
"name": "@scrypted/unifi-protect",
"type": "module",
"version": "0.0.164",
"description": "Unifi Protect Plugin for Scrypted",
"author": "Scrypted",
@@ -28,20 +29,22 @@
"DeviceProvider",
"Settings"
],
"babel": true,
"pluginDependencies": [
"@scrypted/prebuffer-mixin"
]
},
"devDependencies": {
"@types/node": "^20.4.2",
"@types/ws": "^8.5.5"
"@types/node": "^22.9.4",
"@types/ws": "^8.5.13"
},
"dependencies": {
"@koush/unifi-protect": "file:../../external/unifi-protect",
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"axios": "^1.4.0",
"ws": "^8.13.0"
"axios": "^1.7.8",
"unifi-protect": "^4.16.0",
"ws": "^8.18.0"
},
"optionalDependencies": {
"@adobe/fetch": "^4.1.9"
}
}

View File

@@ -96,16 +96,16 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
}
});
const camera = this.findCamera() as any;
const camera = this.findCamera();
await this.protect.api.updateCamera(camera, {
await this.protect.api.updateDevice(camera, {
privacyZones,
} as any);
}
async ptzCommand(command: PanTiltZoomCommand): Promise<void> {
const camera = this.findCamera() as any;
await this.protect.api.updateCamera(camera, {
const camera = this.findCamera();
await this.protect.api.updateDevice(camera, {
ispSettings: {
zoomPosition: Math.abs(command.zoom * 100),
}
@@ -113,8 +113,8 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
}
async setStatusLight(on: boolean) {
const camera = this.findCamera() as any;
await this.protect.api.updateCamera(camera, {
const camera = this.findCamera();
await this.protect.api.updateDevice(camera, {
ledSettings: {
isEnabled: on,
}
@@ -170,8 +170,9 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
const ffmpegInput = JSON.parse(buffer.toString()) as FFmpegInput;
const camera = this.findCamera();
const params = new URLSearchParams({ camera: camera.id });
const response = await this.protect.loginFetch(this.protect.api.wsUrl() + "/talkback?" + params.toString());
const endpoint = new URL(this.protect.api.getApiEndpoint("talkback"));
endpoint.searchParams.set('camera', camera.id);
const response = await this.protect.loginFetch(endpoint.toString());
const tb = response.data as Record<string, string>;
// Adjust the URL for our address.
@@ -375,7 +376,7 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
}
findCamera() {
const id = this.protect.findId(this.nativeId);
return this.protect.api.cameras.find(camera => camera.id === id);
return this.protect.api.bootstrap.cameras.find(camera => camera.id === id);
}
async getVideoStream(options?: MediaStreamOptions): Promise<MediaObject> {
const camera = this.findCamera();
@@ -441,7 +442,9 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
const sanitizedBitrate = Math.min(channel.maxBitrate, Math.max(channel.minBitrate, bitrate));
this.console.log(channel.name, 'bitrate change requested', bitrate, 'clamped to', sanitizedBitrate);
channel.bitrate = sanitizedBitrate;
const cameraResult = await this.protect.api.updateCameraChannels(camera);
const cameraResult = await this.protect.api.updateDevice(camera, {
channels: camera.channels,
});
if (!cameraResult) {
throw new Error("setVideoStreamOptions failed")
}
@@ -458,7 +461,7 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
setMotionDetected(motionDetected: boolean) {
this.motionDetected = motionDetected;
if ((this.findCamera().featureFlags as any as FeatureFlagsShim).hasPackageCamera) {
if (this.findCamera().featureFlags.hasPackageCamera) {
if (deviceManager.getNativeIds().includes(this.packageCameraNativeId)) {
this.ensurePackageCamera();
this.packageCamera.motionDetected = motionDetected;
@@ -480,7 +483,7 @@ export class UnifiCamera extends ScryptedDeviceBase implements Notifier, Interco
text: title.substring(0, 30),
type: 'CUSTOM_MESSAGE',
};
this.protect.api.updateCamera(this.findCamera(), {
this.protect.api.updateDevice(this.findCamera(), {
lcdMessage: payload,
})

View File

@@ -14,23 +14,23 @@ export class UnifiLight extends ScryptedDeviceBase implements OnOff, Brightness,
this.console.log(protectLight);
}
async turnOff(): Promise<void> {
const result = await this.protect.api.updateLight(this.findLight(), { lightOnSettings: { isLedForceOn: false } });
const result = await this.protect.api.updateDevice(this.findLight(), { lightOnSettings: { isLedForceOn: false } });
if (!result)
this.console.error('turnOff failed.');
}
async turnOn(): Promise<void> {
const result = await this.protect.api.updateLight(this.findLight(), { lightOnSettings: { isLedForceOn: true } });
const result = await this.protect.api.updateDevice(this.findLight(), { lightOnSettings: { isLedForceOn: true } });
if (!result)
this.console.error('turnOn failed.');
}
async setBrightness(brightness: number): Promise<void> {
const ledLevel = Math.round(((brightness as number) / 20) + 1);
this.protect.api.updateLight(this.findLight(), { lightDeviceSettings: { ledLevel } });
this.protect.api.updateDevice(this.findLight(), { lightDeviceSettings: { ledLevel } });
}
findLight() {
const id = this.protect.findId(this.nativeId);
return this.protect.api.lights.find(light => light.id === id);
return this.protect.api.bootstrap.lights.find(light => light.id === id);
}
updateState(light?: Readonly<ProtectLightConfig>) {

View File

@@ -1,9 +1,8 @@
import { Lock, LockState, ScryptedDeviceBase } from "@scrypted/sdk";
import { UnifiProtect } from "./main";
import { ProtectDoorLockConfig } from "./unifi-protect";
export class UnifiLock extends ScryptedDeviceBase implements Lock {
constructor(public protect: UnifiProtect, nativeId: string, protectLock: Readonly<ProtectDoorLockConfig>) {
constructor(public protect: UnifiProtect, nativeId: string, protectLock: any) {
super(nativeId);
this.updateState(protectLock);
@@ -11,23 +10,23 @@ export class UnifiLock extends ScryptedDeviceBase implements Lock {
}
async lock(): Promise<void> {
await this.protect.loginFetch(this.protect.api.doorlocksUrl() + `/${this.findLock().id}/close`, {
await this.protect.loginFetch(this.protect.api.getApiEndpoint('doorlocks') + `/${this.findLock().id}/close`, {
method: 'POST',
});
}
async unlock(): Promise<void> {
await this.protect.loginFetch(this.protect.api.doorlocksUrl() + `/${this.findLock().id}/open`, {
await this.protect.loginFetch(this.protect.api.getApiEndpoint('doorlocks') + `/${this.findLock().id}/open`, {
method: 'POST',
});
}
findLock() {
const id = this.protect.findId(this.nativeId);
return this.protect.api.doorlocks.find(doorlock => doorlock.id === id);
return (this.protect.api.bootstrap.doorlocks as any).find(doorlock => doorlock.id === id);
}
updateState(lock?: Readonly<ProtectDoorLockConfig>) {
updateState(lock?: any) {
lock = lock || this.findLock();
if (!lock)
return;

View File

@@ -4,12 +4,12 @@ import sdk, { Device, DeviceProvider, ObjectDetectionResult, ObjectsDetected, Sc
import { StorageSettings } from "@scrypted/sdk/storage-settings";
import axios from "axios";
import { UnifiCamera } from "./camera";
import { debounceFingerprintDetected, debounceMotionDetected } from "./camera-sensors";
import { UnifiLight } from "./light";
import { UnifiLock } from "./lock";
import { debounceFingerprintDetected, debounceMotionDetected } from "./camera-sensors";
import { UnifiSensor } from "./sensor";
import { FeatureFlagsShim, LastSeenShim } from "./shim";
import { ProtectApi, ProtectApiUpdates, ProtectNvrUpdatePayloadCameraUpdate, ProtectNvrUpdatePayloadEventAdd } from "./unifi-protect";
import { FeatureFlagsShim } from "./shim";
import { ProtectApi, ProtectCameraConfigInterface, ProtectEventAddInterface, ProtectEventPacket } from "./unifi-protect";
const { deviceManager } = sdk;
@@ -54,10 +54,10 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
return;
}
const device = this.api.cameras?.find(c => c.id === packet.action.id)
|| this.api.lights?.find(c => c.id === packet.action.id)
|| this.api.doorlocks?.find(c => c.id === packet.action.id)
|| this.api.sensors?.find(c => c.id === packet.action.id);
const device = this.api.bootstrap.cameras?.find(c => c.id === packet.action.id)
|| this.api.bootstrap.lights?.find(c => c.id === packet.action.id)
|| (this.api.bootstrap.doorlocks as any)?.find(c => c.id === packet.action.id)
|| this.api.bootstrap.sensors?.find(c => c.id === packet.action.id);
if (!device) {
return;
@@ -100,8 +100,7 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
})
}
listener(event: Buffer) {
const updatePacket = ProtectApiUpdates.decodeUpdatePacket(this.console, event);
listener(updatePacket: ProtectEventPacket) {
if (!updatePacket)
return;
@@ -109,27 +108,27 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
const unifiDevice = this.handleUpdatePacket(updatePacket);
switch (updatePacket.action.modelKey) {
switch (updatePacket.header.modelKey) {
case "sensor":
case "doorlock":
case "light":
case "camera": {
if (!unifiDevice) {
this.console.log('unknown device, sync needed?', updatePacket.action.id);
this.console.log('unknown device, sync needed?', updatePacket.header.id);
return;
}
if (updatePacket.action.action !== "update") {
unifiDevice.console.log('non update', updatePacket.action.action);
if (updatePacket.header.action !== "update") {
unifiDevice.console.log('non update', updatePacket.header.action);
return;
}
unifiDevice.updateState();
if (updatePacket.action.modelKey === "doorlock")
if (updatePacket.header.modelKey === "doorlock")
return;
const payload = updatePacket.payload as any as ProtectNvrUpdatePayloadCameraUpdate & LastSeenShim;
const payload = updatePacket.payload as ProtectCameraConfigInterface;
if (updatePacket.action.modelKey !== "camera")
if (updatePacket.header.modelKey !== "camera")
return;
const unifiCamera = unifiDevice as UnifiCamera;
@@ -142,19 +141,19 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
break;
}
case "event": {
if (updatePacket.action.action !== "add") {
if ((updatePacket?.payload as any)?.end && updatePacket.action.id) {
const payload = updatePacket.payload as ProtectEventAddInterface;
if (updatePacket.header.action !== "add") {
if (payload.end && updatePacket.header.id) {
// unifi reports the event ended but it seems to take a moment before the snapshot
// is actually ready.
setTimeout(() => {
const running = this.runningEvents.get(updatePacket.action.id);
const running = this.runningEvents.get(updatePacket.header.id);
running?.resolve?.(undefined)
}, 2000);
}
return;
}
const payload = updatePacket.payload as ProtectNvrUpdatePayloadEventAdd;
if (!payload.camera)
return;
const nativeId = this.getNativeId({ id: payload.camera }, false);
@@ -166,7 +165,7 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
}
const detectionId = payload.id;
const actionId = updatePacket.action.id;
const actionId = updatePacket.header.id;
let resolve: (value: unknown) => void;
const promise = new Promise(r => resolve = r);
@@ -249,7 +248,26 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
this.console.log(message, ...parameters);
}
reconnecting = false;
wsTimeout: NodeJS.Timeout;
reconnect(reason: string) {
return async () => {
if (this.reconnecting)
return;
this.reconnecting = true;
this.api?.reset();
this.console.error('Event Listener reconnecting in 10 seconds:', reason);
await sleep(10000);
this.discoverDevices(0);
}
}
async discoverDevices(duration: number) {
this.api?.reset();
this.reconnecting = false;
clearTimeout(this.wsTimeout);
const ip = this.getSetting('ip');
const username = this.getSetting('username');
const password = this.getSetting('password');
@@ -271,10 +289,8 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
return
}
this.api?.eventsWs?.removeAllListeners();
this.api?.eventsWs?.close();
if (!this.api) {
this.api = new ProtectApi(ip, username, password, {
this.api = new ProtectApi({
debug() { },
error: (...args) => {
this.console.error(...args);
@@ -284,48 +300,37 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
});
}
let reconnecting = false;
const reconnect = (reason: string) => {
return async () => {
if (reconnecting)
return;
reconnecting = true;
this.api?.eventsWs?.close();
this.api?.eventsWs?.emit('close');
this.api?.eventsWs?.removeAllListeners();
if (this.api.eventsWs) {
this.console.warn('Event Listener failed to close. Requesting plugin restart.');
deviceManager.requestRestart();
}
this.console.error('Event Listener reconnecting in 10 seconds:', reason);
await sleep(10000);
this.discoverDevices(0);
}
}
try {
if (!await this.api.refreshDevices()) {
reconnect('refresh failed')();
const loginResult = await this.api.login(ip, username, password);
if (!loginResult) {
this.log.a('Login failed. Check credentials.');
return;
}
if (!await this.api.getBootstrap()) {
this.reconnect('refresh failed')();
return;
}
let wsTimeout: NodeJS.Timeout;
const resetWsTimeout = () => {
clearTimeout(wsTimeout);
wsTimeout = setTimeout(reconnect('timeout'), 5 * 60 * 1000);
clearTimeout(this.wsTimeout);
this.wsTimeout = setTimeout(() => this.reconnect('timeout'), 5 * 60 * 1000);
};
resetWsTimeout();
this.api.eventsWs?.on('message', (data) => {
this.api.on('message', message => {
resetWsTimeout();
this.listener(data as Buffer);
});
this.api.eventsWs?.on('close', reconnect('close'));
this.api.eventsWs?.on('error', reconnect('error'));
this.listener(message);
})
const devices: Device[] = [];
for (let camera of this.api.cameras || []) {
if (!this.api.bootstrap.cameras.length) {
this.console.warn('no cameras found. is this an admin account? cancelling sync.');
return;
}
for (let camera of this.api.bootstrap.cameras || []) {
if (camera.isAdoptedByOther) {
this.console.log('skipping camera that is adopted by another nvr', camera.id, camera.name);
continue;
@@ -347,7 +352,9 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
}
if (needUpdate) {
camera = await this.api.updateCameraChannels(camera);
camera = await this.api.updateDevice(camera, {
channels: camera.channels,
});
if (!camera) {
this.log.a('Unable to enable RTSP and IDR interval on camera. Is this an admin account?');
continue;
@@ -392,7 +399,7 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
if (camera.featureFlags.hasLcdScreen) {
d.interfaces.push(ScryptedInterface.Notifier);
}
if ((camera.featureFlags as any as FeatureFlagsShim).hasPackageCamera) {
if (camera.featureFlags.hasPackageCamera) {
d.interfaces.push(ScryptedInterface.DeviceProvider);
}
if (camera.featureFlags.hasLedStatus) {
@@ -405,7 +412,7 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
devices.push(d);
}
for (const sensor of this.api.sensors || []) {
for (const sensor of this.api.bootstrap.sensors || []) {
const d: Device = {
providerNativeId: this.nativeId,
name: sensor.name,
@@ -413,7 +420,7 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
info: {
manufacturer: 'Ubiquiti',
model: sensor.type,
ip: sensor.host,
ip: (sensor as any).host,
firmware: sensor.firmwareVersion,
version: sensor.hardwareRevision,
serialNumber: sensor.id,
@@ -433,7 +440,7 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
devices.push(d);
}
for (const light of this.api.lights || []) {
for (const light of this.api.bootstrap.lights || []) {
const d: Device = {
providerNativeId: this.nativeId,
name: light.name,
@@ -458,7 +465,7 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
devices.push(d);
}
for (const lock of this.api.doorlocks || []) {
for (const lock of (this.api.bootstrap.doorlocks as any) || []) {
const d: Device = {
providerNativeId: this.nativeId,
name: lock.name,
@@ -490,12 +497,12 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
}
// handle package cameras as a sub device
for (const camera of this.api.cameras) {
for (const camera of this.api.bootstrap.cameras) {
const devices: Device[] = [];
const providerNativeId = this.getNativeId(camera, true);
if ((camera.featureFlags as any as FeatureFlagsShim).hasPackageCamera) {
if (camera.featureFlags.hasPackageCamera) {
const nativeId = providerNativeId + '-packageCamera';
const d: Device = {
providerNativeId,
@@ -569,25 +576,25 @@ export class UnifiProtect extends ScryptedDeviceBase implements Settings, Device
return this.locks.get(nativeId);
const id = this.findId(nativeId);
const camera = this.api.cameras.find(camera => camera.id === id);
const camera = this.api.bootstrap.cameras.find(camera => camera.id === id);
if (camera) {
const ret = new UnifiCamera(this, nativeId, camera);
this.cameras.set(nativeId, ret);
return ret;
}
const sensor = this.api.sensors.find(sensor => sensor.id === id);
const sensor = this.api.bootstrap.sensors.find(sensor => sensor.id === id);
if (sensor) {
const ret = new UnifiSensor(this, nativeId, sensor);
this.sensors.set(nativeId, ret);
return ret;
}
const light = this.api.lights.find(light => light.id === id);
const light = this.api.bootstrap.lights.find(light => light.id === id);
if (light) {
const ret = new UnifiLight(this, nativeId, light);
this.lights.set(nativeId, ret);
return ret;
}
const lock = this.api.doorlocks?.find(lock => lock.id === id);
const lock = (this.api.bootstrap.doorlocks as any)?.find(lock => lock.id === id);
if (lock) {
const ret = new UnifiLock(this, nativeId, lock);
this.locks.set(nativeId, ret);

View File

@@ -15,7 +15,7 @@ export class UnifiSensor extends ScryptedDeviceBase implements Thermometer, Humi
findSensor() {
const id = this.protect.findId(this.nativeId);
return this.protect.api.sensors.find(sensor => sensor.id === id);
return this.protect.api.bootstrap.sensors.find(sensor => sensor.id === id);
}
async setTemperatureUnit(temperatureUnit: TemperatureUnit): Promise<void> {

View File

@@ -1,13 +1,8 @@
export interface FeatureFlagsShim {
hasPackageCamera: boolean;
hasFingerprintSensor: boolean;
}
export interface LastSeenShim {
lastSeen: number;
}
export interface PrivacyZone {
id: number;
name: string;

View File

@@ -1,3 +1,3 @@
// export * from '@koush/unifi-protect'
export * from '@koush/unifi-protect/src/index'
export * from 'unifi-protect'
// export * from '../../../external/unifi-protect/src/index'

View File

@@ -1,9 +1,9 @@
{
"compilerOptions": {
"module": "Node16",
"module": "es2022",
"target": "ES2021",
"resolveJsonModule": true,
"moduleResolution": "Node16",
"moduleResolution": "bundler",
"esModuleInterop": true,
"sourceMap": true
},