mirror of
https://github.com/koush/scrypted.git
synced 2026-02-07 16:02:13 +00:00
Compare commits
825 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1688fcc126 | ||
|
|
99cae0ba31 | ||
|
|
a7b00b9e91 | ||
|
|
3f2a62c6f2 | ||
|
|
3fc318a370 | ||
|
|
aed8575aa0 | ||
|
|
2e28b50588 | ||
|
|
2e87cc380f | ||
|
|
1fdd2d4b01 | ||
|
|
53b23b2ca8 | ||
|
|
54016a9c78 | ||
|
|
d207a3b824 | ||
|
|
e72a74d008 | ||
|
|
d1b907e45b | ||
|
|
4a4c47ffe2 | ||
|
|
f6baf99935 | ||
|
|
b5cc138e2b | ||
|
|
40738a74cf | ||
|
|
d2b1f104ca | ||
|
|
6cb4f589c0 | ||
|
|
5cf2b26630 | ||
|
|
e7f16af04c | ||
|
|
6287b9deaa | ||
|
|
b9b5fdb712 | ||
|
|
c85af9c8a5 | ||
|
|
069f765507 | ||
|
|
0e587abc79 | ||
|
|
47770c0a8d | ||
|
|
82d1c3afe5 | ||
|
|
1c9b52ce4f | ||
|
|
adcd9fa537 | ||
|
|
91e2c2870b | ||
|
|
1fc892815d | ||
|
|
38ed1acc15 | ||
|
|
3bdc9ab930 | ||
|
|
bfa6346333 | ||
|
|
fcbb308cb8 | ||
|
|
f137edcc8c | ||
|
|
53e6f083b9 | ||
|
|
0f96fdb4bc | ||
|
|
96ea3f3b27 | ||
|
|
a31d6482af | ||
|
|
be16bf7858 | ||
|
|
1dad0126bc | ||
|
|
9292ebbe48 | ||
|
|
0b3a1a1998 | ||
|
|
b5d58b6899 | ||
|
|
215a56f70e | ||
|
|
c593701e72 | ||
|
|
46351f2fd7 | ||
|
|
9bce4acd14 | ||
|
|
cba20ec887 | ||
|
|
7c41516cce | ||
|
|
1f209072ba | ||
|
|
8978bff8a9 | ||
|
|
04c500b855 | ||
|
|
8b4859579c | ||
|
|
90deaf1161 | ||
|
|
de56a8c653 | ||
|
|
a5215ae92b | ||
|
|
73cd40b540 | ||
|
|
93556dd404 | ||
|
|
125b436cb6 | ||
|
|
0a4ea032f5 | ||
|
|
c658cee5c9 | ||
|
|
6589176c8b | ||
|
|
6c4c83f655 | ||
|
|
8d4124adda | ||
|
|
b7cda86df7 | ||
|
|
6622e13e51 | ||
|
|
cbc45da679 | ||
|
|
e7d06c66af | ||
|
|
ea02bc3b6f | ||
|
|
2b43cb7d15 | ||
|
|
f3c0362e18 | ||
|
|
817ae42250 | ||
|
|
8043f83f20 | ||
|
|
d33ab5dbcf | ||
|
|
2b1674bea8 | ||
|
|
f045e59258 | ||
|
|
9125aafc07 | ||
|
|
6f5244ec9f | ||
|
|
f1eb2f988a | ||
|
|
1f659d9a72 | ||
|
|
dd98f12f2a | ||
|
|
2063e3822a | ||
|
|
f7495a7a76 | ||
|
|
fddb9c655f | ||
|
|
297e7a7b4f | ||
|
|
29e080f6b6 | ||
|
|
c72ea24794 | ||
|
|
ada80796de | ||
|
|
1ebcf32998 | ||
|
|
79765ba58e | ||
|
|
ff4665520c | ||
|
|
be5b810335 | ||
|
|
fdc99b7fa6 | ||
|
|
f730d13cbd | ||
|
|
af02753cef | ||
|
|
9334d1c2a4 | ||
|
|
71ecc07e2b | ||
|
|
5310dd5ff6 | ||
|
|
adf1a10659 | ||
|
|
2ecc26c914 | ||
|
|
9a49416831 | ||
|
|
f0eff01898 | ||
|
|
edd071739f | ||
|
|
ab81c568bc | ||
|
|
62470df0af | ||
|
|
19b83eb056 | ||
|
|
b75d4cbfd4 | ||
|
|
8c0bb7b205 | ||
|
|
ef64515e56 | ||
|
|
302272e437 | ||
|
|
80e433f6ef | ||
|
|
60786aba2b | ||
|
|
256fde46f6 | ||
|
|
e1a7dd367e | ||
|
|
8612ba3462 | ||
|
|
ab638f26be | ||
|
|
02b881a2d2 | ||
|
|
35475b03e2 | ||
|
|
0b55c777f8 | ||
|
|
68f86d214c | ||
|
|
2abea2d25b | ||
|
|
1c2f17b9f9 | ||
|
|
e3d4800e4f | ||
|
|
d2f175715b | ||
|
|
93c1a699f1 | ||
|
|
41570e9134 | ||
|
|
3ef75854c2 | ||
|
|
c88a638f4e | ||
|
|
793c4da33a | ||
|
|
68f071660e | ||
|
|
8ea5b6aca6 | ||
|
|
2f13c77444 | ||
|
|
981ad183f5 | ||
|
|
8748be82ef | ||
|
|
a347fc2b73 | ||
|
|
002bf3b52c | ||
|
|
72abcd79ec | ||
|
|
86e5b824c7 | ||
|
|
43f6f176f0 | ||
|
|
bc543aa28e | ||
|
|
e90db378e8 | ||
|
|
f2907532aa | ||
|
|
866706505a | ||
|
|
59db3b622c | ||
|
|
7451b9903a | ||
|
|
aded2e43b1 | ||
|
|
031a7527e1 | ||
|
|
2aca568707 | ||
|
|
6b22d34831 | ||
|
|
429d9ec5a6 | ||
|
|
b426668146 | ||
|
|
8bce14f834 | ||
|
|
7511abf768 | ||
|
|
180c12e8cc | ||
|
|
1ed7d03a20 | ||
|
|
9e7b57f154 | ||
|
|
205fdb0222 | ||
|
|
d8f3edee1e | ||
|
|
90c9efc8a6 | ||
|
|
3893ccd776 | ||
|
|
1b154f14bc | ||
|
|
2e3eba4350 | ||
|
|
450f05910a | ||
|
|
22505c9226 | ||
|
|
7120bf86ff | ||
|
|
b49742204f | ||
|
|
6fda76a5e8 | ||
|
|
08bd785d45 | ||
|
|
aa9ddb35aa | ||
|
|
7997c07179 | ||
|
|
a67e24d5dc | ||
|
|
0d4da0dd06 | ||
|
|
993e903f3b | ||
|
|
fbb11a5312 | ||
|
|
ea72d2159b | ||
|
|
1892fdb529 | ||
|
|
1e16793b20 | ||
|
|
2f6c577b47 | ||
|
|
212306449b | ||
|
|
16445bc38e | ||
|
|
b6e9e15d4f | ||
|
|
39abd49ea0 | ||
|
|
05b9b49732 | ||
|
|
1857acac66 | ||
|
|
fedf184847 | ||
|
|
d2afac0dd6 | ||
|
|
6844b55983 | ||
|
|
379dabc182 | ||
|
|
df3c751f2d | ||
|
|
da714d1f94 | ||
|
|
34ee29b7b4 | ||
|
|
4c48f50e01 | ||
|
|
81a5a4349c | ||
|
|
8526c92dcc | ||
|
|
73fefeec26 | ||
|
|
6060b50856 | ||
|
|
d29cd7e421 | ||
|
|
8589283135 | ||
|
|
837dae5f02 | ||
|
|
c26aa2d01e | ||
|
|
c98eca23ab | ||
|
|
eb5d1ac4f6 | ||
|
|
37b0e46dd0 | ||
|
|
042dd84520 | ||
|
|
62d5c145c2 | ||
|
|
1ea3774849 | ||
|
|
9d8345e901 | ||
|
|
9ed850e327 | ||
|
|
957d27b8ef | ||
|
|
b74a957ecb | ||
|
|
debaedfd8c | ||
|
|
0123a97e3c | ||
|
|
a32d47e192 | ||
|
|
90ed8bd3f5 | ||
|
|
c4f4002f55 | ||
|
|
1ea2828e78 | ||
|
|
eb864456df | ||
|
|
51af4f07ff | ||
|
|
f6201acf2a | ||
|
|
96ac479c73 | ||
|
|
0c08875de3 | ||
|
|
bd05fc1b5d | ||
|
|
5a0d325718 | ||
|
|
015794c1d1 | ||
|
|
02d5b429b7 | ||
|
|
e169d154e7 | ||
|
|
01c7b5674a | ||
|
|
a7a1aed0dc | ||
|
|
6bb3f0fd19 | ||
|
|
7828de9d50 | ||
|
|
ea77bb29d0 | ||
|
|
bb184247d0 | ||
|
|
dbc45173ae | ||
|
|
95a23b2882 | ||
|
|
212883e84b | ||
|
|
1200537d62 | ||
|
|
5f6adc9449 | ||
|
|
7d17236ca7 | ||
|
|
028401362a | ||
|
|
69927be4f4 | ||
|
|
ffee1c5cc2 | ||
|
|
ebc3a03e2c | ||
|
|
4246e3c476 | ||
|
|
3fce0838f1 | ||
|
|
2609e301fe | ||
|
|
f4737bf2ac | ||
|
|
fc102aa526 | ||
|
|
9ef33e156f | ||
|
|
881865a0cb | ||
|
|
be5643cc53 | ||
|
|
7e6eba1596 | ||
|
|
27dde776a6 | ||
|
|
b24159a22a | ||
|
|
b6c242b9d5 | ||
|
|
2fbaa12caa | ||
|
|
eb5a497e82 | ||
|
|
66a0ea08ec | ||
|
|
0527baf14a | ||
|
|
c7c5c6eed5 | ||
|
|
143c950c19 | ||
|
|
8d0bb0fa97 | ||
|
|
964274e50c | ||
|
|
e9844528aa | ||
|
|
0609fc8986 | ||
|
|
9331b71433 | ||
|
|
21f8239db7 | ||
|
|
86042ec3fe | ||
|
|
cdb87fb268 | ||
|
|
63dcd35b17 | ||
|
|
951c3b9be6 | ||
|
|
ed642bb3fe | ||
|
|
8093cdd3d9 | ||
|
|
fcbfc3a73f | ||
|
|
94945a48bd | ||
|
|
e360ede5cb | ||
|
|
bc9ec73567 | ||
|
|
cd7e570508 | ||
|
|
1b06c9c11d | ||
|
|
154ab42d15 | ||
|
|
1929f6e8ed | ||
|
|
58bfa17cfe | ||
|
|
38c7006055 | ||
|
|
b5e16b45a9 | ||
|
|
9c13668812 | ||
|
|
a1ca724d6b | ||
|
|
1b032d669c | ||
|
|
c492c15081 | ||
|
|
ee7076384b | ||
|
|
717cac721a | ||
|
|
af41c853bc | ||
|
|
109b716753 | ||
|
|
07930508fe | ||
|
|
a291abe375 | ||
|
|
f4f34b2da8 | ||
|
|
3b4de526ba | ||
|
|
5de67fca86 | ||
|
|
98dc0b1b6d | ||
|
|
a05595ecc7 | ||
|
|
87be4648f1 | ||
|
|
60e51adb41 | ||
|
|
ace7720fe1 | ||
|
|
b9eb74d403 | ||
|
|
fb7353383d | ||
|
|
bee119b486 | ||
|
|
0b6ffc2b87 | ||
|
|
3863527b4d | ||
|
|
51c48f4a1c | ||
|
|
4c138e9b4c | ||
|
|
e762c305a3 | ||
|
|
5bce335288 | ||
|
|
8201e9883a | ||
|
|
74e5884285 | ||
|
|
9cffd9ffbe | ||
|
|
d8b617f2ae | ||
|
|
aeb564aa5d | ||
|
|
45f672883a | ||
|
|
c0ff857a1b | ||
|
|
64f7e31f54 | ||
|
|
6b55f8876e | ||
|
|
718a31f2c5 | ||
|
|
c1e1d50fa5 | ||
|
|
75c4a1939f | ||
|
|
0d703c2aff | ||
|
|
0a6e4fda75 | ||
|
|
4c2de9e443 | ||
|
|
b8a4fedf1a | ||
|
|
79d9f1d4a1 | ||
|
|
983213c578 | ||
|
|
7dd3d71ebd | ||
|
|
493f8deeef | ||
|
|
b29f2d5ee1 | ||
|
|
96bda10123 | ||
|
|
3294700d31 | ||
|
|
0cf77d4c76 | ||
|
|
953841e3a5 | ||
|
|
393c1017df | ||
|
|
f50176d14a | ||
|
|
7f2bf0b542 | ||
|
|
9e3990400c | ||
|
|
95eed80735 | ||
|
|
be43d0c017 | ||
|
|
386ea9a98a | ||
|
|
9b40978f61 | ||
|
|
f0ee435cd0 | ||
|
|
30748784ef | ||
|
|
8310e33719 | ||
|
|
1d18697161 | ||
|
|
d500b3fd6c | ||
|
|
95ae916b6c | ||
|
|
ec3e16f20f | ||
|
|
30d28f543c | ||
|
|
e0cce24999 | ||
|
|
409b25f8b0 | ||
|
|
8f278abec8 | ||
|
|
d6179dab82 | ||
|
|
ed186e2142 | ||
|
|
3c021bb2c8 | ||
|
|
c522edc622 | ||
|
|
022a103bcb | ||
|
|
efd125b6e4 | ||
|
|
19f7688a65 | ||
|
|
7f18e4629c | ||
|
|
dfe2c937a1 | ||
|
|
47d7a23a3d | ||
|
|
0ea609c80c | ||
|
|
71ee5727f1 | ||
|
|
2383f16112 | ||
|
|
7d5defd736 | ||
|
|
cbf4cf0579 | ||
|
|
422dd94e5c | ||
|
|
076f5e27f1 | ||
|
|
645de2e5fd | ||
|
|
dcf24a77d7 | ||
|
|
7065365a47 | ||
|
|
b82520776e | ||
|
|
638c1f77fd | ||
|
|
73a489ea37 | ||
|
|
77d69f025a | ||
|
|
3bc14ad248 | ||
|
|
03e5a9dec1 | ||
|
|
57b790c332 | ||
|
|
ce2ea63be7 | ||
|
|
2dd4721b7f | ||
|
|
667075dfad | ||
|
|
7abdb06b66 | ||
|
|
43e5822c93 | ||
|
|
bc579514e7 | ||
|
|
825100f94e | ||
|
|
803bfc1560 | ||
|
|
b2013a54ed | ||
|
|
f252407935 | ||
|
|
516f2a2a7b | ||
|
|
c1677ce691 | ||
|
|
5028fb812d | ||
|
|
2db4e2579f | ||
|
|
b339ca6cd2 | ||
|
|
f100999cb1 | ||
|
|
2863756bd6 | ||
|
|
cc408850a0 | ||
|
|
ed1ceeda51 | ||
|
|
df09d8e92a | ||
|
|
298ac960d1 | ||
|
|
62d4d55aae | ||
|
|
a2121c0dc5 | ||
|
|
9b5ea27c0b | ||
|
|
0b0e90fc04 | ||
|
|
d8aff609bf | ||
|
|
d8283c261a | ||
|
|
e3aca964be | ||
|
|
a96025c45f | ||
|
|
6afd4b4579 | ||
|
|
f97669949d | ||
|
|
0a0a31574f | ||
|
|
90fb751a22 | ||
|
|
b8d06fada5 | ||
|
|
2cecb1686f | ||
|
|
db03775530 | ||
|
|
cccbc33f1a | ||
|
|
5f23873366 | ||
|
|
e43accae67 | ||
|
|
b3a0cda6f9 | ||
|
|
58c3348282 | ||
|
|
a9e6d76e99 | ||
|
|
3b58936387 | ||
|
|
3a14ab81c8 | ||
|
|
291178a7b5 | ||
|
|
b65faf1a79 | ||
|
|
9d8a1353c0 | ||
|
|
b29d793178 | ||
|
|
d8e406d415 | ||
|
|
4529872fd6 | ||
|
|
fa86c31340 | ||
|
|
94ded75d40 | ||
|
|
887b61cd7a | ||
|
|
48e3d30987 | ||
|
|
02dba3cd71 | ||
|
|
195769034d | ||
|
|
39c08aa378 | ||
|
|
fa8056d38e | ||
|
|
145f116c68 | ||
|
|
15b6f336e4 | ||
|
|
8b46f0a466 | ||
|
|
a20cc5cd89 | ||
|
|
3d068929fd | ||
|
|
928f9b7579 | ||
|
|
c1c5a42645 | ||
|
|
12643cdde2 | ||
|
|
0bff96a6e6 | ||
|
|
4e7e67de54 | ||
|
|
65c4a30004 | ||
|
|
309a1dc11f | ||
|
|
b7904b73b2 | ||
|
|
9e9ddbc5f3 | ||
|
|
ceda54f91b | ||
|
|
1d4052b839 | ||
|
|
6a5d6e6617 | ||
|
|
f55cc6066f | ||
|
|
527714e434 | ||
|
|
8a1633ffa3 | ||
|
|
56b2ab9c4f | ||
|
|
d330e2eb9d | ||
|
|
b55e7cacb3 | ||
|
|
c70375db06 | ||
|
|
2c23021d40 | ||
|
|
84a4ef4539 | ||
|
|
7f3db0549b | ||
|
|
de0e1784a3 | ||
|
|
5a8798638e | ||
|
|
14da49728c | ||
|
|
55423b2d09 | ||
|
|
596106247b | ||
|
|
5472d90368 | ||
|
|
fcf58413fc | ||
|
|
0d03b91753 | ||
|
|
2fd088e4d6 | ||
|
|
c6933198b2 | ||
|
|
210e684a22 | ||
|
|
53cc4b6ef3 | ||
|
|
d58d138a68 | ||
|
|
c0199a2b76 | ||
|
|
badb1905ce | ||
|
|
735c2dce7b | ||
|
|
ffae3f246f | ||
|
|
31b424f89f | ||
|
|
3b7acc3a90 | ||
|
|
7e66d1ac7f | ||
|
|
a613da069e | ||
|
|
40b73c6589 | ||
|
|
ef16ca83a2 | ||
|
|
76bf1d0d3f | ||
|
|
3d5ccf25d1 | ||
|
|
36fcb713d9 | ||
|
|
e306631850 | ||
|
|
17400fa886 | ||
|
|
c6dc628616 | ||
|
|
f974653e73 | ||
|
|
b83880a8a3 | ||
|
|
ee4d8f52df | ||
|
|
3854b75c6e | ||
|
|
07c3173506 | ||
|
|
2894ab1b96 | ||
|
|
99995ea882 | ||
|
|
d6560fbbe4 | ||
|
|
7205583104 | ||
|
|
8479a16d3d | ||
|
|
409aad4794 | ||
|
|
a29d009e5c | ||
|
|
6772419ccf | ||
|
|
38746ee743 | ||
|
|
c5cb3ffa90 | ||
|
|
e119056267 | ||
|
|
590ad3de37 | ||
|
|
6cd412de88 | ||
|
|
33ca0242b1 | ||
|
|
68d3f10888 | ||
|
|
7a844aac84 | ||
|
|
6f2bb9fd9e | ||
|
|
12e47993a4 | ||
|
|
b0396b77bd | ||
|
|
07c2314376 | ||
|
|
cee140e49f | ||
|
|
a3963af6e7 | ||
|
|
8ff28418b3 | ||
|
|
08a5c2f2b3 | ||
|
|
286bd5b19e | ||
|
|
59f3c2e3ad | ||
|
|
ea1b394061 | ||
|
|
5dc1af76e8 | ||
|
|
771bbd834b | ||
|
|
418724f860 | ||
|
|
2ecf48bc60 | ||
|
|
d19b942d2c | ||
|
|
08e724759d | ||
|
|
80031bc80b | ||
|
|
beb53c672c | ||
|
|
0dc75bf737 | ||
|
|
59008fb964 | ||
|
|
b119e5ee00 | ||
|
|
01d0f4c72a | ||
|
|
9fe3f1a4db | ||
|
|
60bf112ebd | ||
|
|
45aa443889 | ||
|
|
08f4922860 | ||
|
|
899970405a | ||
|
|
b4a3960e43 | ||
|
|
0514e62d78 | ||
|
|
3621e58d4c | ||
|
|
506b24026f | ||
|
|
98b67f5d56 | ||
|
|
33c95aa0e8 | ||
|
|
7d8f86bb6c | ||
|
|
d6717cc58b | ||
|
|
673f8e3b2a | ||
|
|
cae87ba414 | ||
|
|
13362fd53e | ||
|
|
d9f2ba0665 | ||
|
|
64a0f90a9a | ||
|
|
88300910a2 | ||
|
|
7face43d54 | ||
|
|
6a9f35ce2a | ||
|
|
effe76f251 | ||
|
|
58d5539cb8 | ||
|
|
d956ee06d0 | ||
|
|
8ddf91d13b | ||
|
|
3f65cd4f6d | ||
|
|
3ffdbf9d2b | ||
|
|
a51754b0e3 | ||
|
|
e8ee21e567 | ||
|
|
420f070035 | ||
|
|
c78cbc04d3 | ||
|
|
dddf565fbe | ||
|
|
0516ca810d | ||
|
|
fac67696a9 | ||
|
|
c62d4bd3fd | ||
|
|
877e1d4992 | ||
|
|
35b5cddd95 | ||
|
|
a86fb128d9 | ||
|
|
983daae971 | ||
|
|
9b687e3286 | ||
|
|
abfd0ffe35 | ||
|
|
407afa1d8c | ||
|
|
9bafe97ef6 | ||
|
|
cb151e79d8 | ||
|
|
7e6230d7b0 | ||
|
|
7d95de389a | ||
|
|
2ce187bc98 | ||
|
|
100671265e | ||
|
|
965d5af631 | ||
|
|
a19f356a66 | ||
|
|
a520357a23 | ||
|
|
d92d130a7c | ||
|
|
c8dd7d2f04 | ||
|
|
b85b589675 | ||
|
|
9b4cbed28f | ||
|
|
6b1794d32f | ||
|
|
aefe4b6849 | ||
|
|
a68395174a | ||
|
|
8a56e789b7 | ||
|
|
06ef146c5b | ||
|
|
4121cbd400 | ||
|
|
2d3bb8798d | ||
|
|
b7b6ac0f87 | ||
|
|
e5fb65d75e | ||
|
|
290b73f3d9 | ||
|
|
f717e87306 | ||
|
|
b80ac7c60d | ||
|
|
997a4732ec | ||
|
|
6e08f11578 | ||
|
|
87c4814e6f | ||
|
|
2e0e009719 | ||
|
|
77399038e9 | ||
|
|
fae66619fb | ||
|
|
d979b9ec0c | ||
|
|
975319a65d | ||
|
|
7b5aa4ba2d | ||
|
|
670739c82b | ||
|
|
8511bd15a8 | ||
|
|
06d3c89274 | ||
|
|
e13f3eb2f1 | ||
|
|
001918d613 | ||
|
|
c859c3aa40 | ||
|
|
2bce019677 | ||
|
|
6ba3386157 | ||
|
|
51e66d98f9 | ||
|
|
6484804649 | ||
|
|
b2a05c099d | ||
|
|
898331da4c | ||
|
|
9044e782b2 | ||
|
|
aedb985941 | ||
|
|
9ba22e4058 | ||
|
|
ab0afb61ae | ||
|
|
bf00ba0adc | ||
|
|
d564cf1b62 | ||
|
|
544dfb3b24 | ||
|
|
21eeab6c3c | ||
|
|
cf9af910be | ||
|
|
e2e65f93af | ||
|
|
b271567428 | ||
|
|
a88a295d9a | ||
|
|
38ba31ca7d | ||
|
|
1c8ff2493b | ||
|
|
5c9f62e6b6 | ||
|
|
6fd8018c52 | ||
|
|
d900ddf5f1 | ||
|
|
e3a8d311ce | ||
|
|
8bbc3d5470 | ||
|
|
00cf987cec | ||
|
|
7e5dcae64a | ||
|
|
cb67237d7c | ||
|
|
4be848c440 | ||
|
|
b33422b066 | ||
|
|
77418684da | ||
|
|
08cf9f7774 | ||
|
|
9f2fabf9c0 | ||
|
|
e2e1c7be44 | ||
|
|
ba030ba197 | ||
|
|
a4f37bdc16 | ||
|
|
f6c7b00562 | ||
|
|
b951614f7c | ||
|
|
f1dfdb3494 | ||
|
|
ffbd25b13b | ||
|
|
4f03fe2420 | ||
|
|
ffdb386afa | ||
|
|
9eeeaa79d0 | ||
|
|
4163142d1e | ||
|
|
71cddc67e0 | ||
|
|
2cbc4eb54f | ||
|
|
fc94fb4221 | ||
|
|
85ed41c590 | ||
|
|
59f889a200 | ||
|
|
7dc476fe02 | ||
|
|
f5070f1ff1 | ||
|
|
15283e13f0 | ||
|
|
0cde5bf8e7 | ||
|
|
fe3a1a023d | ||
|
|
369dcff2bd | ||
|
|
ed341a12b1 | ||
|
|
00e523e268 | ||
|
|
4e25aedbe7 | ||
|
|
45bd3cbb7c | ||
|
|
8e34bc2130 | ||
|
|
457fc96332 | ||
|
|
e2186401bf | ||
|
|
a19d916ef0 | ||
|
|
42bc7dc644 | ||
|
|
f9d6308154 | ||
|
|
dcb6627fb1 | ||
|
|
1d5c71d617 | ||
|
|
d5157fb868 | ||
|
|
98096845dc | ||
|
|
28ac97f4c9 | ||
|
|
2fc39e3979 | ||
|
|
9c89c3c2b8 | ||
|
|
15c7747f48 | ||
|
|
940d4b7fd4 | ||
|
|
a1c8ce754e | ||
|
|
5e6364850a | ||
|
|
8df52e7595 | ||
|
|
1e004d6700 | ||
|
|
4570f9cd38 | ||
|
|
601cd39ba4 | ||
|
|
923475fab2 | ||
|
|
21ce5dfad4 | ||
|
|
2bd3592aad | ||
|
|
44f083ca23 | ||
|
|
cc7271f0a2 | ||
|
|
11a1a1134d | ||
|
|
70cfa13e67 | ||
|
|
291f90b2b2 | ||
|
|
d0ae7eb841 | ||
|
|
8444102cca | ||
|
|
5a1c052c77 | ||
|
|
fb7eeece54 | ||
|
|
d479bcece9 | ||
|
|
deefac2347 | ||
|
|
53808a04b7 | ||
|
|
a1785c2658 | ||
|
|
601cf46b1e | ||
|
|
6bba1b1cbd | ||
|
|
ab0122420b | ||
|
|
74ae2aab91 | ||
|
|
c5fa131a44 | ||
|
|
8dcf4dda9f | ||
|
|
cd59125ada | ||
|
|
d284eb6738 | ||
|
|
a78cc943cc | ||
|
|
7ddeda1595 | ||
|
|
f02dfa5e14 | ||
|
|
b2a4f20381 | ||
|
|
dec3c354f0 | ||
|
|
2ee581d48d | ||
|
|
d74c3a3fc5 | ||
|
|
405d9f0c09 | ||
|
|
db25c5babe | ||
|
|
d5c90ab8da | ||
|
|
81a5c143d6 | ||
|
|
18c6edd310 | ||
|
|
a1d7a0d9ca | ||
|
|
5d5078534d | ||
|
|
537a968e2e | ||
|
|
ebf2176618 | ||
|
|
f435f8eff5 | ||
|
|
4520d1d29f | ||
|
|
f8c16edaae | ||
|
|
ea86065d99 | ||
|
|
ed5c7b126c | ||
|
|
806e015823 | ||
|
|
41c4cbc96c | ||
|
|
143a0b2c41 | ||
|
|
f582db3f11 | ||
|
|
103855ca50 | ||
|
|
70c6fe4c68 | ||
|
|
c85d45050f | ||
|
|
16a39ac76a | ||
|
|
fdc7519db0 | ||
|
|
83af0c5ec7 | ||
|
|
ee22686bff | ||
|
|
7dc1f9736a | ||
|
|
6e2aa37d75 | ||
|
|
fbaa8a31cf | ||
|
|
fa89a5ad24 | ||
|
|
464deaf35e | ||
|
|
9cc8f50ff7 | ||
|
|
c17a1184cc | ||
|
|
b5004739c3 | ||
|
|
d01c0fa72b | ||
|
|
bb9f3d5aab | ||
|
|
b23daa6735 | ||
|
|
bb8b0125b6 | ||
|
|
8e5f44f998 | ||
|
|
9015af4902 | ||
|
|
7902a091a9 | ||
|
|
615357befb | ||
|
|
34b26c81dc | ||
|
|
ea99a54e1b | ||
|
|
f726826391 | ||
|
|
dc5148c856 | ||
|
|
373c11ffee | ||
|
|
bea1f019b4 | ||
|
|
29c98777e9 | ||
|
|
9eb5029128 | ||
|
|
33607796d1 | ||
|
|
f23fa0c335 | ||
|
|
e6cfecfc1a | ||
|
|
44346d5b33 | ||
|
|
19da68884b | ||
|
|
544349de8d | ||
|
|
6f90b1a0e3 | ||
|
|
fbbb9163d7 | ||
|
|
445581eefa | ||
|
|
096c036ea2 | ||
|
|
b2e5801426 | ||
|
|
41061854f1 | ||
|
|
d91e625973 | ||
|
|
ec5b59a00c | ||
|
|
172790b18f | ||
|
|
de0e6ee955 | ||
|
|
69d7ff2ced | ||
|
|
3c237eac91 | ||
|
|
694c195024 | ||
|
|
c1f0281030 | ||
|
|
fa218cbcbd | ||
|
|
a89700acc2 | ||
|
|
82fb24e275 | ||
|
|
eef67a9383 | ||
|
|
1180d9fa2c | ||
|
|
57734f1d3c | ||
|
|
dace750829 | ||
|
|
f359a7167a | ||
|
|
39c0759d1b | ||
|
|
fee90334fb | ||
|
|
80db6e50ab | ||
|
|
1fa6c2d842 | ||
|
|
8b39c4c22c | ||
|
|
4b6fd5b5a8 | ||
|
|
f2d1909b6d | ||
|
|
7917fb96dc | ||
|
|
ad5fae98f1 | ||
|
|
8412eb36fe | ||
|
|
822455383b | ||
|
|
2d4357e4c0 |
50
.github/workflows/docker-HEAD.yml
vendored
50
.github/workflows/docker-HEAD.yml
vendored
@@ -1,50 +0,0 @@
|
||||
name: Publish Scrypted (git HEAD)
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
node: ["16-bullseye"]
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to Github Container Registry
|
||||
uses: docker/login-action@v1
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image (scrypted)
|
||||
uses: docker/build-push-action@v2
|
||||
with:
|
||||
build-args: BASE=${{ matrix.node }}
|
||||
context: .
|
||||
file: docker/Dockerfile.HEAD
|
||||
platforms: linux/amd64,linux/arm64,linux/armhf
|
||||
push: true
|
||||
tags: |
|
||||
koush/scrypted:HEAD
|
||||
ghcr.io/koush/scrypted:HEAD
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
57
.github/workflows/docker-common.yml
vendored
57
.github/workflows/docker-common.yml
vendored
@@ -2,54 +2,69 @@ name: Publish Scrypted Common
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
release:
|
||||
types: [published]
|
||||
schedule:
|
||||
# publish the common base once a month.
|
||||
- cron: '30 8 2 * *'
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
build:
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: self-hosted
|
||||
strategy:
|
||||
matrix:
|
||||
NODE_VERSION: ["18"]
|
||||
BUILDPACK_DEPS_BASE: ["bullseye"]
|
||||
BASE: ["jammy"]
|
||||
FLAVOR: ["full", "lite", "thin"]
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v2
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v1
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: 192.168.2.124
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: 192.168.2.119
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v1
|
||||
|
||||
uses: docker/setup-buildx-action@v2
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
append: |
|
||||
- endpoint: ssh://koush@192.168.2.124
|
||||
platforms: linux/arm64
|
||||
- endpoint: ssh://koush@192.168.2.119
|
||||
platforms: linux/armhf
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
username: ${{ secrets.DOCKER_USERNAME }}
|
||||
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||
|
||||
- name: Login to Github Container Registry
|
||||
uses: docker/login-action@v1
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image (scrypted-common)
|
||||
uses: docker/build-push-action@v2
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
build-args: NODE_VERSION=${{ matrix.NODE_VERSION }}
|
||||
context: docker/
|
||||
file: docker/Dockerfile.${{ matrix.FLAVOR }}
|
||||
platforms: linux/amd64,linux/arm64,linux/armhf
|
||||
build-args: |
|
||||
NODE_VERSION=${{ matrix.NODE_VERSION }}
|
||||
BASE=${{ matrix.BASE }}
|
||||
context: install/docker/
|
||||
file: install/docker/Dockerfile.${{ matrix.FLAVOR }}
|
||||
platforms: linux/amd64,linux/armhf,linux/arm64
|
||||
push: true
|
||||
tags: |
|
||||
koush/scrypted-common:${{ matrix.NODE_VERSION }}-${{ matrix.BUILDPACK_DEPS_BASE }}-${{ matrix.FLAVOR }}
|
||||
# ${{ matrix.NODE_VERSION == '16-bullseye' && 'koush/scrypted-common:latest' || '' }}
|
||||
koush/scrypted-common:${{ matrix.NODE_VERSION }}-${{ matrix.BASE }}-${{ matrix.FLAVOR }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
89
.github/workflows/docker.yml
vendored
89
.github/workflows/docker.yml
vendored
@@ -1,48 +1,65 @@
|
||||
name: Publish Scrypted
|
||||
name: Publish Scrypted Docker Image
|
||||
|
||||
on:
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
docker_tag:
|
||||
description: 'Docker Tag'
|
||||
tag:
|
||||
description: "The npm tag used to build the Docker image. The tag will be resolved as a specific version on npm, and that will be used to version the docker image."
|
||||
required: true
|
||||
package_version:
|
||||
description: 'Package Version'
|
||||
publish_tag:
|
||||
description: "The versioned tag for the published Docker image. NPM will use the minor version, Docker should only specify a patch version."
|
||||
required: false
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
push_to_registry:
|
||||
build:
|
||||
name: Push Docker image to Docker Hub
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: self-hosted
|
||||
strategy:
|
||||
matrix:
|
||||
BASE: ["18-bullseye-full", "18-bullseye-lite", "18-bullseye-thin"]
|
||||
BASE: ["18-jammy-full", "18-jammy-lite", "18-jammy-thin"]
|
||||
SUPERVISOR: ["", ".s6"]
|
||||
steps:
|
||||
- name: Check out the repo
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: get-npm-version
|
||||
id: package-version
|
||||
uses: martinbeentjes/npm-get-version-action@master
|
||||
|
||||
- name: NPM Package Request
|
||||
id: npm-request
|
||||
uses: fjogeleit/http-request-action@v1
|
||||
with:
|
||||
path: server
|
||||
url: 'https://registry.npmjs.org/@scrypted/server'
|
||||
method: 'GET'
|
||||
|
||||
- name: Print Version
|
||||
run: echo "Version ${{ github.event.inputs.package_version || steps.package-version.outputs.current-version }}"
|
||||
|
||||
- name: Get current date
|
||||
id: date
|
||||
run: echo "::set-output name=date::$(date +'%Y-%m-%d')"
|
||||
- name: Set NPM Version
|
||||
id: package-version
|
||||
run: echo "NPM_VERSION=${{ fromJson(steps.npm-request.outputs.response)['dist-tags'][ github.event.inputs.tag] }}" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Set up QEMU
|
||||
uses: docker/setup-qemu-action@v2
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: 192.168.2.124
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up SSH
|
||||
uses: MrSquaare/ssh-setup-action@v2
|
||||
with:
|
||||
host: 192.168.2.119
|
||||
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v2
|
||||
|
||||
with:
|
||||
platforms: linux/amd64
|
||||
append: |
|
||||
- endpoint: ssh://koush@192.168.2.124
|
||||
platforms: linux/arm64
|
||||
- endpoint: ssh://koush@192.168.2.119
|
||||
platforms: linux/armhf
|
||||
|
||||
- name: Login to Docker Hub
|
||||
uses: docker/login-action@v2
|
||||
with:
|
||||
@@ -56,25 +73,31 @@ jobs:
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Build and push Docker image (scrypted)
|
||||
uses: docker/build-push-action@v3
|
||||
- name: Build and push Docker image
|
||||
uses: docker/build-push-action@v4
|
||||
with:
|
||||
build-args: |
|
||||
BASE=${{ matrix.BASE }}
|
||||
SCRYPTED_INSTALL_VERSION=${{ github.event.inputs.package_version }}
|
||||
context: docker/
|
||||
file: docker/Dockerfile${{ matrix.SUPERVISOR }}
|
||||
SCRYPTED_INSTALL_VERSION=${{ steps.package-version.outputs.NPM_VERSION }}
|
||||
context: install/docker/
|
||||
file: install/docker/Dockerfile${{ matrix.SUPERVISOR }}
|
||||
platforms: linux/amd64,linux/arm64,linux/armhf
|
||||
push: true
|
||||
tags: |
|
||||
${{ format('koush/scrypted:{0}{1}-v{2}', matrix.BASE, matrix.SUPERVISOR, github.event.inputs.package_version || steps.package-version.outputs.current-version) }}
|
||||
${{ matrix.BASE == '18-bullseye-full' && matrix.SUPERVISOR == '.s6' && format('koush/scrypted:{0}', github.event.inputs.docker_tag) || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-lite' && matrix.SUPERVISOR == '' && 'koush/scrypted:lite' || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-thin' && matrix.SUPERVISOR == '' && 'koush/scrypted:thin' || '' }}
|
||||
${{ format('koush/scrypted:{0}{1}-v{2}', matrix.BASE, matrix.SUPERVISOR, github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
|
||||
${{ matrix.BASE == '18-jammy-full' && matrix.SUPERVISOR == '.s6' && format('koush/scrypted:{0}', github.event.inputs.tag) || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '18-jammy-full' && matrix.SUPERVISOR == '' && 'koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '18-jammy-lite' && matrix.SUPERVISOR == '' && 'koush/scrypted:lite' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '18-jammy-thin' && matrix.SUPERVISOR == '' && 'koush/scrypted:thin' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '18-jammy-lite' && matrix.SUPERVISOR == '.s6' && 'koush/scrypted:lite-s6' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '18-jammy-thin' && matrix.SUPERVISOR == '.s6' && 'koush/scrypted:thin-s6' || '' }}
|
||||
|
||||
${{ format('ghcr.io/koush/scrypted:{0}{1}-v{2}', matrix.BASE, matrix.SUPERVISOR, github.event.inputs.package_version || steps.package-version.outputs.current-version) }}
|
||||
${{ matrix.BASE == '18-bullseye-full' && matrix.SUPERVISOR == '.s6' && format('ghcr.io/koush/scrypted:{0}', github.event.inputs.docker_tag) || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-lite' && matrix.SUPERVISOR == '' && 'ghcr.io/koush/scrypted:lite' || '' }}
|
||||
${{ github.event.inputs.docker_tag == 'latest' && matrix.BASE == '18-bullseye-thin' && matrix.SUPERVISOR == '' && 'ghcr.io/koush/scrypted:thin' || '' }}
|
||||
${{ format('ghcr.io/koush/scrypted:{0}{1}-v{2}', matrix.BASE, matrix.SUPERVISOR, github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}
|
||||
${{ matrix.BASE == '18-jammy-full' && matrix.SUPERVISOR == '.s6' && format('ghcr.io/koush/scrypted:{0}', github.event.inputs.tag) || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '18-jammy-full' && matrix.SUPERVISOR == '' && 'ghcr.io/koush/scrypted:full' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '18-jammy-lite' && matrix.SUPERVISOR == '' && 'ghcr.io/koush/scrypted:lite' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '18-jammy-thin' && matrix.SUPERVISOR == '' && 'ghcr.io/koush/scrypted:thin' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '18-jammy-lite' && matrix.SUPERVISOR == '.s6' && 'ghcr.io/koush/scrypted:lite-s6' || '' }}
|
||||
${{ github.event.inputs.tag == 'latest' && matrix.BASE == '18-jammy-thin' && matrix.SUPERVISOR == '.s6' && 'ghcr.io/koush/scrypted:thin-s6' || '' }}
|
||||
cache-from: type=gha
|
||||
cache-to: type=gha,mode=max
|
||||
|
||||
60
.github/workflows/test.yml
vendored
Normal file
60
.github/workflows/test.yml
vendored
Normal file
@@ -0,0 +1,60 @@
|
||||
name: Test
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: ["main"]
|
||||
paths: ["docker/**", ".github/workflows/test.yml"]
|
||||
pull_request:
|
||||
paths: ["docker/**", ".github/workflows/test.yml"]
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
test_linux_local:
|
||||
name: Test Linux local installation
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run install script
|
||||
run: |
|
||||
cat ./install/local/install-scrypted-dependencies-linux.sh | sudo SERVICE_USER=$USER bash
|
||||
|
||||
- name: Test server is running
|
||||
run: |
|
||||
systemctl status scrypted.service
|
||||
curl -k --retry 20 --retry-all-errors --retry-max-time 600 https://localhost:10443/
|
||||
|
||||
test_mac_local:
|
||||
name: Test Mac local installation
|
||||
runs-on: macos-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run install script
|
||||
run: |
|
||||
mkdir -p ~/.scrypted
|
||||
bash ./install/local/install-scrypted-dependencies-mac.sh
|
||||
|
||||
- name: Test server is running
|
||||
run: |
|
||||
curl -k --retry 20 --retry-all-errors --retry-max-time 600 https://localhost:10443/
|
||||
|
||||
test_windows_local:
|
||||
name: Test Windows local installation
|
||||
runs-on: windows-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Run install script
|
||||
run: |
|
||||
.\install\local\install-scrypted-dependencies-win.ps1
|
||||
|
||||
- name: Test server is running
|
||||
run: |
|
||||
curl -k --retry 20 --retry-all-errors --retry-max-time 600 https://localhost:10443/
|
||||
13
.gitmodules
vendored
13
.gitmodules
vendored
@@ -1,6 +1,3 @@
|
||||
[submodule "plugins/homekit/HAP-NodeJS"]
|
||||
path = external/HAP-NodeJS
|
||||
url = ../../koush/HAP-NodeJS
|
||||
[submodule "plugins/unifi-protect/src/unifi-protect"]
|
||||
path = external/unifi-protect
|
||||
url = ../../koush/unifi-protect.git
|
||||
@@ -19,6 +16,7 @@
|
||||
[submodule "external/ring-client-api"]
|
||||
path = external/ring-client-api
|
||||
url = ../../koush/ring
|
||||
branch = fork
|
||||
[submodule "plugins/vscode-typescript"]
|
||||
path = plugins/vscode-typescript
|
||||
url = ../../koush/scrypted-vscode-typescript/
|
||||
@@ -28,21 +26,12 @@
|
||||
[submodule "plugins/zwave/file-stream-rotator"]
|
||||
path = plugins/zwave/file-stream-rotator
|
||||
url = ../../koush/file-stream-rotator.git
|
||||
[submodule "external/push-receiver"]
|
||||
path = external/push-receiver
|
||||
url = ../../koush/push-receiver.git
|
||||
[submodule "sdk/developer.scrypted.app"]
|
||||
path = sdk/developer.scrypted.app
|
||||
url = ../../koush/developer.scrypted.app
|
||||
[submodule "plugins/sample-cameraprovider"]
|
||||
path = plugins/sample-cameraprovider
|
||||
url = ../../koush/scrypted-sample-cameraprovider
|
||||
[submodule "plugins/objectdetector/node-moving-things-tracker"]
|
||||
path = plugins/objectdetector/node-moving-things-tracker
|
||||
url = ../../koush/node-moving-things-tracker.git
|
||||
[submodule "plugins/tensorflow-lite/sort_oh"]
|
||||
path = plugins/tensorflow-lite/sort_oh
|
||||
url = ../../koush/sort_oh.git
|
||||
[submodule "plugins/cloud/node-nat-upnp"]
|
||||
path = plugins/cloud/node-nat-upnp
|
||||
url = ../../koush/node-nat-upnp.git
|
||||
|
||||
@@ -23,6 +23,7 @@ Select the appropriate guide. After installation is finished, remember to visit
|
||||
* Windows
|
||||
* [Local Installation](https://github.com/koush/scrypted/wiki/Installation:-Windows)
|
||||
* [WSL2 Installation](https://github.com/koush/scrypted/wiki/Installation:-WSL2-Windows)
|
||||
* [Home Assistant OS](https://github.com/koush/scrypted/wiki/Installation:-Home-Assistant-OS)
|
||||
<!-- * Docker Desktop is [not supported](https://github.com/koush/scrypted/wiki/Installation:-Docker-Desktop). -->
|
||||
* [ReadyNAS: Docker](https://github.com/koush/scrypted/wiki/Installation:-Docker-ReadyNAS)
|
||||
* [Synology: Docker](https://github.com/koush/scrypted/wiki/Installation:-Docker-Synology-NAS)
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
export class Deferred<T> {
|
||||
finished = false;
|
||||
resolve!: (value: T|PromiseLike<T>) => void;
|
||||
reject!: (error: Error) => void;
|
||||
resolve!: (value: T|PromiseLike<T>) => this;
|
||||
reject!: (error: Error) => this;
|
||||
promise: Promise<T> = new Promise((resolve, reject) => {
|
||||
this.resolve = v => {
|
||||
this.finished = true;
|
||||
resolve(v);
|
||||
return this;
|
||||
};
|
||||
this.reject = e => {
|
||||
this.finished = true;
|
||||
reject(e);
|
||||
return this;
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
@@ -2,14 +2,15 @@ import type { TranspileOptions } from "typescript";
|
||||
import sdk, { ScryptedDeviceBase, MixinDeviceBase, ScryptedInterface, ScryptedDeviceType } from "@scrypted/sdk";
|
||||
import vm from "vm";
|
||||
import fs from 'fs';
|
||||
import { newThread } from '@scrypted/server/src/threading';
|
||||
import { ScriptDevice } from "./monaco/script-device";
|
||||
import { ScryptedInterfaceDescriptors } from "@scrypted/sdk";
|
||||
import fetch from 'node-fetch-commonjs';
|
||||
import { PluginAPIProxy } from '../../../server/src/plugin/plugin-api';
|
||||
import { SystemManagerImpl } from '../../../server/src/plugin/system';
|
||||
|
||||
const { systemManager, deviceManager, mediaManager, endpointManager } = sdk;
|
||||
|
||||
function tsCompile(source: string, options: TranspileOptions = null): string {
|
||||
export async function tsCompile(source: string, options: TranspileOptions = null): Promise<string> {
|
||||
const ts = require("typescript");
|
||||
const { ScriptTarget } = ts;
|
||||
|
||||
@@ -25,27 +26,6 @@ function tsCompile(source: string, options: TranspileOptions = null): string {
|
||||
return ts.transpileModule(source, options).outputText;
|
||||
}
|
||||
|
||||
async function tsCompileThread(source: string, options: TranspileOptions = null): Promise<string> {
|
||||
return newThread({
|
||||
source, options,
|
||||
customRequire: '__webpack_require__',
|
||||
}, ({ source, options }) => {
|
||||
const ts = global.require("typescript");
|
||||
const { ScriptTarget } = ts;
|
||||
|
||||
// Default options -- you could also perform a merge, or use the project tsconfig.json
|
||||
if (null === options) {
|
||||
options = {
|
||||
compilerOptions: {
|
||||
target: ScriptTarget.ESNext,
|
||||
module: ts.ModuleKind.CommonJS
|
||||
}
|
||||
};
|
||||
}
|
||||
return ts.transpileModule(source, options).outputText;
|
||||
});
|
||||
}
|
||||
|
||||
function getTypeDefs() {
|
||||
const scryptedTypesDefs = fs.readFileSync('@types/sdk/types.d.ts').toString();
|
||||
const scryptedIndexDefs = fs.readFileSync('@types/sdk/index.d.ts').toString();
|
||||
@@ -61,14 +41,27 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
|
||||
}, extraLibs);
|
||||
const allScripts = Object.values(libs).join('\n').toString() + script;
|
||||
let compiled: string;
|
||||
const worker = sdk.fork<{
|
||||
tsCompile: typeof tsCompile,
|
||||
}>();
|
||||
worker.worker.on('error', () => { })
|
||||
try {
|
||||
compiled = await tsCompileThread(allScripts);
|
||||
const result = await worker.result;
|
||||
compiled = await result.tsCompile(allScripts);
|
||||
}
|
||||
catch (e) {
|
||||
device.log.e('Error compiling typescript.');
|
||||
device.console.error(e);
|
||||
throw e;
|
||||
}
|
||||
finally {
|
||||
worker.worker.terminate();
|
||||
}
|
||||
|
||||
const smProxy = new SystemManagerImpl();
|
||||
smProxy.state = systemManager.getSystemState();
|
||||
const apiProxy = new PluginAPIProxy(sdk.pluginHostAPI);
|
||||
smProxy.api = apiProxy;
|
||||
|
||||
const allParams = Object.assign({}, params, {
|
||||
sdk,
|
||||
@@ -76,7 +69,7 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
|
||||
fetch,
|
||||
ScryptedDeviceBase,
|
||||
MixinDeviceBase,
|
||||
systemManager,
|
||||
systemManager: smProxy,
|
||||
deviceManager,
|
||||
endpointManager,
|
||||
mediaManager,
|
||||
@@ -111,6 +104,7 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
|
||||
return {
|
||||
value,
|
||||
defaultExport,
|
||||
apiProxy,
|
||||
};
|
||||
}
|
||||
catch (e) {
|
||||
|
||||
@@ -4,8 +4,11 @@ import { EventEmitter } from 'events';
|
||||
import { Server } from 'net';
|
||||
import { Duplex } from 'stream';
|
||||
import { cloneDeep } from './clone-deep';
|
||||
import { Deferred } from "./deferred";
|
||||
import { listenZeroSingleClient } from './listen-cluster';
|
||||
import { ffmpegLogInitialOutput, safeKillFFmpeg, safePrintFFmpegArguments } from './media-helpers';
|
||||
import { createRtspParser } from "./rtsp-server";
|
||||
import { parseSdp } from "./sdp-utils";
|
||||
import { StreamChunk, StreamParser } from './stream-parser';
|
||||
|
||||
const { mediaManager } = sdk;
|
||||
@@ -57,9 +60,13 @@ export async function parseResolution(cp: ChildProcess) {
|
||||
}
|
||||
|
||||
async function parseInputToken(cp: ChildProcess, token: string) {
|
||||
let processed = 0;
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
cp.on('exit', () => reject(new Error('ffmpeg exited while waiting to parse stream information: ' + token)));
|
||||
const parser = (data: Buffer) => {
|
||||
processed += data.length;
|
||||
if (processed > 10000)
|
||||
return resolve(undefined);
|
||||
const stdout: string = data.toString().split('Output ')[0];
|
||||
const idx = stdout.lastIndexOf(`${token}: `);
|
||||
if (idx !== -1) {
|
||||
@@ -77,7 +84,11 @@ async function parseInputToken(cp: ChildProcess, token: string) {
|
||||
};
|
||||
cp.stdout.on('data', parser);
|
||||
cp.stderr.on('data', parser);
|
||||
});
|
||||
})
|
||||
.finally(() => {
|
||||
cp.stdout.removeAllListeners('data');
|
||||
cp.stderr.removeAllListeners('data');
|
||||
});
|
||||
}
|
||||
|
||||
export async function parseVideoCodec(cp: ChildProcess) {
|
||||
@@ -158,8 +169,6 @@ export async function startParserSession<T extends string>(ffmpegInput: FFmpegIn
|
||||
|
||||
const args = ffmpegInput.inputArguments.slice();
|
||||
|
||||
let needSdp = false;
|
||||
|
||||
const ensureActive = (killed: () => void) => {
|
||||
if (!isActive) {
|
||||
killed();
|
||||
@@ -211,11 +220,6 @@ export async function startParserSession<T extends string>(ffmpegInput: FFmpegIn
|
||||
}
|
||||
}
|
||||
|
||||
if (needSdp) {
|
||||
args.push('-sdp_file', `pipe:${pipeCount++}`);
|
||||
stdio.push('pipe');
|
||||
}
|
||||
|
||||
// start ffmpeg process with child process pipes
|
||||
args.unshift('-hide_banner');
|
||||
safePrintFFmpegArguments(console, args);
|
||||
@@ -225,20 +229,7 @@ export async function startParserSession<T extends string>(ffmpegInput: FFmpegIn
|
||||
ffmpegLogInitialOutput(console, cp, undefined, options?.storage);
|
||||
cp.on('exit', () => kill(new Error('ffmpeg exited')));
|
||||
|
||||
let sdp: Promise<Buffer[]>;
|
||||
if (needSdp) {
|
||||
sdp = new Promise<Buffer[]>(resolve => {
|
||||
const ret: Buffer[] = [];
|
||||
cp.stdio[pipeCount - 1].on('data', buffer => {
|
||||
ret.push(buffer);
|
||||
resolve(ret);
|
||||
});
|
||||
})
|
||||
}
|
||||
else {
|
||||
sdp = Promise.resolve([]);
|
||||
}
|
||||
|
||||
const deferredStart = new Deferred<void>();
|
||||
// now parse the created pipes
|
||||
const start = () => {
|
||||
for (const p of startParsers) {
|
||||
@@ -257,6 +248,7 @@ export async function startParserSession<T extends string>(ffmpegInput: FFmpegIn
|
||||
const { resetActivityTimer } = setupActivityTimer(container, kill, events, options?.timeout);
|
||||
|
||||
for await (const chunk of parser.parse(pipe as any, parseInt(inputVideoResolution?.[2]), parseInt(inputVideoResolution?.[3]))) {
|
||||
await deferredStart.promise;
|
||||
events.emit(container, chunk);
|
||||
resetActivityTimer();
|
||||
}
|
||||
@@ -268,13 +260,22 @@ export async function startParserSession<T extends string>(ffmpegInput: FFmpegIn
|
||||
});
|
||||
};
|
||||
|
||||
// tbh parsing stdout is super sketchy way of doing this.
|
||||
parseAudioCodec(cp).then(result => inputAudioCodec = result);
|
||||
parseResolution(cp).then(result => inputVideoResolution = result);
|
||||
await parseVideoCodec(cp).then(result => inputVideoCodec = result);
|
||||
const rtsp = (options.parsers as any).rtsp as ReturnType<typeof createRtspParser>;
|
||||
rtsp.sdp.then(sdp => {
|
||||
const parsed = parseSdp(sdp);
|
||||
const audio = parsed.msections.find(msection=>msection.type === 'audio');
|
||||
const video = parsed.msections.find(msection=>msection.type === 'video');
|
||||
inputVideoCodec = video?.codec;
|
||||
inputAudioCodec = audio?.codec;
|
||||
});
|
||||
|
||||
const sdp = rtsp.sdp.then(sdpString => [Buffer.from(sdpString)]);
|
||||
start();
|
||||
|
||||
return {
|
||||
start,
|
||||
start() {
|
||||
deferredStart.resolve();
|
||||
},
|
||||
sdp,
|
||||
get inputAudioCodec() {
|
||||
return inputAudioCodec;
|
||||
@@ -361,8 +362,7 @@ export interface RebroadcasterOptions {
|
||||
},
|
||||
}
|
||||
|
||||
export async function handleRebroadcasterClient(duplex: Promise<Duplex> | Duplex, options?: RebroadcasterOptions) {
|
||||
const socket = await duplex;
|
||||
export function handleRebroadcasterClient(socket: Duplex, options?: RebroadcasterOptions) {
|
||||
const firstWriteData = (data: StreamChunk) => {
|
||||
if (data.startStream) {
|
||||
socket.write(data.startStream)
|
||||
|
||||
@@ -62,4 +62,4 @@ export async function bind(server: dgram.Socket, port: number) {
|
||||
}
|
||||
}
|
||||
|
||||
export { listenZero, listenZeroSingleClient } from "@scrypted/server/src/listen-zero";
|
||||
export { listenZero, listenZeroSingleClient, ListenZeroSingleClientTimeoutError } from "@scrypted/server/src/listen-zero";
|
||||
|
||||
@@ -250,7 +250,8 @@ export class BrowserSignalingSession implements RTCSignalingSession {
|
||||
function logSendCandidate(console: Console, type: string, session: RTCSignalingSession): RTCSignalingSendIceCandidate {
|
||||
return async (candidate) => {
|
||||
try {
|
||||
console.log(`${type} trickled candidate:`, candidate.sdpMLineIndex, candidate.candidate);
|
||||
if (localStorage.getItem('debugLog') === 'true')
|
||||
console.log(`${type} trickled candidate:`, candidate.sdpMLineIndex, candidate.candidate);
|
||||
await session.addIceCandidate(candidate);
|
||||
}
|
||||
catch (e) {
|
||||
@@ -297,7 +298,7 @@ export async function connectRTCSignalingClients(
|
||||
if (offerOptions?.offer && answerOptions?.offer)
|
||||
throw new Error('Both RTC clients have offers and can not negotiate. Consider implementing this in @scrypted/webrtc.');
|
||||
|
||||
if (offerOptions?.requiresOffer && answerOptions.requiresOffer)
|
||||
if (offerOptions?.requiresOffer && answerOptions?.requiresOffer)
|
||||
throw new Error('Both RTC clients require offers and can not negotiate.');
|
||||
|
||||
offerSetup.type = 'offer';
|
||||
@@ -308,11 +309,13 @@ export async function connectRTCSignalingClients(
|
||||
|
||||
const offer = await offerClient.createLocalDescription('offer', offerSetup as RTCAVSignalingSetup,
|
||||
disableTrickle ? undefined : answerQueue.queueSendCandidate);
|
||||
console.log('offer sdp', offer.sdp);
|
||||
if (localStorage.getItem('debugLog') === 'true')
|
||||
console.log('offer sdp', offer.sdp);
|
||||
await answerClient.setRemoteDescription(offer, answerSetup as RTCAVSignalingSetup);
|
||||
const answer = await answerClient.createLocalDescription('answer', answerSetup as RTCAVSignalingSetup,
|
||||
disableTrickle ? undefined : offerQueue.queueSendCandidate);
|
||||
console.log('answer sdp', answer.sdp);
|
||||
if (localStorage.getItem('debugLog') === 'true')
|
||||
console.log('answer sdp', answer.sdp);
|
||||
await offerClient.setRemoteDescription(answer, offerSetup as RTCAVSignalingSetup);
|
||||
offerQueue.flush();
|
||||
answerQueue.flush();
|
||||
|
||||
@@ -4,7 +4,7 @@ import { once } from 'events';
|
||||
import { BASIC } from 'http-auth-utils/dist/index';
|
||||
import { parseHTTPHeadersQuotedKeyValueSet } from 'http-auth-utils/dist/utils';
|
||||
import net from 'net';
|
||||
import { Duplex, Readable } from 'stream';
|
||||
import { Duplex, Readable, Writable } from 'stream';
|
||||
import tls from 'tls';
|
||||
import { Deferred } from './deferred';
|
||||
import { closeQuiet, createBindUdp, createBindZero, listenZeroSingleClient } from './listen-cluster';
|
||||
@@ -13,6 +13,7 @@ import { readLength, readLine } from './read-stream';
|
||||
import { MSection, parseSdp } from './sdp-utils';
|
||||
import { sleep } from './sleep';
|
||||
import { StreamChunk, StreamParser, StreamParserOptions } from './stream-parser';
|
||||
import { URL } from 'url';
|
||||
|
||||
const REQUIRED_WWW_AUTHENTICATE_KEYS = ['realm', 'nonce'];
|
||||
|
||||
@@ -47,6 +48,29 @@ export async function readMessage(client: Readable): Promise<string[]> {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export async function readBody(client: Readable, response: Headers) {
|
||||
const cl = parseInt(response['content-length']);
|
||||
if (cl)
|
||||
return readLength(client, cl)
|
||||
}
|
||||
|
||||
|
||||
export function writeMessage(client: Writable, messageLine: string, body: Buffer, headers: Headers, console?: Console) {
|
||||
let message = messageLine !== undefined ? `${messageLine}\r\n` : '';
|
||||
if (body)
|
||||
headers['Content-Length'] = body.length.toString();
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
message += `${key}: ${value}\r\n`;
|
||||
}
|
||||
message += '\r\n';
|
||||
client.write(message);
|
||||
console?.log('rtsp outgoing message\n', message);
|
||||
console?.log();
|
||||
if (body)
|
||||
client.write(body);
|
||||
}
|
||||
|
||||
// https://yumichan.net/video-processing/video-compression/introduction-to-h264-nal-unit/
|
||||
|
||||
export const H264_NAL_TYPE_RESERVED0 = 0;
|
||||
@@ -105,6 +129,16 @@ export function getNaluTypes(streamChunk: StreamChunk) {
|
||||
return getNaluTypesInNalu(streamChunk.chunks[streamChunk.chunks.length - 1].subarray(12))
|
||||
}
|
||||
|
||||
export function getNaluFragmentInformation(nalu: Buffer) {
|
||||
const naluType = nalu[0] & 0x1f;
|
||||
const fua = naluType === H264_NAL_TYPE_FU_A;
|
||||
return {
|
||||
fua,
|
||||
fuaStart: fua && !!(nalu[1] & 0x80),
|
||||
fuaEnd: fua && !!(nalu[1] & 0x40),
|
||||
}
|
||||
}
|
||||
|
||||
export function getNaluTypesInNalu(nalu: Buffer, fuaRequireStart = false, fuaRequireEnd = false) {
|
||||
const ret = new Set<number>();
|
||||
const naluType = nalu[0] & 0x1f;
|
||||
@@ -284,18 +318,7 @@ export class RtspBase {
|
||||
}
|
||||
|
||||
write(messageLine: string, headers: Headers, body?: Buffer) {
|
||||
let message = `${messageLine}\r\n`;
|
||||
if (body)
|
||||
headers['Content-Length'] = body.length.toString();
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
message += `${key}: ${value}\r\n`;
|
||||
}
|
||||
message += '\r\n';
|
||||
this.client.write(message);
|
||||
this.console?.log('rtsp outgoing message\n', message);
|
||||
this.console?.log();
|
||||
if (body)
|
||||
this.client.write(body);
|
||||
writeMessage(this.client, messageLine, body, headers, this.console);
|
||||
}
|
||||
|
||||
async readMessage(): Promise<string[]> {
|
||||
@@ -568,7 +591,7 @@ export class RtspClient extends RtspBase {
|
||||
const username = decodeURIComponent(authedUrl.username);
|
||||
const password = decodeURIComponent(authedUrl.password);
|
||||
|
||||
const strippedUrl = new URL(url);
|
||||
const strippedUrl = new URL(url.toString());
|
||||
strippedUrl.username = '';
|
||||
strippedUrl.password = '';
|
||||
|
||||
@@ -590,9 +613,7 @@ export class RtspClient extends RtspBase {
|
||||
}
|
||||
|
||||
async readBody(response: Headers) {
|
||||
const cl = parseInt(response['content-length']);
|
||||
if (cl)
|
||||
return readLength(this.client, cl)
|
||||
return readBody(this.client, response);
|
||||
}
|
||||
|
||||
async request(method: string, headers?: Headers, path?: string, body?: Buffer, authenticating?: boolean): Promise<RtspServerResponse> {
|
||||
@@ -660,7 +681,7 @@ export class RtspClient extends RtspBase {
|
||||
});
|
||||
}
|
||||
|
||||
async setup(options: RtspClientTcpSetupOptions | RtspClientUdpSetupOptions) {
|
||||
async setup(options: RtspClientTcpSetupOptions | RtspClientUdpSetupOptions, headers?: Headers) {
|
||||
const protocol = options.type === 'udp' ? '' : '/TCP';
|
||||
const client = options.type === 'udp' ? 'client_port' : 'interleaved';
|
||||
let port: number;
|
||||
@@ -676,9 +697,9 @@ export class RtspClient extends RtspBase {
|
||||
port = options.dgram.address().port;
|
||||
options.dgram.on('message', data => options.onRtp(undefined, data));
|
||||
}
|
||||
const headers: any = {
|
||||
headers = Object.assign({
|
||||
Transport: `RTP/AVP${protocol};unicast;${client}=${port}-${port + 1}`,
|
||||
};
|
||||
}, headers);
|
||||
const response = await this.request('SETUP', headers, options.path);
|
||||
let interleaved: {
|
||||
begin: number;
|
||||
|
||||
@@ -217,14 +217,12 @@ const acontrol = 'a=control:';
|
||||
const artpmap = 'a=rtpmap:';
|
||||
export function parseMSection(msection: string[]) {
|
||||
const control = msection.find(line => line.startsWith(acontrol))?.substring(acontrol.length);
|
||||
const rtpmapFirst = msection.find(line => line.startsWith(artpmap));
|
||||
const mline = parseMLine(msection[0]);
|
||||
|
||||
let codec = parseRtpMap(mline.type, rtpmapFirst).codec;
|
||||
|
||||
const rtpmaps = msection.filter(line => line.startsWith(artpmap)).map(line => parseRtpMap(mline.type, line));
|
||||
|
||||
const rawRtpmaps = msection.filter(line => line.startsWith(artpmap));
|
||||
const rtpmaps = rawRtpmaps.map(line => parseRtpMap(mline.type, line));
|
||||
const codec = parseRtpMap(mline.type, rawRtpmaps[0]).codec;
|
||||
let direction: string;
|
||||
|
||||
for (const checkDirection of ['sendonly', 'sendrecv', 'recvonly', 'inactive']) {
|
||||
const found = msection.find(line => line === 'a=' + checkDirection);
|
||||
if (found) {
|
||||
|
||||
56
common/test/rtsp-proxy.ts
Normal file
56
common/test/rtsp-proxy.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import net from 'net';
|
||||
import { listenZero } from '../src/listen-cluster';
|
||||
import { RtspClient, RtspServer } from '../src/rtsp-server';
|
||||
|
||||
async function main() {
|
||||
const server = net.createServer(async serverSocket => {
|
||||
const client = new RtspClient('rtsp://localhost:57594/911db962087f904d');
|
||||
await client.options();
|
||||
const describeResponse = await client.describe();
|
||||
const sdp = describeResponse.body.toString();
|
||||
const server = new RtspServer(serverSocket, sdp, true);
|
||||
const setupResponse = await server.handlePlayback();
|
||||
if (setupResponse !== 'play') {
|
||||
serverSocket.destroy();
|
||||
client.client.destroy();
|
||||
return;
|
||||
}
|
||||
console.log('playback handled');
|
||||
|
||||
let channel = 0;
|
||||
for (const track of Object.keys(server.setupTracks)) {
|
||||
const setupTrack = server.setupTracks[track];
|
||||
await client.setup({
|
||||
// type: 'udp',
|
||||
|
||||
type: 'tcp',
|
||||
port: channel,
|
||||
|
||||
path: setupTrack.control,
|
||||
onRtp(rtspHeader, rtp) {
|
||||
server.sendTrack(setupTrack.control, rtp, false);
|
||||
},
|
||||
});
|
||||
|
||||
channel += 2;
|
||||
}
|
||||
|
||||
|
||||
await client.play();
|
||||
console.log('client playing');
|
||||
await client.readLoop();
|
||||
});
|
||||
|
||||
let port: number;
|
||||
if (false) {
|
||||
port = await listenZero(server);
|
||||
}
|
||||
else {
|
||||
port = 5555;
|
||||
server.listen(5555)
|
||||
}
|
||||
|
||||
console.log(`rtsp://127.0.0.1:${port}`);
|
||||
}
|
||||
|
||||
main();
|
||||
@@ -1,78 +0,0 @@
|
||||
################################################################
|
||||
# THIS FILE IS GENERATED. DO NOT EDIT.
|
||||
################################################################
|
||||
################################################################
|
||||
# Begin section generated from template/Dockerfile.full.header
|
||||
# This common file will be used by both Docker and the linux
|
||||
# install script.
|
||||
################################################################
|
||||
ARG BUILDPACK_DEPS_BASE="bullseye"
|
||||
FROM buildpack-deps:${BUILDPACK_DEPS_BASE} as header
|
||||
|
||||
# switch to nvm?
|
||||
ARG NODE_VERSION=18
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
# Coral Edge TPU
|
||||
# https://coral.ai/docs/accelerator/get-started/#runtime-on-linux
|
||||
RUN echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | tee /etc/apt/sources.list.d/coral-edgetpu.list
|
||||
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
|
||||
RUN apt-get -y update
|
||||
RUN apt-get -y install libedgetpu1-std
|
||||
|
||||
RUN apt-get -y install software-properties-common apt-utils
|
||||
RUN apt-get -y update
|
||||
RUN apt-get -y upgrade
|
||||
|
||||
# base development stuff
|
||||
RUN apt-get -y install \
|
||||
build-essential \
|
||||
cmake \
|
||||
gcc \
|
||||
libgirepository1.0-dev \
|
||||
libglib2.0-dev \
|
||||
pkg-config
|
||||
|
||||
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
|
||||
RUN apt-get -y install \
|
||||
gstreamer1.0-tools libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-alsa
|
||||
|
||||
RUN bash -c 'if [ $(uname -m) == "x86_64" ]; then apt-get -y install gstreamer1.0-vaapi; fi'
|
||||
|
||||
# python native
|
||||
RUN apt-get -y install \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-gi \
|
||||
python3-gst-1.0 \
|
||||
python3-matplotlib \
|
||||
python3-numpy \
|
||||
python3-opencv \
|
||||
python3-pil \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
python3-skimage \
|
||||
python3-wheel
|
||||
|
||||
# python pip
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
RUN python3 -m pip install aiofiles debugpy typing_extensions typing psutil
|
||||
|
||||
################################################################
|
||||
# End section generated from template/Dockerfile.full.header
|
||||
################################################################
|
||||
################################################################
|
||||
# Begin section generated from template/Dockerfile.full.footer
|
||||
################################################################
|
||||
FROM header as base
|
||||
|
||||
ENV SCRYPTED_DOCKER_SERVE="true"
|
||||
ENV SCRYPTED_CAN_RESTART="true"
|
||||
ENV SCRYPTED_VOLUME="/server/volume"
|
||||
ENV SCRYPTED_INSTALL_PATH="/server"
|
||||
|
||||
################################################################
|
||||
# End section generated from template/Dockerfile.full.footer
|
||||
################################################################
|
||||
@@ -1,40 +0,0 @@
|
||||
ARG BUILDPACK_DEPS_BASE="bullseye"
|
||||
FROM buildpack-deps:${BUILDPACK_DEPS_BASE} as header
|
||||
|
||||
# switch to nvm?
|
||||
ARG NODE_VERSION=18
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
RUN apt-get -y update
|
||||
RUN apt-get -y upgrade
|
||||
RUN apt-get -y install software-properties-common apt-utils
|
||||
RUN apt-get -y update
|
||||
|
||||
# base development stuff
|
||||
RUN apt-get -y install \
|
||||
build-essential \
|
||||
gcc \
|
||||
libgirepository1.0-dev \
|
||||
libglib2.0-dev \
|
||||
pkg-config
|
||||
|
||||
# python native
|
||||
RUN apt-get -y install \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-gi \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
python3-wheel
|
||||
|
||||
|
||||
# python pip
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
RUN python3 -m pip install aiofiles debugpy typing_extensions typing psutil
|
||||
|
||||
ENV SCRYPTED_DOCKER_SERVE="true"
|
||||
ENV SCRYPTED_CAN_RESTART="true"
|
||||
ENV SCRYPTED_VOLUME="/server/volume"
|
||||
ENV SCRYPTED_INSTALL_PATH="/server"
|
||||
@@ -1,25 +0,0 @@
|
||||
ARG BUILDPACK_DEPS_BASE="bullseye"
|
||||
FROM buildpack-deps:${BUILDPACK_DEPS_BASE} as header
|
||||
|
||||
# switch to nvm?
|
||||
ARG NODE_VERSION=18
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
RUN apt-get -y update
|
||||
RUN apt-get -y upgrade
|
||||
RUN apt-get -y install software-properties-common apt-utils
|
||||
RUN apt-get -y update
|
||||
|
||||
# base development stuff
|
||||
RUN apt-get -y install \
|
||||
build-essential \
|
||||
gcc \
|
||||
libglib2.0-dev \
|
||||
pkg-config
|
||||
|
||||
ENV SCRYPTED_DOCKER_SERVE="true"
|
||||
ENV SCRYPTED_CAN_RESTART="true"
|
||||
ENV SCRYPTED_VOLUME="/server/volume"
|
||||
ENV SCRYPTED_INSTALL_PATH="/server"
|
||||
@@ -1,13 +0,0 @@
|
||||
################################################################
|
||||
# Begin section generated from template/Dockerfile.full.footer
|
||||
################################################################
|
||||
FROM header as base
|
||||
|
||||
ENV SCRYPTED_DOCKER_SERVE="true"
|
||||
ENV SCRYPTED_CAN_RESTART="true"
|
||||
ENV SCRYPTED_VOLUME="/server/volume"
|
||||
ENV SCRYPTED_INSTALL_PATH="/server"
|
||||
|
||||
################################################################
|
||||
# End section generated from template/Dockerfile.full.footer
|
||||
################################################################
|
||||
@@ -1,62 +0,0 @@
|
||||
################################################################
|
||||
# Begin section generated from template/Dockerfile.full.header
|
||||
# This common file will be used by both Docker and the linux
|
||||
# install script.
|
||||
################################################################
|
||||
ARG BUILDPACK_DEPS_BASE="bullseye"
|
||||
FROM buildpack-deps:${BUILDPACK_DEPS_BASE} as header
|
||||
|
||||
# switch to nvm?
|
||||
ARG NODE_VERSION=18
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
|
||||
RUN apt-get update
|
||||
RUN apt-get install -y nodejs
|
||||
|
||||
# Coral Edge TPU
|
||||
# https://coral.ai/docs/accelerator/get-started/#runtime-on-linux
|
||||
RUN echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | tee /etc/apt/sources.list.d/coral-edgetpu.list
|
||||
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
|
||||
RUN apt-get -y update
|
||||
RUN apt-get -y install libedgetpu1-std
|
||||
|
||||
RUN apt-get -y install software-properties-common apt-utils
|
||||
RUN apt-get -y update
|
||||
RUN apt-get -y upgrade
|
||||
|
||||
# base development stuff
|
||||
RUN apt-get -y install \
|
||||
build-essential \
|
||||
cmake \
|
||||
gcc \
|
||||
libgirepository1.0-dev \
|
||||
libglib2.0-dev \
|
||||
pkg-config
|
||||
|
||||
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
|
||||
RUN apt-get -y install \
|
||||
gstreamer1.0-tools libgstreamer1.0-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-plugins-ugly gstreamer1.0-libav gstreamer1.0-alsa
|
||||
|
||||
RUN bash -c 'if [ $(uname -m) == "x86_64" ]; then apt-get -y install gstreamer1.0-vaapi; fi'
|
||||
|
||||
# python native
|
||||
RUN apt-get -y install \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-gi \
|
||||
python3-gst-1.0 \
|
||||
python3-matplotlib \
|
||||
python3-numpy \
|
||||
python3-opencv \
|
||||
python3-pil \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
python3-skimage \
|
||||
python3-wheel
|
||||
|
||||
# python pip
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
RUN python3 -m pip install aiofiles debugpy typing_extensions typing psutil
|
||||
|
||||
################################################################
|
||||
# End section generated from template/Dockerfile.full.header
|
||||
################################################################
|
||||
1
external/HAP-NodeJS
vendored
1
external/HAP-NodeJS
vendored
Submodule external/HAP-NodeJS deleted from 3fe1f920f5
1
external/push-receiver
vendored
1
external/push-receiver
vendored
Submodule external/push-receiver deleted from d054e083d6
2
external/ring-client-api
vendored
2
external/ring-client-api
vendored
Submodule external/ring-client-api updated: 7ef505251f...81f6570f59
2
external/werift
vendored
2
external/werift
vendored
Submodule external/werift updated: 3d58683cdf...91be7cf469
48
install/config.yaml
Executable file
48
install/config.yaml
Executable file
@@ -0,0 +1,48 @@
|
||||
# Home Assistant Addon Configuration
|
||||
name: Scrypted
|
||||
version: "18-bullseye-full.s6-v0.23.0"
|
||||
slug: scrypted
|
||||
description: Scrypted is a high performance home video integration and automation platform
|
||||
url: "https://github.com/koush/scrypted"
|
||||
arch:
|
||||
- amd64
|
||||
- aarch64
|
||||
- armv7
|
||||
init: false
|
||||
ingress: true
|
||||
ingress_port: 11080
|
||||
panel_icon: mdi:memory
|
||||
hassio_api: true
|
||||
homeassistant_api: true
|
||||
ingress_stream: true
|
||||
host_network: true
|
||||
gpio: true
|
||||
usb: true
|
||||
uart: true
|
||||
video: true
|
||||
image: "ghcr.io/koush/scrypted"
|
||||
environment:
|
||||
SCRYPTED_INSTALL_PLUGIN: "@scrypted/homeassistant"
|
||||
SCRYPTED_VOLUME: "/data/scrypted_data"
|
||||
SCRYPTED_NVR_VOLUME: "/data/scrypted_nvr"
|
||||
SCRYPTED_ADMIN_ADDRESS: "172.30.32.2"
|
||||
SCRYPTED_ADMIN_USERNAME: "homeassistant"
|
||||
SCRYPTED_INSTALL_ENVIRONMENT: "ha"
|
||||
backup_exclude:
|
||||
- '/server/**'
|
||||
- '/data/scrypted_nvr/**'
|
||||
- '/data/scrypted_data/plugins/**'
|
||||
map:
|
||||
- config:rw
|
||||
- media:rw
|
||||
devices:
|
||||
- /dev/mem
|
||||
- /dev/dri/renderD128
|
||||
- /dev/apex_0
|
||||
- /dev/apex_1
|
||||
- /dev/apex_2
|
||||
- /dev/apex_3
|
||||
- /dev/dri/card0
|
||||
- /dev/vchiq
|
||||
- /dev/video10
|
||||
- /dev/video0
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG BASE="18-bullseye-full"
|
||||
ARG BASE="18-jammy-full"
|
||||
FROM koush/scrypted-common:${BASE}
|
||||
|
||||
WORKDIR /
|
||||
@@ -1,4 +1,4 @@
|
||||
ARG BASE="16-bullseye"
|
||||
ARG BASE="16-jammy"
|
||||
FROM koush/scrypted-common:${BASE}
|
||||
|
||||
WORKDIR /
|
||||
131
install/docker/Dockerfile.full
Normal file
131
install/docker/Dockerfile.full
Normal file
@@ -0,0 +1,131 @@
|
||||
################################################################
|
||||
# THIS FILE IS GENERATED. DO NOT EDIT.
|
||||
################################################################
|
||||
################################################################
|
||||
# Begin section generated from template/Dockerfile.full.header
|
||||
# This common file will be used by both Docker and the linux
|
||||
# install script.
|
||||
################################################################
|
||||
ARG BASE="jammy"
|
||||
FROM ubuntu:${BASE} as header
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# base tools and development stuff
|
||||
RUN apt-get update && apt-get -y install \
|
||||
curl software-properties-common apt-utils \
|
||||
build-essential \
|
||||
cmake \
|
||||
gcc \
|
||||
libcairo2-dev \
|
||||
libgirepository1.0-dev \
|
||||
pkg-config && \
|
||||
apt-get -y update && \
|
||||
apt-get -y upgrade
|
||||
|
||||
ARG NODE_VERSION=18
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
|
||||
RUN apt-get update && apt-get install -y nodejs
|
||||
|
||||
# python native
|
||||
RUN apt-get -y install \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
python3-wheel
|
||||
|
||||
# Coral Edge TPU
|
||||
# https://coral.ai/docs/accelerator/get-started/#runtime-on-linux
|
||||
RUN echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | tee /etc/apt/sources.list.d/coral-edgetpu.list
|
||||
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
|
||||
RUN apt-get -y update && apt-get -y install libedgetpu1-std
|
||||
|
||||
# these are necessary for pillow-simd, additional on disk size is small
|
||||
# but could consider removing this.
|
||||
RUN apt-get -y install \
|
||||
libjpeg-dev zlib1g-dev
|
||||
|
||||
# plugins support fallback to pillow, but vips is faster.
|
||||
RUN apt-get -y install \
|
||||
libvips
|
||||
|
||||
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
|
||||
RUN apt-get -y install \
|
||||
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-alsa \
|
||||
gstreamer1.0-vaapi
|
||||
|
||||
# python3 gstreamer bindings
|
||||
RUN apt-get -y install \
|
||||
python3-gst-1.0
|
||||
|
||||
# python 3.9 from ppa.
|
||||
# 3.9 is the version with prebuilt support for tensorflow lite
|
||||
RUN add-apt-repository ppa:deadsnakes/ppa && \
|
||||
apt-get -y install \
|
||||
python3.9 \
|
||||
python3.9-dev \
|
||||
python3.9-distutils
|
||||
|
||||
# armv7l does not have wheels for any of these
|
||||
# and compile times would forever, if it works at all.
|
||||
# furthermore, it's possible to run 32bit docker on 64bit arm,
|
||||
# which causes weird behavior in python which looks at the arch version
|
||||
# which still reports 64bit, even if running in 32bit docker.
|
||||
# this scenario is not supported and will be reported at runtime.
|
||||
# this bit is not necessary on amd64, but leaving it for consistency.
|
||||
RUN apt-get -y install \
|
||||
python3-matplotlib \
|
||||
python3-numpy \
|
||||
python3-opencv \
|
||||
python3-pil \
|
||||
python3-skimage
|
||||
|
||||
# python pip
|
||||
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
|
||||
|
||||
# pyvips is broken on x86 due to mismatch ffi
|
||||
# https://stackoverflow.com/questions/62658237/it-seems-that-the-version-of-the-libffi-library-seen-at-runtime-is-different-fro
|
||||
|
||||
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
RUN python3 -m pip install --force-reinstall --no-binary :all: cffi
|
||||
RUN python3 -m pip install debugpy typing_extensions psutil
|
||||
|
||||
RUN python3.9 -m pip install --upgrade pip
|
||||
RUN python3.9 -m pip install --force-reinstall --no-binary :all: cffi
|
||||
RUN python3.9 -m pip install debugpy typing_extensions psutil
|
||||
|
||||
################################################################
|
||||
# End section generated from template/Dockerfile.full.header
|
||||
################################################################
|
||||
################################################################
|
||||
# Begin section generated from template/Dockerfile.full.footer
|
||||
################################################################
|
||||
FROM header as base
|
||||
|
||||
# intel opencl gpu for openvino
|
||||
RUN bash -c "if [ \"$(uname -m)\" == \"x86_64\" ]; \
|
||||
then \
|
||||
apt-get update && apt-get install -y gpg-agent && \
|
||||
rm -f /usr/share/keyrings/intel-graphics.gpg && \
|
||||
curl -L https://repositories.intel.com/graphics/intel-graphics.key | gpg --dearmor --output /usr/share/keyrings/intel-graphics.gpg && \
|
||||
echo 'deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu jammy arc' | tee /etc/apt/sources.list.d/intel.gpu.jammy.list && \
|
||||
apt-get -y update && \
|
||||
apt-get -y install intel-opencl-icd intel-media-va-driver-non-free && \
|
||||
apt-get -y dist-upgrade; \
|
||||
fi"
|
||||
|
||||
ENV SCRYPTED_INSTALL_ENVIRONMENT="docker"
|
||||
ENV SCRYPTED_CAN_RESTART="true"
|
||||
ENV SCRYPTED_VOLUME="/server/volume"
|
||||
ENV SCRYPTED_INSTALL_PATH="/server"
|
||||
|
||||
# changing this forces pip and npm to perform reinstalls.
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION=20230608
|
||||
ENV SCRYPTED_DOCKER_FLAVOR=full
|
||||
|
||||
################################################################
|
||||
# End section generated from template/Dockerfile.full.footer
|
||||
################################################################
|
||||
43
install/docker/Dockerfile.lite
Normal file
43
install/docker/Dockerfile.lite
Normal file
@@ -0,0 +1,43 @@
|
||||
ARG BASE="jammy"
|
||||
FROM ubuntu:${BASE} as header
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# base tools and development stuff
|
||||
RUN apt-get update && apt-get -y install \
|
||||
curl software-properties-common apt-utils \
|
||||
build-essential \
|
||||
cmake \
|
||||
gcc \
|
||||
libcairo2-dev \
|
||||
libgirepository1.0-dev \
|
||||
pkg-config && \
|
||||
apt-get -y update && \
|
||||
apt-get -y upgrade
|
||||
|
||||
ARG NODE_VERSION=18
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
|
||||
RUN apt-get update && apt-get install -y nodejs
|
||||
|
||||
# python native
|
||||
RUN apt-get -y install \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
python3-wheel
|
||||
|
||||
# python pip
|
||||
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
RUN python3 -m pip install debugpy typing_extensions psutil
|
||||
|
||||
ENV SCRYPTED_INSTALL_ENVIRONMENT="docker"
|
||||
ENV SCRYPTED_CAN_RESTART="true"
|
||||
ENV SCRYPTED_VOLUME="/server/volume"
|
||||
ENV SCRYPTED_INSTALL_PATH="/server"
|
||||
|
||||
# changing this forces pip and npm to perform reinstalls.
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION=20230608
|
||||
ENV SCRYPTED_DOCKER_FLAVOR=lite
|
||||
22
install/docker/Dockerfile.nvidia
Normal file
22
install/docker/Dockerfile.nvidia
Normal file
@@ -0,0 +1,22 @@
|
||||
FROM koush/18-jammy-full.s6
|
||||
|
||||
WORKDIR /
|
||||
|
||||
# Install miniconda
|
||||
ENV CONDA_DIR /opt/conda
|
||||
RUN wget --quiet https://repo.anaconda.com/miniconda/Miniconda3-latest-Linux-x86_64.sh -O ~/miniconda.sh && \
|
||||
/bin/bash ~/miniconda.sh -b -p /opt/conda
|
||||
# Put conda in path so we can use conda activate
|
||||
ENV PATH=$CONDA_DIR/bin:$PATH
|
||||
|
||||
RUN conda install -c conda-forge cudatoolkit=11.2.2 cudnn=8.1.0
|
||||
ENV CONDA_PREFIX=/opt/conda
|
||||
ENV LD_LIBRARY_PATH=$LD_LIBRARY_PATH:$CONDA_PREFIX/lib/
|
||||
|
||||
# this is a copy pasta, seems to need a reinstall.
|
||||
# python pip
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
# pyvips is broken on x86 due to mismatch ffi
|
||||
# https://stackoverflow.com/questions/62658237/it-seems-that-the-version-of-the-libffi-library-seen-at-runtime-is-different-fro
|
||||
RUN python3 -m pip install --force-reinstall --no-binary :all: cffi
|
||||
RUN python3 -m pip install debugpy typing_extensions psutil
|
||||
@@ -1,11 +1,12 @@
|
||||
ARG BASE="18-bullseye-full"
|
||||
ARG BASE="18-jammy-full"
|
||||
FROM koush/scrypted-common:${BASE}
|
||||
|
||||
# avahi advertiser support
|
||||
RUN apt-get -y install \
|
||||
RUN apt-get update && apt-get -y install \
|
||||
libnss-mdns \
|
||||
avahi-discover \
|
||||
libavahi-compat-libdnssd-dev
|
||||
libavahi-compat-libdnssd-dev \
|
||||
xz-utils
|
||||
|
||||
# copy configurations and scripts
|
||||
COPY fs /
|
||||
22
install/docker/Dockerfile.thin
Normal file
22
install/docker/Dockerfile.thin
Normal file
@@ -0,0 +1,22 @@
|
||||
ARG BASE="jammy"
|
||||
FROM ubuntu:${BASE} as header
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get -y update && \
|
||||
apt-get -y upgrade && \
|
||||
apt-get -y install curl software-properties-common apt-utils
|
||||
|
||||
# switch to nvm?
|
||||
ARG NODE_VERSION=18
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash - && apt-get update && apt-get install -y nodejs
|
||||
|
||||
ENV SCRYPTED_INSTALL_ENVIRONMENT="docker"
|
||||
ENV SCRYPTED_CAN_RESTART="true"
|
||||
ENV SCRYPTED_VOLUME="/server/volume"
|
||||
ENV SCRYPTED_INSTALL_PATH="/server"
|
||||
|
||||
# changing this forces pip and npm to perform reinstalls.
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION=20230608
|
||||
ENV SCRYPTED_DOCKER_FLAVOR=thin
|
||||
3
install/docker/docker-build-nvidia.sh
Executable file
3
install/docker/docker-build-nvidia.sh
Executable file
@@ -0,0 +1,3 @@
|
||||
./docker-build.sh
|
||||
|
||||
docker build -t koush/scrypted:18-jammy-full.nvidia -f Dockerfile.nvidia
|
||||
@@ -3,15 +3,16 @@
|
||||
set -x
|
||||
|
||||
NODE_VERSION=18
|
||||
BUILDPACK_DEPS_BASE=bullseye
|
||||
SCRYPTED_INSTALL_VERSION=beta
|
||||
IMAGE_BASE=jammy
|
||||
FLAVOR=full
|
||||
BASE=$NODE_VERSION-$BUILDPACK_DEPS_BASE-$FLAVOR
|
||||
BASE=$NODE_VERSION-$IMAGE_BASE-$FLAVOR
|
||||
echo $BASE
|
||||
SUPERVISOR=.s6
|
||||
SUPERVISOR_BASE=$BASE$SUPERVISOR
|
||||
|
||||
docker build -t koush/scrypted-common:$BASE -f Dockerfile.$FLAVOR \
|
||||
--build-arg NODE_VERSION=$NODE_VERSION --build-arg BUILDPACK_DEPS_BASE=$BUILDPACK_DEPS_BASE . && \
|
||||
--build-arg NODE_VERSION=$NODE_VERSION --build-arg BASE=$IMAGE_BASE . && \
|
||||
\
|
||||
docker build -t koush/scrypted:$SUPERVISOR_BASE -f Dockerfile$SUPERVISOR \
|
||||
--build-arg BASE=$BASE .
|
||||
--build-arg BASE=$BASE --build-arg SCRYPTED_INSTALL_VERSION=$SCRYPTED_INSTALL_VERSION .
|
||||
@@ -32,14 +32,17 @@ services:
|
||||
restart: unless-stopped
|
||||
network_mode: host
|
||||
|
||||
# uncomment this and a line below as needed.
|
||||
# devices:
|
||||
# zwave usb serial device
|
||||
# - /dev/ttyACM0:/dev/ttyACM0
|
||||
# all usb devices, such as coral tpu
|
||||
# - /dev/bus/usb:/dev/bus/usb
|
||||
# intel hardware accelerated video decoding
|
||||
# - /dev/dri:/dev/dri
|
||||
devices:
|
||||
# hardware accelerated video decoding, opencl, etc.
|
||||
- /dev/dri:/dev/dri
|
||||
# uncomment below as necessary.
|
||||
# zwave usb serial device
|
||||
# - /dev/ttyACM0:/dev/ttyACM0
|
||||
# all usb devices, such as coral tpu
|
||||
# - /dev/bus/usb:/dev/bus/usb
|
||||
# coral PCI devices
|
||||
# - /dev/apex_0:/dev/apex_0
|
||||
# - /dev/apex_1:/dev/apex_1
|
||||
|
||||
volumes:
|
||||
- ~/.scrypted/volume:/server/volume
|
||||
@@ -54,6 +57,11 @@ services:
|
||||
# target: /nvr
|
||||
# volume:
|
||||
# nocopy: true
|
||||
|
||||
# uncomment the following lines to expose Avahi, an mDNS advertiser.
|
||||
# make sure Avahi is running on the host machine, otherwise this will not work.
|
||||
# - /var/run/dbus:/var/run/dbus
|
||||
# - /var/run/avahi-daemon/socket:/var/run/avahi-daemon/socket
|
||||
# logging is noisy and will unnecessarily wear on flash storage.
|
||||
# scrypted has per device in memory logging that is preferred.
|
||||
logging:
|
||||
@@ -85,4 +93,4 @@ services:
|
||||
# Must match the port in the auto update url above.
|
||||
- 10444:8080
|
||||
# check for updates once an hour (interval is in seconds)
|
||||
command: --interval 3600 --cleanup
|
||||
command: --interval 3600 --cleanup --scope scrypted
|
||||
@@ -1,7 +1,7 @@
|
||||
[server]
|
||||
#host-name=
|
||||
use-ipv4=yes
|
||||
use-ipv6=no
|
||||
use-ipv6=yes
|
||||
enable-dbus=yes
|
||||
ratelimit-interval-usec=1000000
|
||||
ratelimit-burst=1000
|
||||
@@ -14,4 +14,4 @@ rlimit-core=0
|
||||
rlimit-data=4194304
|
||||
rlimit-fsize=0
|
||||
rlimit-nofile=768
|
||||
rlimit-stack=4194304
|
||||
rlimit-stack=4194304
|
||||
@@ -42,7 +42,7 @@ fi
|
||||
WATCHTOWER_HTTP_API_TOKEN=$(echo $RANDOM | md5sum)
|
||||
DOCKER_COMPOSE_YML=$SCRYPTED_HOME/docker-compose.yml
|
||||
echo "Created $DOCKER_COMPOSE_YML"
|
||||
curl -s https://raw.githubusercontent.com/koush/scrypted/main/docker/docker-compose.yml | sed s/SET_THIS_TO_SOME_RANDOM_TEXT/"$(echo $RANDOM | md5sum)"/g > $DOCKER_COMPOSE_YML
|
||||
curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/docker-compose.yml | sed s/SET_THIS_TO_SOME_RANDOM_TEXT/"$(echo $RANDOM | md5sum | head -c 32)"/g > $DOCKER_COMPOSE_YML
|
||||
|
||||
echo "Setting permissions on $SCRYPTED_HOME"
|
||||
chown -R $SERVICE_USER $SCRYPTED_HOME
|
||||
30
install/docker/template/Dockerfile.full.footer
Normal file
30
install/docker/template/Dockerfile.full.footer
Normal file
@@ -0,0 +1,30 @@
|
||||
################################################################
|
||||
# Begin section generated from template/Dockerfile.full.footer
|
||||
################################################################
|
||||
FROM header as base
|
||||
|
||||
# intel opencl gpu for openvino
|
||||
RUN bash -c "if [ \"$(uname -m)\" == \"x86_64\" ]; \
|
||||
then \
|
||||
apt-get update && apt-get install -y gpg-agent && \
|
||||
rm -f /usr/share/keyrings/intel-graphics.gpg && \
|
||||
curl -L https://repositories.intel.com/graphics/intel-graphics.key | gpg --dearmor --output /usr/share/keyrings/intel-graphics.gpg && \
|
||||
echo 'deb [arch=amd64,i386 signed-by=/usr/share/keyrings/intel-graphics.gpg] https://repositories.intel.com/graphics/ubuntu jammy arc' | tee /etc/apt/sources.list.d/intel.gpu.jammy.list && \
|
||||
apt-get -y update && \
|
||||
apt-get -y install intel-opencl-icd intel-media-va-driver-non-free && \
|
||||
apt-get -y dist-upgrade; \
|
||||
fi"
|
||||
|
||||
ENV SCRYPTED_INSTALL_ENVIRONMENT="docker"
|
||||
ENV SCRYPTED_CAN_RESTART="true"
|
||||
ENV SCRYPTED_VOLUME="/server/volume"
|
||||
ENV SCRYPTED_INSTALL_PATH="/server"
|
||||
|
||||
# changing this forces pip and npm to perform reinstalls.
|
||||
# if this base image changes, this version must be updated.
|
||||
ENV SCRYPTED_BASE_VERSION=20230608
|
||||
ENV SCRYPTED_DOCKER_FLAVOR=full
|
||||
|
||||
################################################################
|
||||
# End section generated from template/Dockerfile.full.footer
|
||||
################################################################
|
||||
98
install/docker/template/Dockerfile.full.header
Normal file
98
install/docker/template/Dockerfile.full.header
Normal file
@@ -0,0 +1,98 @@
|
||||
################################################################
|
||||
# Begin section generated from template/Dockerfile.full.header
|
||||
# This common file will be used by both Docker and the linux
|
||||
# install script.
|
||||
################################################################
|
||||
ARG BASE="jammy"
|
||||
FROM ubuntu:${BASE} as header
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
# base tools and development stuff
|
||||
RUN apt-get update && apt-get -y install \
|
||||
curl software-properties-common apt-utils \
|
||||
build-essential \
|
||||
cmake \
|
||||
gcc \
|
||||
libcairo2-dev \
|
||||
libgirepository1.0-dev \
|
||||
pkg-config && \
|
||||
apt-get -y update && \
|
||||
apt-get -y upgrade
|
||||
|
||||
ARG NODE_VERSION=18
|
||||
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
|
||||
RUN apt-get update && apt-get install -y nodejs
|
||||
|
||||
# python native
|
||||
RUN apt-get -y install \
|
||||
python3 \
|
||||
python3-dev \
|
||||
python3-pip \
|
||||
python3-setuptools \
|
||||
python3-wheel
|
||||
|
||||
# Coral Edge TPU
|
||||
# https://coral.ai/docs/accelerator/get-started/#runtime-on-linux
|
||||
RUN echo "deb https://packages.cloud.google.com/apt coral-edgetpu-stable main" | tee /etc/apt/sources.list.d/coral-edgetpu.list
|
||||
RUN curl https://packages.cloud.google.com/apt/doc/apt-key.gpg | apt-key add -
|
||||
RUN apt-get -y update && apt-get -y install libedgetpu1-std
|
||||
|
||||
# these are necessary for pillow-simd, additional on disk size is small
|
||||
# but could consider removing this.
|
||||
RUN apt-get -y install \
|
||||
libjpeg-dev zlib1g-dev
|
||||
|
||||
# plugins support fallback to pillow, but vips is faster.
|
||||
RUN apt-get -y install \
|
||||
libvips
|
||||
|
||||
# gstreamer native https://gstreamer.freedesktop.org/documentation/installing/on-linux.html?gi-language=c#install-gstreamer-on-ubuntu-or-debian
|
||||
RUN apt-get -y install \
|
||||
gstreamer1.0-tools gstreamer1.0-plugins-base gstreamer1.0-plugins-good gstreamer1.0-plugins-bad gstreamer1.0-libav gstreamer1.0-alsa \
|
||||
gstreamer1.0-vaapi
|
||||
|
||||
# python3 gstreamer bindings
|
||||
RUN apt-get -y install \
|
||||
python3-gst-1.0
|
||||
|
||||
# python 3.9 from ppa.
|
||||
# 3.9 is the version with prebuilt support for tensorflow lite
|
||||
RUN add-apt-repository ppa:deadsnakes/ppa && \
|
||||
apt-get -y install \
|
||||
python3.9 \
|
||||
python3.9-dev \
|
||||
python3.9-distutils
|
||||
|
||||
# armv7l does not have wheels for any of these
|
||||
# and compile times would forever, if it works at all.
|
||||
# furthermore, it's possible to run 32bit docker on 64bit arm,
|
||||
# which causes weird behavior in python which looks at the arch version
|
||||
# which still reports 64bit, even if running in 32bit docker.
|
||||
# this scenario is not supported and will be reported at runtime.
|
||||
# this bit is not necessary on amd64, but leaving it for consistency.
|
||||
RUN apt-get -y install \
|
||||
python3-matplotlib \
|
||||
python3-numpy \
|
||||
python3-opencv \
|
||||
python3-pil \
|
||||
python3-skimage
|
||||
|
||||
# python pip
|
||||
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
|
||||
|
||||
# pyvips is broken on x86 due to mismatch ffi
|
||||
# https://stackoverflow.com/questions/62658237/it-seems-that-the-version-of-the-libffi-library-seen-at-runtime-is-different-fro
|
||||
|
||||
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
|
||||
RUN python3 -m pip install --upgrade pip
|
||||
RUN python3 -m pip install --force-reinstall --no-binary :all: cffi
|
||||
RUN python3 -m pip install debugpy typing_extensions psutil
|
||||
|
||||
RUN python3.9 -m pip install --upgrade pip
|
||||
RUN python3.9 -m pip install --force-reinstall --no-binary :all: cffi
|
||||
RUN python3.9 -m pip install debugpy typing_extensions psutil
|
||||
|
||||
################################################################
|
||||
# End section generated from template/Dockerfile.full.header
|
||||
################################################################
|
||||
BIN
install/icon.png
Normal file
BIN
install/icon.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -45,10 +45,10 @@ ARG() {
|
||||
}
|
||||
|
||||
ENV() {
|
||||
echo "ignoring ENV $1"
|
||||
export $@
|
||||
}
|
||||
|
||||
source <(curl -s https://raw.githubusercontent.com/koush/scrypted/main/docker/template/Dockerfile.full.header)
|
||||
source <(curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/template/Dockerfile.full.header)
|
||||
|
||||
if [ -z "$SERVICE_USER" ]
|
||||
then
|
||||
@@ -40,48 +40,29 @@ echo "Installing Scrypted dependencies..."
|
||||
RUN_IGNORE xcode-select --install
|
||||
RUN brew update
|
||||
RUN_IGNORE brew install node@18
|
||||
# needed by scrypted-ffmpeg
|
||||
RUN_IGNORE brew install sdl2
|
||||
# gstreamer plugins
|
||||
RUN_IGNORE brew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-plugins-ugly
|
||||
# gst python bindings
|
||||
RUN_IGNORE brew install gst-python
|
||||
# python image library
|
||||
RUN_IGNORE brew install pillow
|
||||
# snapshot plugin and others
|
||||
RUN brew install libvips
|
||||
# dlib
|
||||
RUN brew install cmake
|
||||
|
||||
### HACK WORKAROUND
|
||||
### https://github.com/koush/scrypted/issues/544
|
||||
|
||||
brew unpin gstreamer
|
||||
brew unpin gst-python
|
||||
brew unpin gst-plugins-ugly
|
||||
brew unpin gst-plugins-good
|
||||
brew unpin gst-plugins-base
|
||||
brew unpin gst-plugins-good
|
||||
brew unpin gst-plugins-bad
|
||||
|
||||
brew unlink gstreamer
|
||||
brew unlink gst-python
|
||||
brew unlink gst-plugins-ugly
|
||||
brew unlink gst-plugins-good
|
||||
brew unlink gst-plugins-base
|
||||
brew unlink gst-plugins-bad
|
||||
|
||||
curl -O https://raw.githubusercontent.com/Homebrew/homebrew-core/49a8667f0c1a6579fe887bc0fa1c0ce682eb01c8/Formula/gstreamer.rb && brew install ./gstreamer.rb
|
||||
curl -O https://raw.githubusercontent.com/Homebrew/homebrew-core/49a8667f0c1a6579fe887bc0fa1c0ce682eb01c8/Formula/gst-python.rb && brew install ./gst-python.rb
|
||||
curl -O https://raw.githubusercontent.com/Homebrew/homebrew-core/49a8667f0c1a6579fe887bc0fa1c0ce682eb01c8/Formula/gst-plugins-ugly.rb && brew install ./gst-plugins-ugly.rb
|
||||
curl -O https://raw.githubusercontent.com/Homebrew/homebrew-core/49a8667f0c1a6579fe887bc0fa1c0ce682eb01c8/Formula/gst-plugins-good.rb && brew install ./gst-plugins-good.rb
|
||||
curl -O https://raw.githubusercontent.com/Homebrew/homebrew-core/49a8667f0c1a6579fe887bc0fa1c0ce682eb01c8/Formula/gst-plugins-base.rb && brew install ./gst-plugins-base.rb
|
||||
curl -O https://raw.githubusercontent.com/Homebrew/homebrew-core/49a8667f0c1a6579fe887bc0fa1c0ce682eb01c8/Formula/gst-plugins-bad.rb && brew install ./gst-plugins-bad.rb
|
||||
|
||||
brew pin gstreamer
|
||||
brew pin gst-python
|
||||
brew pin gst-plugins-ugly
|
||||
brew pin gst-plugins-good
|
||||
brew pin gst-plugins-base
|
||||
brew pin gst-plugins-bad
|
||||
brew unpin gst-plugins-ugly
|
||||
brew unpin gst-libav
|
||||
brew unpin gst-python
|
||||
|
||||
### END HACK WORKAROUND
|
||||
|
||||
# gstreamer plugins
|
||||
RUN_IGNORE brew install gstreamer gst-plugins-base gst-plugins-good gst-plugins-bad gst-libav
|
||||
# gst python bindings
|
||||
RUN_IGNORE brew install gst-python
|
||||
|
||||
ARCH=$(arch)
|
||||
if [ "$ARCH" = "arm64" ]
|
||||
then
|
||||
@@ -102,7 +83,11 @@ then
|
||||
fi
|
||||
|
||||
RUN python$PYTHON_VERSION -m pip install --upgrade pip
|
||||
RUN python$PYTHON_VERSION -m pip install aiofiles debugpy typing_extensions typing opencv-python psutil
|
||||
if [ "$PYTHON_VERSION" != "3.10" ]
|
||||
then
|
||||
RUN python$PYTHON_VERSION -m pip install typing
|
||||
fi
|
||||
RUN python$PYTHON_VERSION -m pip install debugpy typing_extensions opencv-python psutil
|
||||
|
||||
echo "Installing Scrypted Launch Agent..."
|
||||
|
||||
@@ -20,7 +20,7 @@ $env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";"
|
||||
|
||||
|
||||
py $SCRYPTED_WINDOWS_PYTHON_VERSION -m pip install --upgrade pip
|
||||
py $SCRYPTED_WINDOWS_PYTHON_VERSION -m pip install aiofiles debugpy typing_extensions typing opencv-python
|
||||
py $SCRYPTED_WINDOWS_PYTHON_VERSION -m pip install debugpy typing_extensions typing opencv-python
|
||||
|
||||
npx -y scrypted@latest install-server
|
||||
|
||||
BIN
install/logo.png
Normal file
BIN
install/logo.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 38 KiB |
@@ -27,13 +27,6 @@ echo "sdk > npm run build"
|
||||
npm run build
|
||||
popd
|
||||
|
||||
pushd external/HAP-NodeJS
|
||||
echo "external/HAP-NodeJS > npm install"
|
||||
npm install
|
||||
echo "external/HAP-NodeJS > npm run build"
|
||||
npm run build
|
||||
popd
|
||||
|
||||
pushd external/werift
|
||||
echo "external/werift > npm install"
|
||||
npm install
|
||||
|
||||
3
packages/cli/.vscode/launch.json
vendored
3
packages/cli/.vscode/launch.json
vendored
@@ -22,7 +22,8 @@
|
||||
"args": [
|
||||
"ffplay",
|
||||
"Kitchen",
|
||||
"getVideoStream"
|
||||
"getRecordingStream",
|
||||
"{\"startTime\":1677699495709}"
|
||||
],
|
||||
"sourceMaps": true,
|
||||
"resolveSourceMapLocations": [
|
||||
|
||||
717
packages/cli/package-lock.json
generated
717
packages/cli/package-lock.json
generated
@@ -1,119 +1,183 @@
|
||||
{
|
||||
"name": "scrypted",
|
||||
"version": "1.0.58",
|
||||
"lockfileVersion": 2,
|
||||
"version": "1.0.67",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "scrypted",
|
||||
"version": "1.0.58",
|
||||
"version": "1.0.67",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/types": "^0.0.6",
|
||||
"adm-zip": "^0.5.9",
|
||||
"@scrypted/client": "^1.1.43",
|
||||
"@scrypted/types": "^0.2.66",
|
||||
"adm-zip": "^0.5.10",
|
||||
"axios": "^0.21.4",
|
||||
"engine.io-client": "^5.2.0",
|
||||
"ip": "^1.1.8",
|
||||
"linkfs": "^2.1.0",
|
||||
"memfs": "^3.4.1",
|
||||
"mkdirp": "^1.0.4",
|
||||
"readline-sync": "^1.4.10",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.5",
|
||||
"tslib": "^2.3.1"
|
||||
"semver": "^7.3.8",
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted": "dist/packages/cli/src/main.js"
|
||||
"scrypted": "dist/main.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mkdirp": "^1.0.2",
|
||||
"@types/node": "^18.14.2",
|
||||
"@types/readline-sync": "^1.4.4",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@types/semver": "^7.3.9",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript": "^4.8.2"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.1",
|
||||
"extraneous": 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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-consumer": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz",
|
||||
"integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 12"
|
||||
"@types/semver": "^7.3.13",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@cspotcode/source-map-support": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
|
||||
"integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
|
||||
"version": "0.8.1",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz",
|
||||
"integrity": "sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-consumer": "0.8.0"
|
||||
"@jridgewell/trace-mapping": "0.3.9"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"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/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.9",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz",
|
||||
"integrity": "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.0.3",
|
||||
"@jridgewell/sourcemap-codec": "^1.4.10"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/client": {
|
||||
"version": "1.1.43",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/client/-/client-1.1.43.tgz",
|
||||
"integrity": "sha512-qpeGdqFga/Fx51MoF3E0iBPCjE/SDEIVdGh8Ws5dqw38bxUJD264c9NsNyCguLKyYguErKTAWnQkzqhO0bUbaA==",
|
||||
"dependencies": {
|
||||
"@scrypted/types": "^0.2.66",
|
||||
"axios": "^0.25.0",
|
||||
"engine.io-client": "^6.4.0",
|
||||
"rimraf": "^3.0.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/client/node_modules/axios": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
|
||||
"integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.7"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/client/node_modules/engine.io-client": {
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz",
|
||||
"integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"ws": "~8.11.0",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/client/node_modules/engine.io-parser": {
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz",
|
||||
"integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/client/node_modules/ws": {
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
|
||||
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"utf-8-validate": "^5.0.2"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"bufferutil": {
|
||||
"optional": true
|
||||
},
|
||||
"utf-8-validate": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/types": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.0.6.tgz",
|
||||
"integrity": "sha512-r/attybPcJvBNll3g+k8i2jQwQiu0izoBazZ+Kvsdeayr3Mbzm1NaBkwbUPICroWJKY+jlfoaZSQt4eGTX+vog=="
|
||||
"version": "0.2.66",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.2.66.tgz",
|
||||
"integrity": "sha512-AL2iD7OmpqZlQMlpZKUBHpzL7H1IHhwKOi9uhRbVwG7EIDwenTspqtziH2Hyu0+XeCLf+gN69uQB6Qlz+QPf9A=="
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
|
||||
},
|
||||
"node_modules/@tsconfig/node10": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
|
||||
"integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==",
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
|
||||
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tsconfig/node12": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
|
||||
"integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==",
|
||||
"version": "1.0.11",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
|
||||
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tsconfig/node14": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
|
||||
"integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
|
||||
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@tsconfig/node16": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz",
|
||||
"integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.3.tgz",
|
||||
"integrity": "sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/glob": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
|
||||
"integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
|
||||
"version": "8.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-8.1.0.tgz",
|
||||
"integrity": "sha512-IO+MJPVhoqz+28h1qLAcBEH2+xHMK6MTyHJc7MTnnYb6wsoLR29POVGJ7LycmVXIqyy/4/2ShP5sUwTXuOwb/w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@types/minimatch": "*",
|
||||
"@types/minimatch": "^5.1.2",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/minimatch": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
|
||||
"integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
|
||||
"version": "5.1.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz",
|
||||
"integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/mkdirp": {
|
||||
@@ -126,9 +190,9 @@
|
||||
}
|
||||
},
|
||||
"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==",
|
||||
"version": "18.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz",
|
||||
"integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/readline-sync": {
|
||||
@@ -148,15 +212,15 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/semver": {
|
||||
"version": "7.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz",
|
||||
"integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==",
|
||||
"version": "7.3.13",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.13.tgz",
|
||||
"integrity": "sha512-21cFJr9z3g5dW8B0CVI9g2O9beqaThGQ6ZFBqHfwhzLDKUxaqTIy3vnfah/UPkfOiF2pLq+tGz+W8RyCskuslw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
|
||||
"integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
|
||||
"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"
|
||||
@@ -175,9 +239,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/adm-zip": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz",
|
||||
"integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg==",
|
||||
"version": "0.5.10",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.10.tgz",
|
||||
"integrity": "sha512-x0HvcHqVJNTPk/Bw8JbLWlWoo6Wwnsug0fnYYro1HBrjxZ3G7/AZk7Ahv8JwDe1uIcz8eBqvu86FuF1POiG7vQ==",
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
}
|
||||
@@ -204,7 +268,7 @@
|
||||
"node_modules/base64-arraybuffer": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
|
||||
"integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI=",
|
||||
"integrity": "sha512-a1eIFi4R9ySrbiMuyTGx5e92uRH5tQY6kArNcFaKBUleIoLjdjBg7Zxm3Mqm3Kmkf27HLR/1fnxX9q8GQ7Iavg==",
|
||||
"engines": {
|
||||
"node": ">= 0.6.0"
|
||||
}
|
||||
@@ -226,7 +290,7 @@
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"node_modules/create-require": {
|
||||
"version": "1.1.1",
|
||||
@@ -235,9 +299,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
@@ -288,9 +352,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.8",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
|
||||
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==",
|
||||
"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",
|
||||
@@ -306,25 +370,20 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/fs-monkey": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz",
|
||||
"integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q=="
|
||||
},
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
||||
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
@@ -338,12 +397,12 @@
|
||||
"node_modules/has-cors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
|
||||
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
|
||||
"integrity": "sha512-g5VNKdkFuUuVCP9gYfDJHjK2nqdQJ7aDLTnycnc2+RvsOQbuLdF5pm7vuE5J76SEBIQjs4kQY/BWq74JUmjbXA=="
|
||||
},
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
@@ -359,11 +418,6 @@
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
|
||||
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
|
||||
},
|
||||
"node_modules/linkfs": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/linkfs/-/linkfs-2.1.0.tgz",
|
||||
"integrity": "sha512-kmsGcmpvjStZ0ATjuHycBujtNnXiZR28BTivEu0gAMDTT7GEyodcK6zSRtu6xsrdorrPZEIN380x7BD7xEYkew=="
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
@@ -381,17 +435,6 @@
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/memfs": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz",
|
||||
"integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==",
|
||||
"dependencies": {
|
||||
"fs-monkey": "1.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
@@ -422,7 +465,7 @@
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -440,7 +483,7 @@
|
||||
"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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -468,9 +511,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||
"version": "7.3.8",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
|
||||
"integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
@@ -482,12 +525,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/ts-node": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.3.0.tgz",
|
||||
"integrity": "sha512-RYIy3i8IgpFH45AX4fQHExrT8BxDeKTdC83QFJkNzkvt8uFB6QJ8XMyhynYiKMLxt9a7yuXaDBZNOYS3XjDcYw==",
|
||||
"version": "10.9.1",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.1.tgz",
|
||||
"integrity": "sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-support": "0.7.0",
|
||||
"@cspotcode/source-map-support": "^0.8.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
"@tsconfig/node12": "^1.0.7",
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
@@ -498,11 +541,13 @@
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"v8-compile-cache-lib": "^3.0.1",
|
||||
"yn": "3.1.1"
|
||||
},
|
||||
"bin": {
|
||||
"ts-node": "dist/bin.js",
|
||||
"ts-node-cwd": "dist/bin-cwd.js",
|
||||
"ts-node-esm": "dist/bin-esm.js",
|
||||
"ts-node-script": "dist/bin-script.js",
|
||||
"ts-node-transpile-only": "dist/bin-transpile.js",
|
||||
"ts-script": "dist/bin-script-deprecated.js"
|
||||
@@ -523,14 +568,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
|
||||
"version": "2.5.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.5.0.tgz",
|
||||
"integrity": "sha512-336iVw3rtn2BUK7ORdIAHTyxHGRIHVReokCR3XjbckJMK7ms8FysBfhLR8IXnAgy7T0PTPNBWKiH514FOW/WSg=="
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz",
|
||||
"integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==",
|
||||
"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",
|
||||
@@ -540,10 +585,16 @@
|
||||
"node": ">=4.2.0"
|
||||
}
|
||||
},
|
||||
"node_modules/v8-compile-cache-lib": {
|
||||
"version": "3.0.1",
|
||||
"resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz",
|
||||
"integrity": "sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "7.4.6",
|
||||
@@ -581,7 +632,7 @@
|
||||
"node_modules/yeast": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
|
||||
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
|
||||
"integrity": "sha512-8HFIh676uyGYP6wP13R/j6OJ/1HwJ46snpvzE7aHAN3Ryqh2yX6Xox2B4CUmTwwOIzlG3Bs7ocsP5dZH/R1Qbg=="
|
||||
},
|
||||
"node_modules/yn": {
|
||||
"version": "3.1.1",
|
||||
@@ -592,413 +643,5 @@
|
||||
"node": ">=6"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@cspotcode/source-map-consumer": {
|
||||
"version": "0.8.0",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz",
|
||||
"integrity": "sha512-41qniHzTU8yAGbCp04ohlmSrZf8bkf/iJsl3V0dRGsQN/5GFfx+LbCSsCpp2gqrqjTVg/K6O8ycoV35JIwAzAg==",
|
||||
"dev": true
|
||||
},
|
||||
"@cspotcode/source-map-support": {
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/@cspotcode/source-map-support/-/source-map-support-0.7.0.tgz",
|
||||
"integrity": "sha512-X4xqRHqN8ACt2aHVe51OxeA2HjbcL4MqFqXkrmQszJ1NOUuUu5u6Vqx/0lZSVNku7velL5FC/s5uEAj1lsBMhA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@cspotcode/source-map-consumer": "0.8.0"
|
||||
}
|
||||
},
|
||||
"@scrypted/types": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.0.6.tgz",
|
||||
"integrity": "sha512-r/attybPcJvBNll3g+k8i2jQwQiu0izoBazZ+Kvsdeayr3Mbzm1NaBkwbUPICroWJKY+jlfoaZSQt4eGTX+vog=="
|
||||
},
|
||||
"@tsconfig/node10": {
|
||||
"version": "1.0.8",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz",
|
||||
"integrity": "sha512-6XFfSQmMgq0CFLY1MslA/CPUfhIL919M1rMsa5lP2P097N2Wd1sSX0tx1u4olM16fLNhtHZpRhedZJphNJqmZg==",
|
||||
"dev": true
|
||||
},
|
||||
"@tsconfig/node12": {
|
||||
"version": "1.0.9",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.9.tgz",
|
||||
"integrity": "sha512-/yBMcem+fbvhSREH+s14YJi18sp7J9jpuhYByADT2rypfajMZZN4WQ6zBGgBKp53NKmqI36wFYDb3yaMPurITw==",
|
||||
"dev": true
|
||||
},
|
||||
"@tsconfig/node14": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.1.tgz",
|
||||
"integrity": "sha512-509r2+yARFfHHE7T6Puu2jjkoycftovhXRqW328PDXTVGKihlb1P8Z9mMZH04ebyajfRY7dedfGynlrFHJUQCg==",
|
||||
"dev": true
|
||||
},
|
||||
"@tsconfig/node16": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.2.tgz",
|
||||
"integrity": "sha512-eZxlbI8GZscaGS7kkc/trHTT5xgrjH3/1n2JDwusC9iahPKWMRvRjJSAN5mCXviuTGQ/lHnhvv8Q1YTpnfz9gA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/glob": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz",
|
||||
"integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/minimatch": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/minimatch": {
|
||||
"version": "3.0.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-3.0.5.tgz",
|
||||
"integrity": "sha512-Klz949h02Gz2uZCMGwDUSDS1YBlTdDDgbWHi+81l29tQALUtvz4rAYi5uoVhE5Lagoq6DeqAUlbrHvW/mXDgdQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/mkdirp": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/mkdirp/-/mkdirp-1.0.2.tgz",
|
||||
"integrity": "sha512-o0K1tSO0Dx5X6xlU5F1D6625FawhC3dU3iqr25lluNv/+/QIVH8RLNEiVokgIZo+mz+87w/3Mkg/VvQS+J51fQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.11.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.1.tgz",
|
||||
"integrity": "sha512-PYGcJHL9mwl1Ek3PLiYgyEKtwTMmkMw4vbiyz/ps3pfdRYLVv+SN7qHVAImrjdAXxgluDEw6Ph4lyv+m9UpRmA==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/readline-sync": {
|
||||
"version": "1.4.4",
|
||||
"resolved": "https://registry.npmjs.org/@types/readline-sync/-/readline-sync-1.4.4.tgz",
|
||||
"integrity": "sha512-cFjVIoiamX7U6zkO2VPvXyTxbFDdiRo902IarJuPVxBhpDnXhwSaVE86ip+SCuyWBbEioKCkT4C88RNTxBM1Dw==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-F3OznnSLAUxFrCEu/L5PY8+ny8DtcFRjx7fZZ9bycvXRi3KPTRS9HOitGZwvPg0juRhXFWIeKX58cnX5YqLohQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/glob": "*",
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/semver": {
|
||||
"version": "7.3.9",
|
||||
"resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.3.9.tgz",
|
||||
"integrity": "sha512-L/TMpyURfBkf+o/526Zb6kd/tchUP3iBDEPjqjb+U2MAJhVRxxrmr2fwpe08E7QsV7YLcpq0tUaQ9O9x97ZIxQ==",
|
||||
"dev": true
|
||||
},
|
||||
"acorn": {
|
||||
"version": "8.5.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.5.0.tgz",
|
||||
"integrity": "sha512-yXbYeFy+jUuYd3/CDcg2NkIYE991XYX/bje7LmjJigUciaeO1JR4XxXgCIV1/Zc/dRuFEyw1L0pbA+qynJkW5Q==",
|
||||
"dev": true
|
||||
},
|
||||
"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
|
||||
},
|
||||
"adm-zip": {
|
||||
"version": "0.5.9",
|
||||
"resolved": "https://registry.npmjs.org/adm-zip/-/adm-zip-0.5.9.tgz",
|
||||
"integrity": "sha512-s+3fXLkeeLjZ2kLjCBwQufpI5fuN+kIGBxu6530nVQZGVol0d7Y/M88/xw9HGGUcJjKf8LutN3VPRUBq6N7Ajg=="
|
||||
},
|
||||
"arg": {
|
||||
"version": "4.1.3",
|
||||
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
|
||||
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.21.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
|
||||
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.0"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"base64-arraybuffer": {
|
||||
"version": "0.1.4",
|
||||
"resolved": "https://registry.npmjs.org/base64-arraybuffer/-/base64-arraybuffer-0.1.4.tgz",
|
||||
"integrity": "sha1-mBjHngWbE1X5fgQooBfIOOkLqBI="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"component-emitter": {
|
||||
"version": "1.3.0",
|
||||
"resolved": "https://registry.npmjs.org/component-emitter/-/component-emitter-1.3.0.tgz",
|
||||
"integrity": "sha512-Rd3se6QB+sO1TwqZjscQrurpEPIfO0/yYnSin6Q/rD3mOutHvUrCAhJub3r90uNb+SESBuE0QYoB90YdfatsRg=="
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"create-require": {
|
||||
"version": "1.1.1",
|
||||
"resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz",
|
||||
"integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==",
|
||||
"dev": true
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"diff": {
|
||||
"version": "4.0.2",
|
||||
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
|
||||
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
|
||||
"dev": true
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-5.2.0.tgz",
|
||||
"integrity": "sha512-BcIBXGBkT7wKecwnfrSV79G2X5lSUSgeAGgoo60plXf8UsQEvCQww/KMwXSMhVjb98fFYNq20CC5eo8IOAPqsg==",
|
||||
"requires": {
|
||||
"base64-arraybuffer": "0.1.4",
|
||||
"component-emitter": "~1.3.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~4.0.1",
|
||||
"has-cors": "1.1.0",
|
||||
"parseqs": "0.0.6",
|
||||
"parseuri": "0.0.6",
|
||||
"ws": "~7.4.2",
|
||||
"xmlhttprequest-ssl": "~2.0.0",
|
||||
"yeast": "0.1.2"
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-4.0.3.tgz",
|
||||
"integrity": "sha512-xEAAY0msNnESNPc00e19y5heTPX4y/TJ36gr8t1voOaNmTojP9b3oK3BbJLFufW2XFPQaaijpFewm2g2Um3uqA==",
|
||||
"requires": {
|
||||
"base64-arraybuffer": "0.1.4"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.8",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
|
||||
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA=="
|
||||
},
|
||||
"fs-monkey": {
|
||||
"version": "1.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz",
|
||||
"integrity": "sha512-cybjIfiiE+pTWicSCLFHSrXZ6EilF30oh91FDP9S2B051prEa7QWfrVTQm10/dDpswBDXZugPa1Ogu8Yh+HV0Q=="
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
||||
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"has-cors": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/has-cors/-/has-cors-1.1.0.tgz",
|
||||
"integrity": "sha1-XkdHk/fqmEPRu5nCPu9J/xJv/zk="
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"ip": {
|
||||
"version": "1.1.8",
|
||||
"resolved": "https://registry.npmjs.org/ip/-/ip-1.1.8.tgz",
|
||||
"integrity": "sha512-PuExPYUiu6qMBQb4l06ecm6T6ujzhmh+MeJcW9wa89PoAz5pvd4zPgN5WJV104mb6S2T1AwNIAaB70JNrLQWhg=="
|
||||
},
|
||||
"linkfs": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/linkfs/-/linkfs-2.1.0.tgz",
|
||||
"integrity": "sha512-kmsGcmpvjStZ0ATjuHycBujtNnXiZR28BTivEu0gAMDTT7GEyodcK6zSRtu6xsrdorrPZEIN380x7BD7xEYkew=="
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"make-error": {
|
||||
"version": "1.3.6",
|
||||
"resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz",
|
||||
"integrity": "sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==",
|
||||
"dev": true
|
||||
},
|
||||
"memfs": {
|
||||
"version": "3.4.1",
|
||||
"resolved": "https://registry.npmjs.org/memfs/-/memfs-3.4.1.tgz",
|
||||
"integrity": "sha512-1c9VPVvW5P7I85c35zAdEr1TD5+F11IToIHIlrVIcflfnzPkJa0ZoYEoEdYDP8KgPFoSZ/opDrUsAoZWym3mtw==",
|
||||
"requires": {
|
||||
"fs-monkey": "1.0.3"
|
||||
}
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"mkdirp": {
|
||||
"version": "1.0.4",
|
||||
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
|
||||
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw=="
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"parseqs": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseqs/-/parseqs-0.0.6.tgz",
|
||||
"integrity": "sha512-jeAGzMDbfSHHA091hr0r31eYfTig+29g3GKKE/PPbEQ65X0lmMwlEoqmhzu0iztID5uJpZsFlUPDP8ThPL7M8w=="
|
||||
},
|
||||
"parseuri": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/parseuri/-/parseuri-0.0.6.tgz",
|
||||
"integrity": "sha512-AUjen8sAkGgao7UyCX6Ahv0gIK2fABKmYjvP4xmy5JaKvcbTRueIqIPHLAfq30xJddqSE033IOMUSOMCcK3Sow=="
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"readline-sync": {
|
||||
"version": "1.4.10",
|
||||
"resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.10.tgz",
|
||||
"integrity": "sha512-gNva8/6UAe8QYepIQH/jQ2qn91Qj0B9sYjMBBs3QOB8F2CXcKgLxQaJRP76sWVRQt+QU+8fAkCbCvjjMFu7Ycw=="
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.5",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz",
|
||||
"integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==",
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
},
|
||||
"ts-node": {
|
||||
"version": "10.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.3.0.tgz",
|
||||
"integrity": "sha512-RYIy3i8IgpFH45AX4fQHExrT8BxDeKTdC83QFJkNzkvt8uFB6QJ8XMyhynYiKMLxt9a7yuXaDBZNOYS3XjDcYw==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@cspotcode/source-map-support": "0.7.0",
|
||||
"@tsconfig/node10": "^1.0.7",
|
||||
"@tsconfig/node12": "^1.0.7",
|
||||
"@tsconfig/node14": "^1.0.0",
|
||||
"@tsconfig/node16": "^1.0.2",
|
||||
"acorn": "^8.4.1",
|
||||
"acorn-walk": "^8.1.1",
|
||||
"arg": "^4.1.0",
|
||||
"create-require": "^1.1.0",
|
||||
"diff": "^4.0.1",
|
||||
"make-error": "^1.1.1",
|
||||
"yn": "3.1.1"
|
||||
}
|
||||
},
|
||||
"tslib": {
|
||||
"version": "2.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz",
|
||||
"integrity": "sha512-77EbyPPpMz+FRFRuAFlWMtmgUWGe9UOG2Z25NqCwiIjRhOf5iKGuzSe5P2w1laq+FkRy4p+PCuVkJSGkzTEKVw=="
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.8.2",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.8.2.tgz",
|
||||
"integrity": "sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"ws": {
|
||||
"version": "7.4.6",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
|
||||
"requires": {}
|
||||
},
|
||||
"xmlhttprequest-ssl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
|
||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A=="
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
|
||||
},
|
||||
"yeast": {
|
||||
"version": "0.1.2",
|
||||
"resolved": "https://registry.npmjs.org/yeast/-/yeast-0.1.2.tgz",
|
||||
"integrity": "sha1-AI4G2AlDIMNy28L47XagymyKxBk="
|
||||
},
|
||||
"yn": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
|
||||
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
{
|
||||
"name": "scrypted",
|
||||
"version": "1.0.58",
|
||||
"version": "1.0.67",
|
||||
"description": "",
|
||||
"main": "./dist/packages/cli/src/main.js",
|
||||
"main": "./dist/main.js",
|
||||
"bin": {
|
||||
"scrypted": "./dist/packages/cli/src/main.js"
|
||||
"scrypted": "./dist/main.js"
|
||||
},
|
||||
"scripts": {
|
||||
"prebuild": "rimraf dist",
|
||||
@@ -16,25 +16,25 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/types": "^0.0.6",
|
||||
"adm-zip": "^0.5.9",
|
||||
"@scrypted/client": "^1.1.43",
|
||||
"@scrypted/types": "^0.2.66",
|
||||
"adm-zip": "^0.5.10",
|
||||
"axios": "^0.21.4",
|
||||
"engine.io-client": "^5.2.0",
|
||||
"ip": "^1.1.8",
|
||||
"linkfs": "^2.1.0",
|
||||
"memfs": "^3.4.1",
|
||||
"mkdirp": "^1.0.4",
|
||||
"readline-sync": "^1.4.10",
|
||||
"rimraf": "^3.0.2",
|
||||
"semver": "^7.3.5",
|
||||
"tslib": "^2.3.1"
|
||||
"semver": "^7.3.8",
|
||||
"tslib": "^2.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/mkdirp": "^1.0.2",
|
||||
"@types/node": "^18.14.2",
|
||||
"@types/readline-sync": "^1.4.4",
|
||||
"@types/rimraf": "^3.0.2",
|
||||
"@types/semver": "^7.3.9",
|
||||
"ts-node": "^10.2.1",
|
||||
"typescript": "^4.8.2"
|
||||
"@types/semver": "^7.3.13",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ import readline from 'readline-sync';
|
||||
import https from 'https';
|
||||
import mkdirp from 'mkdirp';
|
||||
import { installServe, serveMain } from './service';
|
||||
import { connectScryptedClient } from '../../client/src/index';
|
||||
import { ScryptedMimeTypes, FFmpegInput } from '../../../sdk/types/src/types.input';
|
||||
import { connectScryptedClient } from '@scrypted/client';
|
||||
import { ScryptedMimeTypes, FFmpegInput } from '@scrypted/types';
|
||||
import semver from 'semver';
|
||||
import child_process from 'child_process';
|
||||
|
||||
@@ -126,7 +126,7 @@ async function runCommand() {
|
||||
if (!device)
|
||||
throw new Error('device not found: ' + idOrName);
|
||||
const method = process.argv[4];
|
||||
const args = process.argv.slice(5).map(arg => () => {
|
||||
const args = process.argv.slice(5).map(arg => {
|
||||
try {
|
||||
return JSON.parse(arg);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,16 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"resolveJsonModule": true,
|
||||
"module": "commonjs",
|
||||
"target": "ESNext",
|
||||
"noImplicitAny": true,
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"declaration": true
|
||||
"module": "commonjs",
|
||||
"target": "esnext",
|
||||
"noImplicitAny": true,
|
||||
"outDir": "./dist",
|
||||
"esModuleInterop": true,
|
||||
"sourceMap": true,
|
||||
"inlineSources": true,
|
||||
"declaration": true,
|
||||
"resolveJsonModule": true,
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
"src/**/*"
|
||||
],
|
||||
}
|
||||
@@ -23,10 +23,19 @@ async function example() {
|
||||
if (!backyard)
|
||||
throw new Error('Device not found');
|
||||
|
||||
backyard.listen(ScryptedInterface.ObjectDetector, (source, details, data) => {
|
||||
backyard.listen(ScryptedInterface.ObjectDetector, async (source, details, data) => {
|
||||
const results = data as ObjectsDetected;
|
||||
console.log(results);
|
||||
})
|
||||
console.log('detection results', results);
|
||||
// detections that are flagged for retention will have a detectionId.
|
||||
// tf etc won't retain automatically, and this requires a wrapping detector like Scrypted NVR Object Detection
|
||||
// to decide which frames to keep. Otherwise saving all images would be extremely poor performance.
|
||||
if (!results.detectionId)
|
||||
return;
|
||||
|
||||
const media = await backyard.getDetectionInput(results.detectionId);
|
||||
const jpeg = await sdk.mediaManager.convertMediaObjectToBuffer(media, 'image/jpeg');
|
||||
// do something with the buffer like save to disk or send to a service.
|
||||
});
|
||||
}
|
||||
|
||||
example();
|
||||
|
||||
285
packages/client/package-lock.json
generated
285
packages/client/package-lock.json
generated
@@ -1,58 +1,29 @@
|
||||
{
|
||||
"name": "@scrypted/client",
|
||||
"version": "1.1.40",
|
||||
"lockfileVersion": 2,
|
||||
"version": "1.1.54",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/client",
|
||||
"version": "1.1.40",
|
||||
"version": "1.1.54",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@scrypted/types": "^0.2.65",
|
||||
"@scrypted/types": "^0.2.91",
|
||||
"axios": "^0.25.0",
|
||||
"engine.io-client": "^6.2.2",
|
||||
"engine.io-client": "^6.4.0",
|
||||
"rimraf": "^3.0.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/ip": "^1.1.0",
|
||||
"@types/node": "^17.0.17",
|
||||
"typescript": "^4.7.4"
|
||||
"@types/node": "^18.14.2",
|
||||
"typescript": "^4.9.5"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.1",
|
||||
"extraneous": 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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
}
|
||||
},
|
||||
"../../sdk/types": {
|
||||
"name": "@scrypted/types",
|
||||
"version": "0.0.9",
|
||||
"extraneous": true,
|
||||
"license": "ISC",
|
||||
"devDependencies": {}
|
||||
},
|
||||
"../common": {
|
||||
"extraneous": true
|
||||
},
|
||||
"../sdk/types": {
|
||||
"extraneous": true
|
||||
},
|
||||
"node_modules/@scrypted/types": {
|
||||
"version": "0.2.65",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.2.65.tgz",
|
||||
"integrity": "sha512-V/gfPy+xeRds6WMHwU6trt2YBkH9qcC/3Bx9q5hOxpE+rZSL4ru+nvlaumCRM3mSNWXBav4nbd23JCoGJ0F2eA=="
|
||||
"version": "0.2.91",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.2.91.tgz",
|
||||
"integrity": "sha512-GfWil8cl2QwlTXk506ZXDALQfuv7zN48PtPlpmBMO/IYTQFtb+RB2zr+FwC9gdvRaZgs9NCCS2Fiig1OY7uxdQ=="
|
||||
},
|
||||
"node_modules/@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
@@ -69,9 +40,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "17.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.18.tgz",
|
||||
"integrity": "sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==",
|
||||
"version": "18.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz",
|
||||
"integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/axios": {
|
||||
@@ -99,12 +70,12 @@
|
||||
"node_modules/concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"version": "4.3.4",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
|
||||
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
|
||||
"dependencies": {
|
||||
"ms": "2.1.2"
|
||||
},
|
||||
@@ -118,29 +89,29 @@
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-client": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz",
|
||||
"integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==",
|
||||
"version": "6.4.0",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.4.0.tgz",
|
||||
"integrity": "sha512-GyKPDyoEha+XZ7iEqam49vz6auPnNJ9ZBfy89f+rMMas8AuiMWOZ9PVzu8xb9ZC6rafUqiGHSCfu22ih66E+1g==",
|
||||
"dependencies": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"ws": "~8.2.3",
|
||||
"ws": "~8.11.0",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/engine.io-parser": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz",
|
||||
"integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg==",
|
||||
"version": "5.0.6",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.6.tgz",
|
||||
"integrity": "sha512-tjuoZDMAdEhVnSFleYPCtdL2GXwVTGtNjoeJd9IhIG3C1xs9uwxqRNEu5WpnDZCaozwVlK/nuQhpodhXSIMaxw==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/follow-redirects": {
|
||||
"version": "1.14.8",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
|
||||
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA==",
|
||||
"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",
|
||||
@@ -159,17 +130,17 @@
|
||||
"node_modules/fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
|
||||
},
|
||||
"node_modules/glob": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
||||
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
||||
"version": "7.2.3",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
|
||||
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
|
||||
"dependencies": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"minimatch": "^3.1.1",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
},
|
||||
@@ -183,7 +154,7 @@
|
||||
"node_modules/inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
|
||||
"dependencies": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
@@ -213,7 +184,7 @@
|
||||
"node_modules/once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
|
||||
"dependencies": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
@@ -221,7 +192,7 @@
|
||||
"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": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
|
||||
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
|
||||
"engines": {
|
||||
"node": ">=0.10.0"
|
||||
}
|
||||
@@ -241,9 +212,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
||||
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
|
||||
"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",
|
||||
@@ -256,12 +227,12 @@
|
||||
"node_modules/wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
|
||||
},
|
||||
"node_modules/ws": {
|
||||
"version": "8.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
|
||||
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
|
||||
"version": "8.11.0",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.11.0.tgz",
|
||||
"integrity": "sha512-HPG3wQd9sNQoT9xHyNCXoDUa+Xw/VevmY9FoHyQ+g+rrMn4j6FB4np7Z0OhdTgjx6MgQLK7jwSy1YecU1+4Asg==",
|
||||
"engines": {
|
||||
"node": ">=10.0.0"
|
||||
},
|
||||
@@ -286,177 +257,5 @@
|
||||
"node": ">=0.4.0"
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/types": {
|
||||
"version": "0.2.65",
|
||||
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.2.65.tgz",
|
||||
"integrity": "sha512-V/gfPy+xeRds6WMHwU6trt2YBkH9qcC/3Bx9q5hOxpE+rZSL4ru+nvlaumCRM3mSNWXBav4nbd23JCoGJ0F2eA=="
|
||||
},
|
||||
"@socket.io/component-emitter": {
|
||||
"version": "3.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz",
|
||||
"integrity": "sha512-+9jVqKhRSpsc591z5vX+X5Yyw+he/HCB4iQ/RYxw35CEPaY1gnsNE43nf9n9AaYjAQrTiI/mOwKUKdUs9vf7Xg=="
|
||||
},
|
||||
"@types/ip": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/ip/-/ip-1.1.0.tgz",
|
||||
"integrity": "sha512-dwNe8gOoF70VdL6WJBwVHtQmAX4RMd62M+mAB9HQFjG1/qiCLM/meRy95Pd14FYBbEDwCq7jgJs89cHpLBu4HQ==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "17.0.18",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-17.0.18.tgz",
|
||||
"integrity": "sha512-eKj4f/BsN/qcculZiRSujogjvp5O/k4lOW5m35NopjZM/QwLOR075a8pJW5hD+Rtdm2DaCVPENS6KtSQnUD6BA==",
|
||||
"dev": true
|
||||
},
|
||||
"axios": {
|
||||
"version": "0.25.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.25.0.tgz",
|
||||
"integrity": "sha512-cD8FOb0tRH3uuEe6+evtAbgJtfxr7ly3fQjYcMcuPlgkwVS9xboaVIpcDV+cYQe+yGykgwZCs1pzjntcGa6l5g==",
|
||||
"requires": {
|
||||
"follow-redirects": "^1.14.7"
|
||||
}
|
||||
},
|
||||
"balanced-match": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
|
||||
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
|
||||
},
|
||||
"brace-expansion": {
|
||||
"version": "1.1.11",
|
||||
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
|
||||
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
|
||||
"requires": {
|
||||
"balanced-match": "^1.0.0",
|
||||
"concat-map": "0.0.1"
|
||||
}
|
||||
},
|
||||
"concat-map": {
|
||||
"version": "0.0.1",
|
||||
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
|
||||
"integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
|
||||
},
|
||||
"debug": {
|
||||
"version": "4.3.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz",
|
||||
"integrity": "sha512-/zxw5+vh1Tfv+4Qn7a5nsbcJKPaSvCDhojn6FEl9vupwK2VCSDtEiEtqr8DFtzYFOdz63LBkxec7DYuc2jon6Q==",
|
||||
"requires": {
|
||||
"ms": "2.1.2"
|
||||
}
|
||||
},
|
||||
"engine.io-client": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.2.2.tgz",
|
||||
"integrity": "sha512-8ZQmx0LQGRTYkHuogVZuGSpDqYZtCM/nv8zQ68VZ+JkOpazJ7ICdsSpaO6iXwvaU30oFg5QJOJWj8zWqhbKjkQ==",
|
||||
"requires": {
|
||||
"@socket.io/component-emitter": "~3.1.0",
|
||||
"debug": "~4.3.1",
|
||||
"engine.io-parser": "~5.0.3",
|
||||
"ws": "~8.2.3",
|
||||
"xmlhttprequest-ssl": "~2.0.0"
|
||||
}
|
||||
},
|
||||
"engine.io-parser": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.0.4.tgz",
|
||||
"integrity": "sha512-+nVFp+5z1E3HcToEnO7ZIj3g+3k9389DvWtvJZz0T6/eOCPIyyxehFcedoYrZQrp0LgQbD9pPXhpMBKMd5QURg=="
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.8",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.8.tgz",
|
||||
"integrity": "sha512-1x0S9UVJHsQprFcEC/qnNzBLcIxsjAV905f/UkQxbclCsoTWlacCNOpQa/anodLl2uaEKFhfWOvM2Qg77+15zA=="
|
||||
},
|
||||
"fs.realpath": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
|
||||
"integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
|
||||
},
|
||||
"glob": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
|
||||
"integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
|
||||
"requires": {
|
||||
"fs.realpath": "^1.0.0",
|
||||
"inflight": "^1.0.4",
|
||||
"inherits": "2",
|
||||
"minimatch": "^3.0.4",
|
||||
"once": "^1.3.0",
|
||||
"path-is-absolute": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"inflight": {
|
||||
"version": "1.0.6",
|
||||
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
|
||||
"integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
|
||||
"requires": {
|
||||
"once": "^1.3.0",
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"inherits": {
|
||||
"version": "2.0.4",
|
||||
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
|
||||
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
|
||||
},
|
||||
"minimatch": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
|
||||
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
|
||||
"requires": {
|
||||
"brace-expansion": "^1.1.7"
|
||||
}
|
||||
},
|
||||
"ms": {
|
||||
"version": "2.1.2",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
|
||||
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
|
||||
},
|
||||
"once": {
|
||||
"version": "1.4.0",
|
||||
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
|
||||
"integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
|
||||
"requires": {
|
||||
"wrappy": "1"
|
||||
}
|
||||
},
|
||||
"path-is-absolute": {
|
||||
"version": "1.0.1",
|
||||
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
|
||||
"integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
|
||||
},
|
||||
"rimraf": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
|
||||
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
|
||||
"requires": {
|
||||
"glob": "^7.1.3"
|
||||
}
|
||||
},
|
||||
"typescript": {
|
||||
"version": "4.7.4",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.7.4.tgz",
|
||||
"integrity": "sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==",
|
||||
"dev": true
|
||||
},
|
||||
"wrappy": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
|
||||
"integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
|
||||
},
|
||||
"ws": {
|
||||
"version": "8.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-8.2.3.tgz",
|
||||
"integrity": "sha512-wBuoj1BDpC6ZQ1B7DWQBYVLphPWkm8i9Y0/3YdHjHKHiohOJ1ws+3OccDWtH+PoC9DZD5WOTrJvNbWvjS6JWaA==",
|
||||
"requires": {}
|
||||
},
|
||||
"xmlhttprequest-ssl": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.0.0.tgz",
|
||||
"integrity": "sha512-QKxVRxiRACQcVuQEYFsI1hhkrMlrXHPegbbd1yn9UHOmRxY+si12nQYzri3vbzt8VdTTRviqcKxcyllFas5z2A=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/client",
|
||||
"version": "1.1.40",
|
||||
"version": "1.1.54",
|
||||
"description": "",
|
||||
"main": "dist/packages/client/src/index.js",
|
||||
"scripts": {
|
||||
@@ -13,13 +13,13 @@
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/ip": "^1.1.0",
|
||||
"@types/node": "^17.0.17",
|
||||
"typescript": "^4.7.4"
|
||||
"@types/node": "^18.14.2",
|
||||
"typescript": "^4.9.5"
|
||||
},
|
||||
"dependencies": {
|
||||
"@scrypted/types": "^0.2.65",
|
||||
"@scrypted/types": "^0.2.91",
|
||||
"axios": "^0.25.0",
|
||||
"engine.io-client": "^6.2.2",
|
||||
"engine.io-client": "^6.4.0",
|
||||
"rimraf": "^3.0.2"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import { RTCConnectionManagement, RTCSignalingSession, ScryptedStatic } from "@scrypted/types";
|
||||
import { MediaObjectOptions, RTCConnectionManagement, RTCSignalingSession, ScryptedStatic } from "@scrypted/types";
|
||||
import axios, { AxiosRequestConfig } from 'axios';
|
||||
import * as eio from 'engine.io-client';
|
||||
import { SocketOptions } from 'engine.io-client';
|
||||
@@ -7,6 +7,7 @@ import { timeoutPromise } from "../../../common/src/promise-utils";
|
||||
import { BrowserSignalingSession, waitPeerConnectionIceConnected, waitPeerIceConnectionClosed } from "../../../common/src/rtc-signaling";
|
||||
import { DataChannelDebouncer } from "../../../plugins/webrtc/src/datachannel-debouncer";
|
||||
import type { IOSocket } from '../../../server/src/io';
|
||||
import { MediaObject } from '../../../server/src/plugin/mediaobject';
|
||||
import type { MediaObjectRemote } from '../../../server/src/plugin/plugin-api';
|
||||
import { attachPluginRemote } from '../../../server/src/plugin/plugin-remote';
|
||||
import { RpcPeer } from '../../../server/src/rpc';
|
||||
@@ -77,25 +78,48 @@ export interface ScryptedClientOptions extends Partial<ScryptedLoginOptions> {
|
||||
transports?: string[];
|
||||
}
|
||||
|
||||
function isInstalledApp() {
|
||||
return globalThis.navigator?.userAgent.includes('InstalledApp');
|
||||
}
|
||||
|
||||
function isRunningStandalone() {
|
||||
return globalThis.matchMedia?.('(display-mode: standalone)').matches || globalThis.navigator?.userAgent.includes('InstalledApp');
|
||||
return globalThis.matchMedia?.('(display-mode: standalone)').matches || isInstalledApp();
|
||||
}
|
||||
|
||||
export async function logoutScryptedClient(baseUrl?: string) {
|
||||
const url = baseUrl ? new URL('/logout', baseUrl).toString() : '/logout';
|
||||
const url = combineBaseUrl(baseUrl, 'logout');
|
||||
const response = await axios(url, {
|
||||
withCredentials: true,
|
||||
});
|
||||
return response.data;
|
||||
}
|
||||
|
||||
export function getCurrentBaseUrl() {
|
||||
// an endpoint within scrypted will be served at /endpoint/[org/][id]
|
||||
// find the endpoint prefix and anything prior to that will be the server base url.
|
||||
const url = new URL(window.location.href);
|
||||
url.search = '';
|
||||
url.hash = '';
|
||||
let endpointPath = window.location.pathname;
|
||||
const parts = endpointPath.split('/');
|
||||
const index = parts.findIndex(p => p === 'endpoint');
|
||||
if (index === -1) {
|
||||
// console.warn('path not recognized, does not contain the segment "endpoint".')
|
||||
return undefined;
|
||||
}
|
||||
const keep = parts.slice(0, index);
|
||||
keep.push('');
|
||||
url.pathname = keep.join('/');
|
||||
return url.toString();
|
||||
}
|
||||
|
||||
export async function loginScryptedClient(options: ScryptedLoginOptions) {
|
||||
let { baseUrl, username, password, change_password, maxAge } = options;
|
||||
// pwa should stay logged in for a year.
|
||||
if (!maxAge && isRunningStandalone())
|
||||
maxAge = 365 * 24 * 60 * 60 * 1000;
|
||||
|
||||
const url = `${baseUrl || ''}/login`;
|
||||
const url = combineBaseUrl(baseUrl, 'login');
|
||||
const response = await axios.post(url, {
|
||||
username,
|
||||
password,
|
||||
@@ -128,7 +152,7 @@ export async function loginScryptedClient(options: ScryptedLoginOptions) {
|
||||
|
||||
export async function checkScryptedClientLogin(options?: ScryptedConnectionOptions) {
|
||||
let { baseUrl } = options || {};
|
||||
const url = `${baseUrl || ''}/login`;
|
||||
const url = combineBaseUrl(baseUrl, 'login');
|
||||
const response = await axios.get(url, {
|
||||
withCredentials: true,
|
||||
...options?.axiosConfig,
|
||||
@@ -137,6 +161,7 @@ export async function checkScryptedClientLogin(options?: ScryptedConnectionOptio
|
||||
const directAddress = response.headers['x-scrypted-direct-address'];
|
||||
|
||||
return {
|
||||
hostname: response.data.hostname as string,
|
||||
redirect: response.data.redirect as string,
|
||||
username: response.data.username as string,
|
||||
expiration: response.data.expiration as number,
|
||||
@@ -144,6 +169,7 @@ export async function checkScryptedClientLogin(options?: ScryptedConnectionOptio
|
||||
error: response.data.error as string,
|
||||
authorization: response.data.authorization as string,
|
||||
queryToken: response.data.queryToken as any,
|
||||
token: response.data.token as string,
|
||||
addresses: response.data.addresses as string[],
|
||||
scryptedCloud,
|
||||
directAddress,
|
||||
@@ -174,9 +200,12 @@ export function redirectScryptedLogin(options?: {
|
||||
globalThis.location.href = redirect_uri;
|
||||
}
|
||||
|
||||
export function combineBaseUrl(baseUrl: string, rootPath: string) {
|
||||
return baseUrl ? new URL(rootPath, baseUrl).toString() : '/' + rootPath;
|
||||
}
|
||||
|
||||
export async function redirectScryptedLogout(baseUrl?: string) {
|
||||
baseUrl = baseUrl || '';
|
||||
globalThis.location.href = `${baseUrl}/logout`;
|
||||
globalThis.location.href = combineBaseUrl(baseUrl, 'logout');
|
||||
}
|
||||
|
||||
export async function connectScryptedClient(options: ScryptedClientOptions): Promise<ScryptedClientStatic> {
|
||||
@@ -218,9 +247,10 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
}
|
||||
|
||||
let socket: IOClientSocket;
|
||||
const endpointPath = `/endpoint/${pluginId}`;
|
||||
const eioPath = `endpoint/${pluginId}/engine.io/api`;
|
||||
const eioEndpoint = baseUrl ? new URL(eioPath, baseUrl).pathname : '/' + eioPath;
|
||||
const eioOptions: Partial<SocketOptions> = {
|
||||
path: `${endpointPath}/engine.io/api`,
|
||||
path: eioEndpoint,
|
||||
withCredentials: true,
|
||||
extraHeaders,
|
||||
rejectUnauthorized: false,
|
||||
@@ -237,14 +267,15 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
// if the cert has been accepted. Other browsers seem fine.
|
||||
// So the default is not to connect to IP addresses on Chrome, but do so on other browsers.
|
||||
const isChrome = globalThis.navigator?.userAgent.includes('Chrome');
|
||||
const isNotChromeOrIsInstalledApp = !isChrome || isInstalledApp();
|
||||
|
||||
const addresses: string[] = [];
|
||||
const localAddressDefault = !isChrome;
|
||||
const localAddressDefault = isNotChromeOrIsInstalledApp;
|
||||
if (((scryptedCloud && options.local === undefined && localAddressDefault) || options.local) && localAddresses) {
|
||||
addresses.push(...localAddresses);
|
||||
}
|
||||
|
||||
const directAddressDefault = directAddress && (!isChrome || !isIPAddress(directAddress));
|
||||
const directAddressDefault = directAddress && (isNotChromeOrIsInstalledApp || !isIPAddress(directAddress));
|
||||
if (((scryptedCloud && options.direct === undefined && directAddressDefault) || options.direct) && directAddress) {
|
||||
addresses.push(directAddress);
|
||||
}
|
||||
@@ -504,23 +535,8 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
|
||||
} = scrypted;
|
||||
console.log('api attached', Date.now() - start);
|
||||
|
||||
mediaManager.createMediaObject = async (data, mimeType, options) => {
|
||||
const mo: MediaObjectRemote & {
|
||||
[RpcPeer.PROPERTY_PROXY_PROPERTIES]: any,
|
||||
[RpcPeer.PROPERTY_JSON_DISABLE_SERIALIZATION]: true,
|
||||
} = {
|
||||
[RpcPeer.PROPERTY_JSON_DISABLE_SERIALIZATION]: true,
|
||||
[RpcPeer.PROPERTY_PROXY_PROPERTIES]: {
|
||||
mimeType,
|
||||
sourceId: options?.sourceId,
|
||||
},
|
||||
mimeType,
|
||||
sourceId: options?.sourceId,
|
||||
async getData() {
|
||||
return data;
|
||||
},
|
||||
};
|
||||
return mo;
|
||||
mediaManager.createMediaObject = async<T extends MediaObjectOptions>(data: any, mimeType: string, options: T) => {
|
||||
return new MediaObject(mimeType, data, options) as any;
|
||||
}
|
||||
|
||||
const { browserSignalingSession, connectionManagementId, updateSessionId } = rpcPeer.params;
|
||||
|
||||
4
packages/h264-repacketizer/package-lock.json
generated
4
packages/h264-repacketizer/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/h264-packetizer",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.7",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/h264-packetizer",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.7",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.18",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/h264-repacketizer",
|
||||
"version": "0.0.6",
|
||||
"version": "0.0.7",
|
||||
"description": "",
|
||||
"main": "dist/index.js",
|
||||
"scripts": {
|
||||
|
||||
2
plugins/alexa/.vscode/settings.json
vendored
2
plugins/alexa/.vscode/settings.json
vendored
@@ -1,4 +1,4 @@
|
||||
|
||||
{
|
||||
"scrypted.debugHost": "127.0.0.1",
|
||||
"scrypted.debugHost": "koushik-ubuntu",
|
||||
}
|
||||
324
plugins/alexa/package-lock.json
generated
324
plugins/alexa/package-lock.json
generated
@@ -1,175 +1,107 @@
|
||||
{
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.0.20",
|
||||
"lockfileVersion": 2,
|
||||
"version": "0.2.5",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.0.20",
|
||||
"version": "0.2.5",
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
"name": "@scrypted/common",
|
||||
"version": "1.0.1",
|
||||
"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"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.9.0"
|
||||
"@scrypted/sdk": "../../sdk",
|
||||
"@types/node": "^18.4.2"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.39",
|
||||
"version": "0.2.101",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.16.7",
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
"adm-zip": "^0.4.13",
|
||||
"axios": "^0.21.4",
|
||||
"babel-loader": "^8.2.3",
|
||||
"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",
|
||||
"typescript": "^4.9.3",
|
||||
"webpack": "^5.74.0",
|
||||
"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-readme": "bin/scrypted-readme.js",
|
||||
"scrypted-setup-project": "bin/scrypted-setup-project.js",
|
||||
"scrypted-webpack": "bin/scrypted-webpack.js"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/node": "^18.11.18",
|
||||
"@types/stringify-object": "^4.0.0",
|
||||
"stringify-object": "^3.3.0",
|
||||
"ts-node": "^10.4.0",
|
||||
"typedoc": "^0.23.21"
|
||||
}
|
||||
},
|
||||
"../../server": {
|
||||
"version": "0.4.9",
|
||||
"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"
|
||||
},
|
||||
"bin": {
|
||||
"scrypted-serve": "bin/scrypted-serve"
|
||||
},
|
||||
"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"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"node-pty-prebuilt-multiarch": "^0.10.1-pre.5"
|
||||
}
|
||||
},
|
||||
"node_modules/@scrypted/common": {
|
||||
"resolved": "../../common",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@scrypted/sdk": {
|
||||
"resolved": "../../sdk",
|
||||
"link": true
|
||||
},
|
||||
"node_modules/@scrypted/server": {
|
||||
"resolved": "../../server",
|
||||
"link": 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=="
|
||||
"version": "18.14.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.14.2.tgz",
|
||||
"integrity": "sha512-1uEQxww3DaghA0RxqHx0O0ppVlo43pJhepY51OxuQIKHpjbnYLA7vcdwioNPzIqmC2u3I/dmylcqjlh0e7AyUA==",
|
||||
"dev": true
|
||||
},
|
||||
"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/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": "0.24.0",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-0.24.0.tgz",
|
||||
"integrity": "sha512-Q6cWsys88HoPgAaFAVUb0WpPk0O8iTeisR9IMqy9G8AbO4NlpVknrnQS03zzF9PGAWgO3cgletO3VjV/P7VztA==",
|
||||
"version": "1.3.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
|
||||
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.14.4"
|
||||
"follow-redirects": "^1.15.0",
|
||||
"form-data": "^4.0.0",
|
||||
"proxy-from-env": "^1.1.0"
|
||||
}
|
||||
},
|
||||
"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/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/follow-redirects": {
|
||||
"version": "1.14.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
|
||||
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w==",
|
||||
"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",
|
||||
@@ -185,6 +117,43 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"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/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/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/uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
@@ -193,124 +162,5 @@
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"@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=="
|
||||
},
|
||||
"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"
|
||||
}
|
||||
},
|
||||
"follow-redirects": {
|
||||
"version": "1.14.9",
|
||||
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz",
|
||||
"integrity": "sha512-MQDfihBQYMcyy5dhRDJUHcw7lb2Pv/TuE6xP1vyraLukNDHKbDxDNaOE3NbCAdKQApno+GPRyo1YAp89yCjK4w=="
|
||||
},
|
||||
"uuid": {
|
||||
"version": "9.0.0",
|
||||
"resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.0.tgz",
|
||||
"integrity": "sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg=="
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/alexa",
|
||||
"version": "0.1.0",
|
||||
"version": "0.2.5",
|
||||
"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": "../../sdk"
|
||||
}
|
||||
}
|
||||
|
||||
221
plugins/alexa/src/alexa.ts
Normal file
221
plugins/alexa/src/alexa.ts
Normal file
@@ -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<NS = string, N = string> {
|
||||
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<NS = string, N = string, P = Payload> {
|
||||
header: Header<NS, N>;
|
||||
endpoint: Endpoint;
|
||||
payload: P;
|
||||
}
|
||||
|
||||
export interface Event<NS = string, N = string, P = Payload> {
|
||||
header: Header<NS, N>;
|
||||
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<NS = string, N = string, P = Payload> {
|
||||
event: Event<NS, N, P>;
|
||||
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;
|
||||
}
|
||||
131
plugins/alexa/src/common.ts
Normal file
131
plugins/alexa/src/common.ts
Normal file
@@ -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);
|
||||
}
|
||||
34
plugins/alexa/src/handlers.ts
Normal file
34
plugins/alexa/src/handlers.ts
Normal file
@@ -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<void>
|
||||
export type AlexaDeviceHandler<T> = (request: HttpRequest, response: AlexaHttpResponse, directive: Directive, device: ScryptedDevice & T) => Promise<void>
|
||||
|
||||
export const alexaDeviceHandlers = new Map<string, AlexaDeviceHandler<any>>();
|
||||
export const alexaHandlers = new Map<string, AlexaHandler>();
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -1,18 +1,26 @@
|
||||
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;
|
||||
|
||||
function debug(...args: any[]) {
|
||||
if (DEBUG)
|
||||
console.debug(...args);
|
||||
}
|
||||
|
||||
class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, MixinProvider, Settings {
|
||||
storageSettings = new StorageSettings(this, {
|
||||
tokenInfo: {
|
||||
hide: true,
|
||||
@@ -22,96 +30,202 @@ 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.',
|
||||
type: 'string',
|
||||
readonly: true
|
||||
},
|
||||
debug: {
|
||||
title: 'Debug Events',
|
||||
description: 'Log all events to the console. This will be very noisy and should not be left enabled.',
|
||||
type: 'boolean',
|
||||
onPut(oldValue: boolean, newValue: boolean) {
|
||||
DEBUG = newValue;
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
handlers = new Map<string, AlexaHandler>();
|
||||
accessToken: Promise<string>;
|
||||
validAuths = new Set<string>();
|
||||
devices = new Map<string, ScryptedDevice>();
|
||||
|
||||
constructor(nativeId?: string) {
|
||||
super(nativeId);
|
||||
|
||||
this.handlers.set('Alexa.Authorization', this.alexaAuthorization);
|
||||
this.handlers.set('Alexa.Discovery', this.alexaDiscovery);
|
||||
DEBUG = this.storageSettings.values.debug ?? false;
|
||||
|
||||
this.syncDevices();
|
||||
alexaHandlers.set('Alexa.Authorization/AcceptGrant', this.onAlexaAuthorization);
|
||||
alexaHandlers.set('Alexa.Discovery/Discover', this.onDiscoverEndpoints);
|
||||
|
||||
systemManager.listen(async (eventSource, eventDetails, eventData) => {
|
||||
if (!eventSource)
|
||||
return;
|
||||
this.start();
|
||||
}
|
||||
|
||||
if (!this.storageSettings.values.syncedDevices.includes(eventSource.id))
|
||||
return;
|
||||
async start() {
|
||||
|
||||
const supportedType = supportedTypes.get(eventSource.type);
|
||||
if (!supportedType) {
|
||||
this.console.warn(`${eventSource.name} no longer supported type?`);
|
||||
return;
|
||||
for (const id of Object.keys(systemManager.getSystemState())) {
|
||||
const device = systemManager.getDeviceById(id);
|
||||
await this.tryEnableMixin(device);
|
||||
}
|
||||
|
||||
systemManager.listen((async (eventSource: ScryptedDevice | undefined, eventDetails: EventDetails, eventData: any) => {
|
||||
const status = await this.tryEnableMixin(eventSource);
|
||||
|
||||
// sync new devices when added or removed
|
||||
if (status === DeviceMixinStatus.Setup)
|
||||
await this.syncEndpoints();
|
||||
|
||||
if (status === DeviceMixinStatus.Setup || status === DeviceMixinStatus.AlreadySetup) {
|
||||
|
||||
if (!this.devices.has(eventSource.id)) {
|
||||
this.devices.set(eventSource.id, eventSource);
|
||||
eventSource.listen(ScryptedInterface.ObjectDetector, this.deviceListen.bind(this));
|
||||
}
|
||||
|
||||
this.deviceListen(eventSource, eventDetails, eventData);
|
||||
}
|
||||
}).bind(this));
|
||||
|
||||
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,
|
||||
await this.syncEndpoints();
|
||||
}
|
||||
|
||||
private async tryEnableMixin(device: ScryptedDevice): Promise<DeviceMixinStatus> {
|
||||
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;
|
||||
}
|
||||
|
||||
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
|
||||
const available = supportedTypes.has(type);
|
||||
|
||||
if (available)
|
||||
return [];
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
async getMixin(device: ScryptedDevice, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any }): Promise<any> {
|
||||
return device;
|
||||
}
|
||||
|
||||
async releaseMixin(id: string, mixinDevice: any): Promise<void> {
|
||||
const device = systemManager.getDeviceById(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<void> {
|
||||
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;
|
||||
|
||||
let report = await supportedType.sendEvent(eventSource, eventDetails, eventData);
|
||||
|
||||
if (!report && eventDetails.eventInterface === ScryptedInterface.Online) {
|
||||
report = {};
|
||||
}
|
||||
|
||||
if (!report && eventDetails.eventInterface === ScryptedInterface.Battery) {
|
||||
report = {};
|
||||
}
|
||||
|
||||
if (!report) {
|
||||
this.console.warn(`${eventDetails.eventInterface}.${eventDetails.property} not supported for device ${eventSource.type}`);
|
||||
return;
|
||||
}
|
||||
|
||||
debug("event", eventDetails.eventInterface, eventDetails.property, eventSource.type);
|
||||
|
||||
let data = {
|
||||
"event": {
|
||||
"header": {
|
||||
"messageId": createMessageId(),
|
||||
"namespace": report?.event?.header?.namespace ?? "Alexa",
|
||||
"name": report?.event?.header?.name ?? "ChangeReport",
|
||||
"payloadVersion": "3"
|
||||
},
|
||||
"context": report?.context
|
||||
}
|
||||
"endpoint": {
|
||||
"endpointId": eventSource.id,
|
||||
},
|
||||
payload: report?.event?.payload
|
||||
},
|
||||
context: report?.context
|
||||
} as ChangeReport;
|
||||
|
||||
data = addOnline(data, eventSource);
|
||||
data = addBattery(data, eventSource);
|
||||
data = addPowerSensor(data, eventSource);
|
||||
data = addOnline(data, eventSource);
|
||||
data = addBattery(data, eventSource);
|
||||
|
||||
// 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,
|
||||
};
|
||||
// nothing to report
|
||||
if (data.context === undefined && data.event.payload === undefined)
|
||||
return;
|
||||
|
||||
data = await this.addAccessToken(data);
|
||||
|
||||
await this.postEvent(data);
|
||||
});
|
||||
await this.postEvent(data);
|
||||
}
|
||||
|
||||
private async addAccessToken(data: any) : Promise<any> {
|
||||
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<Setting[]> {
|
||||
return this.storageSettings.getSettings();
|
||||
}
|
||||
|
||||
putSetting(key: string, value: SettingValue): Promise<void> {
|
||||
return this.storageSettings.putSetting(key, value);
|
||||
}
|
||||
|
||||
async getMixin(mixinDevice: any, mixinDeviceInterfaces: ScryptedInterface[], mixinDeviceState: { [key: string]: any; }): Promise<any> {
|
||||
return mixinDevice;
|
||||
}
|
||||
|
||||
async releaseMixin(id: string, mixinDevice: any): Promise<void> {
|
||||
const device = systemManager.getDeviceById(id);
|
||||
if (device.mixins?.includes(this.id)) {
|
||||
return;
|
||||
}
|
||||
this.console.log('release mixin', id);
|
||||
this.log.a(`${device.name} was removed. The Alexa plugin will reload momentarily.`);
|
||||
deviceManager.requestRestart();
|
||||
}
|
||||
|
||||
readonly endpoints: string[] = [
|
||||
'api.amazonalexa.com',
|
||||
'api.eu.amazonalexa.com',
|
||||
@@ -146,6 +260,8 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
|
||||
const endpoint = await this.getAlexaEndpoint();
|
||||
const self = this;
|
||||
|
||||
debug("send event to alexa", data);
|
||||
|
||||
return axios.post(`https://${endpoint}/v3/events`, data, {
|
||||
headers: {
|
||||
'Authorization': 'Bearer ' + accessToken,
|
||||
@@ -160,25 +276,59 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
|
||||
});
|
||||
}
|
||||
|
||||
async syncDevices() {
|
||||
const endpoints = await this.addOrUpdateReport();
|
||||
async getEndpoints() : Promise<DiscoveryEndpoint[]> {
|
||||
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 +338,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 +392,6 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
|
||||
})
|
||||
}
|
||||
|
||||
async canMixin(type: ScryptedDeviceType, interfaces: string[]): Promise<string[]> {
|
||||
const discovery = isSupported({
|
||||
type,
|
||||
interfaces,
|
||||
} as any);
|
||||
|
||||
if (!discovery)
|
||||
return;
|
||||
return [];
|
||||
}
|
||||
|
||||
getAccessToken(): Promise<string> {
|
||||
if (this.accessToken)
|
||||
return this.accessToken;
|
||||
@@ -306,9 +468,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 +482,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 +499,7 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
|
||||
},
|
||||
"payload": {}
|
||||
}
|
||||
}));
|
||||
});
|
||||
} catch (error) {
|
||||
this.console.error(`AcceptGrant.Response failed because ${error}`);
|
||||
|
||||
@@ -363,14 +511,15 @@ class AlexaPlugin extends AutoenableMixinProvider implements HttpRequestHandler,
|
||||
}
|
||||
}
|
||||
|
||||
createEndpoint(device: ScryptedDevice): DiscoveryEndpoint<any> {
|
||||
async getEndpointForDevice(device: ScryptedDevice) : Promise<DiscoveryEndpoint> {
|
||||
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 +529,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 +556,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,79 +579,25 @@ 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<any>[]) {
|
||||
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<any>[] = [];
|
||||
|
||||
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<any> = {
|
||||
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 {
|
||||
debug("making authorization request to Scrypted");
|
||||
|
||||
await axios.get('https://home.scrypted.app/_punch/getcookie', {
|
||||
headers: {
|
||||
'Authorization': authorization,
|
||||
@@ -501,42 +608,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 body = JSON.parse(request.body);
|
||||
const { directive } = body;
|
||||
const { namespace, name } = directive.header;
|
||||
|
||||
const capHandler = capabilityHandlers.get(namespace);
|
||||
if (capHandler) {
|
||||
const device = systemManager.getDeviceById(directive.endpoint.endpointId);
|
||||
if (!device) {
|
||||
response.send('Not Found', {
|
||||
code: 404,
|
||||
});
|
||||
return;
|
||||
}
|
||||
const mapName = `${namespace}/${name}`;
|
||||
|
||||
return capHandler.apply(this, [request, response, directive, device]);
|
||||
debug("received directive from alexa", mapName, body);
|
||||
|
||||
const handler = alexaHandlers.get(mapName);
|
||||
if (handler)
|
||||
return handler.apply(this, [request, response, directive]);
|
||||
|
||||
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
|
||||
debug("response to alexa directive", 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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,5 +0,0 @@
|
||||
import {v4 as uuidv4} from 'uuid';
|
||||
|
||||
export function createMessageId() {
|
||||
return uuidv4();
|
||||
}
|
||||
@@ -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<any>[] {
|
||||
const capabilities: Capability<any>[] = [
|
||||
{
|
||||
"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<Partial<DiscoveryEndpoint>> {
|
||||
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<StateReport> {
|
||||
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<Partial<Report>>{
|
||||
return reportCameraState(device);
|
||||
},
|
||||
async sendEvent(eventSource: ScryptedDevice & MotionSensor, eventDetails, eventData): Promise<EventReport> {
|
||||
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
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
};
|
||||
sendEvent(eventSource: ScryptedDevice & MotionSensor & ObjectDetector, eventDetails, eventData): Promise<Partial<Report>> {
|
||||
return sendCameraEvent(eventSource, eventDetails, eventData);
|
||||
}
|
||||
});
|
||||
|
||||
export const rtcHandlers = new Map<string, AlexaCapabilityHandler<any>>();
|
||||
|
||||
export class AlexaSignalingSession implements RTCSignalingSession {
|
||||
constructor(public response: HttpResponse, public directive: any) {
|
||||
}
|
||||
|
||||
async getOptions(): Promise<RTCSignalingOptions> {
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async createLocalDescription(type: "offer" | "answer", setup: RTCAVSignalingSetup, sendIceCandidate: RTCSignalingSendIceCandidate): Promise<RTCSessionDescriptionInit> {
|
||||
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<void> {
|
||||
throw new Error("Alexa does not support trickle ICE");
|
||||
}
|
||||
|
||||
async setRemoteDescription(description: RTCSessionDescriptionInit, setup: RTCAVSignalingSetup): Promise<void> {
|
||||
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));
|
||||
});
|
||||
|
||||
194
plugins/alexa/src/types/camera/capabilities.ts
Normal file
194
plugins/alexa/src/types/camera/capabilities.ts
Normal file
@@ -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<Partial<Report>>{
|
||||
let data = {
|
||||
context: {
|
||||
properties: []
|
||||
}
|
||||
|
||||
} as Partial<StateReport>;
|
||||
|
||||
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<Partial<Report>> {
|
||||
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<ObjectDetectionEvent>;
|
||||
|
||||
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<ChangeReport>;
|
||||
|
||||
return undefined;
|
||||
};
|
||||
|
||||
export async function getCameraCapabilities(device: ScryptedDevice): Promise<DiscoveryCapability[]> {
|
||||
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;
|
||||
};
|
||||
159
plugins/alexa/src/types/camera/handlers.ts
Normal file
159
plugins/alexa/src/types/camera/handlers.ts
Normal file
@@ -0,0 +1,159 @@
|
||||
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<RTCSignalingOptions> {
|
||||
return {
|
||||
proxy: true,
|
||||
offer: {
|
||||
type: 'offer',
|
||||
sdp: this.directive.payload.offer.value,
|
||||
},
|
||||
disableTrickle: true,
|
||||
disableTurn: true,
|
||||
// this could be a low resolution screen, no way of knowning, so never send a 1080p stream
|
||||
screen: {
|
||||
devicePixelRatio: 1,
|
||||
width: 1280,
|
||||
height: 720
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async createLocalDescription(type: "offer" | "answer", setup: RTCAVSignalingSetup, sendIceCandidate: RTCSignalingSendIceCandidate): Promise<RTCSessionDescriptionInit> {
|
||||
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<void> {
|
||||
throw new Error("Alexa does not support trickle ICE");
|
||||
}
|
||||
|
||||
async setRemoteDescription(description: RTCSessionDescriptionInit, setup: RTCAVSignalingSetup): Promise<void> {
|
||||
|
||||
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<string, RTCSessionControl>();
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -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<void>
|
||||
export type AlexaCapabilityHandler<T> = (request: HttpRequest, response: HttpResponse, directive: Directive, device: ScryptedDevice & T) => Promise<void>
|
||||
|
||||
export const supportedTypes = new Map<ScryptedDeviceType, SupportedType>();
|
||||
export const capabilityHandlers = new Map<string, AlexaCapabilityHandler<any>>();
|
||||
export const alexaHandlers = new Map<string, AlexaCapabilityHandler<any>>();
|
||||
|
||||
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<DiscoveryEndpoint<any>>;
|
||||
sendEvent(eventSource: ScryptedDevice, eventDetails: EventDetails, eventData: any): Promise<EventReport>;
|
||||
reportState(device: ScryptedDevice): Promise<StateReport>;
|
||||
}
|
||||
|
||||
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);
|
||||
});
|
||||
@@ -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<Partial<DiscoveryEndpoint>> {
|
||||
let capabilities: any[] = [];
|
||||
let category: DisplayCategory = 'DOORBELL';
|
||||
|
||||
const capabilities = getCameraCapabilities(device);
|
||||
capabilities.push(
|
||||
{
|
||||
"type": "AlexaInterface",
|
||||
"interface": "Alexa.DoorbellEventSource",
|
||||
"version": "3",
|
||||
"proactivelyReported": true
|
||||
} as any,
|
||||
);
|
||||
|
||||
return {
|
||||
displayCategories: ['CAMERA'],
|
||||
capabilities
|
||||
if (device.interfaces.includes(ScryptedInterface.RTCSignalingChannel)) {
|
||||
capabilities = await getCameraCapabilities(device);
|
||||
category = 'CAMERA';
|
||||
}
|
||||
},
|
||||
async reportState(device: ScryptedDevice & MotionSensor): Promise<StateReport>{
|
||||
|
||||
if (device.interfaces.includes(ScryptedInterface.BinarySensor)) {
|
||||
capabilities.push(
|
||||
{
|
||||
"type": "AlexaInterface",
|
||||
"interface": "Alexa.DoorbellEventSource",
|
||||
"version": "3",
|
||||
"proactivelyReported": true
|
||||
} as any,
|
||||
);
|
||||
}
|
||||
|
||||
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<EventReport> {
|
||||
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<Partial<Report>>{
|
||||
return reportCameraState(device);
|
||||
},
|
||||
async sendEvent(eventSource: ScryptedDevice & MotionSensor & ObjectDetector, eventDetails, eventData): Promise<Partial<Report>> {
|
||||
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<DoorbellPressEvent>;
|
||||
}
|
||||
});
|
||||
|
||||
@@ -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<Partial<DiscoveryEndpoint>> {
|
||||
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<StateReport> {
|
||||
async sendReport(eventSource: ScryptedDevice & EntrySensor): Promise<Partial<Report>> {
|
||||
return {
|
||||
type: 'state',
|
||||
namespace: 'Alexa',
|
||||
name: 'StateReport',
|
||||
context: {
|
||||
"properties": [
|
||||
{
|
||||
@@ -142,14 +138,12 @@ addSupportedType(ScryptedDeviceType.Garage, {
|
||||
}
|
||||
};
|
||||
},
|
||||
async sendEvent(eventSource: ScryptedDevice & EntrySensor, eventDetails, eventData): Promise<EventReport> {
|
||||
async sendEvent(eventSource: ScryptedDevice & Entry & EntrySensor, eventDetails, eventData): Promise<Partial<Report>> {
|
||||
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<ChangeReport>;
|
||||
},
|
||||
async setState(eventSource: ScryptedDevice & Entry & EntrySensor, payload: any): Promise<Partial<Report>> {
|
||||
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
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
31
plugins/alexa/src/types/garagedoor/handlers.ts
Normal file
31
plugins/alexa/src/types/garagedoor/handlers.ts
Normal file
@@ -0,0 +1,31 @@
|
||||
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";
|
||||
|
||||
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);
|
||||
@@ -1,6 +1,21 @@
|
||||
import { ScryptedDeviceType, ScryptedDevice, EventDetails } from '@scrypted/sdk';
|
||||
import { DiscoveryEndpoint, Report } from '../alexa';
|
||||
|
||||
export interface SupportedType {
|
||||
discover(device: ScryptedDevice): Promise<Partial<DiscoveryEndpoint>>;
|
||||
sendEvent(device: ScryptedDevice, eventDetails: EventDetails, eventData: any): Promise<Partial<Report>>;
|
||||
sendReport(device: ScryptedDevice): Promise<Partial<Report>>;
|
||||
setState?(device: ScryptedDevice, payload: any): Promise<Partial<Report>>;
|
||||
}
|
||||
|
||||
export const supportedTypes = new Map<ScryptedDeviceType, SupportedType>();
|
||||
|
||||
import '../handlers';
|
||||
import './camera';
|
||||
import './camera/handlers';
|
||||
import './doorbell';
|
||||
import './garagedoor';
|
||||
|
||||
export { isSupported} from './common';
|
||||
|
||||
import './switch';
|
||||
import './switch/handlers';
|
||||
import './sensor';
|
||||
import './securitysystem';
|
||||
179
plugins/alexa/src/types/securitysystem.ts
Normal file
179
plugins/alexa/src/types/securitysystem.ts
Normal file
@@ -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<Partial<DiscoveryEndpoint>> {
|
||||
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<Partial<Report>> {
|
||||
let data = {
|
||||
context: {
|
||||
properties: []
|
||||
}
|
||||
|
||||
} as Partial<StateReport>;
|
||||
|
||||
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<Partial<Report>> {
|
||||
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<ChangeReport>;
|
||||
}
|
||||
|
||||
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<ChangeReport>;
|
||||
}
|
||||
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
196
plugins/alexa/src/types/sensor.ts
Normal file
196
plugins/alexa/src/types/sensor.ts
Normal file
@@ -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<Partial<DiscoveryEndpoint>> {
|
||||
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<Partial<Report>> {
|
||||
let data = {
|
||||
context: {
|
||||
properties: []
|
||||
}
|
||||
|
||||
} as Partial<StateReport>;
|
||||
|
||||
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<Partial<Report>> {
|
||||
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<ChangeReport>;
|
||||
|
||||
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<ChangeReport>;
|
||||
|
||||
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<ChangeReport>;
|
||||
|
||||
return undefined;
|
||||
}
|
||||
});
|
||||
71
plugins/alexa/src/types/switch.ts
Normal file
71
plugins/alexa/src/types/switch.ts
Normal file
@@ -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<Partial<DiscoveryEndpoint>> {
|
||||
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<Partial<Report>> {
|
||||
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<Partial<Report>> {
|
||||
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<ChangeReport>;
|
||||
}
|
||||
});
|
||||
55
plugins/alexa/src/types/switch/handlers.ts
Normal file
55
plugins/alexa/src/types/switch/handlers.ts
Normal file
@@ -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);
|
||||
});
|
||||
20
plugins/amcrest/package-lock.json
generated
20
plugins/amcrest/package-lock.json
generated
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.119",
|
||||
"version": "0.0.122",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.119",
|
||||
"version": "0.0.122",
|
||||
"license": "Apache",
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
@@ -16,7 +16,7 @@
|
||||
"multiparty": "^4.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.0"
|
||||
"@types/node": "^18.15.11"
|
||||
}
|
||||
},
|
||||
"../../common": {
|
||||
@@ -36,7 +36,7 @@
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.68",
|
||||
"version": "0.2.87",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"@babel/preset-typescript": "^7.18.6",
|
||||
@@ -100,9 +100,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "16.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.0.tgz",
|
||||
"integrity": "sha512-8MLkBIYQMuhRBQzGN9875bYsOhPnf/0rgXGo66S2FemHkhbn9qtsz9ywV1iCG+vbjigE4WUNVvw37Dx+L0qsPg=="
|
||||
"version": "18.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
||||
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q=="
|
||||
},
|
||||
"node_modules/auth-header": {
|
||||
"version": "1.0.0",
|
||||
@@ -291,9 +291,9 @@
|
||||
}
|
||||
},
|
||||
"@types/node": {
|
||||
"version": "16.11.0",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.0.tgz",
|
||||
"integrity": "sha512-8MLkBIYQMuhRBQzGN9875bYsOhPnf/0rgXGo66S2FemHkhbn9qtsz9ywV1iCG+vbjigE4WUNVvw37Dx+L0qsPg=="
|
||||
"version": "18.15.11",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.15.11.tgz",
|
||||
"integrity": "sha512-E5Kwq2n4SbMzQOn6wnmBjuK9ouqlURrcZDVfbo9ftDDTFt3nk7ZKK4GMOzoYgnpQJKcxwQw+lGaBvvlMo0qN/Q=="
|
||||
},
|
||||
"auth-header": {
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/amcrest",
|
||||
"version": "0.0.119",
|
||||
"version": "0.0.122",
|
||||
"description": "Amcrest Plugin for Scrypted",
|
||||
"author": "Scrypted",
|
||||
"license": "Apache",
|
||||
@@ -36,12 +36,12 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@koush/axios-digest-auth": "^0.8.5",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@scrypted/common": "file:../../common",
|
||||
"@scrypted/sdk": "file:../../sdk",
|
||||
"@types/multiparty": "^0.0.33",
|
||||
"multiparty": "^4.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "^16.11.0"
|
||||
"@types/node": "^18.15.11"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,6 +33,16 @@ export class AmcrestCameraClient {
|
||||
});
|
||||
}
|
||||
|
||||
async reboot() {
|
||||
const response = await this.digestAuth.request({
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
method: "GET",
|
||||
responseType: 'text',
|
||||
url: `http://${this.ip}/cgi-bin/magicBox.cgi?action=reboot`,
|
||||
});
|
||||
return response.data as string;
|
||||
}
|
||||
|
||||
async checkTwoWayAudio() {
|
||||
const response = await this.digestAuth.request({
|
||||
httpsAgent: amcrestHttpsAgent,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { ffmpegLogInitialOutput } from '@scrypted/common/src/media-helpers';
|
||||
import { readLength } from "@scrypted/common/src/read-stream";
|
||||
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, MediaStreamOptions, PictureOptions, RequestRecordingStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, VideoCameraConfiguration, VideoRecorder } from "@scrypted/sdk";
|
||||
import sdk, { Camera, DeviceCreatorSettings, DeviceInformation, FFmpegInput, Intercom, MediaObject, MediaStreamOptions, PictureOptions, Reboot, RequestRecordingStreamOptions, ResponseMediaStreamOptions, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes, Setting, VideoCameraConfiguration, VideoRecorder } from "@scrypted/sdk";
|
||||
import child_process, { ChildProcess } from 'child_process';
|
||||
import { PassThrough, Readable, Stream } from "stream";
|
||||
import { OnvifIntercom } from "../../onvif/src/onvif-intercom";
|
||||
@@ -23,7 +23,7 @@ function findValue(blob: string, prefix: string, key: string) {
|
||||
return parts[1];
|
||||
}
|
||||
|
||||
class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration, Camera, Intercom, VideoRecorder {
|
||||
class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration, Camera, Intercom, VideoRecorder, Reboot {
|
||||
eventStream: Stream;
|
||||
cp: ChildProcess;
|
||||
client: AmcrestCameraClient;
|
||||
@@ -37,9 +37,15 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
this.storage.removeItem('amcrestDoorbell');
|
||||
}
|
||||
|
||||
this.updateDevice();
|
||||
this.updateDeviceInfo();
|
||||
}
|
||||
|
||||
async reboot() {
|
||||
const client = this.getClient();
|
||||
await client.reboot();
|
||||
}
|
||||
|
||||
getRecordingStreamCurrentTime(recordingStream: MediaObject): Promise<number> {
|
||||
throw new Error("Method not implemented.");
|
||||
}
|
||||
@@ -440,6 +446,29 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
return this.videoStreamOptions;
|
||||
}
|
||||
|
||||
updateDevice() {
|
||||
const doorbellType = this.storage.getItem('doorbellType');
|
||||
const isDoorbell = doorbellType === AMCREST_DOORBELL_TYPE || doorbellType === DAHUA_DOORBELL_TYPE;
|
||||
// true is the legacy value before onvif was added.
|
||||
const twoWayAudio = this.storage.getItem('twoWayAudio') === 'true'
|
||||
|| this.storage.getItem('twoWayAudio') === 'ONVIF'
|
||||
|| this.storage.getItem('twoWayAudio') === 'Amcrest';
|
||||
|
||||
const interfaces = this.provider.getInterfaces();
|
||||
let type: ScryptedDeviceType = undefined;
|
||||
if (isDoorbell) {
|
||||
type = ScryptedDeviceType.Doorbell;
|
||||
interfaces.push(ScryptedInterface.BinarySensor)
|
||||
}
|
||||
if (isDoorbell || twoWayAudio) {
|
||||
interfaces.push(ScryptedInterface.Intercom);
|
||||
}
|
||||
const continuousRecording = this.storage.getItem('continuousRecording') === 'true';
|
||||
if (continuousRecording)
|
||||
interfaces.push(ScryptedInterface.VideoRecorder);
|
||||
this.provider.updateDevice(this.nativeId, this.name, interfaces, type);
|
||||
}
|
||||
|
||||
async putSetting(key: string, value: string) {
|
||||
if (key === 'continuousRecording') {
|
||||
if (value === 'true') {
|
||||
@@ -461,27 +490,8 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
this.videoStreamOptions = undefined;
|
||||
|
||||
super.putSetting(key, value);
|
||||
const doorbellType = this.storage.getItem('doorbellType');
|
||||
const isDoorbell = doorbellType === AMCREST_DOORBELL_TYPE || doorbellType === DAHUA_DOORBELL_TYPE;
|
||||
// true is the legacy value before onvif was added.
|
||||
const twoWayAudio = this.storage.getItem('twoWayAudio') === 'true'
|
||||
|| this.storage.getItem('twoWayAudio') === 'ONVIF'
|
||||
|| this.storage.getItem('twoWayAudio') === 'Amcrest';
|
||||
|
||||
const interfaces = this.provider.getInterfaces();
|
||||
let type: ScryptedDeviceType = undefined;
|
||||
if (isDoorbell) {
|
||||
type = ScryptedDeviceType.Doorbell;
|
||||
interfaces.push(ScryptedInterface.BinarySensor)
|
||||
}
|
||||
if (isDoorbell || twoWayAudio) {
|
||||
interfaces.push(ScryptedInterface.Intercom);
|
||||
}
|
||||
const continuousRecording = this.storage.getItem('continuousRecording') === 'true';
|
||||
if (continuousRecording)
|
||||
interfaces.push(ScryptedInterface.VideoRecorder);
|
||||
this.provider.updateDevice(this.nativeId, this.name, interfaces, type);
|
||||
|
||||
|
||||
this.updateDevice();
|
||||
this.updateDeviceInfo();
|
||||
}
|
||||
|
||||
@@ -576,6 +586,7 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
|
||||
class AmcrestProvider extends RtspProvider {
|
||||
getAdditionalInterfaces() {
|
||||
return [
|
||||
ScryptedInterface.Reboot,
|
||||
ScryptedInterface.VideoCameraConfiguration,
|
||||
ScryptedInterface.Camera,
|
||||
ScryptedInterface.AudioSensor,
|
||||
@@ -616,7 +627,7 @@ class AmcrestProvider extends RtspProvider {
|
||||
this.console.warn('Error probing two way audio', e);
|
||||
}
|
||||
}
|
||||
settings.newCamera ||= 'Hikvision Camera';
|
||||
settings.newCamera ||= 'Amcrest Camera';
|
||||
|
||||
nativeId = await super.createDevice(settings, nativeId);
|
||||
|
||||
|
||||
2
plugins/arlo/.vscode/settings.json
vendored
2
plugins/arlo/.vscode/settings.json
vendored
@@ -22,6 +22,6 @@
|
||||
//"scrypted.volumeRoot": "${config:scrypted.serverRoot}/volume",
|
||||
|
||||
"python.analysis.extraPaths": [
|
||||
"./node_modules/@scrypted/sdk/scrypted_python"
|
||||
"./node_modules/@scrypted/sdk/types/scrypted_python"
|
||||
]
|
||||
}
|
||||
@@ -1,13 +1,25 @@
|
||||
# Arlo Plugin for Scrypted
|
||||
|
||||
The Arlo Plugin connects Scrypted to Arlo cloud, allowing you to access all of your Arlo cameras in Scrypted.
|
||||
The Arlo Plugin connects Scrypted to Arlo Cloud, allowing you to access all of your Arlo cameras in Scrypted.
|
||||
|
||||
It is highly recommended to create a dedicated Arlo account for use with this plugin and share your cameras from your main account, as Arlo only permits one connection to their servers per account. Using a separate account allows you to use the Arlo app or website simultaneously with this plugin.
|
||||
It is highly recommended to create a dedicated Arlo account for use with this plugin and share your cameras from your main account, as Arlo only permits one active login to their servers per account. Using a separate account allows you to use the Arlo app or website simultaneously with this plugin, otherwise logging in from one place will log you out from all other devices.
|
||||
|
||||
The account you use for this plugin must have either SMS or email set as the default 2FA option. Once you enter your username and password on the plugin settings page, you should receive a 2FA code through your default 2FA option. Enter that code into the provided box, and your cameras will appear in Scrypted. Or, see below for configuring IMAP to auto-login with 2FA.
|
||||
|
||||
If you experience any trouble logging in, clear the username and password boxes, reload the plugin, and try again.
|
||||
|
||||
## General Setup Notes
|
||||
|
||||
* Ensure that your Arlo account's default 2FA option is set to either SMS or email.
|
||||
* Motion events notifications should be turned on in the Arlo app. If you are receiving motion push notifications, Scrypted will also receive motion events.
|
||||
* Disable smart detection and any cloud/local recording in the Arlo app. Arlo Cloud only permits one active stream per camera, so any smart detection or recording features may prevent downstream plugins (e.g. Homekit) from successfully pulling the video feed after a motion event.
|
||||
* It is highly recommended to enable the Rebroadcast plugin to allow multiple downstream plugins to pull the video feed within Scrypted.
|
||||
* If there is no audio on your camera, switch to the `FFmpeg (TCP)` parser under the `Cloud RTSP` settings.
|
||||
* Prebuffering should only be enabled if the camera is wired to a persistent power source, such as a wall outlet. Prebuffering will only work if your camera does not have a battery or `Plugged In to External Power` is selected.
|
||||
* The plugin supports pulling RTSP or DASH streams from Arlo Cloud. It is recommended to use RTSP for the lowest latency streams. DASH is inconsistent in reliability, and may return finicky codecs that require additional FFmpeg output arguments, e.g. `-vcodec h264`.
|
||||
|
||||
Note that streaming cameras uses extra Internet bandwidth, since video and audio packets will need to travel from the camera through your network, out to Arlo Cloud, and then back to your network and into Scrypted.
|
||||
|
||||
## IMAP 2FA
|
||||
|
||||
The Arlo Plugin supports using the IMAP protocol to check an email mailbox for Arlo 2FA codes. This requires you to specify an email 2FA option as the default in your Arlo account settings.
|
||||
|
||||
6
plugins/arlo/package-lock.json
generated
6
plugins/arlo/package-lock.json
generated
@@ -1,19 +1,19 @@
|
||||
{
|
||||
"name": "@scrypted/arlo",
|
||||
"version": "0.6.5",
|
||||
"version": "0.7.29",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "@scrypted/arlo",
|
||||
"version": "0.6.5",
|
||||
"version": "0.7.29",
|
||||
"devDependencies": {
|
||||
"@scrypted/sdk": "file:../../sdk"
|
||||
}
|
||||
},
|
||||
"../../sdk": {
|
||||
"name": "@scrypted/sdk",
|
||||
"version": "0.2.63",
|
||||
"version": "0.2.101",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "@scrypted/arlo",
|
||||
"version": "0.6.5",
|
||||
"version": "0.7.29",
|
||||
"description": "Arlo Plugin for Scrypted",
|
||||
"keywords": [
|
||||
"scrypted",
|
||||
|
||||
@@ -24,12 +24,15 @@ limitations under the License.
|
||||
# Import helper classes that are part of this library.
|
||||
|
||||
from .request import Request
|
||||
from .host_picker import pick_host
|
||||
from .mqtt_stream_async import MQTTStream
|
||||
from .sse_stream_async import EventStream
|
||||
from .logging import logger
|
||||
|
||||
|
||||
# Import all of the other stuff.
|
||||
from datetime import datetime
|
||||
from datetime import datetime, timedelta
|
||||
from cachetools import cached, TTLCache
|
||||
import scrypted_arlo_go
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
@@ -37,6 +40,8 @@ import base64
|
||||
import math
|
||||
import random
|
||||
import time
|
||||
import uuid
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
|
||||
stream_class = MQTTStream
|
||||
|
||||
@@ -77,8 +82,11 @@ USER_AGENTS = {
|
||||
class Arlo(object):
|
||||
BASE_URL = 'my.arlo.com'
|
||||
AUTH_URL = 'ocapi-app.arlo.com'
|
||||
BACKUP_AUTH_HOSTS = list(scrypted_arlo_go.BACKUP_AUTH_HOSTS())
|
||||
TRANSID_PREFIX = 'web'
|
||||
|
||||
random.shuffle(BACKUP_AUTH_HOSTS)
|
||||
|
||||
def __init__(self, username, password):
|
||||
self.username = username
|
||||
self.password = password
|
||||
@@ -136,8 +144,7 @@ class Arlo(object):
|
||||
self.BASE_URL = 'myapi.arlo.com'
|
||||
|
||||
def LoginMFA(self):
|
||||
self.request = Request()
|
||||
|
||||
device_id = str(uuid.uuid4())
|
||||
headers = {
|
||||
'DNT': '1',
|
||||
'schemaVersion': '1',
|
||||
@@ -148,11 +155,33 @@ class Arlo(object):
|
||||
'Referer': f'https://{self.BASE_URL}/',
|
||||
'Source': 'arloCamWeb',
|
||||
'TE': 'Trailers',
|
||||
'x-user-device-id': device_id,
|
||||
'x-user-device-automation-name': 'QlJPV1NFUg==',
|
||||
'x-user-device-type': 'BROWSER',
|
||||
'Host': self.AUTH_URL,
|
||||
}
|
||||
|
||||
self.request = Request()
|
||||
try:
|
||||
auth_host = self.AUTH_URL
|
||||
self.request.options(f'https://{auth_host}/api/auth', headers=headers)
|
||||
logger.info("Using primary authentication host")
|
||||
except Exception as e:
|
||||
# in case cloudflare rejects our auth request...
|
||||
logger.warning(f"Using fallback authentication host due to: {e}")
|
||||
|
||||
auth_host = pick_host([
|
||||
base64.b64decode(h.encode("utf-8")).decode("utf-8")
|
||||
for h in self.BACKUP_AUTH_HOSTS
|
||||
], self.AUTH_URL, "/api/auth")
|
||||
logger.debug(f"Selected backup authentication host {auth_host}")
|
||||
|
||||
self.request = Request(mode="ip")
|
||||
|
||||
# Authenticate
|
||||
self.request.options(f'https://{auth_host}/api/auth', headers=headers)
|
||||
auth_body = self.request.post(
|
||||
f'https://{self.AUTH_URL}/api/auth',
|
||||
f'https://{auth_host}/api/auth',
|
||||
params={
|
||||
'email': self.username,
|
||||
'password': str(base64.b64encode(self.password.encode('utf-8')), 'utf-8'),
|
||||
@@ -167,21 +196,26 @@ class Arlo(object):
|
||||
|
||||
# Retrieve MFA factor id
|
||||
factors_body = self.request.get(
|
||||
f'https://{self.AUTH_URL}/api/getFactors',
|
||||
f'https://{auth_host}/api/getFactors',
|
||||
params={'data': auth_body['data']['issued']},
|
||||
headers=headers,
|
||||
raw=True
|
||||
)
|
||||
factor_id = next(
|
||||
i for i in factors_body['data']['items']
|
||||
if (i['factorType'] == 'EMAIL' or i['factorType'] == 'SMS')
|
||||
and i['factorRole'] == "PRIMARY"
|
||||
)['factorId']
|
||||
iter([
|
||||
i for i in factors_body['data']['items']
|
||||
if (i['factorType'] == 'EMAIL' or i['factorType'] == 'SMS')
|
||||
and i['factorRole'] == "PRIMARY"
|
||||
]),
|
||||
{}
|
||||
).get('factorId')
|
||||
if not factor_id:
|
||||
raise Exception("Could not find valid 2FA method - is the primary 2FA set to either Email or SMS?")
|
||||
|
||||
# Start factor auth
|
||||
start_auth_body = self.request.post(
|
||||
f'https://{self.AUTH_URL}/api/startAuth',
|
||||
{'factorId': factor_id},
|
||||
f'https://{auth_host}/api/startAuth',
|
||||
params={'factorId': factor_id},
|
||||
headers=headers,
|
||||
raw=True
|
||||
)
|
||||
@@ -191,8 +225,8 @@ class Arlo(object):
|
||||
nonlocal self, factor_auth_code, headers
|
||||
|
||||
finish_auth_body = self.request.post(
|
||||
f'https://{self.AUTH_URL}/api/finishAuth',
|
||||
{
|
||||
f'https://{auth_host}/api/finishAuth',
|
||||
params={
|
||||
'factorAuthCode': factor_auth_code,
|
||||
'otp': code
|
||||
},
|
||||
@@ -200,6 +234,11 @@ class Arlo(object):
|
||||
raw=True
|
||||
)
|
||||
|
||||
if finish_auth_body.get('data', {}).get('token') is None:
|
||||
raise Exception("Could not complete 2FA, maybe invalid token? If the error persists, please try reloading the plugin and logging in again.")
|
||||
|
||||
self.request = Request()
|
||||
|
||||
# Update Authorization code with new code
|
||||
headers = {
|
||||
'Auth-Version': '2',
|
||||
@@ -227,7 +266,7 @@ class Arlo(object):
|
||||
when subsequent calls to /notify are made.
|
||||
"""
|
||||
async def heartbeat(self, basestations, interval=30):
|
||||
while self.event_stream and self.event_stream.connected:
|
||||
while self.event_stream and self.event_stream.active:
|
||||
for basestation in basestations:
|
||||
try:
|
||||
self.Ping(basestation)
|
||||
@@ -253,14 +292,16 @@ class Arlo(object):
|
||||
cameras[camera['deviceId']] = camera
|
||||
|
||||
# filter out cameras without basestation, where they are their own basestations
|
||||
# for now, keep doorbells and sirens in the list so they get pings
|
||||
# this is so battery-powered devices do not drain due to pings
|
||||
# for wired devices, keep doorbells, sirens, and arloq in the list so they get pings
|
||||
proper_basestations = {}
|
||||
for basestation in basestations.values():
|
||||
if basestation['deviceId'] == basestation.get('parentId') and basestation['deviceType'] not in ['doorbell', 'siren']:
|
||||
if basestation['deviceId'] == basestation.get('parentId') and \
|
||||
basestation['deviceType'] not in ['doorbell', 'siren', 'arloq', 'arloqs']:
|
||||
continue
|
||||
proper_basestations[basestation['deviceId']] = basestation
|
||||
|
||||
logger.info(f"Will send heartbeat to the following basestations: {list(proper_basestations.keys())}")
|
||||
logger.info(f"Will send heartbeat to the following devices: {list(proper_basestations.keys())}")
|
||||
|
||||
# start heartbeat loop with only basestations
|
||||
asyncio.get_event_loop().create_task(heartbeat(self, list(proper_basestations.values())))
|
||||
@@ -348,7 +389,7 @@ class Arlo(object):
|
||||
body['from'] = self.user_id+'_web'
|
||||
body['to'] = basestation_id
|
||||
|
||||
self.request.post(f'https://{self.BASE_URL}/hmsweb/users/devices/notify/'+body['to'], body, headers={"xcloudId":basestation.get('xCloudId')})
|
||||
self.request.post(f'https://{self.BASE_URL}/hmsweb/users/devices/notify/'+body['to'], params=body, headers={"xcloudId":basestation.get('xCloudId')})
|
||||
return body.get('transId')
|
||||
|
||||
def Ping(self, basestation):
|
||||
@@ -378,7 +419,36 @@ class Arlo(object):
|
||||
return None
|
||||
return stop
|
||||
|
||||
return asyncio.get_event_loop().create_task(self.HandleEvents(basestation, resource, ['is'], callbackwrapper))
|
||||
return asyncio.get_event_loop().create_task(
|
||||
self.HandleEvents(basestation, resource, [('is', 'motionDetected')], callbackwrapper)
|
||||
)
|
||||
|
||||
def SubscribeToAudioEvents(self, basestation, camera, callback):
|
||||
"""
|
||||
Use this method to subscribe to audio events. You must provide a callback function which will get called once per audio event.
|
||||
|
||||
The callback function should have the following signature:
|
||||
def callback(self, event)
|
||||
|
||||
This is an example of handling a specific event, in reality, you'd probably want to write a callback for HandleEvents()
|
||||
that has a big switch statement in it to handle all the various events Arlo produces.
|
||||
|
||||
Returns the Task object that contains the subscription loop.
|
||||
"""
|
||||
resource = f"cameras/{camera.get('deviceId')}"
|
||||
|
||||
def callbackwrapper(self, event):
|
||||
properties = event.get('properties', {})
|
||||
stop = None
|
||||
if 'audioDetected' in properties:
|
||||
stop = callback(properties['audioDetected'])
|
||||
if not stop:
|
||||
return None
|
||||
return stop
|
||||
|
||||
return asyncio.get_event_loop().create_task(
|
||||
self.HandleEvents(basestation, resource, [('is', 'audioDetected')], callbackwrapper)
|
||||
)
|
||||
|
||||
def SubscribeToBatteryEvents(self, basestation, camera, callback):
|
||||
"""
|
||||
@@ -403,7 +473,9 @@ class Arlo(object):
|
||||
return None
|
||||
return stop
|
||||
|
||||
return asyncio.get_event_loop().create_task(self.HandleEvents(basestation, resource, ['is'], callbackwrapper))
|
||||
return asyncio.get_event_loop().create_task(
|
||||
self.HandleEvents(basestation, resource, [('is', 'batteryLevel')], callbackwrapper)
|
||||
)
|
||||
|
||||
def SubscribeToDoorbellEvents(self, basestation, doorbell, callback):
|
||||
"""
|
||||
@@ -437,7 +509,9 @@ class Arlo(object):
|
||||
return None
|
||||
return stop
|
||||
|
||||
return asyncio.get_event_loop().create_task(self.HandleEvents(basestation, resource, ['is'], callbackwrapper))
|
||||
return asyncio.get_event_loop().create_task(
|
||||
self.HandleEvents(basestation, resource, [('is', 'buttonPressed')], callbackwrapper)
|
||||
)
|
||||
|
||||
def SubscribeToSDPAnswers(self, basestation, camera, callback):
|
||||
"""
|
||||
@@ -456,14 +530,16 @@ class Arlo(object):
|
||||
|
||||
def callbackwrapper(self, event):
|
||||
properties = event.get("properties", {})
|
||||
stop = None
|
||||
stop = None
|
||||
if properties.get("type") == "answerSdp":
|
||||
stop = callback(properties.get("data"))
|
||||
if not stop:
|
||||
return None
|
||||
return stop
|
||||
|
||||
return asyncio.get_event_loop().create_task(self.HandleEvents(basestation, resource, ['pushToTalk'], callbackwrapper))
|
||||
return asyncio.get_event_loop().create_task(
|
||||
self.HandleEvents(basestation, resource, ['pushToTalk'], callbackwrapper)
|
||||
)
|
||||
|
||||
def SubscribeToCandidateAnswers(self, basestation, camera, callback):
|
||||
"""
|
||||
@@ -482,14 +558,16 @@ class Arlo(object):
|
||||
|
||||
def callbackwrapper(self, event):
|
||||
properties = event.get("properties", {})
|
||||
stop = None
|
||||
stop = None
|
||||
if properties.get("type") == "answerCandidate":
|
||||
stop = callback(properties.get("data"))
|
||||
if not stop:
|
||||
return None
|
||||
return stop
|
||||
|
||||
return asyncio.get_event_loop().create_task(self.HandleEvents(basestation, resource, ['pushToTalk'], callbackwrapper))
|
||||
return asyncio.get_event_loop().create_task(
|
||||
self.HandleEvents(basestation, resource, ['pushToTalk'], callbackwrapper)
|
||||
)
|
||||
|
||||
async def HandleEvents(self, basestation, resource, actions, callback):
|
||||
"""
|
||||
@@ -502,9 +580,17 @@ class Arlo(object):
|
||||
await self.Subscribe()
|
||||
|
||||
async def loop_action_listener(action):
|
||||
# in this function, action can either be a tuple or a string
|
||||
# if it is a tuple, we expect there to be a property key in the tuple
|
||||
property = None
|
||||
if isinstance(action, tuple):
|
||||
action, property = action
|
||||
if not isinstance(action, str):
|
||||
raise Exception('Actions must be either a tuple or a str')
|
||||
|
||||
seen_events = {}
|
||||
while self.event_stream.active:
|
||||
event, _ = await self.event_stream.get(resource, [action], seen_events)
|
||||
event, _ = await self.event_stream.get(resource, action, property, seen_events)
|
||||
|
||||
if event is None or self.event_stream is None \
|
||||
or self.event_stream.event_stream_stop_event.is_set():
|
||||
@@ -514,7 +600,7 @@ class Arlo(object):
|
||||
response = callback(self, event.item)
|
||||
|
||||
# always requeue so other listeners can see the event too
|
||||
self.event_stream.requeue(event, resource, action)
|
||||
self.event_stream.requeue(event, resource, action, property)
|
||||
|
||||
if response is not None:
|
||||
return response
|
||||
@@ -555,7 +641,7 @@ class Arlo(object):
|
||||
If you pass in a valid device type, as a string or a list, this method will return an array of just those devices that match that type. An example would be ['basestation', 'camera']
|
||||
To filter provisioned or unprovisioned devices pass in a True/False value for filter_provisioned. By default both types are returned.
|
||||
"""
|
||||
devices = self.request.get(f'https://{self.BASE_URL}/hmsweb/v2/users/devices')
|
||||
devices = self._getDevicesImpl()
|
||||
if device_type:
|
||||
devices = [ device for device in devices if device.get('deviceType') in device_type]
|
||||
|
||||
@@ -567,23 +653,34 @@ class Arlo(object):
|
||||
|
||||
return devices
|
||||
|
||||
async def StartStream(self, basestation, camera):
|
||||
@cached(cache=TTLCache(maxsize=1, ttl=60))
|
||||
def _getDevicesImpl(self):
|
||||
devices = self.request.get(f'https://{self.BASE_URL}/hmsweb/v2/users/devices')
|
||||
return devices
|
||||
|
||||
async def StartStream(self, basestation, camera, mode="rtsp"):
|
||||
"""
|
||||
This function returns the url of the rtsp video stream.
|
||||
This stream needs to be called within 30 seconds or else it becomes invalid.
|
||||
It can be streamed with: ffmpeg -re -i 'rtsps://<url>' -acodec copy -vcodec copy test.mp4
|
||||
The request to /users/devices/startStream returns: { url:rtsp://<url>:443/vzmodulelive?egressToken=b<xx>&userAgent=iOS&cameraId=<camid>}
|
||||
|
||||
If mode is set to "dash", returns the url to the mpd file for DASH streaming.
|
||||
"""
|
||||
resource = f"cameras/{camera.get('deviceId')}"
|
||||
|
||||
if mode not in ["rtsp", "dash"]:
|
||||
raise ValueError("mode must be 'rtsp' or 'dash'")
|
||||
|
||||
# nonlocal variable hack for Python 2.x.
|
||||
class nl:
|
||||
stream_url_dict = None
|
||||
|
||||
def trigger(self):
|
||||
ua = USER_AGENTS['arlo'] if mode == "rtsp" else USER_AGENTS["firefox"]
|
||||
nl.stream_url_dict = self.request.post(
|
||||
f'https://{self.BASE_URL}/hmsweb/users/devices/startStream',
|
||||
{
|
||||
params={
|
||||
"to": camera.get('parentId'),
|
||||
"from": self.user_id + "_web",
|
||||
"resource": "cameras/" + camera.get('deviceId'),
|
||||
@@ -596,17 +693,47 @@ class Arlo(object):
|
||||
"cameraId": camera.get('deviceId')
|
||||
}
|
||||
},
|
||||
headers={"xcloudId":camera.get('xCloudId')}
|
||||
headers={"xcloudId":camera.get('xCloudId'), 'User-Agent': ua}
|
||||
)
|
||||
|
||||
def callback(self, event):
|
||||
#return nl.stream_url_dict['url'].replace("rtsp://", "rtsps://")
|
||||
properties = event.get("properties", {})
|
||||
if properties.get("activityState") == "userStreamActive":
|
||||
return nl.stream_url_dict['url'].replace("rtsp://", "rtsps://")
|
||||
if mode == "rtsp":
|
||||
return nl.stream_url_dict['url'].replace("rtsp://", "rtsps://")
|
||||
else:
|
||||
return nl.stream_url_dict['url'].replace(":80", "")
|
||||
return None
|
||||
|
||||
return await self.TriggerAndHandleEvent(basestation, resource, ["is"], trigger, callback)
|
||||
return await self.TriggerAndHandleEvent(
|
||||
basestation,
|
||||
resource,
|
||||
[("is", "activityState")],
|
||||
trigger,
|
||||
callback,
|
||||
)
|
||||
|
||||
def GetMPDHeaders(self, url: str) -> dict:
|
||||
parsed = urlparse(url)
|
||||
query = parse_qs(parsed.query)
|
||||
|
||||
headers = {
|
||||
"Accept": "*/*",
|
||||
"Accept-Encoding": "gzip, deflate, br",
|
||||
"Accept-Language": "en-US,en;q=0.9",
|
||||
"Connection": "keep-alive",
|
||||
"DNT": "1",
|
||||
"Egress-Token": query['egressToken'][0],
|
||||
"Origin": "https://my.arlo.com",
|
||||
"Referer": "https://my.arlo.com/",
|
||||
"User-Agent": USER_AGENTS["firefox"],
|
||||
}
|
||||
return headers
|
||||
|
||||
def GetSIPInfo(self):
|
||||
resp = self.request.get(f'https://{self.BASE_URL}/hmsweb/users/devices/sipInfo')
|
||||
return resp
|
||||
|
||||
def StartPushToTalk(self, basestation, camera):
|
||||
url = f'https://{self.BASE_URL}/hmsweb/users/devices/{self.user_id}_{camera.get("deviceId")}/pushtotalk'
|
||||
@@ -644,15 +771,13 @@ class Arlo(object):
|
||||
async def TriggerFullFrameSnapshot(self, basestation, camera):
|
||||
"""
|
||||
This function causes the camera to record a fullframe snapshot.
|
||||
The presignedFullFrameSnapshotUrl url is returned.
|
||||
Use DownloadSnapshot() to download the actual image file.
|
||||
"""
|
||||
resource = f"cameras/{camera.get('deviceId')}"
|
||||
|
||||
def trigger(self):
|
||||
self.request.post(
|
||||
f"https://{self.BASE_URL}/hmsweb/users/devices/fullFrameSnapshot",
|
||||
{
|
||||
params={
|
||||
"to": camera.get("parentId"),
|
||||
"from": self.user_id + "_web",
|
||||
"resource": "cameras/" + camera.get("deviceId"),
|
||||
@@ -676,4 +801,176 @@ class Arlo(object):
|
||||
return url
|
||||
return None
|
||||
|
||||
return await self.TriggerAndHandleEvent(basestation, resource, ["fullFrameSnapshotAvailable", "lastImageSnapshotAvailable", "is"], trigger, callback)
|
||||
return await self.TriggerAndHandleEvent(
|
||||
basestation,
|
||||
resource,
|
||||
[
|
||||
(action, property)
|
||||
for action in ["fullFrameSnapshotAvailable", "lastImageSnapshotAvailable", "is"]
|
||||
for property in ["presignedFullFrameSnapshotUrl", "presignedLastImageUrl"]
|
||||
],
|
||||
trigger,
|
||||
callback,
|
||||
)
|
||||
|
||||
def SirenOn(self, basestation, camera=None):
|
||||
if camera is not None:
|
||||
resource = f"siren/{camera.get('deviceId')}"
|
||||
return self.Notify(basestation, {
|
||||
"action": "set",
|
||||
"resource": resource,
|
||||
"publishResponse": True,
|
||||
"properties": {
|
||||
"sirenState": "on",
|
||||
"duration": 300,
|
||||
"volume": 8,
|
||||
"pattern": "alarm"
|
||||
}
|
||||
})
|
||||
return self.Notify(basestation, {
|
||||
"action": "set",
|
||||
"resource": "siren",
|
||||
"publishResponse": True,
|
||||
"properties": {
|
||||
"sirenState": "on",
|
||||
"duration": 300,
|
||||
"volume": 8,
|
||||
"pattern": "alarm"
|
||||
}
|
||||
})
|
||||
|
||||
def SirenOff(self, basestation, camera=None):
|
||||
if camera is not None:
|
||||
resource = f"siren/{camera.get('deviceId')}"
|
||||
return self.Notify(basestation, {
|
||||
"action": "set",
|
||||
"resource": resource,
|
||||
"publishResponse": True,
|
||||
"properties": {
|
||||
"sirenState": "off",
|
||||
"duration": 300,
|
||||
"volume": 8,
|
||||
"pattern": "alarm"
|
||||
}
|
||||
})
|
||||
return self.Notify(basestation, {
|
||||
"action": "set",
|
||||
"resource": "siren",
|
||||
"publishResponse": True,
|
||||
"properties": {
|
||||
"sirenState": "off",
|
||||
"duration": 300,
|
||||
"volume": 8,
|
||||
"pattern": "alarm"
|
||||
}
|
||||
})
|
||||
|
||||
def SpotlightOn(self, basestation, camera):
|
||||
resource = f"cameras/{camera.get('deviceId')}"
|
||||
return self.Notify(basestation, {
|
||||
"action": "set",
|
||||
"resource": resource,
|
||||
"publishResponse": True,
|
||||
"properties": {
|
||||
"spotlight": {
|
||||
"enabled": True,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
def SpotlightOff(self, basestation, camera):
|
||||
resource = f"cameras/{camera.get('deviceId')}"
|
||||
return self.Notify(basestation, {
|
||||
"action": "set",
|
||||
"resource": resource,
|
||||
"publishResponse": True,
|
||||
"properties": {
|
||||
"spotlight": {
|
||||
"enabled": False,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
def FloodlightOn(self, basestation, camera):
|
||||
resource = f"cameras/{camera.get('deviceId')}"
|
||||
return self.Notify(basestation, {
|
||||
"action": "set",
|
||||
"resource": resource,
|
||||
"publishResponse": True,
|
||||
"properties": {
|
||||
"floodlight": {
|
||||
"on": True,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
def FloodlightOff(self, basestation, camera):
|
||||
resource = f"cameras/{camera.get('deviceId')}"
|
||||
return self.Notify(basestation, {
|
||||
"action": "set",
|
||||
"resource": resource,
|
||||
"publishResponse": True,
|
||||
"properties": {
|
||||
"floodlight": {
|
||||
"on": False,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
def GetLibrary(self, device, from_date: datetime, to_date: datetime):
|
||||
"""
|
||||
This call returns the following:
|
||||
presignedContentUrl is a link to the actual video in Amazon AWS.
|
||||
presignedThumbnailUrl is a link to the thumbnail .jpg of the actual video in Amazon AWS.
|
||||
[
|
||||
{
|
||||
"mediaDurationSecond": 30,
|
||||
"contentType": "video/mp4",
|
||||
"name": "XXXXXXXXXXXXX",
|
||||
"presignedContentUrl": "https://arlos3-prod-z2.s3.amazonaws.com/XXXXXXX_XXXX_XXXX_XXXX_XXXXXXXXXXXXX/XXX-XXXXXXX/XXXXXXXXXXXXX/recordings/XXXXXXXXXXXXX.mp4?AWSAccessKeyId=XXXXXXXXXXXXXXXXXXXX&Expires=1472968703&Signature=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
"lastModified": 1472881430181,
|
||||
"localCreatedDate": XXXXXXXXXXXXX,
|
||||
"presignedThumbnailUrl": "https://arlos3-prod-z2.s3.amazonaws.com/XXXXXXX_XXXX_XXXX_XXXX_XXXXXXXXXXXXX/XXX-XXXXXXX/XXXXXXXXXXXXX/recordings/XXXXXXXXXXXXX_thumb.jpg?AWSAccessKeyId=XXXXXXXXXXXXXXXXXXXX&Expires=1472968703&Signature=XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
|
||||
"reason": "motionRecord",
|
||||
"deviceId": "XXXXXXXXXXXXX",
|
||||
"createdBy": "XXXXXXXXXXXXX",
|
||||
"createdDate": "20160903",
|
||||
"timeZone": "America/Chicago",
|
||||
"ownerId": "XXX-XXXXXXX",
|
||||
"utcCreatedDate": XXXXXXXXXXXXX,
|
||||
"currentState": "new",
|
||||
"mediaDuration": "00:00:30"
|
||||
}
|
||||
]
|
||||
"""
|
||||
# give the query range a bit of buffer
|
||||
from_date_internal = from_date - timedelta(days=1)
|
||||
to_date_internal = to_date + timedelta(days=1)
|
||||
|
||||
return [
|
||||
result for result in
|
||||
self._getLibraryCached(from_date_internal.strftime("%Y%m%d"), to_date_internal.strftime("%Y%m%d"))
|
||||
if result["deviceId"] == device["deviceId"]
|
||||
and datetime.fromtimestamp(int(result["name"]) / 1000.0) <= to_date
|
||||
and datetime.fromtimestamp(int(result["name"]) / 1000.0) >= from_date
|
||||
]
|
||||
|
||||
@cached(cache=TTLCache(maxsize=512, ttl=60))
|
||||
def _getLibraryCached(self, from_date: str, to_date: str):
|
||||
logger.debug(f"Library cache miss for {from_date}, {to_date}")
|
||||
return self.request.post(
|
||||
f'https://{self.BASE_URL}/hmsweb/users/library',
|
||||
params={
|
||||
'dateFrom': from_date,
|
||||
'dateTo': to_date
|
||||
}
|
||||
)
|
||||
|
||||
def GetSmartFeatures(self, device) -> dict:
|
||||
smart_features = self._getSmartFeaturesCached()
|
||||
key = f"{device['owner']['ownerId']}_{device['deviceId']}"
|
||||
return smart_features["features"].get(key, {})
|
||||
|
||||
@cached(cache=TTLCache(maxsize=1, ttl=60))
|
||||
def _getSmartFeaturesCached(self) -> dict:
|
||||
return self.request.get(f'https://{self.BASE_URL}/hmsweb/users/subscription/smart/features')
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user