Compare commits

...

298 Commits

Author SHA1 Message Date
Koushik Dutta
8a640758d1 server/cli: fix login issues 2024-01-22 09:11:02 -08:00
Koushik Dutta
9be913af26 sample-cameraprovider: update 2024-01-21 15:39:48 -08:00
Koushik Dutta
da17bee516 sdk: publish 2024-01-21 15:38:28 -08:00
Koushik Dutta
48d9790051 cli: fix https://github.com/koush/scrypted/issues/1277 2024-01-21 14:52:10 -08:00
Koushik Dutta
c43014348d sdk: prevent unnecessary JSON exceptions 2024-01-21 14:42:44 -08:00
Koushik Dutta
cec3a592ba client: add missing dependency 2024-01-21 14:42:24 -08:00
Koushik Dutta
c446ddcdf4 Merge branch 'main' of github.com:koush/scrypted 2024-01-21 12:45:24 -08:00
Koushik Dutta
72f79ea8ef core: fix certificate login error, fix backup/restore in ha 2024-01-21 12:45:19 -08:00
Brett Jia
41988699d0 server: expose backup as a service (#1275)
* server: expose backup as a service

* move restore into new backup service
2024-01-20 21:37:56 -08:00
Koushik Dutta
5151c520d4 Update config.yaml 2024-01-18 19:21:11 -08:00
Koushik Dutta
e1abe717fa postrelease 2024-01-18 13:30:02 -08:00
Koushik Dutta
c7a9ca06be server: use existing service control restart 2024-01-18 13:29:50 -08:00
Koushik Dutta
9827f15f5f server: pass through restart hook 2024-01-18 13:25:22 -08:00
Koushik Dutta
d245a722e2 postrelease 2024-01-18 13:20:25 -08:00
Koushik Dutta
c8e94c0386 Merge branch 'main' of github.com:koush/scrypted 2024-01-18 13:15:41 -08:00
Koushik Dutta
8c6e7b997a ui: implement backup/restore 2024-01-18 13:15:37 -08:00
Johannes Bosecker
9abc7ca139 amcrest: Implemented other intercom codec for Dahua doorbells (G.711A). (#1273) 2024-01-18 12:36:11 -08:00
Koushik Dutta
2a943eb5e0 postbeta 2024-01-18 09:33:13 -08:00
Koushik Dutta
a4fe78a48b ha: publish 2024-01-17 22:07:56 -08:00
Koushik Dutta
50ff0833c9 server: new node min verison 2024-01-17 10:26:30 -08:00
Koushik Dutta
c94085a6c7 zwave: smoke alarm support 2024-01-17 10:25:48 -08:00
Koushik Dutta
c477437456 server: add hook for restart 2024-01-14 15:47:25 -08:00
Koushik Dutta
0da96130fe Merge branch 'main' of github.com:koush/scrypted 2024-01-14 15:36:17 -08:00
Koushik Dutta
fdbf7ab60b server: implement backup/restore 2024-01-14 15:36:08 -08:00
Long Zheng
0cecfb86ff npm-install.sh install auth-fetch package (#1267) 2024-01-14 14:46:23 -08:00
Koushik Dutta
9a195c6207 homekit: fix prune crash 2024-01-14 14:25:30 -08:00
Koushik Dutta
47021a7743 server: reduce deps 2024-01-14 14:25:19 -08:00
Koushik Dutta
01400cf206 core: prep for server fakefs removal 2024-01-14 14:24:43 -08:00
Koushik Dutta
99da29a738 postrelease 2024-01-14 08:06:50 -08:00
Koushik Dutta
6378c5953a server: bump core 2024-01-14 08:06:42 -08:00
Koushik Dutta
846034d7c8 core: fix login 2024-01-14 08:06:25 -08:00
Koushik Dutta
ad47f14922 ha: publish 2024-01-13 22:45:05 -08:00
Koushik Dutta
0066379b1e postrelease 2024-01-13 22:18:49 -08:00
Koushik Dutta
54193251ab server: bump minimum core version 2024-01-13 22:17:40 -08:00
Koushik Dutta
cd5e169439 core: publish 2024-01-13 22:16:18 -08:00
Koushik Dutta
249a87bd4c cameras: fix validation skip, update auth lib 2024-01-13 19:29:35 -08:00
Koushik Dutta
803400c2d8 homekit: bump deps and publish 2024-01-13 18:34:04 -08:00
Koushik Dutta
0e700a53d0 snapshot: give periodic snapshots a moment to return 2024-01-13 17:05:26 -08:00
Koushik Dutta
72647b0099 Update Dockerfile.lite 2024-01-13 16:17:38 -08:00
Koushik Dutta
85e41180b2 Update docker-common.yml 2024-01-13 16:14:38 -08:00
Koushik Dutta
18bf012bb5 docker: udpate base 2024-01-13 16:12:43 -08:00
Koushik Dutta
3f2b8de169 Merge branch 'main' of github.com:koush/scrypted 2024-01-13 16:01:37 -08:00
Koushik Dutta
c568c9a37d shrink lite build further 2024-01-13 16:01:12 -08:00
Koushik Dutta
a831c48f5f server: fix stupid esm mime dependency 2024-01-12 18:22:41 -08:00
Koushik Dutta
f0357d45f2 server: fix stupid esm mime dependency 2024-01-12 18:14:31 -08:00
Koushik Dutta
479b3ce1f3 postrelease 2024-01-12 16:39:30 -08:00
Koushik Dutta
bd7c7de8a5 Update package.json 2024-01-12 16:37:55 -08:00
Koushik Dutta
a06e786d19 client: Fix cors credentials 2024-01-12 15:51:06 -08:00
Koushik Dutta
dff05e733e server: update lockfile 2024-01-12 15:39:29 -08:00
Koushik Dutta
76e34af149 postrelease 2024-01-12 15:13:29 -08:00
Koushik Dutta
4f2e9e88e1 Merge branch 'main' of github.com:koush/scrypted 2024-01-12 09:27:04 -08:00
Koushik Dutta
2761b0745a client: remove http-auth-utils dependency 2024-01-12 09:26:09 -08:00
Brett Jia
ea78b7f59e core: fix scheduler (#1259) 2024-01-12 09:10:31 -08:00
Koushik Dutta
b8d233f08d videoanalysis: cpu usage wip 2024-01-11 20:08:57 -08:00
Koushik Dutta
f4e93c82a2 onvif: publish with latest http-auth-utils 2024-01-11 20:08:39 -08:00
Koushik Dutta
c72c34f954 common: remove node-fetch 2024-01-11 20:06:51 -08:00
Koushik Dutta
baec4e71da remove axios-digest-auth 2024-01-11 19:31:18 -08:00
Koushik Dutta
245c6e3006 doorbird: remove axios digest auth 2024-01-11 19:29:15 -08:00
Koushik Dutta
566c18251c snapshot: add support for periodic snapshots via http 2024-01-11 13:15:18 -08:00
Koushik Dutta
f18e58a108 sdk/homekit/snapshot: simplify periodic snapshot requests. remove homekit debouncer. 2024-01-11 11:29:03 -08:00
Koushik Dutta
5c6ea09d3e videoanalysis: implement dynamic cpu performance profiling 2024-01-11 11:04:41 -08:00
Koushik Dutta
646a9f214a snapshot: Update url fetcher 2024-01-11 09:58:23 -08:00
Koushik Dutta
6506c4236f various: fixup http lib 2024-01-11 00:37:21 -08:00
Koushik Dutta
a3e27ce8f8 Merge branch 'main' of github.com:koush/scrypted 2024-01-11 00:35:26 -08:00
Koushik Dutta
a54d5a5f59 cli: remove axios 2024-01-11 00:35:21 -08:00
Koushik Dutta
7136759f8f Update install-scrypted-proxmox.sh 2024-01-11 00:32:48 -08:00
Koushik Dutta
049d9898b3 http/auth: simplify 2024-01-10 20:45:50 -08:00
Koushik Dutta
6d9e21b7b8 server: Fix import 2024-01-10 16:27:10 -08:00
Koushik Dutta
bfd1aef5d1 client: fixup 2024-01-10 16:23:51 -08:00
Koushik Dutta
5f02e6a272 various: more http refactoring 2024-01-10 16:08:38 -08:00
Koushik Dutta
56bbf00edc various: update http auth utils usage 2024-01-10 13:16:00 -08:00
Koushik Dutta
0c66456a87 sdk: limit chunks to 1. add support for multiple chunks. 2024-01-10 13:06:04 -08:00
Koushik Dutta
6b589b8d5a postrelease 2024-01-10 12:56:02 -08:00
Koushik Dutta
aef218c653 server: update http fetch usage, add support for relative require 2024-01-10 12:50:45 -08:00
Koushik Dutta
aea24a84f0 cameras: update http utils again 2024-01-10 11:13:37 -08:00
Koushik Dutta
bab3bef0d1 cameras: update http utils again 2024-01-10 11:13:17 -08:00
Koushik Dutta
56bda46ae9 webrtc: fix multiple ipv4/ipv6 address usage 2024-01-09 22:44:33 -08:00
Koushik Dutta
368b0fc26a onvif: fix motion resbuscribe 2024-01-09 20:17:13 -08:00
Koushik Dutta
fa50d6faab various: remove axios digest auth 2024-01-09 13:52:51 -08:00
Koushik Dutta
115d168cd3 snapshot: remove axios 2024-01-09 13:12:44 -08:00
Koushik Dutta
7ab93e8883 Merge branch 'main' of github.com:koush/scrypted 2024-01-09 13:04:09 -08:00
Koushik Dutta
e25cf860f0 common: replacement implementation for axios digest auth 2024-01-09 13:04:00 -08:00
Koushik Dutta
b5b56d81a8 Proxmox: fix error check 2024-01-08 17:58:44 -08:00
Koushik Dutta
e7b6cb021c Update install-scrypted-proxmox.sh 2024-01-08 10:45:05 -08:00
Koushik Dutta
e96f374432 Update install-scrypted-proxmox.sh 2024-01-08 10:03:26 -08:00
Koushik Dutta
c4126d7569 onvif: publish 2024-01-08 08:54:36 -08:00
Billy Zoellers
074ba733a3 ONVIF: ignore invalid (array) ONVIF events from Axis cameras (#1253) 2024-01-08 08:50:47 -08:00
Koushik Dutta
622f494703 onvif: publish fixes 2024-01-07 20:03:35 -08:00
Koushik Dutta
28c8b97c26 ha: fix submodule 2024-01-07 20:02:03 -08:00
Brett Jia
c0af17b38d core: query latest docker image via scrypted server (#1247) 2024-01-06 23:54:38 -08:00
Koushik Dutta
44a82a1afa reolink: publish 2024-01-06 16:59:26 -08:00
Koushik Dutta
c9b4c14e35 Merge branch 'main' of github.com:koush/scrypted 2024-01-06 16:59:13 -08:00
Koushik Dutta
76534f1368 onvif: fix unsubscribe crash 2024-01-06 16:58:50 -08:00
Koushik Dutta
3f9a863961 onvif/reolink: fix onvif motion debouncing on motion stop 2024-01-06 10:28:17 -08:00
Koushik Dutta
83b0ebd5f0 docker: fix nvidia script 2024-01-06 09:33:43 -08:00
Koushik Dutta
b923a4ea27 docker: fix nvidia script 2024-01-06 09:32:05 -08:00
Koushik Dutta
941d213087 docker: fix nvidia script 2024-01-06 09:30:09 -08:00
Koushik Dutta
0e11b8b4c5 docker: remove pip reinstall step 2024-01-06 09:29:58 -08:00
Koushik Dutta
1be14878d1 docker: fix nvidia 2024-01-06 09:26:08 -08:00
Koushik Dutta
3e323911c7 proxmox: install script shoud auto boot 2024-01-05 12:27:11 -08:00
Koushik Dutta
ca36ab2d2d ha: publish 2024-01-04 12:27:17 -08:00
Koushik Dutta
a80e95912e python plugins: trigger redownload of deps 2024-01-04 11:35:24 -08:00
Koushik Dutta
4432d8dd67 postrelease 2024-01-04 10:38:49 -08:00
Koushik Dutta
2d83d3ba97 external: update axios digfest auth 2024-01-04 10:03:00 -08:00
Koushik Dutta
1078faef62 common: fix regression with not sending sigterm to ffmpeg 2024-01-04 10:00:41 -08:00
Koushik Dutta
47a981e15a various: Update axios digest auth 2024-01-04 09:59:27 -08:00
Koushik Dutta
e253cab555 opencv: use wheel for aarch64 2024-01-03 19:00:51 -08:00
Koushik Dutta
80124ca83b ha/proxmox: release 2024-01-03 18:55:51 -08:00
Koushik Dutta
f883d8738c docker: update base image 2024-01-03 13:15:05 -08:00
Koushik Dutta
bdca3b545c docker: update base image 2024-01-03 13:09:17 -08:00
Koushik Dutta
3ba02c44ab docker: disable node 20 buikds 2024-01-03 11:13:43 -08:00
Koushik Dutta
fe4733bb97 gha: enable ghcr common publishing 2024-01-03 11:12:48 -08:00
Koushik Dutta
7ff893fbd3 docker: switch to ghcr 2024-01-03 11:05:55 -08:00
Chris Jones
e79c544690 Fix Home Assistant Addon backup exclusions. Closes #1239 (#1240) 2024-01-03 07:23:09 -08:00
Koushik Dutta
13bf44ce50 postrelease 2024-01-02 23:08:53 -08:00
Koushik Dutta
544531122d server: stop console spam if mixin is deleted 2024-01-02 22:13:13 -08:00
Koushik Dutta
778f0b7ad1 proxmox: update script 2024-01-02 14:03:17 -08:00
Koushik Dutta
35e8a86593 snapshot: warn qemu cpu 2024-01-02 13:55:17 -08:00
Koushik Dutta
c370773af4 common: missing file 2024-01-02 12:12:51 -08:00
Koushik Dutta
184f293b92 core: fix new script creation 2024-01-02 09:13:06 -08:00
Koushik Dutta
6e10172f7e Merge branch 'main' of github.com:koush/scrypted 2024-01-01 21:53:12 -08:00
Koushik Dutta
c5ae2cd539 onvif: forgotten file re motion sensor reset 2024-01-01 21:52:54 -08:00
Koushik Dutta
e40566e89c reolink: ptz 2024-01-01 21:52:43 -08:00
Koushik Dutta
59ccd4e4d8 docker: remove armv7 2024-01-01 18:24:51 -08:00
Koushik Dutta
ae80eb7727 docker: remove armv7 2024-01-01 18:24:35 -08:00
Koushik Dutta
f054172dcf postrelease 2024-01-01 15:43:52 -08:00
Koushik Dutta
0d7fb9e13c postrelease 2024-01-01 14:46:06 -08:00
Koushik Dutta
a526816b07 sdk/server: add mechanism for requesting device refresh 2024-01-01 14:44:00 -08:00
Koushik Dutta
563e16b08f sdk: update 2024-01-01 14:42:33 -08:00
Koushik Dutta
fd56990d64 core: watch for script worker exits 2024-01-01 14:12:27 -08:00
Koushik Dutta
d7aaf57e8f core: move scripts into their own workers. 2024-01-01 14:10:17 -08:00
Koushik Dutta
a2d50d54d5 proxmox: delete coral, make user install it. 2024-01-01 00:21:59 -08:00
Koushik Dutta
1f86745252 proxmox: install script 2024-01-01 00:15:09 -08:00
Koushik Dutta
1f4343ba2e proxmox: install script 2024-01-01 00:14:54 -08:00
Koushik Dutta
3ad311898f docker: pillow simd 2023-12-31 22:12:54 -08:00
Koushik Dutta
799e5b53c7 Merge branch 'main' of github.com:koush/scrypted 2023-12-31 21:57:08 -08:00
Koushik Dutta
833e5b34ab docker/lxc: update 2023-12-31 21:57:03 -08:00
Koushik Dutta
c99ac28e89 wyze: write_eof may fail? 2023-12-30 18:53:53 -08:00
Koushik Dutta
841475cb97 wyze: better ffmpeg kill method 2023-12-30 18:53:15 -08:00
Koushik Dutta
4b03a3a458 reolink: debounce motion on motion end 2023-12-28 13:32:14 -08:00
Koushik Dutta
d686dd815c videoanalysis: fix bug with modified default classes 2023-12-28 12:20:28 -08:00
Koushik Dutta
e0386a8922 tensorflow-lite: remove python codecs dependency 2023-12-28 12:13:36 -08:00
Koushik Dutta
9ef3478c88 Merge branch 'main' of github.com:koush/scrypted 2023-12-28 12:08:55 -08:00
Koushik Dutta
690d160f33 openvino: update openvino dependency 2023-12-28 12:08:37 -08:00
Koushik Dutta
59ff987bca server: update deps 2023-12-28 09:56:42 -08:00
Koushik Dutta
1669f17c96 postbeta 2023-12-27 22:38:23 -08:00
Koushik Dutta
b0bfd4e05e wyze: update dwb 2023-12-27 21:56:32 -08:00
Koushik Dutta
7152671913 wyze: update dwb 2023-12-27 21:55:46 -08:00
Koushik Dutta
537c178699 postbeta 2023-12-27 21:51:37 -08:00
Koushik Dutta
77ecee110b videoanalysis: fixup detection types for nvr 2023-12-27 21:40:50 -08:00
Koushik Dutta
29b163a7d8 wyze: update dwb 2023-12-27 21:01:51 -08:00
Koushik Dutta
5d74e80e90 postbeta 2023-12-27 20:13:10 -08:00
Koushik Dutta
764b6441d5 postbeta 2023-12-27 20:12:58 -08:00
Koushik Dutta
e2c43cb4ff onvif: missing file 2023-12-27 20:12:34 -08:00
Koushik Dutta
7b6d094e8c wyze: improve cpu usage 2023-12-27 20:12:06 -08:00
Koushik Dutta
3dfb2db02a snapshot: update deps 2023-12-27 20:11:45 -08:00
Koushik Dutta
e5a549db6a update werift 2023-12-27 19:31:34 -08:00
Koushik Dutta
d500c815fe local: add support for intel and tflite installation 2023-12-26 16:23:59 -08:00
Koushik Dutta
5f71c59b5a local: add support for intel and tflite installation 2023-12-26 16:15:05 -08:00
Koushik Dutta
27407942a5 local: add support for intel and tflite installation 2023-12-26 16:14:55 -08:00
Koushik Dutta
11b6963744 docker: remove libvips 2023-12-26 15:28:39 -08:00
Koushik Dutta
b9ee8866f0 docker: logging 2023-12-26 15:20:21 -08:00
Koushik Dutta
bc80d31eaa docker: add logging 2023-12-26 15:14:19 -08:00
Koushik Dutta
327688232c docker: more prompt fixups 2023-12-26 14:25:30 -08:00
Koushik Dutta
2883a4ce46 docker: more prompt fixups 2023-12-26 14:24:04 -08:00
Koushik Dutta
1ad2fb915d docker: more prompt fixups 2023-12-26 14:23:28 -08:00
Koushik Dutta
fb701a32b7 docker: intel graphics script 2023-12-26 14:19:56 -08:00
Koushik Dutta
7a8c661bb3 docker: remove bash invocation 2023-12-26 14:06:53 -08:00
Koushik Dutta
54d72fb371 docker: gpg should overwrite 2023-12-26 13:56:44 -08:00
Koushik Dutta
e48812cec7 linux: SERVICE_USER_ROOT flag 2023-12-26 13:53:46 -08:00
Koushik Dutta
6c2db072c4 linux: warn root install 2023-12-26 13:51:33 -08:00
Koushik Dutta
4bf2c0b614 docker: add node gpg 2023-12-26 13:49:13 -08:00
Koushik Dutta
a93cdb0ae4 docker: fix quoting 2023-12-26 13:21:24 -08:00
Koushik Dutta
ff85b7abc6 docker: update all node install scripts 2023-12-26 13:17:32 -08:00
Koushik Dutta
46dfb8d98e docker: Fix node version arg 2023-12-26 13:11:51 -08:00
Koushik Dutta
5240200f0f docker: update node install script 2023-12-26 13:04:56 -08:00
Koushik Dutta
3bcb94fc6b reolink: increase motion timeout 2023-12-25 12:03:14 -08:00
Koushik Dutta
a596bc712c reolink: add support for native object detection 2023-12-25 12:02:25 -08:00
Koushik Dutta
f6d2dc456e server: fix bug with constantly reissuing certs 2023-12-24 21:49:08 -08:00
Koushik Dutta
441cce239e wyze: set video timeout to 5s 2023-12-23 19:15:27 -08:00
Koushik Dutta
3016df32d1 Merge branch 'main' of github.com:koush/scrypted 2023-12-23 18:38:11 -08:00
Koushik Dutta
5bd8ed0b1a wyze: fix video inactivity leaks 2023-12-23 18:38:07 -08:00
Koushik Dutta
79286a5138 docker: allow root install 2023-12-22 22:56:09 -08:00
Koushik Dutta
8874e01072 wyze: fix broken setting 2023-12-22 15:39:28 -08:00
Koushik Dutta
0223a9f0f6 wyze: use correct fps when fetching frames 2023-12-22 11:31:58 -08:00
Koushik Dutta
890c2667d0 wyze: implement ptz 2023-12-22 10:34:07 -08:00
Koushik Dutta
ca14764e17 wyze: suppress ffmpeg 2023-12-22 09:13:32 -08:00
Koushik Dutta
1030d7d03c rebroadcast: fix rtsp server with RFC4571 parser 2023-12-22 08:23:43 -08:00
Koushik Dutta
2d40320868 ha: update 2023-12-21 22:53:01 -08:00
Koushik Dutta
3e32c3d019 werift: update 2023-12-21 22:50:32 -08:00
Koushik Dutta
1f9fa3966f wyze: send codec info on startup 2023-12-21 22:49:59 -08:00
Koushik Dutta
c2d86237d6 wyze: refactor for multiprocessing 2023-12-21 21:55:34 -08:00
Koushik Dutta
5cfcfafc00 postrelease 2023-12-21 21:51:42 -08:00
Koushik Dutta
35b4028a47 rpc: how did this ever work? 2023-12-21 21:37:11 -08:00
Koushik Dutta
bf6038a5d3 postbeta 2023-12-21 21:37:01 -08:00
Koushik Dutta
e1b2216543 docker: correct gh action 2023-12-21 21:25:10 -08:00
Koushik Dutta
89c1682421 docker: fixup 2023-12-21 21:24:41 -08:00
Koushik Dutta
5a4c527c59 docker: fixup gh action 2023-12-21 21:22:29 -08:00
Koushik Dutta
9d9c10aa1e docker: remove armv7 2023-12-21 21:21:53 -08:00
Koushik Dutta
ccf20a5fca docker: remove arm7 2023-12-21 21:20:52 -08:00
Koushik Dutta
692e7964a7 rebroadcast: fixes for wyze 2023-12-21 21:10:17 -08:00
Koushik Dutta
57e38072b1 server: fix static vs instance properties 2023-12-21 21:10:09 -08:00
Koushik Dutta
4e8e862482 postbeta 2023-12-21 21:09:41 -08:00
Koushik Dutta
eddcef8e54 rebroadcast: add trigger to reload video streams 2023-12-21 10:39:19 -08:00
Koushik Dutta
09edc6d75e wyze: add bitrate select 2023-12-21 10:28:25 -08:00
Koushik Dutta
72c7c43d79 wyze: fix race condition around teardown 2023-12-21 00:20:49 -08:00
Koushik Dutta
805f471ff9 wyze: add audio support 2023-12-21 00:10:43 -08:00
Koushik Dutta
6f797d53ec rebroadcast/webrtc: fix audio sample rate assumptions 2023-12-21 00:09:20 -08:00
Koushik Dutta
4903a0efcd sdk: update 2023-12-20 23:54:52 -08:00
Koushik Dutta
36e3fcf429 rebroadcast: fix support for rfc4571 2023-12-20 23:50:08 -08:00
Koushik Dutta
78a126fe0a wyze: make plugin visible on npm 2023-12-20 19:09:51 -08:00
Koushik Dutta
5029baf2d4 wyze: add support for substream 2023-12-20 18:05:06 -08:00
Koushik Dutta
769bc014a8 wyze: disable substream 2023-12-20 14:51:37 -08:00
Koushik Dutta
096700486a wyze: initial commit 2023-12-20 14:51:20 -08:00
Koushik Dutta
b3a7d6be9c videoanalysis: smart motion sensor now retains the last thumbnail 2023-12-20 09:36:02 -08:00
Koushik Dutta
05751bce44 Merge branch 'main' of github.com:koush/scrypted 2023-12-19 09:19:51 -08:00
Koushik Dutta
dced62a527 videoanalysis: publish 2023-12-19 09:19:46 -08:00
1vivy
359f1cfc2f docker: fixup nvidia example (#1228) 2023-12-18 15:19:56 -08:00
Koushik Dutta
d4cae8abbb common: add packet loss util to browser signaling session 2023-12-18 12:36:57 -08:00
Koushik Dutta
0e6b3346ed common: add packet loss util to browser signaling session 2023-12-18 12:34:36 -08:00
Koushik Dutta
2409cc457c sdk: remove startId to simplify implementations 2023-12-18 09:41:51 -08:00
Koushik Dutta
0b794aa381 sdk: remove reverseOrder to simplify implementations 2023-12-18 09:34:55 -08:00
Koushik Dutta
98017a5aa6 snapshot: debounce pics for 2 seconds 2023-12-17 22:58:35 -08:00
Koushik Dutta
f2e7cc4017 rebroadcast: validate device is cam or doorbell 2023-12-16 10:16:10 -08:00
Koushik Dutta
d7201a16a7 snapshot/videoanalysis: publish 2023-12-15 10:56:20 -08:00
Koushik Dutta
99d1dc7282 mqtt: support default external brokers 2023-12-13 23:51:44 -08:00
Koushik Dutta
18ae09e41c snapshot: fixup internal invocation 2023-12-13 12:22:20 -08:00
Koushik Dutta
2ebe774e59 snapshot: beta plugin that bypasses media manager for local urls 2023-12-13 12:11:49 -08:00
Koushik Dutta
b887b8a47c core: add weight to core snapshot 2023-12-13 10:51:23 -08:00
Koushik Dutta
8e391dee2f Merge branch 'main' of github.com:koush/scrypted 2023-12-13 09:07:06 -08:00
Koushik Dutta
469f693d58 snapshot: use internal prebuffer converter rather than media manager 2023-12-13 09:07:02 -08:00
slyoldfox
1c96a7d492 bticino: Implement HKSV recording for the bticino and switch the stream to rtsp (#1220)
* Implement HKSV recording for the bticino and switch the stream to rtsp

* Implement HKSV recording for the bticino and switch the stream to rtsp

---------

Co-authored-by: Marc Vanbrabant <marc@foreach.be>
2023-12-12 09:53:53 -08:00
Koushik Dutta
3f1b45c435 rebroadcast: fix unhandled rejection on stream startup failure 2023-12-12 09:44:59 -08:00
Koushik Dutta
4b715e55d2 server: beta 2023-12-11 13:01:01 -08:00
Koushik Dutta
75dc63acc3 postbeta 2023-12-11 13:00:45 -08:00
Koushik Dutta
6c79f42bb7 videoanalysis: add min object detection time 2023-12-11 10:22:32 -08:00
Koushik Dutta
9d4f006caa snapshot: fix vips rgba/gray ops 2023-12-11 09:28:24 -08:00
Koushik Dutta
05b206f897 cloud: dont attempt to register with server if not logged in 2023-12-10 10:12:39 -08:00
Koushik Dutta
1f22218b23 server: recreate cert with valid date range on startup 2023-12-10 09:21:40 -08:00
Koushik Dutta
c9568df165 mqtt: add online stat 2023-12-08 18:47:45 -08:00
Koushik Dutta
c98e91cd39 videoanalysis: add zone choices to smart sensor 2023-12-07 22:35:15 -08:00
Koushik Dutta
e3ecff04ce videoanalysis: publish 2023-12-07 13:04:51 -08:00
Koushik Dutta
f9f50f34c3 common: update digest auth 2023-12-07 12:41:07 -08:00
Koushik Dutta
cd298f7d76 common: shuffle some polygon code 2023-12-07 12:33:58 -08:00
Koushik Dutta
c95248fce0 videoanalysis: use lib that supports convex polys 2023-12-07 12:27:44 -08:00
Koushik Dutta
e50f3fa793 sdk/videoanalysis: remove filterMode from sdk. internal to video analysis only. 2023-12-07 09:43:33 -08:00
Koushik Dutta
c74be7e90f objectdetector: add zone option to smart sensor 2023-12-07 09:37:02 -08:00
Koushik Dutta
4d288727ce sdk: use a filterMode rather than a new zone type 2023-12-07 09:02:11 -08:00
Koushik Dutta
1f19dc191d sdk: add zone that doesnt filter, only used for observation 2023-12-07 08:57:14 -08:00
Koushik Dutta
37d4e5be73 homekit: send client size hints to prevent apple tv crash 2023-12-06 10:10:13 -08:00
Koushik Dutta
e64ec98211 mqtt: add temp, humidity, flood 2023-12-05 22:29:36 -08:00
Koushik Dutta
8b6c0c4f7b homekit: add hint for adaptive bitrate 2023-12-05 22:09:23 -08:00
Koushik Dutta
3b16c68c75 sdk: add fingerprint to getVideoStream 2023-12-05 22:07:36 -08:00
Koushik Dutta
67be05880c core: handle node pty failure 2023-12-04 19:28:46 -08:00
Koushik Dutta
414a9403c2 mqtt: implement basic autodiscovery 2023-12-04 13:58:59 -08:00
Koushik Dutta
053106415c tapo: publish 2023-12-02 09:25:23 -08:00
S.Feng
f3690af92a tapo: fix new firmware using sha256 hash (#1208)
ref:
5d3953a948
2023-12-02 09:04:11 -08:00
Koushik Dutta
c4cc12fdff snapshot: publish 2023-12-01 16:57:04 -08:00
Koushik Dutta
58e8772f7c sdk: publish 2023-12-01 14:17:25 -08:00
Koushik Dutta
4ae9b72471 sdk: add stream resize feedback 2023-12-01 08:51:39 -08:00
Koushik Dutta
a8c64aa2d4 sdk: improve adaptive flags 2023-12-01 08:40:08 -08:00
Koushik Dutta
8ccbba485a snapshot: publish 2023-11-30 09:59:25 -08:00
Koushik Dutta
2ec192e0fd snapshot: fix thumbnail generation near leading bounds 2023-11-30 09:59:09 -08:00
Koushik Dutta
e257953338 sdk: add landmarks and clip paths to detections 2023-11-30 08:26:14 -08:00
Koushik Dutta
9e80eca8e1 Merge branch 'main' of github.com:koush/scrypted 2023-11-29 13:17:27 -08:00
Koushik Dutta
172b32fd47 sdk: update detection result fields 2023-11-29 13:17:22 -08:00
slyoldfox
a6bf055b85 Avoid 'No audio stream detected' in prebuffer when speex is the inputAudioCodec (#1203)
Co-authored-by: Marc Vanbrabant <marc@foreach.be>
2023-11-28 08:51:19 -08:00
Koushik Dutta
dab5be1103 alexa: fix potential response race 2023-11-27 19:42:11 -08:00
Koushik Dutta
126c489934 external: update axios digest auth 2023-11-27 19:06:36 -08:00
Koushik Dutta
7f714b3d6a Merge branch 'main' of github.com:koush/scrypted 2023-11-27 19:06:16 -08:00
Koushik Dutta
fde3c47d8c common: improve ffmpeg kill func, add queue end promise 2023-11-27 19:04:09 -08:00
Koushik Dutta
4b1623dfce Update bug_report.md 2023-11-27 08:55:31 -08:00
Koushik Dutta
1e62f7a418 Update bug_report.md 2023-11-27 08:52:04 -08:00
Koushik Dutta
83c9d9a4a6 external: update axios digest auth 2023-11-26 20:13:03 -08:00
Koushik Dutta
b42afe0ca0 external: update axios digest auth 2023-11-26 20:11:27 -08:00
Koushik Dutta
e8e5f9b33e snapshot: add imageOp util function 2023-11-26 18:53:44 -08:00
Koushik Dutta
15916d83b8 rebroadcast: rollback wallclocks change, it is preventing frame updates in webassembly decoder 2023-11-26 18:31:16 -08:00
Koushik Dutta
c1327974b2 ring: update ring-client-api 2023-11-26 18:31:04 -08:00
Koushik Dutta
33e2291912 webrtc: reduce preference of turn 2023-11-26 16:10:53 -08:00
Koushik Dutta
2d2c5c436f ring: publish 2023-11-25 08:18:50 -08:00
Koushik Dutta
8088ae20b1 reolink/rebroadcast: enable wallclock timestamps on rtmp 2023-11-24 19:16:28 -08:00
Koushik Dutta
4c658b8d99 mqtt: default to empty args 2023-11-24 09:04:42 -08:00
Koushik Dutta
aab78ec797 mqtt: support invoking methods 2023-11-24 09:02:26 -08:00
Koushik Dutta
11ecff985d snapshot: fix file pathing on windows 2023-11-23 19:38:17 -08:00
Koushik Dutta
80a1a78a79 install: fix env 2023-11-23 09:12:43 -08:00
Koushik Dutta
7875c51d62 install: prevent usage of global libvips 2023-11-23 08:50:28 -08:00
Koushik Dutta
b04aa75117 alexa: fix race condition in sendResponse 2023-11-22 21:23:01 -08:00
Koushik Dutta
fc7d1eaf32 snapshot: consolidate image ops 2023-11-22 20:26:53 -08:00
Koushik Dutta
e5a7a55be8 snapshot: refactor 2023-11-22 14:33:33 -08:00
Koushik Dutta
fa9a2eb947 ha: publish 2023-11-22 13:38:39 -08:00
Koushik Dutta
30891e0769 snapshot: lazy load sharp 2023-11-22 13:37:17 -08:00
Koushik Dutta
fb8256709a postrelease 2023-11-22 13:07:51 -08:00
Koushik Dutta
06d0a4a2f1 server: verup 2023-11-22 13:07:41 -08:00
Koushik Dutta
2fb6e0a368 server: fix connectRPCObject in python. cache/optimize connect code. 2023-11-22 13:00:33 -08:00
Koushik Dutta
c6ed0d8729 snapshot: publish latest with sharp fallbacks 2023-11-22 11:49:42 -08:00
Koushik Dutta
67c6f63dbe Merge branch 'main' of github.com:koush/scrypted 2023-11-22 11:16:10 -08:00
Koushik Dutta
e62b4ad68b snapshot: publish beta with sharp + fallback 2023-11-22 11:16:05 -08:00
slyoldfox
bfec5eb3f3 Support turning on/off ringer (#1193)
Support turning on/off answering machine

Co-authored-by: Marc Vanbrabant <marc@foreach.be>
2023-11-22 10:59:56 -08:00
241 changed files with 18365 additions and 18395 deletions

View File

@@ -7,6 +7,17 @@ assignees: ''
---
# Github Issues is not a Forum
**This issue tracker is not for hardware support or feature requests**. If you are troubleshooting adding a device for the first time, use Discord, Reddit, or Github Discussions. However, if something **was working**, and is now **no longer working**, you may create a Github issue.
Created issues that do not meet these requirements or are improperly filled out will be immediately closed.
# New Issue Instructions
1. Delete this section and everything above it.
2. Fill out the sections below.
**Describe the bug**
A clear and concise description of what the bug is. The issue tracker is only for reporting bugs in Scrypted, for general support check Discord. Hardrware support requests or assistance requests will be immediately closed.

View File

@@ -10,9 +10,12 @@ jobs:
# runs-on: ubuntu-latest
strategy:
matrix:
NODE_VERSION: ["18", "20"]
NODE_VERSION: [
"18",
# "20"
]
BASE: ["jammy"]
FLAVOR: ["full", "lite", "thin"]
FLAVOR: ["full", "lite"]
steps:
- name: Check out the repo
uses: actions/checkout@v3
@@ -26,21 +29,13 @@ jobs:
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
- name: Set up SSH
uses: MrSquaare/ssh-setup-action@v2
with:
host: ${{ secrets.DOCKER_SSH_HOST_ARM7 }}
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
platforms: linux/arm64,linux/armhf
platforms: linux/arm64
append: |
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_ARM64 }}
platforms: linux/arm64
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_ARM7 }}
platforms: linux/armhf
- name: Login to Docker Hub
uses: docker/login-action@v2
@@ -63,9 +58,10 @@ jobs:
BASE=${{ matrix.BASE }}
context: install/docker/
file: install/docker/Dockerfile.${{ matrix.FLAVOR }}
platforms: linux/amd64,linux/armhf,linux/arm64
platforms: linux/amd64,linux/arm64
push: true
tags: |
koush/scrypted-common:${{ matrix.NODE_VERSION }}-${{ matrix.BASE }}-${{ matrix.FLAVOR }}
ghcr.io/koush/scrypted-common:${{ matrix.NODE_VERSION }}-${{ matrix.BASE }}-${{ matrix.FLAVOR }}
cache-from: type=gha
cache-to: type=gha,mode=max

View File

@@ -52,21 +52,13 @@ jobs:
host: ${{ secrets.DOCKER_SSH_HOST_ARM64 }}
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
- name: Set up SSH
uses: MrSquaare/ssh-setup-action@v2
with:
host: ${{ secrets.DOCKER_SSH_HOST_ARM7 }}
private-key: ${{ secrets.DOCKER_SSH_PRIVATE_KEY }}
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v2
with:
platforms: linux/arm64,linux/armhf
platforms: linux/arm64
append: |
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_ARM64 }}
platforms: linux/arm64
- endpoint: ssh://${{ secrets.DOCKER_SSH_USER }}@${{ secrets.DOCKER_SSH_HOST_ARM7 }}
platforms: linux/armhf
- name: Login to Docker Hub
uses: docker/login-action@v2
@@ -89,7 +81,7 @@ jobs:
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
platforms: linux/amd64,linux/arm64
push: true
tags: |
${{ format('koush/scrypted:{0}{1}-v{2}', matrix.BASE, matrix.SUPERVISOR, github.event.inputs.publish_tag || steps.package-version.outputs.NPM_VERSION) }}

9
.gitmodules vendored
View File

@@ -7,9 +7,6 @@
[submodule "plugins/tensorflow/face-api.js"]
path = external/face-api.js
url = ../../koush/face-api.js
[submodule "external/axios-digest-auth"]
path = external/axios-digest-auth
url = ../../koush/axios-digest-auth
[submodule "external/scrypted-ffmpeg"]
path = external/scrypted-ffmpeg
url = ../../koush/scrypted-ffmpeg
@@ -35,3 +32,9 @@
[submodule "plugins/cloud/node-nat-upnp"]
path = plugins/cloud/external/node-nat-upnp
url = ../../koush/node-nat-upnp.git
[submodule "plugins/wyze/docker-wyze-bridge"]
path = plugins/wyze/docker-wyze-bridge
url = ../../koush/docker-wyze-bridge.git
[submodule "plugins/onvif/onvif"]
path = plugins/onvif/onvif
url = ../../koush/onvif.git

View File

@@ -5,17 +5,19 @@
"version": "0.2.0",
"configurations": [
{
"type": "pwa-node",
"name": "ts-node",
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
"args": [
"${workspaceFolder}/src/test.ts",
],
"program": "${workspaceFolder}/dist/common/src/test.js",
"preLaunchTask": "npm: build",
"outFiles": [
"${workspaceFolder}/**/*.js"
]
}
"runtimeArgs": [
"-r",
"ts-node/register"
],
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
},
]
}

608
common/package-lock.json generated
View File

@@ -1,22 +1,22 @@
{
"name": "@scrypted/common",
"version": "1.0.2",
"version": "1.0.1",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/common",
"version": "1.0.2",
"version": "1.0.1",
"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"
"http-auth-utils": "^5.0.1",
"typescript": "^5.3.3"
},
"devDependencies": {
"@types/node": "^16.9.0"
"@types/node": "^20.11.0",
"ts-node": "^10.9.2"
}
},
"../external/werift/packages/webrtc": {
@@ -74,12 +74,12 @@
},
"../sdk": {
"name": "@scrypted/sdk",
"version": "0.2.68",
"version": "0.3.4",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^0.21.4",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
@@ -111,68 +111,103 @@
},
"../server": {
"name": "@scrypted/server",
"version": "0.6.19",
"version": "0.82.0",
"license": "ISC",
"dependencies": {
"@ffmpeg-installer/ffmpeg": "^1.1.0",
"@mapbox/node-pre-gyp": "^1.0.10",
"@scrypted/types": "^0.2.63",
"adm-zip": "^0.5.9",
"axios": "^0.21.4",
"body-parser": "^1.19.0",
"@mapbox/node-pre-gyp": "^1.0.11",
"@scrypted/types": "^0.3.4",
"adm-zip": "^0.5.10",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"debug": "^4.3.4",
"engine.io": "^6.2.0",
"engine.io": "^6.5.4",
"express": "^4.18.2",
"ffmpeg-static": "^5.1.0",
"ffmpeg-static": "^5.2.0",
"follow-redirects": "^1.15.4",
"http-auth": "^4.2.0",
"ip": "^1.1.8",
"level": "^6.0.1",
"level": "^8.0.0",
"linkfs": "^2.1.0",
"lodash": "^4.17.21",
"memfs": "^3.4.7",
"memfs": "^4.6.0",
"mime": "^3.0.0",
"mkdirp": "^1.0.4",
"nan": "^2.17.0",
"nan": "^2.18.0",
"node-dijkstra": "^2.5.0",
"node-forge": "^1.3.1",
"node-gyp": "^8.4.1",
"router": "^1.3.7",
"semver": "^7.3.8",
"node-gyp": "^10.0.1",
"router": "^1.3.8",
"semver": "^7.5.4",
"sharp": "^0.33.1",
"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"
"tar": "^6.2.0",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"whatwg-mimetype": "^4.0.0",
"ws": "^8.16.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"
"@types/adm-zip": "^0.5.5",
"@types/cookie-parser": "^1.4.6",
"@types/debug": "^4.1.12",
"@types/express": "^4.17.21",
"@types/follow-redirects": "^1.14.4",
"@types/http-auth": "^4.1.4",
"@types/ip": "^1.1.3",
"@types/lodash": "^4.14.202",
"@types/mime": "^3.0.4",
"@types/node-dijkstra": "^2.5.6",
"@types/node-forge": "^1.3.10",
"@types/pem": "^1.14.4",
"@types/semver": "^7.5.6",
"@types/source-map-support": "^0.5.10",
"@types/tar": "^6.1.10",
"@types/whatwg-mimetype": "^3.0.2",
"@types/ws": "^8.5.10"
},
"optionalDependencies": {
"node-pty-prebuilt-multiarch": "^0.10.1-pre.5"
}
},
"node_modules/@cspotcode/source-map-support": {
"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": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"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/sdk": {
"resolved": "../sdk",
"link": true
@@ -181,120 +216,215 @@
"resolved": "../server",
"link": true
},
"node_modules/@types/node": {
"version": "16.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz",
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==",
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
"dev": true
},
"node_modules/fetch-blob": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz",
"integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
},
{
"type": "paypal",
"url": "https://paypal.me/jimmywarting"
}
],
"node_modules/@tsconfig/node12": {
"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.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.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true
},
"node_modules/@types/node": {
"version": "20.11.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz",
"integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==",
"dev": true,
"dependencies": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
},
"engines": {
"node": "^12.20 || >= 14.13"
"undici-types": "~5.26.4"
}
},
"node_modules/formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"dependencies": {
"fetch-blob": "^3.1.2"
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=12.20.0"
"node": ">=0.4.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz",
"integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"node_modules/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
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/http-auth-utils": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/http-auth-utils/-/http-auth-utils-3.0.2.tgz",
"integrity": "sha512-cQ8957aiUX0lgV1620uIGKGJc0sEuD/QK4ueZ0hb60MGbO0f6ahcuIgPjamAD98D/AUGizKVm+dNvUVHs0f4Ow==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/http-auth-utils/-/http-auth-utils-5.0.1.tgz",
"integrity": "sha512-YPiLVYdwpBEWB85iWYg7V/ZW3mBfPLCTFQWEiPAA5CKXHJOAPbnJ0xDHLiE6KbPRvrYCwLuWqTG0fLfXVjMUcQ==",
"dependencies": {
"yerror": "^6.0.0"
"yerror": "^8.0.0"
},
"engines": {
"node": ">=12.19.0"
"node": ">=18.16.0"
}
},
"node_modules/node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/jimmywarting"
"node_modules/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
},
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.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",
"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"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
{
"type": "github",
"url": "https://paypal.me/jimmywarting"
"@swc/wasm": {
"optional": true
}
],
"engines": {
"node": ">=10.5.0"
}
},
"node_modules/node-fetch-commonjs": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.1.1.tgz",
"integrity": "sha512-TgkdVJdiEaauzWwB9NoD4TvHZFtG6KKEffvotWf9WNIyoRZHsCFjGfb3bhkIXrMt3YFgFi8ZApbwWoe1h3XTpA==",
"dependencies": {
"formdata-polyfill": "^4.0.10",
"web-streams-polyfill": "^3.1.1"
},
"engines": {
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/node-fetch"
}
},
"node_modules/typescript": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA==",
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=4.2.0"
"node": ">=14.17"
}
},
"node_modules/web-streams-polyfill": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
"integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA==",
"engines": {
"node": ">= 8"
}
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"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/yerror": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/yerror/-/yerror-6.0.1.tgz",
"integrity": "sha512-0Bxo+NyeucjxhmPB5z3lmI/N/cOu8L1Q8JVta6/I5G6J/JhCSSPwk8qt9N4yOFSjwkvhDwzUSQglfBIAllvi1Q==",
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/yerror/-/yerror-8.0.0.tgz",
"integrity": "sha512-FemWD5/UqNm8ffj8oZIbjWXIF2KE0mZssggYpdaQkWDDgXBQ/35PNIxEuz6/YLn9o0kOxDBNJe8x8k9ljD7k/g==",
"engines": {
"node": ">=12.19.0"
"node": ">=18.16.0"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"engines": {
"node": ">=6"
}
}
},
"dependencies": {
"@cspotcode/source-map-support": {
"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,
"requires": {
"@jridgewell/trace-mapping": "0.3.9"
}
},
"@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
"dev": true
},
"@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"dev": true
},
"@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,
"requires": {
"@jridgewell/resolve-uri": "^3.0.3",
"@jridgewell/sourcemap-codec": "^1.4.10"
}
},
"@scrypted/sdk": {
"version": "file:../sdk",
"requires": {
@@ -302,7 +432,7 @@
"@types/node": "^18.11.18",
"@types/stringify-object": "^4.0.0",
"adm-zip": "^0.4.13",
"axios": "^0.21.4",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
@@ -322,117 +452,181 @@
"@scrypted/server": {
"version": "file:../server",
"requires": {
"@ffmpeg-installer/ffmpeg": "^1.1.0",
"@mapbox/node-pre-gyp": "^1.0.10",
"@scrypted/types": "^0.2.63",
"@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",
"@mapbox/node-pre-gyp": "^1.0.11",
"@scrypted/types": "^0.3.4",
"@types/adm-zip": "^0.5.5",
"@types/cookie-parser": "^1.4.6",
"@types/debug": "^4.1.12",
"@types/express": "^4.17.21",
"@types/follow-redirects": "^1.14.4",
"@types/http-auth": "^4.1.4",
"@types/ip": "^1.1.3",
"@types/lodash": "^4.14.202",
"@types/mime": "^3.0.4",
"@types/node-dijkstra": "^2.5.6",
"@types/node-forge": "^1.3.10",
"@types/pem": "^1.14.4",
"@types/semver": "^7.5.6",
"@types/source-map-support": "^0.5.10",
"@types/tar": "^6.1.10",
"@types/whatwg-mimetype": "^3.0.2",
"@types/ws": "^8.5.10",
"adm-zip": "^0.5.10",
"body-parser": "^1.20.2",
"cookie-parser": "^1.4.6",
"debug": "^4.3.4",
"engine.io": "^6.2.0",
"engine.io": "^6.5.4",
"express": "^4.18.2",
"ffmpeg-static": "^5.1.0",
"ffmpeg-static": "^5.2.0",
"follow-redirects": "^1.15.4",
"http-auth": "^4.2.0",
"ip": "^1.1.8",
"level": "^6.0.1",
"level": "^8.0.0",
"linkfs": "^2.1.0",
"lodash": "^4.17.21",
"memfs": "^3.4.7",
"memfs": "^4.6.0",
"mime": "^3.0.0",
"mkdirp": "^1.0.4",
"nan": "^2.17.0",
"nan": "^2.18.0",
"node-dijkstra": "^2.5.0",
"node-forge": "^1.3.1",
"node-gyp": "^8.4.1",
"node-gyp": "^10.0.1",
"node-pty-prebuilt-multiarch": "^0.10.1-pre.5",
"router": "^1.3.7",
"semver": "^7.3.8",
"router": "^1.3.8",
"semver": "^7.5.4",
"sharp": "^0.33.1",
"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"
"tar": "^6.2.0",
"tslib": "^2.6.2",
"typescript": "^5.3.3",
"whatwg-mimetype": "^4.0.0",
"ws": "^8.16.0"
}
},
"@types/node": {
"version": "16.9.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-16.9.0.tgz",
"integrity": "sha512-nmP+VR4oT0pJUPFbKE4SXj3Yb4Q/kz3M9dSAO1GGMebRKWHQxLfDNmU/yh3xxCJha3N60nQ/JwXWwOE/ZSEVag==",
"@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
"integrity": "sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA==",
"dev": true
},
"fetch-blob": {
"version": "3.1.4",
"resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.1.4.tgz",
"integrity": "sha512-Eq5Xv5+VlSrYWEqKrusxY1C3Hm/hjeAsCGVG3ft7pZahlUAChpGZT/Ms1WmSLnEAisEXszjzu/s+ce6HZB2VHA==",
"@tsconfig/node12": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@tsconfig/node12/-/node12-1.0.11.tgz",
"integrity": "sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==",
"dev": true
},
"@tsconfig/node14": {
"version": "1.0.3",
"resolved": "https://registry.npmjs.org/@tsconfig/node14/-/node14-1.0.3.tgz",
"integrity": "sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==",
"dev": true
},
"@tsconfig/node16": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true
},
"@types/node": {
"version": "20.11.0",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.0.tgz",
"integrity": "sha512-o9bjXmDNcF7GbM4CNQpmi+TutCgap/K3w1JyKgxAjqx41zp9qlIAVFi0IhCNsJcXolEqLWhbFbEeL0PvYm4pcQ==",
"dev": true,
"requires": {
"node-domexception": "^1.0.0",
"web-streams-polyfill": "^3.0.3"
"undici-types": "~5.26.4"
}
},
"formdata-polyfill": {
"version": "4.0.10",
"resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
"integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
"requires": {
"fetch-blob": "^3.1.2"
}
"acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true
},
"acorn-walk": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz",
"integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==",
"dev": true
},
"arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"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
},
"diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true
},
"http-auth-utils": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/http-auth-utils/-/http-auth-utils-3.0.2.tgz",
"integrity": "sha512-cQ8957aiUX0lgV1620uIGKGJc0sEuD/QK4ueZ0hb60MGbO0f6ahcuIgPjamAD98D/AUGizKVm+dNvUVHs0f4Ow==",
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/http-auth-utils/-/http-auth-utils-5.0.1.tgz",
"integrity": "sha512-YPiLVYdwpBEWB85iWYg7V/ZW3mBfPLCTFQWEiPAA5CKXHJOAPbnJ0xDHLiE6KbPRvrYCwLuWqTG0fLfXVjMUcQ==",
"requires": {
"yerror": "^6.0.0"
"yerror": "^8.0.0"
}
},
"node-domexception": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz",
"integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ=="
"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
},
"node-fetch-commonjs": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/node-fetch-commonjs/-/node-fetch-commonjs-3.1.1.tgz",
"integrity": "sha512-TgkdVJdiEaauzWwB9NoD4TvHZFtG6KKEffvotWf9WNIyoRZHsCFjGfb3bhkIXrMt3YFgFi8ZApbwWoe1h3XTpA==",
"ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"requires": {
"formdata-polyfill": "^4.0.10",
"web-streams-polyfill": "^3.1.1"
"@cspotcode/source-map-support": "^0.8.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",
"v8-compile-cache-lib": "^3.0.1",
"yn": "3.1.1"
}
},
"typescript": {
"version": "4.4.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.4.3.tgz",
"integrity": "sha512-4xfscpisVgqqDfPaJo5vkd+Qd/ItkoagnHpufr+i2QCHBsNYp+G7UAoyFl8aPtx879u38wPV65rZ8qbGZijalA=="
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw=="
},
"web-streams-polyfill": {
"version": "3.2.0",
"resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.2.0.tgz",
"integrity": "sha512-EqPmREeOzttaLRm5HS7io98goBgZ7IVz79aDvqjD0kYXLtFZTc0T/U6wHTPKyIjb+MdN7DFIIX6hgdBEpWmfPA=="
"undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"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
},
"yerror": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/yerror/-/yerror-6.0.1.tgz",
"integrity": "sha512-0Bxo+NyeucjxhmPB5z3lmI/N/cOu8L1Q8JVta6/I5G6J/JhCSSPwk8qt9N4yOFSjwkvhDwzUSQglfBIAllvi1Q=="
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/yerror/-/yerror-8.0.0.tgz",
"integrity": "sha512-FemWD5/UqNm8ffj8oZIbjWXIF2KE0mZssggYpdaQkWDDgXBQ/35PNIxEuz6/YLn9o0kOxDBNJe8x8k9ljD7k/g=="
},
"yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true
}
}
}

View File

@@ -11,13 +11,13 @@
"author": "",
"license": "ISC",
"dependencies": {
"@scrypted/server": "file:../server",
"@scrypted/sdk": "file:../sdk",
"http-auth-utils": "^3.0.2",
"node-fetch-commonjs": "^3.1.1",
"typescript": "^4.4.3"
"@scrypted/server": "file:../server",
"http-auth-utils": "^5.0.1",
"typescript": "^5.3.3"
},
"devDependencies": {
"@types/node": "^16.9.0"
"@types/node": "^20.11.0",
"ts-node": "^10.9.2"
}
}

View File

@@ -1,30 +0,0 @@
import sdk from "@scrypted/sdk";
const { systemManager, log } = sdk;
export async function alertRecommendedPlugins(plugins: { [pkg: string]: string }) {
const pluginsComponent = await systemManager.getComponent('plugins');
let recommended: any;
try {
recommended = JSON.parse(localStorage.getItem('alert-recommended'));
}
catch (e) {
recommended = {};
}
for (const plugin of Object.keys(plugins)) {
try {
if (recommended[plugin])
continue;
recommended[plugin] = true;
localStorage.setItem('alert-recommended', JSON.stringify(recommended));
const id = await pluginsComponent.getIdForPluginId(plugin);
if (id)
continue;
const name = plugins[plugin];
log.a(`Installation of the ${name} plugin is also recommended. origin:/#/component/plugin/install/${plugin}`)
}
catch (e) {
}
}
}

View File

@@ -5,6 +5,7 @@ class EndError extends Error {
export function createAsyncQueue<T>() {
let ended: Error | undefined;
const endDeferred = new Deferred<void>();
const waiting: Deferred<T>[] = [];
const queued: { item: T, dequeued?: Deferred<void> }[] = [];
@@ -75,7 +76,8 @@ export function createAsyncQueue<T>() {
if (ended)
return false;
// catch to prevent unhandled rejection.
ended = e || new EndError()
ended = e || new EndError();
endDeferred.resolve();
while (waiting.length) {
waiting.shift().reject(ended);
}
@@ -124,6 +126,7 @@ export function createAsyncQueue<T>() {
get ended() {
return ended;
},
endPromise: endDeferred.promise,
take,
clear() {
return clear();

View File

@@ -1,12 +1,9 @@
import type { TranspileOptions } from "typescript";
import sdk, { ScryptedDeviceBase, MixinDeviceBase, ScryptedInterface, ScryptedDeviceType } from "@scrypted/sdk";
import vm from "vm";
import sdk, { MixinDeviceBase, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedInterfaceDescriptors } from "@scrypted/sdk";
import fs from 'fs';
import type { TranspileOptions } from "typescript";
import vm from "vm";
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';
import path from 'path';
const { systemManager, deviceManager, mediaManager, endpointManager } = sdk;
@@ -26,9 +23,13 @@ export async function tsCompile(source: string, options: TranspileOptions = null
return ts.transpileModule(source, options).outputText;
}
export function readFileAsString(f: string) {
return fs.readFileSync(f).toString();;
}
function getTypeDefs() {
const scryptedTypesDefs = fs.readFileSync('@types/sdk/types.d.ts').toString();
const scryptedIndexDefs = fs.readFileSync('@types/sdk/index.d.ts').toString();
const scryptedTypesDefs = readFileAsString('@types/sdk/types.d.ts');
const scryptedIndexDefs = readFileAsString('@types/sdk/index.d.ts');
return {
scryptedIndexDefs,
scryptedTypesDefs,
@@ -58,18 +59,12 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
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,
fs: require('realfs'),
fetch,
ScryptedDeviceBase,
MixinDeviceBase,
systemManager: smProxy,
systemManager,
deviceManager,
endpointManager,
mediaManager,
@@ -104,7 +99,6 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
return {
value,
defaultExport,
apiProxy,
};
}
catch (e) {
@@ -115,7 +109,7 @@ export async function scryptedEval(device: ScryptedDeviceBase, script: string, e
}
export function createMonacoEvalDefaults(extraLibs: { [lib: string]: string }) {
const bufferTypeDefs = fs.readFileSync('@types/node/buffer.d.ts').toString();
const bufferTypeDefs= readFileAsString('@types/node/buffer.d.ts');
const safeLibs = {
bufferTypeDefs,

View File

@@ -0,0 +1,50 @@
import { httpFetch, httpFetchParseIncomingMessage } from '../../server/src/fetch/http-fetch';
import type { IncomingMessage } from 'http';
import type { Readable } from 'stream';
import { createAuthFetch } from '../../packages/auth-fetch/src/auth-fetch';
export type { HttpFetchOptions, HttpFetchResponseType } from '../../server/src/fetch/http-fetch';
export type { AuthFetchCredentialState, AuthFetchOptions } from '../../packages/auth-fetch/src/auth-fetch';
export const authHttpFetch = createAuthFetch<Readable, IncomingMessage>(httpFetch, httpFetchParseIncomingMessage);
function ensureType<T>(v: T) {
}
async function test() {
const a = await authHttpFetch({
credential: undefined,
url: 'http://example.com',
});
ensureType<Buffer>(a.body);
const b = await authHttpFetch({
credential: undefined,
url: 'http://example.com',
responseType: 'json',
});
ensureType<any>(b.body);
const c = await authHttpFetch({
credential: undefined,
url: 'http://example.com',
responseType: 'readable',
});
ensureType<IncomingMessage>(c.body);
const d = await authHttpFetch({
credential: undefined,
url: 'http://example.com',
responseType: 'buffer',
});
ensureType<Buffer>(d.body);
const e = await authHttpFetch({
credential: undefined,
url: 'http://example.com',
responseType: 'text',
});
ensureType<string>(e.body);
}

View File

@@ -78,11 +78,17 @@ export function createPromiseDebouncer<T>() {
export function createMapPromiseDebouncer<T>() {
const map = new Map<string, Promise<T>>();
return (key: any, func: () => Promise<T>): Promise<T> => {
return (key: any, debounce: number, func: () => Promise<T>): Promise<T> => {
const keyStr = JSON.stringify(key);
let value = map.get(keyStr);
if (!value) {
value = func().finally(() => map.delete(keyStr));
value = func().finally(() => {
if (!debounce) {
map.delete(keyStr);
return;
}
setTimeout(() => map.delete(keyStr), debounce);
});
map.set(keyStr, value);
}
return value;

View File

@@ -59,13 +59,13 @@ export async function read16BELengthLoop(readable: Readable, options: {
export class StreamEndError extends Error {
constructor() {
super()
super('stream ended');
}
}
export async function readLength(readable: Readable, length: number): Promise<Buffer> {
if (readable.readableEnded || readable.destroyed)
throw new Error("stream ended");
throw new StreamEndError();
if (!length) {
return Buffer.alloc(0);

View File

@@ -73,6 +73,14 @@ function createOptions() {
return options;
}
// can be called on anything with getStats, ie for receiver specific reports or connection reports.
export async function getPacketsLost(t: { getStats(): Promise<RTCStatsReport> }) {
const stats = await t.getStats();
const packetsLost = ([...stats.values()] as { packetsLost: number }[]).filter(stat => 'packetsLost' in stat).map(stat => stat.packetsLost);
const total = packetsLost.reduce((p, c) => p + c, 0);
return total;
}
export class BrowserSignalingSession implements RTCSignalingSession {
private pc: RTCPeerConnection;
pcDeferred = new Deferred<RTCPeerConnection>();
@@ -90,6 +98,10 @@ export class BrowserSignalingSession implements RTCSignalingSession {
return this.options;
}
async getPacketsLost() {
return getPacketsLost(this.pc);
}
async setMicrophone(enabled: boolean) {
if (this.microphone && enabled && !this.micEnabled) {
this.micEnabled = true;

View File

@@ -1,8 +1,6 @@
import crypto, { randomBytes } from 'crypto';
import dgram from 'dgram';
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, Writable } from 'stream';
import tls from 'tls';
@@ -363,7 +361,7 @@ export class RtspClient extends RtspBase {
}
}
writeRequest(method: string, headers?: Headers, path?: string, body?: Buffer) {
async writeRequest(method: string, headers?: Headers, path?: string, body?: Buffer) {
headers = headers || {};
let fullUrl = this.url;
@@ -390,7 +388,7 @@ export class RtspClient extends RtspBase {
headers['User-Agent'] = 'Scrypted';
if (this.wwwAuthenticate)
headers['Authorization'] = this.createAuthorizationHeader(method, new URL(fullUrl));
headers['Authorization'] = await this.createAuthorizationHeader(method, new URL(fullUrl));
if (this.session)
headers['Session'] = this.session;
@@ -531,10 +529,13 @@ export class RtspClient extends RtspBase {
}
}
createAuthorizationHeader(method: string, url: URL) {
async createAuthorizationHeader(method: string, url: URL) {
if (!this.wwwAuthenticate)
throw new Error('no WWW-Authenticate found');
const { BASIC } = await import('http-auth-utils');
const { parseHTTPHeadersQuotedKeyValueSet } = await import('http-auth-utils/dist/utils');
if (this.wwwAuthenticate.includes('Basic')) {
const hash = BASIC.computeHash(url);
return `Basic ${hash}`;
@@ -586,7 +587,7 @@ export class RtspClient extends RtspBase {
}
async request(method: string, headers?: Headers, path?: string, body?: Buffer, authenticating?: boolean): Promise<RtspServerResponse> {
this.writeRequest(method, headers, path, body);
await this.writeRequest(method, headers, path, body);
const message = this.requestTimeout ? await timeoutPromise(this.requestTimeout, this.readMessage()) : await this.readMessage();
const statusLine = message[0];

View File

@@ -207,6 +207,10 @@ export function parseRtpMap(mlineType: string, rtpmap: string) {
codec = 'pcm_s16be';
ffmpegEncoder = 'pcm_s16be';
}
else if (rtpmap?.includes('speex')) {
codec = 'speex';
ffmpegEncoder = 'libspeex';
}
else if (rtpmap?.includes('h264')) {
codec = 'h264';
}

View File

@@ -1,6 +1,6 @@
# Home Assistant Addon Configuration
name: Scrypted
version: "18-jammy-full.s6-v0.66.0"
version: "18-jammy-full.s6-v0.88.0"
slug: scrypted
description: Scrypted is a high performance home video integration and automation platform
url: "https://github.com/koush/scrypted"
@@ -29,9 +29,9 @@ environment:
SCRYPTED_ADMIN_USERNAME: "homeassistant"
SCRYPTED_INSTALL_ENVIRONMENT: "ha"
backup_exclude:
- '/server/**'
- '/data/scrypted_nvr/**'
- '/data/scrypted_data/plugins/**'
- '*/server/**'
- '*/scrypted_nvr/**'
- '*/scrypted_data/plugins/**'
map:
- config:rw
- media:rw

View File

@@ -1,5 +1,5 @@
ARG BASE="18-jammy-full"
FROM koush/scrypted-common:${BASE}
FROM ghcr.io/koush/scrypted-common:${BASE}
WORKDIR /
# cache bust
@@ -14,4 +14,8 @@ WORKDIR /server
# https://github.com/nodejs/node/issues/41145#issuecomment-992948130
ENV NODE_OPTIONS="--dns-result-order=ipv4first"
# changing this forces pip and npm to perform reinstalls.
# if this base image changes, this version must be updated.
ENV SCRYPTED_BASE_VERSION="20241303"
CMD npm --prefix /server exec scrypted-serve

View File

@@ -1,5 +1,5 @@
ARG BASE="16-jammy"
FROM koush/scrypted-common:${BASE}
FROM ghcr.io/koush/scrypted-common:${BASE}
WORKDIR /
RUN git clone --depth=1 https://github.com/koush/scrypted

View File

@@ -25,10 +25,14 @@ RUN apt-get update && apt-get -y install \
apt-get -y upgrade
ARG NODE_VERSION=18
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
RUN apt-get install -y ca-certificates curl gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_"$NODE_VERSION".x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && apt-get install -y nodejs
# python native
RUN echo "Installing python."
RUN apt-get -y install \
python3 \
python3-dev \
@@ -38,44 +42,25 @@ RUN apt-get -y install \
# these are necessary for pillow-simd, additional on disk size is small
# but could consider removing this.
RUN echo "Installing pillow-simd dependencies."
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 echo "Installing gstreamer."
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 echo "Installing gstreamer bindings."
RUN apt-get -y install \
python3-gst-1.0
# 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
# allow pip to install to system
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 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
################################################################
@@ -87,20 +72,11 @@ RUN python3 -m pip install debugpy typing_extensions psutil
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"
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
# python 3.9 from ppa.
# 3.9 is the version with prebuilt support for tensorflow lite
RUN add-apt-repository ppa:deadsnakes/ppa && \
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
apt-get -y install \
python3.9 \
python3.9-dev \
@@ -110,7 +86,6 @@ RUN add-apt-repository ppa:deadsnakes/ppa && \
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
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
# Coral Edge TPU
@@ -127,9 +102,6 @@ ENV SCRYPTED_INSTALL_PATH="/server"
RUN test -f "/usr/bin/ffmpeg"
ENV SCRYPTED_FFMPEG_PATH="/usr/bin/ffmpeg"
# changing this forces pip and npm to perform reinstalls.
# if this base image changes, this version must be updated.
ENV SCRYPTED_BASE_VERSION="20230727"
ENV SCRYPTED_DOCKER_FLAVOR="full"
################################################################

View File

@@ -6,18 +6,15 @@ 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 \
ffmpeg \
gcc \
libcairo2-dev \
libgirepository1.0-dev \
pkg-config && \
ffmpeg && \
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 install -y ca-certificates curl gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_"$NODE_VERSION".x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && apt-get install -y nodejs
# python native
@@ -41,7 +38,4 @@ ENV SCRYPTED_INSTALL_PATH="/server"
RUN test -f "/usr/bin/ffmpeg"
ENV SCRYPTED_FFMPEG_PATH="/usr/bin/ffmpeg"
# changing this forces pip and npm to perform reinstalls.
# if this base image changes, this version must be updated.
ENV SCRYPTED_BASE_VERSION="20230727"
ENV SCRYPTED_DOCKER_FLAVOR="lite"

View File

@@ -1,4 +1,4 @@
FROM koush/scrypted-common
FROM ghcr.io/koush/scrypted-common
WORKDIR /
COPY . .

View File

@@ -1,4 +1,4 @@
FROM koush/18-jammy-full.s6
FROM ghcr.io/koush/scrypted:18-jammy-full.s6
WORKDIR /
@@ -12,11 +12,3 @@ 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

View File

@@ -1,5 +1,5 @@
ARG BASE="18-jammy-full"
FROM koush/scrypted-common:${BASE}
FROM ghcr.io/koush/scrypted-common:${BASE}
# avahi advertiser support
RUN apt-get update && apt-get -y install \
@@ -44,4 +44,8 @@ WORKDIR /server
# https://github.com/nodejs/node/issues/41145#issuecomment-992948130
ENV NODE_OPTIONS="--dns-result-order=ipv4first"
# changing this forces pip and npm to perform reinstalls.
# if this base image changes, this version must be updated.
ENV SCRYPTED_BASE_VERSION="20241303"
CMD npm --prefix /server exec scrypted-serve

View File

@@ -9,7 +9,7 @@ RUN apt-get -y update && \
# 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
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_VERSION.x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
ENV SCRYPTED_INSTALL_ENVIRONMENT="docker"
ENV SCRYPTED_CAN_RESTART="true"
@@ -19,7 +19,4 @@ ENV SCRYPTED_INSTALL_PATH="/server"
RUN test -f "/usr/bin/ffmpeg"
ENV SCRYPTED_FFMPEG_PATH="/usr/bin/ffmpeg"
# changing this forces pip and npm to perform reinstalls.
# if this base image changes, this version must be updated.
ENV SCRYPTED_BASE_VERSION="20230727"
ENV SCRYPTED_DOCKER_FLAVOR="thin"

View File

@@ -1,4 +1,4 @@
./template/generate-dockerfile.sh
docker build -t koush/scrypted-common -f Dockerfile.common . && \
docker build -t koush/scrypted -f Dockerfile.local .
docker build -t ghcr.io/koush/scrypted-common -f Dockerfile.full . && \
docker build -t ghcr.io/koush/scrypted -f Dockerfile.local .

View File

@@ -1,3 +1,3 @@
./docker-build.sh
docker build -t koush/scrypted:18-jammy-full.nvidia -f Dockerfile.nvidia
docker build -t ghcr.io/koush/scrypted:18-jammy-full.nvidia -f Dockerfile.nvidia .

View File

@@ -11,8 +11,8 @@ echo $BASE
SUPERVISOR=.s6
SUPERVISOR_BASE=$BASE$SUPERVISOR
docker build -t koush/scrypted-common:$BASE -f Dockerfile.$FLAVOR \
docker build -t ghcr.io/koush/scrypted-common:$BASE -f Dockerfile.$FLAVOR \
--build-arg NODE_VERSION=$NODE_VERSION --build-arg BASE=$IMAGE_BASE . && \
\
docker build -t koush/scrypted:$SUPERVISOR_BASE -f Dockerfile$SUPERVISOR \
docker build -t ghcr.io/koush/scrypted:$SUPERVISOR_BASE -f Dockerfile$SUPERVISOR \
--build-arg BASE=$BASE --build-arg SCRYPTED_INSTALL_VERSION=$SCRYPTED_INSTALL_VERSION .

View File

@@ -34,14 +34,14 @@ services:
- SCRYPTED_WEBHOOK_UPDATE_AUTHORIZATION=Bearer SET_THIS_TO_SOME_RANDOM_TEXT
- SCRYPTED_WEBHOOK_UPDATE=http://localhost:10444/v1/update
# Uncomment next 3 lines for Nvidia GPU support.
# - NVIDIA_VISIBLE_DEVICES=all
# - NVIDIA_DRIVER_CAPABILITIES=all
# Uncomment next line to run avahi-daemon inside the container
# Don't use if dbus and avahi run on the host and are bind-mounted
# (see below under "volumes")
# - SCRYPTED_DOCKER_AVAHI=true
# Uncomment next 3 lines for Nvidia GPU support.
# - NVIDIA_VISIBLE_DEVICES=all
# - NVIDIA_DRIVER_CAPABILITIES=all
# runtime: nvidia
volumes:
@@ -90,7 +90,7 @@ services:
container_name: scrypted
restart: unless-stopped
network_mode: host
image: koush/scrypted
image: ghcr.io/koush/scrypted
# logging is noisy and will unnecessarily wear on flash storage.
# scrypted has per device in memory logging that is preferred.

View File

@@ -0,0 +1,16 @@
if [ "$(uname -m)" = "x86_64" ]
then
echo "Installing Intel graphics packages."
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 --yes --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;
exit $?
else
echo "Intel graphics will not be installed on this architecture."
fi
exit 0

View File

@@ -6,19 +6,6 @@ then
exit 0
fi
if [ "$SERVICE_USER" == "root" ]
then
echo "Scrypted SERVICE_USER root is not allowed."
exit 1
fi
USER_HOME=$(eval echo ~$SERVICE_USER)
SCRYPTED_HOME=$USER_HOME/.scrypted
mkdir -p $SCRYPTED_HOME
set -e
cd $SCRYPTED_HOME
function readyn() {
while true; do
read -p "$1 (y/n) " yn
@@ -30,6 +17,22 @@ function readyn() {
done
}
if [ "$SERVICE_USER" == "root" ]
then
readyn "Scrypted will store its files in the root user home directory. Running as a non-root user is recommended. Are you sure?"
if [ "$yn" == "n" ]
then
exit 1
fi
fi
USER_HOME=$(eval echo ~$SERVICE_USER)
SCRYPTED_HOME=$USER_HOME/.scrypted
mkdir -p $SCRYPTED_HOME
set -e
cd $SCRYPTED_HOME
readyn "Install Docker?"
if [ "$yn" == "y" ]

View File

@@ -4,20 +4,11 @@
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"
RUN curl https://raw.githubusercontent.com/koush/scrypted/main/install/docker/install-intel-graphics.sh | bash
# python 3.9 from ppa.
# 3.9 is the version with prebuilt support for tensorflow lite
RUN add-apt-repository ppa:deadsnakes/ppa && \
RUN add-apt-repository -y ppa:deadsnakes/ppa && \
apt-get -y install \
python3.9 \
python3.9-dev \
@@ -27,7 +18,6 @@ RUN add-apt-repository ppa:deadsnakes/ppa && \
RUN rm -f /usr/lib/python**/EXTERNALLY-MANAGED
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
# Coral Edge TPU
@@ -44,9 +34,6 @@ ENV SCRYPTED_INSTALL_PATH="/server"
RUN test -f "/usr/bin/ffmpeg"
ENV SCRYPTED_FFMPEG_PATH="/usr/bin/ffmpeg"
# changing this forces pip and npm to perform reinstalls.
# if this base image changes, this version must be updated.
ENV SCRYPTED_BASE_VERSION="20230727"
ENV SCRYPTED_DOCKER_FLAVOR="full"
################################################################

View File

@@ -22,10 +22,14 @@ RUN apt-get update && apt-get -y install \
apt-get -y upgrade
ARG NODE_VERSION=18
RUN curl -fsSL https://deb.nodesource.com/setup_${NODE_VERSION}.x | bash -
RUN apt-get install -y ca-certificates curl gnupg
RUN mkdir -p /etc/apt/keyrings
RUN curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | gpg --dearmor --yes -o /etc/apt/keyrings/nodesource.gpg
RUN echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_"$NODE_VERSION".x nodistro main" | tee /etc/apt/sources.list.d/nodesource.list
RUN apt-get update && apt-get install -y nodejs
# python native
RUN echo "Installing python."
RUN apt-get -y install \
python3 \
python3-dev \
@@ -35,44 +39,25 @@ RUN apt-get -y install \
# these are necessary for pillow-simd, additional on disk size is small
# but could consider removing this.
RUN echo "Installing pillow-simd dependencies."
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 echo "Installing gstreamer."
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 echo "Installing gstreamer bindings."
RUN apt-get -y install \
python3-gst-1.0
# 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
# allow pip to install to system
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 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
################################################################

View File

@@ -12,6 +12,26 @@ then
exit 1
fi
function readyn() {
while true; do
read -p "$1 (y/n) " yn
case $yn in
[Yy]* ) break;;
[Nn]* ) break;;
* ) echo "Please answer yes or no. (y/n)";;
esac
done
}
if [ "$SERVICE_USER" = "root" ] && [ -z "$SERVICE_USER_ROOT" ]
then
readyn "Scrypted will store its files in the root user home directory. Running as a non-root user is recommended. Are you sure?"
if [ "$yn" == "n" ]
then
exit 1
fi
fi
echo "Stopping existing service if it is running..."
systemctl stop scrypted.service
@@ -49,6 +69,14 @@ ENV() {
}
source <(curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/template/Dockerfile.full.header)
if [ -z "$SCRYPTED_INSTALL_ENVIRONMENT" ]
then
SCRYPTED_INSTALL_ENVIRONMENT=local
fi
if [ "$SCRYPTED_INSTALL_ENVIRONMENT" = "lxc" ]
then
source <(curl -s https://raw.githubusercontent.com/koush/scrypted/main/install/docker/template/Dockerfile.full.footer)
fi
if [ -z "$SERVICE_USER" ]
then
@@ -56,12 +84,6 @@ then
exit 0
fi
if [ "$SERVICE_USER" == "root" ]
then
echo "Scrypted SERVICE_USER root is not allowed."
exit 1
fi
# this is not RUN as we do not care about the result
USER_HOME=$(eval echo ~$SERVICE_USER)
echo "Setting permissions on $USER_HOME/.scrypted"
@@ -84,6 +106,7 @@ ExecStart=/usr/bin/npx -y scrypted serve
Restart=on-failure
RestartSec=3
Environment="NODE_OPTIONS=$NODE_OPTIONS"
Environment="SCRYPTED_INSTALL_ENVIRONMENT=$SCRYPTED_INSTALL_ENVIRONMENT"
[Install]
WantedBy=multi-user.target

View File

@@ -0,0 +1,84 @@
function readyn() {
while true; do
read -p "$1 (y/n) " yn
case $yn in
[Yy]* ) break;;
[Nn]* ) break;;
* ) echo "Please answer yes or no. (y/n)";;
esac
done
}
cd /tmp
SCRYPTED_VERSION=v0.80.0
SCRYPTED_TAR_ZST=scrypted-$SCRYPTED_VERSION.tar.zst
if [ -z "$VMID" ]
then
VMID=10443
fi
echo "Downloading scrypted container backup."
if [ ! -f "$SCRYPTED_TAR_ZST" ]
then
curl -O -L https://github.com/koush/scrypted/releases/download/$SCRYPTED_VERSION/scrypted.tar.zst
mv scrypted.tar.zst $SCRYPTED_TAR_ZST
fi
echo "Checking for existing container."
pct config $VMID
if [ "$?" == "0" ]
then
echo ""
echo "Existing container $VMID found. Run this script with --force to overwrite the existing container."
echo "This will wipe all existing data. Clone the existing container to retain the data, then reassign the owner of the scrypted volume after installation is complete."
echo ""
echo "bash $0 --force"
echo ""
fi
pct restore $VMID $SCRYPTED_TAR_ZST $@
if [ "$?" != "0" ]
then
echo ""
echo "pct restore failed"
echo ""
echo "This may be caused by the server's 'local' storage not supporting containers."
echo "Try running this script again with a different storage device (local-lvm, local-zfs). For example:"
echo ""
echo "bash $0 --storage local-lvm"
echo ""
exit 1
fi
pct set $VMID -net0 name=eth0,bridge=vmbr0,ip=dhcp,ip6=auto
if [ "$?" != "0" ]
then
echo ""
echo "pct set network failed"
echo ""
echo "Ignoring... Please verify your container's network settings."
fi
CONF=/etc/pve/lxc/$VMID.conf
if [ -f "$CONF" ]
then
echo "onboot: 1" >> $CONF
else
echo "$CONF not found? Start on boot must be enabled manually."
fi
echo "Adding udev rule: /etc/udev/rules.d/65-scrypted.rules"
readyn "Add udev rule for hardware acceleration? This may conflict with existing rules."
if [ "$yn" == "y" ]
then
sh -c "echo 'SUBSYSTEM==\"apex\", MODE=\"0666\"' > /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'KERNEL==\"renderD128\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'KERNEL==\"card0\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"1a6e\", ATTRS{idProduct}==\"089a\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
sh -c "echo 'SUBSYSTEM==\"usb\", ATTRS{idVendor}==\"18d1\", ATTRS{idProduct}==\"9302\", MODE=\"0666\"' >> /etc/udev/rules.d/65-scrypted.rules"
udevadm control --reload-rules && udevadm trigger
fi
echo "Scrypted setup is complete and the container resources can be started."
echo "Scrypted NVR users should provide at least 4 cores and 16GB RAM prior to starting."

View File

@@ -14,7 +14,7 @@ cd $(dirname $0)
git submodule init
git submodule update
for directory in sdk common server packages/client
for directory in sdk common server packages/client packages/auth-fetch
do
echo "$directory > npm install"
pushd $directory

11
packages/auth-fetch/.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
node_modules
.DS_Store
.gcloud/
dist/
volume
scrypted.db
out
scrypted.db.bak
.exit
.update
.venv

580
packages/auth-fetch/package-lock.json generated Normal file
View File

@@ -0,0 +1,580 @@
{
"name": "@scrypted/auth-fetch",
"version": "1.0.0",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/auth-fetch",
"version": "1.0.0",
"license": "ISC",
"dependencies": {
"follow-redirects": "^1.15.4",
"http-auth-utils": "^5.0.1"
},
"devDependencies": {
"@types/node": "^20.9.4",
"rimraf": "^5.0.5",
"typescript": "^5.3.2"
}
},
"node_modules/@isaacs/cliui": {
"version": "8.0.2",
"resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz",
"integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==",
"dev": true,
"dependencies": {
"string-width": "^5.1.2",
"string-width-cjs": "npm:string-width@^4.2.0",
"strip-ansi": "^7.0.1",
"strip-ansi-cjs": "npm:strip-ansi@^6.0.1",
"wrap-ansi": "^8.1.0",
"wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
"integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==",
"dev": true,
"optional": true,
"engines": {
"node": ">=14"
}
},
"node_modules/@types/node": {
"version": "20.10.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz",
"integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
"integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
}
},
"node_modules/ansi-styles": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz",
"integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==",
"dev": true,
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/brace-expansion": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
"dev": true,
"dependencies": {
"balanced-match": "^1.0.0"
}
},
"node_modules/color-convert": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
"integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
"dev": true,
"dependencies": {
"color-name": "~1.1.4"
},
"engines": {
"node": ">=7.0.0"
}
},
"node_modules/color-name": {
"version": "1.1.4",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
"dev": true
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
"dev": true,
"dependencies": {
"path-key": "^3.1.0",
"shebang-command": "^2.0.0",
"which": "^2.0.1"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
"integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==",
"dev": true
},
"node_modules/emoji-regex": {
"version": "9.2.2",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz",
"integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==",
"dev": true
},
"node_modules/follow-redirects": {
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/foreground-child": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz",
"integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==",
"dev": true,
"dependencies": {
"cross-spawn": "^7.0.0",
"signal-exit": "^4.0.1"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/glob": {
"version": "10.3.10",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.3.10.tgz",
"integrity": "sha512-fa46+tv1Ak0UPK1TOy/pZrIybNNt4HCv7SDzwyfiOZkvZLEbjsZkJBPtDHVshZjbecAoAGSC20MjLDG/qr679g==",
"dev": true,
"dependencies": {
"foreground-child": "^3.1.0",
"jackspeak": "^2.3.5",
"minimatch": "^9.0.1",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0",
"path-scurry": "^1.10.1"
},
"bin": {
"glob": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/http-auth-utils": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/http-auth-utils/-/http-auth-utils-5.0.1.tgz",
"integrity": "sha512-YPiLVYdwpBEWB85iWYg7V/ZW3mBfPLCTFQWEiPAA5CKXHJOAPbnJ0xDHLiE6KbPRvrYCwLuWqTG0fLfXVjMUcQ==",
"dependencies": {
"yerror": "^8.0.0"
},
"engines": {
"node": ">=18.16.0"
}
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/isexe": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
"dev": true
},
"node_modules/jackspeak": {
"version": "2.3.6",
"resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz",
"integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==",
"dev": true,
"dependencies": {
"@isaacs/cliui": "^8.0.2"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
},
"optionalDependencies": {
"@pkgjs/parseargs": "^0.11.0"
}
},
"node_modules/lru-cache": {
"version": "10.1.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.1.0.tgz",
"integrity": "sha512-/1clY/ui8CzjKFyjdvwPWJUYKiFVXG2I2cY0ssG7h4+hwk+XOIX7ZSG9Q7TW8TW3Kp3BUSqgFWBLgL4PJ+Blag==",
"dev": true,
"engines": {
"node": "14 || >=16.14"
}
},
"node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
"integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==",
"dev": true,
"dependencies": {
"brace-expansion": "^2.0.1"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/minipass": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz",
"integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==",
"dev": true,
"engines": {
"node": ">=16 || 14 >=14.17"
}
},
"node_modules/path-key": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/path-scurry": {
"version": "1.10.1",
"resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.1.tgz",
"integrity": "sha512-MkhCqzzBEpPvxxQ71Md0b1Kk51W01lrYvlMzSUaIzNsODdd7mqhiimSZlr+VegAz5Z6Vzt9Xg2ttE//XBhH3EQ==",
"dev": true,
"dependencies": {
"lru-cache": "^9.1.1 || ^10.0.0",
"minipass": "^5.0.0 || ^6.0.2 || ^7.0.0"
},
"engines": {
"node": ">=16 || 14 >=14.17"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/rimraf": {
"version": "5.0.5",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-5.0.5.tgz",
"integrity": "sha512-CqDakW+hMe/Bz202FPEymy68P+G50RfMQK+Qo5YUqc9SPipvbGjCGKd0RSKEelbsfQuw3g5NZDSrlZZAJurH1A==",
"dev": true,
"dependencies": {
"glob": "^10.3.7"
},
"bin": {
"rimraf": "dist/esm/bin.mjs"
},
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/shebang-command": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
"dev": true,
"dependencies": {
"shebang-regex": "^3.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/shebang-regex": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/signal-exit": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
"dev": true,
"engines": {
"node": ">=14"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/string-width": {
"version": "5.1.2",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz",
"integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==",
"dev": true,
"dependencies": {
"eastasianwidth": "^0.2.0",
"emoji-regex": "^9.2.2",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/string-width-cjs": {
"name": "string-width",
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/string-width-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/string-width-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "7.1.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz",
"integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==",
"dev": true,
"dependencies": {
"ansi-regex": "^6.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
}
},
"node_modules/strip-ansi-cjs": {
"name": "strip-ansi",
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/typescript": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
},
"engines": {
"node": ">=14.17"
}
},
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"node_modules/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
"dev": true,
"dependencies": {
"isexe": "^2.0.0"
},
"bin": {
"node-which": "bin/node-which"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/wrap-ansi": {
"version": "8.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz",
"integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^6.1.0",
"string-width": "^5.0.1",
"strip-ansi": "^7.0.1"
},
"engines": {
"node": ">=12"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs": {
"name": "wrap-ansi",
"version": "7.0.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
"integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
"dev": true,
"dependencies": {
"ansi-styles": "^4.0.0",
"string-width": "^4.1.0",
"strip-ansi": "^6.0.0"
},
"engines": {
"node": ">=10"
},
"funding": {
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"dev": true,
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/ansi-styles": {
"version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true,
"dependencies": {
"color-convert": "^2.0.1"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"node_modules/wrap-ansi-cjs/node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"dev": true
},
"node_modules/wrap-ansi-cjs/node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"dev": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/wrap-ansi-cjs/node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"dev": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/yerror": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/yerror/-/yerror-8.0.0.tgz",
"integrity": "sha512-FemWD5/UqNm8ffj8oZIbjWXIF2KE0mZssggYpdaQkWDDgXBQ/35PNIxEuz6/YLn9o0kOxDBNJe8x8k9ljD7k/g==",
"engines": {
"node": ">=18.16.0"
}
}
}
}

View File

@@ -0,0 +1,23 @@
{
"name": "@scrypted/auth-fetch",
"version": "1.0.0",
"description": "",
"main": "dist/index.js",
"scripts": {
"prebuild": "rimraf dist",
"build": "tsc --outDir dist",
"prepublishOnly": "npm run build",
"test": "echo \"Error: no test specified\" && exit 1"
},
"devDependencies": {
"@types/node": "^20.9.4",
"rimraf": "^5.0.5",
"typescript": "^5.3.2"
},
"dependencies": {
"follow-redirects": "^1.15.4",
"http-auth-utils": "^5.0.1"
},
"author": "",
"license": "ISC"
}

View File

@@ -0,0 +1,135 @@
import { HttpFetchOptions, HttpFetchResponseType, checkStatus, fetcher, getFetchMethod, setDefaultHttpFetchAccept } from '../../../server/src/fetch';
export interface AuthFetchCredentialState {
username: string;
password: string;
}
export interface AuthFetchOptions {
credential?: AuthFetchCredentialState;
}
async function getAuth(options: AuthFetchOptions, url: string | URL, method: string) {
if (!options.credential)
return;
const { BASIC, DIGEST, parseWWWAuthenticateHeader } = await import('http-auth-utils');
const credential = options.credential as AuthFetchCredentialState & {
count?: number;
digest?: ReturnType<typeof parseWWWAuthenticateHeader<typeof DIGEST>>;
basic?: ReturnType<typeof parseWWWAuthenticateHeader<typeof BASIC>>;
};
const { digest, basic } = credential;
if (digest) {
credential.count ||= 0;
++credential.count;
const nc = ('00000000' + credential.count).slice(-8);
const cnonce = [...Array(24)].map(() => Math.floor(Math.random() * 16).toString(16)).join('');
const uri = new URL(url).pathname;
const { DIGEST, buildAuthorizationHeader } = await import('http-auth-utils');
const response = DIGEST.computeHash({
username: options.credential.username,
password: options.credential.password,
method,
uri,
nc,
cnonce,
algorithm: 'MD5',
qop: digest.data.qop!,
...digest.data,
});
const header = buildAuthorizationHeader(DIGEST, {
username: options.credential.username,
uri,
nc,
cnonce,
algorithm: digest.data.algorithm!,
qop: digest.data.qop!,
response,
...digest.data,
});
return header;
}
else if (basic) {
const { BASIC, buildAuthorizationHeader } = await import('http-auth-utils');
const header = buildAuthorizationHeader(BASIC, {
username: options.credential.username,
password: options.credential.password,
});
return header;
}
}
export function createAuthFetch<B, M>(
h: fetcher<B, M>,
parser: (body: M, responseType: HttpFetchResponseType) => Promise<any>
) {
const authHttpFetch = async <T extends HttpFetchOptions<B>>(options: T & AuthFetchOptions): ReturnType<typeof h<T>> => {
const method = getFetchMethod(options);
const headers = new Headers(options.headers);
options.headers = headers;
setDefaultHttpFetchAccept(headers, options.responseType);
const initialHeader = await getAuth(options, options.url, method);
// try to provide an authorization if a session exists, but don't override Authorization if provided already.
// 401 will trigger a proper auth.
if (initialHeader && !headers.has('Authorization'))
headers.set('Authorization', initialHeader);
const initialResponse = await h({
...options,
ignoreStatusCode: true,
responseType: 'readable',
});
if (initialResponse.statusCode !== 401 || !options.credential) {
if (!options?.ignoreStatusCode)
checkStatus(initialResponse.statusCode);
return {
...initialResponse,
body: await parser(initialResponse.body, options.responseType),
};
}
let authenticateHeaders: string | string[] = initialResponse.headers.get('www-authenticate');
if (!authenticateHeaders)
throw new Error('Did not find WWW-Authenticate header.');
if (typeof authenticateHeaders === 'string')
authenticateHeaders = [authenticateHeaders];
const { BASIC, DIGEST, parseWWWAuthenticateHeader } = await import('http-auth-utils');
const parsedHeaders = authenticateHeaders.map(h => parseWWWAuthenticateHeader(h));
const digest = parsedHeaders.find(p => p.type === 'Digest') as ReturnType<typeof parseWWWAuthenticateHeader<typeof DIGEST>>;
const basic = parsedHeaders.find(p => p.type === 'Basic') as ReturnType<typeof parseWWWAuthenticateHeader<typeof BASIC>>;
const credential = options.credential as AuthFetchCredentialState & {
count?: number;
digest?: ReturnType<typeof parseWWWAuthenticateHeader<typeof DIGEST>>;
basic?: ReturnType<typeof parseWWWAuthenticateHeader<typeof BASIC>>;
};
credential.digest = digest;
credential.basic = basic;
if (!digest && !basic)
throw new Error(`Unknown WWW-Authenticate type: ${parsedHeaders[0]?.type}`);
const header = await getAuth(options, options.url, method);
if (header)
headers.set('Authorization', header);
return h(options);
}
return authHttpFetch;
}

View File

@@ -0,0 +1,19 @@
import { createAuthFetch } from "./auth-fetch";
import { httpFetch, httpFetchParseIncomingMessage } from '../../../server/src/fetch/http-fetch';
import type { Readable } from "stream";
import type { IncomingMessage } from "http";
import { domFetch, domFetchParseIncomingMessage } from "../../../server/src/fetch";
function init() {
try {
require('net');
require('events');
return createAuthFetch<Readable, IncomingMessage>(httpFetch, httpFetchParseIncomingMessage);
}
catch (e) {
}
return createAuthFetch<BodyInit, Response>(domFetch, domFetchParseIncomingMessage);
}
export const authFetch = init();

View File

@@ -0,0 +1,16 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "esnext",
"noImplicitAny": true,
"outDir": "./dist",
"esModuleInterop": true,
"sourceMap": true,
"inlineSources": true,
"declaration": true,
"resolveJsonModule": true,
},
"include": [
"src/**/*"
],
}

View File

@@ -21,7 +21,7 @@
],
"preLaunchTask": "npm: build",
"args": [
"shell",
"login",
],
"sourceMaps": true,
"resolveSourceMapLocations": [

View File

@@ -1,17 +1,16 @@
{
"name": "scrypted",
"version": "1.3.0",
"version": "1.3.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "scrypted",
"version": "1.3.0",
"version": "1.3.10",
"license": "ISC",
"dependencies": {
"@scrypted/client": "^1.3.2",
"@scrypted/client": "^1.3.3",
"@scrypted/types": "^0.2.99",
"axios": "^0.25.0",
"engine.io-client": "^6.5.3",
"readline-sync": "^1.4.10",
"semver": "^7.5.4",
@@ -92,16 +91,21 @@
}
},
"node_modules/@scrypted/client": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/@scrypted/client/-/client-1.3.2.tgz",
"integrity": "sha512-PZwjfKUYIMxBYm7V2o0/vAMlQmznKLN/d4rpshb5vV086mnhh578ik3h39awkwoPyWzNGDcYeoBY0BchhwtdOQ==",
"version": "1.3.3",
"resolved": "https://registry.npmjs.org/@scrypted/client/-/client-1.3.3.tgz",
"integrity": "sha512-Wuy7x02TCRy1buaDNX8NOIaL1j4ZXu4dqTTJsKHlPe3+umsBvpwbylD+YyyU8ghQJC6a40Bs5UMsvnCvNa/1fg==",
"dependencies": {
"@scrypted/types": "^0.2.99",
"axios": "^0.25.0",
"@scrypted/types": "^0.3.4",
"engine.io-client": "^6.5.3",
"follow-redirects": "^1.15.4",
"rimraf": "^5.0.5"
}
},
"node_modules/@scrypted/client/node_modules/@scrypted/types": {
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.4.tgz",
"integrity": "sha512-k/YMx8lIWOkePgXfKW9POr12mb+erFU2JKxO7TW92GyW8ojUWw9VOc0PK6O9bybi0vhsEnvMFkO6pO6bAonsVA=="
},
"node_modules/@scrypted/types": {
"version": "0.2.99",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.2.99.tgz",
@@ -206,14 +210,6 @@
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"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/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
@@ -318,9 +314,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"funding": [
{
"type": "individual",

View File

@@ -1,6 +1,6 @@
{
"name": "scrypted",
"version": "1.3.3",
"version": "1.3.10",
"description": "",
"main": "./dist/packages/cli/src/main.js",
"bin": {
@@ -16,19 +16,18 @@
"author": "",
"license": "ISC",
"dependencies": {
"@scrypted/client": "^1.3.2",
"@scrypted/client": "^1.3.3",
"@scrypted/types": "^0.2.99",
"axios": "^0.25.0",
"engine.io-client": "^6.5.3",
"readline-sync": "^1.4.10",
"semver": "^7.5.4",
"tslib": "^2.6.2"
},
"devDependencies": {
"rimraf": "^5.0.5",
"@types/node": "^20.9.4",
"@types/readline-sync": "^1.4.8",
"@types/semver": "^7.5.6",
"rimraf": "^5.0.5",
"ts-node": "^10.9.1",
"typescript": "^5.3.2"
}

View File

@@ -2,20 +2,15 @@
import { connectScryptedClient } from '@scrypted/client';
import { FFmpegInput, ScryptedMimeTypes } from '@scrypted/types';
import axios, { AxiosRequestConfig } from 'axios';
import child_process from 'child_process';
import fs from 'fs';
import https from 'https';
import path from 'path';
import readline from 'readline-sync';
import semver from 'semver';
import { httpFetch } from '../../../server/src/fetch/http-fetch';
import { installServe, serveMain } from './service';
import { connectShell } from './shell';
const httpsAgent = new https.Agent({
rejectUnauthorized: false,
});
if (!semver.gte(process.version, '16.0.0')) {
throw new Error('"node" version out of date. Please update node to v16 or higher.')
}
@@ -45,6 +40,12 @@ interface LoginFile {
[host: string]: Login;
}
function basicAuthHeaders(username: string, password: string) {
const headers = new Headers();
headers.set('Authorization', `Basic ${Buffer.from(username + ":" + password).toString('base64')}`);
return headers;
}
async function doLogin(host: string) {
host = toIpAndPort(host);
@@ -54,15 +55,15 @@ async function doLogin(host: string) {
});
const url = `https://${host}/login`;
const response = await axios(Object.assign({
const response = await httpFetch({
method: 'GET',
auth: {
username,
password,
},
headers: basicAuthHeaders(username, password),
url,
httpsAgent,
}, axiosConfig));
rejectUnauthorized: false,
responseType: 'json',
});
if (response.body.error)
throw new Error(response.body.error);
fs.mkdirSync(scryptedHome, {
recursive: true,
@@ -78,15 +79,12 @@ async function doLogin(host: string) {
login = {};
login = login || {};
login[host] = response.data;
login[host] = response.body;
fs.writeFileSync(loginPath, JSON.stringify(login));
return login;
return login[host];
}
async function getOrDoLogin(host: string): Promise<{
username: string,
token: string,
}> {
async function getOrDoLogin(host: string): Promise<Login> {
let login: LoginFile;
try {
login = JSON.parse(fs.readFileSync(loginPath).toString());
@@ -95,17 +93,12 @@ async function getOrDoLogin(host: string): Promise<{
if (!login[host].username || !login[host].token)
throw new Error();
return login[host];
}
catch (e) {
login = await doLogin(host);
return doLogin(host);
}
return login[host];
}
const axiosConfig: AxiosRequestConfig = {
httpsAgent: new https.Agent({
rejectUnauthorized: false
})
}
async function runCommand() {
@@ -119,9 +112,6 @@ async function runCommand() {
pluginId: '@scrypted/core',
username: login.username,
password: login.token,
axiosConfig: {
httpsAgent,
}
});
const device: any = sdk.systemManager.getDeviceById(idOrName) || sdk.systemManager.getDeviceByName(idOrName);
@@ -158,8 +148,8 @@ async function main() {
}
else if (process.argv[2] === 'login') {
const ip = process.argv[3] || '127.0.0.1';
const token = await doLogin(ip);
console.log('login successful. token:', token);
const login = await doLogin(ip);
console.log('login successful. token:', login.token);
}
else if (process.argv[2] === 'command') {
const { sdk, pendingResult } = await runCommand();
@@ -211,16 +201,17 @@ async function main() {
const login = await getOrDoLogin(ip);
const url = `https://${ip}/web/component/script/install/${pkg}`;
const response = await axios(Object.assign({
const response = await httpFetch({
method: 'POST',
auth: {
username: login.username,
password: login.token,
},
headers: basicAuthHeaders(login.username, login.token),
url,
}, axiosConfig));
rejectUnauthorized: false,
responseType: 'json',
});
if (response.body.error)
throw new Error(response.body.error);
console.log('install successful. id:', response.data.id);
console.log('install successful. id:', response.body.id);
}
else if (process.argv[2] === 'shell') {
console.log = () => { };
@@ -232,9 +223,6 @@ async function main() {
pluginId: '@scrypted/core',
username: login.username,
password: login.token,
axiosConfig: {
httpsAgent,
}
});
const separator = process.argv.indexOf("--");
@@ -258,7 +246,6 @@ async function main() {
console.log(' npx scrypted install @scrypted/rtsp');
console.log(' npx scrypted install @scrypted/rtsp/0.0.51');
console.log(' npx scrypted install @scrypted/rtsp/0.0.51 192.168.2.100');
process.exit(1);
}
}

View File

@@ -18,7 +18,12 @@ async function runCommand(command: string, ...args: string[]) {
command += '.cmd';
console.log('running', command, ...args);
const cp = child_process.spawn(command, args, {
stdio: 'inherit'
stdio: 'inherit',
env: {
...process.env,
// https://github.com/lovell/sharp/blob/eefaa998725cf345227d94b40615e090495c6d09/lib/libvips.js#L115C19-L115C46
SHARP_IGNORE_GLOBAL_LIBVIPS: 'true',
},
});
await once(cp, 'exit');
if (cp.exitCode)

View File

@@ -1,6 +1,6 @@
{
"compilerOptions": {
"module": "commonjs",
"module": "Node16",
"target": "esnext",
"noImplicitAny": true,
"outDir": "./dist",
@@ -8,7 +8,7 @@
"sourceMap": true,
"inlineSources": true,
"declaration": true,
"resolveJsonModule": true,
"moduleResolution": "Node16",
},
"include": [
"src/**/*"

View File

@@ -9,3 +9,4 @@ scrypted.db.bak
.exit
.update
.venv
.vscode/launch.json

View File

@@ -1,27 +0,0 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "ts-node",
"type": "node",
"request": "launch",
"args": [
"${relativeFile}"
],
"runtimeArgs": [
"-r",
"ts-node/register"
],
"env": {
"SCRYPTED_USERNAME": "koush",
"SCRYPTED_PASSWORD": "k9copUSA",
},
"cwd": "${workspaceRoot}",
"protocol": "inspector",
"internalConsoleOptions": "openOnSessionStart"
}
]
}

View File

@@ -1,21 +1,12 @@
import { Camera, VideoCamera, VideoFrameGenerator } from '@scrypted/types';
import { connectScryptedClient } from '../dist/packages/client/src';
import https from 'https';
const httpsAgent = new https.Agent({
rejectUnauthorized: false,
})
async function example() {
const sdk = await connectScryptedClient({
baseUrl: 'https://localhost:10443',
pluginId: "@scrypted/core",
username: process.env.SCRYPTED_USERNAME || 'admin',
password: process.env.SCRYPTED_PASSWORD || 'swordfish',
axiosConfig: {
httpsAgent,
}
});
console.log('server version', sdk.serverVersion);

View File

@@ -1,25 +1,16 @@
import { ObjectDetector, ObjectsDetected, ScryptedInterface } from '@scrypted/types';
import { connectScryptedClient } from '../dist/packages/client/src';
import https from 'https';
const httpsAgent = new https.Agent({
rejectUnauthorized: false,
})
async function example() {
const sdk = await connectScryptedClient({
baseUrl: 'https://localhost:10443',
pluginId: "@scrypted/core",
username: process.env.SCRYPTED_USERNAME || 'admin',
password: process.env.SCRYPTED_PASSWORD || 'swordfish',
axiosConfig: {
httpsAgent,
}
});
console.log('server version', sdk.serverVersion);
const backyard = sdk.systemManager.getDeviceByName<ObjectDetector>("Hikvision Test");
const backyard = sdk.systemManager.getDeviceByName<ObjectDetector>("IP CAMERA");
if (!backyard)
throw new Error('Device not found');

View File

@@ -1,21 +1,12 @@
import { connectScryptedClient } from '../dist/packages/client/src';
import { OnOff } from '@scrypted/types';
import https from 'https';
const httpsAgent = new https.Agent({
rejectUnauthorized: false,
})
async function example() {
const sdk = await connectScryptedClient({
baseUrl: 'https://localhost:10443',
pluginId: "@scrypted/core",
username: process.env.SCRYPTED_USERNAME || 'admin',
password: process.env.SCRYPTED_PASSWORD || 'swordfish',
axiosConfig: {
httpsAgent,
}
});
console.log('server version', sdk.serverVersion);

View File

@@ -1,23 +1,36 @@
{
"name": "@scrypted/client",
"version": "1.3.1",
"version": "1.3.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/client",
"version": "1.3.1",
"version": "1.3.4",
"license": "ISC",
"dependencies": {
"@scrypted/types": "^0.2.99",
"axios": "^0.25.0",
"@scrypted/types": "^0.3.4",
"engine.io-client": "^6.5.3",
"follow-redirects": "^1.15.4",
"rimraf": "^5.0.5"
},
"devDependencies": {
"@types/ip": "^1.1.3",
"@types/node": "^20.9.4",
"typescript": "^5.3.2"
"@types/node": "^20.10.8",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
}
},
"node_modules/@cspotcode/source-map-support": {
"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": {
"@jridgewell/trace-mapping": "0.3.9"
},
"engines": {
"node": ">=12"
}
},
"node_modules/@isaacs/cliui": {
@@ -36,6 +49,31 @@
"node": ">=12"
}
},
"node_modules/@jridgewell/resolve-uri": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz",
"integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==",
"dev": true,
"engines": {
"node": ">=6.0.0"
}
},
"node_modules/@jridgewell/sourcemap-codec": {
"version": "1.4.15",
"resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
"integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==",
"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/@pkgjs/parseargs": {
"version": "0.11.0",
"resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz",
@@ -46,15 +84,39 @@
}
},
"node_modules/@scrypted/types": {
"version": "0.2.99",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.2.99.tgz",
"integrity": "sha512-2J1FH7tpAW5X3rgA70gJ+z0HFM90c/tBA+JXdP1vI1d/0yVmh9TSxnHoCuADN4R2NQXHmoZ6Nbds9kKAQ/25XQ=="
"version": "0.3.4",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.3.4.tgz",
"integrity": "sha512-k/YMx8lIWOkePgXfKW9POr12mb+erFU2JKxO7TW92GyW8ojUWw9VOc0PK6O9bybi0vhsEnvMFkO6pO6bAonsVA=="
},
"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.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.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.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.4",
"resolved": "https://registry.npmjs.org/@tsconfig/node16/-/node16-1.0.4.tgz",
"integrity": "sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==",
"dev": true
},
"node_modules/@types/ip": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/@types/ip/-/ip-1.1.3.tgz",
@@ -65,14 +127,35 @@
}
},
"node_modules/@types/node": {
"version": "20.9.4",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.4.tgz",
"integrity": "sha512-wmyg8HUhcn6ACjsn8oKYjkN/zUzQeNtMy44weTJSM6p4MMzEOuKbA3OjJ267uPCOW7Xex9dyrNTful8XTQYoDA==",
"version": "20.10.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz",
"integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==",
"dev": true,
"dependencies": {
"undici-types": "~5.26.4"
}
},
"node_modules/acorn": {
"version": "8.11.3",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
"integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
"dev": true,
"bin": {
"acorn": "bin/acorn"
},
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/acorn-walk": {
"version": "8.3.1",
"resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.1.tgz",
"integrity": "sha512-TgUZgYvqZprrl7YldZNoa9OciCAyZR+Ejm9eXzKCmjsF5IKp/wgQ7Z/ZpjpGTIUPwrHQIcYeI8qDh4PsEwxMbw==",
"dev": true,
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/ansi-regex": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz",
@@ -95,13 +178,11 @@
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
}
},
"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/arg": {
"version": "4.1.3",
"resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz",
"integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==",
"dev": true
},
"node_modules/balanced-match": {
"version": "1.0.2",
@@ -132,6 +213,12 @@
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
"integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
},
"node_modules/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
},
"node_modules/cross-spawn": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
@@ -161,6 +248,15 @@
}
}
},
"node_modules/diff": {
"version": "4.0.2",
"resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz",
"integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==",
"dev": true,
"engines": {
"node": ">=0.3.1"
}
},
"node_modules/eastasianwidth": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz",
@@ -192,9 +288,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"funding": [
{
"type": "individual",
@@ -284,6 +380,12 @@
"node": "14 || >=16.14"
}
},
"node_modules/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
},
"node_modules/minimatch": {
"version": "9.0.3",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz",
@@ -469,10 +571,53 @@
"node": ">=8"
}
},
"node_modules/ts-node": {
"version": "10.9.2",
"resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz",
"integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==",
"dev": true,
"dependencies": {
"@cspotcode/source-map-support": "^0.8.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",
"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"
},
"peerDependencies": {
"@swc/core": ">=1.2.50",
"@swc/wasm": ">=1.2.50",
"@types/node": "*",
"typescript": ">=2.7"
},
"peerDependenciesMeta": {
"@swc/core": {
"optional": true
},
"@swc/wasm": {
"optional": true
}
}
},
"node_modules/typescript": {
"version": "5.3.2",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.2.tgz",
"integrity": "sha512-6l+RyNy7oAHDfxC4FzSJcz9vnjTKxrLpDG5M2Vu4SHRVNg6xzqZp6LYSR9zjqQTu8DU/f5xwxUdADOkbrIX2gQ==",
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
"dev": true,
"bin": {
"tsc": "bin/tsc",
@@ -488,6 +633,12 @@
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"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/which": {
"version": "2.0.2",
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
@@ -613,6 +764,15 @@
"engines": {
"node": ">=0.4.0"
}
},
"node_modules/yn": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz",
"integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==",
"dev": true,
"engines": {
"node": ">=6"
}
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/client",
"version": "1.3.2",
"version": "1.3.4",
"description": "",
"main": "dist/packages/client/src/index.js",
"scripts": {
@@ -13,13 +13,14 @@
"license": "ISC",
"devDependencies": {
"@types/ip": "^1.1.3",
"@types/node": "^20.9.4",
"typescript": "^5.3.2"
"@types/node": "^20.10.8",
"ts-node": "^10.9.2",
"typescript": "^5.3.3"
},
"dependencies": {
"@scrypted/types": "^0.2.99",
"axios": "^0.25.0",
"@scrypted/types": "^0.3.4",
"engine.io-client": "^6.5.3",
"follow-redirects": "^1.15.4",
"rimraf": "^5.0.5"
}
}

View File

@@ -1,20 +1,34 @@
import { MediaObjectOptions, RTCConnectionManagement, RTCSignalingSession, ScryptedStatic } from "@scrypted/types";
import axios, { AxiosRequestConfig, AxiosRequestHeaders } from 'axios';
import * as eio from 'engine.io-client';
import { SocketOptions } from 'engine.io-client';
import { Deferred } from "../../../common/src/deferred";
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 { ClusterObject, ConnectRPCObject } from '../../../server/src/cluster/connect-rpc-object';
import type { IOSocket } from '../../../server/src/io';
import { MediaObject } from '../../../server/src/plugin/mediaobject';
import { attachPluginRemote } from '../../../server/src/plugin/plugin-remote';
import type { ClusterObject, ConnectRPCObject } from '../../../server/src/cluster/connect-rpc-object';
import { RpcPeer } from '../../../server/src/rpc';
import { createRpcDuplexSerializer, createRpcSerializer } from '../../../server/src/rpc-serializer';
import packageJson from '../package.json';
import { isIPAddress } from "./ip";
import { domFetch } from "../../../server/src/fetch";
import { httpFetch } from '../../../server/src/fetch/http-fetch';
let fetcher: typeof httpFetch | typeof domFetch;
try {
if (process.arch === 'browser' as any)
throw new Error();
require('net');
require('events');
fetcher = httpFetch;
}
catch (e) {
fetcher = domFetch;
}
const sourcePeerId = RpcPeer.generateId();
type IOClientSocket = eio.Socket & IOSocket;
@@ -59,7 +73,6 @@ export interface ScryptedConnectionOptions {
local?: boolean;
webrtc?: boolean;
baseUrl?: string;
axiosConfig?: AxiosRequestConfig;
previousLoginResult?: ScryptedClientLoginResult;
}
@@ -90,10 +103,13 @@ function isRunningStandalone() {
export async function logoutScryptedClient(baseUrl?: string) {
const url = combineBaseUrl(baseUrl, 'logout');
const response = await axios(url, {
const response = await fetcher({
url,
withCredentials: true,
responseType: 'json',
rejectUnauthorized: false,
});
return response.data;
return response.body;
}
export function getCurrentBaseUrl() {
@@ -122,38 +138,43 @@ export async function loginScryptedClient(options: ScryptedLoginOptions) {
maxAge = 365 * 24 * 60 * 60 * 1000;
const url = combineBaseUrl(baseUrl, 'login');
const response = await axios.post(url, {
username,
password,
change_password,
maxAge,
}, {
const response = await fetcher({
url,
body: {
username,
password,
change_password,
maxAge,
},
rejectUnauthorized: false,
withCredentials: true,
...options.axiosConfig,
responseType: 'json',
});
if (response.status !== 200)
throw new Error('status ' + response.status);
if (response.statusCode !== 200)
throw new Error('status ' + response.statusCode);
const { body } = response;
return {
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[],
externalAddresses: response.data.externalAddresses as string[],
error: body.error as string,
authorization: body.authorization as string,
queryToken: body.queryToken as any,
token: body.token as string,
addresses: body.addresses as string[],
externalAddresses: body.externalAddresses as string[],
// the cloud plugin will include this header.
// should maybe move this into the cloud server itself.
scryptedCloud: response.headers['x-scrypted-cloud'] === 'true',
directAddress: response.headers['x-scrypted-direct-address'],
cloudAddress: response.headers['x-scrypted-cloud-address'],
scryptedCloud: response.headers.get('x-scrypted-cloud') === 'true',
directAddress: response.headers.get('x-scrypted-direct-address'),
cloudAddress: response.headers.get('x-scrypted-cloud-address'),
};
}
export async function checkScryptedClientLogin(options?: ScryptedConnectionOptions) {
let { baseUrl } = options || {};
let url = combineBaseUrl(baseUrl, 'login');
const headers: AxiosRequestHeaders = {};
const headers = new Headers();
if (options?.previousLoginResult?.queryToken) {
// headers.Authorization = options?.previousLoginResult?.authorization;
// const search = new URLSearchParams(options.previousLoginResult.queryToken);
@@ -161,32 +182,36 @@ export async function checkScryptedClientLogin(options?: ScryptedConnectionOptio
const token = options?.previousLoginResult.username + ":" + options.previousLoginResult.token;
const hash = Buffer.from(token).toString('base64');
headers.Authorization = `Basic ${hash}`;
headers.set('Authorization', `Basic ${hash}`);
}
const response = await axios.get(url, {
const response = await fetcher({
url,
withCredentials: true,
headers,
...options?.axiosConfig,
rejectUnauthorized: false,
responseType: 'json',
});
const { body } = response;
return {
baseUrl,
hostname: response.data.hostname as string,
redirect: response.data.redirect as string,
username: response.data.username as string,
expiration: response.data.expiration as number,
hasLogin: !!response.data.hasLogin,
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[],
externalAddresses: response.data.externalAddresses as string[],
hostname: body.hostname as string,
redirect: body.redirect as string,
username: body.username as string,
expiration: body.expiration as number,
hasLogin: !!body.hasLogin,
error: body.error as string,
authorization: body.authorization as string,
queryToken: body.queryToken as any,
token: body.token as string,
addresses: body.addresses as string[],
externalAddresses: body.externalAddresses as string[],
// the cloud plugin will include this header.
// should maybe move this into the cloud server itself.
scryptedCloud: response.headers['x-scrypted-cloud'] === 'true',
directAddress: response.headers['x-scrypted-direct-address'],
cloudAddress: response.headers['x-scrypted-cloud-address'],
scryptedCloud: response.headers.get('x-scrypted-cloud') === 'true',
directAddress: response.headers.get('x-scrypted-direct-address'),
cloudAddress: response.headers.get('x-scrypted-cloud-address'),
};
}
@@ -786,7 +811,7 @@ export async function connectScryptedClient(options: ScryptedClientOptions): Pro
return value;
}
const { port, proxyId } = clusterObject;
const { port, proxyId } = clusterObject;
// check if object is already connected
const resolved = await resolveObject(proxyId, port);

View File

@@ -1,24 +1,40 @@
{
"name": "@scrypted/alexa",
"version": "0.2.7",
"version": "0.2.10",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/alexa",
"version": "0.2.7",
"version": "0.2.10",
"dependencies": {
"axios": "^1.3.4",
"uuid": "^9.0.0"
},
"devDependencies": {
"@scrypted/common": "../../common",
"@scrypted/sdk": "../../sdk",
"@types/node": "^18.4.2"
}
},
"../../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"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.2.104",
"version": "0.2.108",
"dev": true,
"license": "ISC",
"dependencies": {
@@ -54,6 +70,13 @@
"typedoc": "^0.23.21"
}
},
"../common": {
"extraneous": true
},
"node_modules/@scrypted/common": {
"resolved": "../../common",
"link": true
},
"node_modules/@scrypted/sdk": {
"resolved": "../../sdk",
"link": true
@@ -70,9 +93,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/axios": {
"version": "1.3.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.4.tgz",
"integrity": "sha512-toYm+Bsyl6VC5wSkfkbbNB6ROv7KY93PEBBL6xyDczaIHasAiv4wPqQ/c4RjoQzipxRD2W5g21cOqQulZ7rHwQ==",
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/alexa",
"version": "0.2.8",
"version": "0.2.10",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",
@@ -39,6 +39,7 @@
},
"devDependencies": {
"@types/node": "^18.4.2",
"@scrypted/sdk": "../../sdk"
"@scrypted/sdk": "../../sdk",
"@scrypted/common": "../../common"
}
}

View File

@@ -79,7 +79,7 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
if (status === DeviceMixinStatus.Setup)
await this.syncEndpoints();
if (status === DeviceMixinStatus.Setup || status === DeviceMixinStatus.AlreadySetup) {
if (status === DeviceMixinStatus.Setup || status === DeviceMixinStatus.AlreadySetup) {
if (!this.devices.has(eventSource.id)) {
this.devices.set(eventSource.id, eventSource);
@@ -142,7 +142,7 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
await this.syncEndpoints();
}
async deviceListen(eventSource: ScryptedDevice | undefined, eventDetails: EventDetails, eventData: any) : Promise<void> {
async deviceListen(eventSource: ScryptedDevice | undefined, eventDetails: EventDetails, eventData: any): Promise<void> {
if (!eventSource)
return;
@@ -194,14 +194,14 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
// nothing to report
if (data.context === undefined && data.event.payload === undefined)
return;
return;
data = await this.addAccessToken(data);
await this.postEvent(data);
}
private async addAccessToken(data: any) : Promise<any> {
private async addAccessToken(data: any): Promise<any> {
const accessToken = await this.getAccessToken();
if (data.event === undefined)
@@ -232,7 +232,7 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
'api.fe.amazonalexa.com'
];
async getAlexaEndpoint() : Promise<string> {
async getAlexaEndpoint(): Promise<string> {
if (this.storageSettings.values.apiEndpoint)
return this.storageSettings.values.apiEndpoint;
@@ -276,7 +276,7 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
});
}
async getEndpoints() : Promise<DiscoveryEndpoint[]> {
async getEndpoints(): Promise<DiscoveryEndpoint[]> {
const endpoints: DiscoveryEndpoint[] = [];
for (const id of Object.keys(systemManager.getSystemState())) {
@@ -284,7 +284,7 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
if (!device.mixins?.includes(this.id))
continue;
const endpoint = await this.getEndpointForDevice(device);
if (endpoint)
endpoints.push(endpoint);
@@ -319,7 +319,7 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
const endpoints = await this.getEndpoints();
if (!endpoints.length)
return [];
return [];
const accessToken = await this.getAccessToken();
const data = {
@@ -448,7 +448,7 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
self.console.warn(error?.response?.data);
self.log.a(error?.response?.data?.error_description);
break;
case 'expired_token':
self.console.warn(error?.response?.data);
self.log.a(error?.response?.data?.error_description);
@@ -480,9 +480,14 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
this.storageSettings.values.tokenInfo = grant;
this.storageSettings.values.apiEndpoint = undefined;
this.accessToken = undefined;
const self = this;
let accessToken = await this.getAccessToken().catch(reason => {
let accessToken: any;
try {
accessToken = await this.getAccessToken();
}
catch (reason) {
self.console.error(`Failed to handle the AcceptGrant directive because ${reason}`);
this.storageSettings.values.tokenInfo = undefined;
@@ -491,36 +496,23 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
response.send(authErrorResponse("ACCEPT_GRANT_FAILED", `Failed to handle the AcceptGrant directive because ${reason}`, directive));
return undefined;
});
if (accessToken !== undefined) {
this.log.clearAlerts();
try {
response.send({
"event": {
"header": {
"namespace": "Alexa.Authorization",
"name": "AcceptGrant.Response",
"messageId": createMessageId(),
"payloadVersion": "3"
},
"payload": {}
}
});
} catch (error) {
this.console.error(`AcceptGrant.Response failed because ${error}`);
this.storageSettings.values.tokenInfo = undefined;
this.storageSettings.values.apiEndpoint = undefined;
this.accessToken = undefined;
throw error;
return;
};
this.log.clearAlerts();
response.send({
"event": {
"header": {
"namespace": "Alexa.Authorization",
"name": "AcceptGrant.Response",
"messageId": createMessageId(),
"payloadVersion": "3"
},
"payload": {}
}
}
});
}
async getEndpointForDevice(device: ScryptedDevice) : Promise<DiscoveryEndpoint> {
async getEndpointForDevice(device: ScryptedDevice): Promise<DiscoveryEndpoint> {
if (!device)
return;
@@ -545,7 +537,7 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
};
let supportedEndpointHealths: any[] = [];
if (device.interfaces.includes(ScryptedInterface.Online)) {
supportedEndpointHealths.push({
"name": "connectivity"
@@ -632,8 +624,10 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
debug("received directive from alexa", mapName, body);
const handler = alexaHandlers.get(mapName);
if (handler)
return handler.apply(this, [request, response, directive]);
if (handler) {
await handler.apply(this, [request, response, directive]);
return;
}
const deviceHandler = alexaDeviceHandlers.get(mapName);
@@ -644,7 +638,8 @@ class AlexaPlugin extends ScryptedDeviceBase implements HttpRequestHandler, Mixi
return;
}
return deviceHandler.apply(this, [request, response, directive, device]);
await deviceHandler.apply(this, [request, response, directive, device]);
return;
} else {
this.console.error(`no handler for: ${mapName}`);
}

View File

@@ -4,6 +4,7 @@ import { v4 as createMessageId } from 'uuid';
import { AlexaHttpResponse, sendDeviceResponse } from "../../common";
import { alexaDeviceHandlers } from "../../handlers";
import { Response, WebRTCAnswerGeneratedForSessionEvent, WebRTCSessionConnectedEvent, WebRTCSessionDisconnectedEvent } from '../../alexa'
import { Deferred } from '@scrypted/common/src/deferred';
export class AlexaSignalingSession implements RTCSignalingSession {
constructor(public response: AlexaHttpResponse, public directive: any) {
@@ -13,7 +14,8 @@ export class AlexaSignalingSession implements RTCSignalingSession {
__proxy_props: { options: RTCSignalingOptions; };
options: RTCSignalingOptions;
remoteDescription = new Deferred<void>();
async getOptions(): Promise<RTCSignalingOptions> {
return this.options;
}
@@ -39,11 +41,17 @@ export class AlexaSignalingSession implements RTCSignalingSession {
}
async createLocalDescription(type: "offer" | "answer", setup: RTCAVSignalingSetup, sendIceCandidate: RTCSignalingSendIceCandidate): Promise<RTCSessionDescriptionInit> {
if (type !== 'offer')
throw new Error('Alexa only supports RTC offer');
if (type !== 'offer') {
const e = new Error('Alexa only supports RTC offer');
this.remoteDescription.reject(e);
throw e;
}
if (sendIceCandidate)
throw new Error("Alexa does not support trickle ICE");
if (sendIceCandidate) {
const e = new Error("Alexa does not support trickle ICE");
this.remoteDescription.reject(e);
throw e;
}
return {
type: type,
@@ -67,15 +75,16 @@ export class AlexaSignalingSession implements RTCSignalingSession {
},
context: undefined
};
data.event.header.name = "AnswerGeneratedForSession";
data.event.header.messageId = createMessageId();
data.event.payload.answer = {
format: 'SDP',
value: description.sdp,
};
this.remoteDescription.resolve();
this.response.send(data);
}
}
@@ -85,13 +94,14 @@ 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,
})
});
await session.remoteDescription.promise;
sessionCache.set(sessionId, control);
});
@@ -115,13 +125,13 @@ alexaDeviceHandlers.set('Alexa.RTCSessionController/SessionConnected', async (re
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,
@@ -130,9 +140,9 @@ alexaDeviceHandlers.set('Alexa.RTCSessionController/SessionDisconnected', async
},
context: undefined
};
data.event.header.messageId = createMessageId();
response.send(data);
});
@@ -152,14 +162,14 @@ alexaDeviceHandlers.set('Alexa.SmartVision.ObjectDetectionSensor/SetObjectDetect
},
"context": {
"properties": [{
"namespace": "Alexa.SmartVision.ObjectDetectionSensor",
"name": "objectDetectionClasses",
"value": detectionTypes.classes.map(type => ({
"imageNetClass": type
})),
timeOfSample: new Date().toISOString(),
uncertaintyInMilliseconds: 0
}]
"namespace": "Alexa.SmartVision.ObjectDetectionSensor",
"name": "objectDetectionClasses",
"value": detectionTypes.classes.map(type => ({
"imageNetClass": type
})),
timeOfSample: new Date().toISOString(),
uncertaintyInMilliseconds: 0
}]
}
};

View File

@@ -1,25 +1,23 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.130",
"version": "0.0.132",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/amcrest",
"version": "0.0.130",
"version": "0.0.132",
"license": "Apache",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@types/multiparty": "^0.0.33",
"multiparty": "^4.2.3"
"@scrypted/sdk": "file:../../sdk"
},
"devDependencies": {
"@types/node": "^18.16.18"
"@types/node": "^20.10.8"
}
},
"../../common": {
"name": "@scrypted/common",
"version": "1.0.1",
"license": "ISC",
"dependencies": {
@@ -27,14 +25,16 @@
"@scrypted/server": "file:../server",
"http-auth-utils": "^3.0.2",
"node-fetch-commonjs": "^3.1.1",
"typescript": "^4.4.3"
"typescript": "^5.3.3"
},
"devDependencies": {
"@types/node": "^16.9.0"
"@types/node": "^20.10.8",
"ts-node": "^10.9.2"
}
},
"../../sdk": {
"version": "0.2.103",
"name": "@scrypted/sdk",
"version": "0.3.4",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
@@ -69,15 +69,6 @@
"typedoc": "^0.23.21"
}
},
"node_modules/@koush/axios-digest-auth": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.5.tgz",
"integrity": "sha512-EZMM0gMJ3hMUD4EuUqSwP6UGt5Vmw2TZtY7Ypec55AnxkExSXM0ySgPtqkAcnL43g1R27yAg/dQL7dRTLMqO3Q==",
"dependencies": {
"auth-header": "^1.0.0",
"axios": "^0.21.4"
}
},
"node_modules/@scrypted/common": {
"resolved": "../../common",
"link": true
@@ -86,150 +77,20 @@
"resolved": "../../sdk",
"link": true
},
"node_modules/@types/multiparty": {
"version": "0.0.33",
"resolved": "https://registry.npmjs.org/@types/multiparty/-/multiparty-0.0.33.tgz",
"integrity": "sha512-Il6cJUpSqgojT7NxbVJUvXkCblm50/yEJYtblISDsNIeNYf4yMAhdizzidUk6h8pJ8yhwK/3Fkb+3Dwcgtwl8w==",
"dependencies": {
"@types/node": "*"
}
},
"node_modules/@types/node": {
"version": "18.16.18",
"resolved": "https://registry.npmjs.org/@types/node/-/node-18.16.18.tgz",
"integrity": "sha512-/aNaQZD0+iSBAGnvvN2Cx92HqE5sZCPZtx2TsK+4nvV23fFe09jVDvpArXr2j9DnYlzuU9WuoykDDc6wqvpNcw=="
},
"node_modules/auth-header": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
},
"node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"version": "20.10.8",
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.8.tgz",
"integrity": "sha512-f8nQs3cLxbAFc00vEU59yf9UyGUftkPaLGfvbVOIDdx2i1b8epBqj2aNGyP19fiyXWvlmZ7qC1XLjAzw/OKIeA==",
"dev": true,
"dependencies": {
"follow-redirects": "^1.14.0"
"undici-types": "~5.26.4"
}
},
"node_modules/depd": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz",
"integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"funding": [
{
"type": "individual",
"url": "https://github.com/sponsors/RubenVerborgh"
}
],
"engines": {
"node": ">=4.0"
},
"peerDependenciesMeta": {
"debug": {
"optional": true
}
}
},
"node_modules/http-errors": {
"version": "1.8.1",
"resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz",
"integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==",
"dependencies": {
"depd": "~1.1.2",
"inherits": "2.0.4",
"setprototypeof": "1.2.0",
"statuses": ">= 1.5.0 < 2",
"toidentifier": "1.0.1"
},
"engines": {
"node": ">= 0.6"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
},
"node_modules/multiparty": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/multiparty/-/multiparty-4.2.3.tgz",
"integrity": "sha512-Ak6EUJZuhGS8hJ3c2fY6UW5MbkGUPMBEGd13djUzoY/BHqV/gTuFWtC6IuVA7A2+v3yjBS6c4or50xhzTQZImQ==",
"dependencies": {
"http-errors": "~1.8.1",
"safe-buffer": "5.2.1",
"uid-safe": "2.1.5"
},
"engines": {
"node": ">= 0.10"
}
},
"node_modules/random-bytes": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/random-bytes/-/random-bytes-1.0.0.tgz",
"integrity": "sha512-iv7LhNVO047HzYR3InF6pUcUsPQiHTM1Qal51DcGSuZFBil1aBBWG5eHPNek7bvILMaYJ/8RU1e8w1AMdHmLQQ==",
"engines": {
"node": ">= 0.8"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/setprototypeof": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz",
"integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw=="
},
"node_modules/statuses": {
"version": "1.5.0",
"resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz",
"integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==",
"engines": {
"node": ">= 0.6"
}
},
"node_modules/toidentifier": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz",
"integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==",
"engines": {
"node": ">=0.6"
}
},
"node_modules/uid-safe": {
"version": "2.1.5",
"resolved": "https://registry.npmjs.org/uid-safe/-/uid-safe-2.1.5.tgz",
"integrity": "sha512-KPHm4VL5dDXKz01UuEd88Df+KzynaohSL9fBh096KWAxSKZQDI2uBrVqtvRM4rwrIrRRKsdLNML/lnaaVSRioA==",
"dependencies": {
"random-bytes": "~1.0.0"
},
"engines": {
"node": ">= 0.8"
}
"node_modules/undici-types": {
"version": "5.26.5",
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
}
}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/amcrest",
"version": "0.0.130",
"version": "0.0.132",
"description": "Amcrest Plugin for Scrypted",
"author": "Scrypted",
"license": "Apache",
@@ -35,13 +35,10 @@
]
},
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",
"@scrypted/common": "file:../../common",
"@scrypted/sdk": "file:../../sdk",
"@types/multiparty": "^0.0.33",
"multiparty": "^4.2.3"
"@scrypted/sdk": "file:../../sdk"
},
"devDependencies": {
"@types/node": "^18.16.18"
"@types/node": "^20.10.8"
}
}

View File

@@ -1,8 +1,6 @@
import AxiosDigestAuth from '@koush/axios-digest-auth';
import { AuthFetchCredentialState, HttpFetchOptions, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
import { Readable } from 'stream';
import https from 'https';
import { IncomingMessage } from 'http';
import { amcrestHttpsAgent, getDeviceInfo } from './probe';
import { getDeviceInfo } from './probe';
export enum AmcrestEvent {
MotionStart = "Code=VideoMotion;action=Start",
@@ -22,35 +20,42 @@ export enum AmcrestEvent {
DahuaTalkPulse = "Code=_CallNoAnswer_;action=Pulse",
}
export class AmcrestCameraClient {
digestAuth: AxiosDigestAuth;
credential: AuthFetchCredentialState;
constructor(public ip: string, username: string, password: string, public console?: Console) {
this.digestAuth = new AxiosDigestAuth({
this.credential = {
username,
password,
};
}
async request(urlOrOptions: string | URL | HttpFetchOptions<Readable>, body?: Readable) {
const response = await authHttpFetch({
...typeof urlOrOptions !== 'string' && !(urlOrOptions instanceof URL) ? urlOrOptions : {
url: urlOrOptions,
},
rejectUnauthorized: false,
credential: this.credential,
body,
});
return response;
}
async reboot() {
const response = await this.digestAuth.request({
httpsAgent: amcrestHttpsAgent,
method: "GET",
responseType: 'text',
const response = await this.request({
url: `http://${this.ip}/cgi-bin/magicBox.cgi?action=reboot`,
responseType: 'text',
});
return response.data as string;
return response.body;
}
async checkTwoWayAudio() {
const response = await this.digestAuth.request({
httpsAgent: amcrestHttpsAgent,
method: "GET",
responseType: 'text',
const response = await this.request({
url: `http://${this.ip}/cgi-bin/devAudioOutput.cgi?action=getCollect`,
responseType: 'text',
});
return (response.data as string).includes('result=1');
return response.body.includes('result=1');
}
// appAutoStart=true
@@ -61,33 +66,27 @@ export class AmcrestCameraClient {
// updateSerial=IPC-AW46WN-S2
// updateSerialCloudUpgrade=IPC-AW46WN-.....
async getDeviceInfo() {
return getDeviceInfo(this.digestAuth, this.ip);
return getDeviceInfo(this.credential, this.ip);
}
async jpegSnapshot(): Promise<Buffer> {
const response = await this.digestAuth.request({
httpsAgent: amcrestHttpsAgent,
method: "GET",
responseType: 'arraybuffer',
const response = await this.request({
url: `http://${this.ip}/cgi-bin/snapshot.cgi`,
timeout: 60000,
});
return Buffer.from(response.data);
return response.body;
}
async listenEvents() {
const url = `http://${this.ip}/cgi-bin/eventManager.cgi?action=attach&codes=[All]`;
console.log('preparing event listener', url);
const response = await this.digestAuth.request({
httpsAgent: amcrestHttpsAgent,
method: "GET",
responseType: 'stream',
const response = await this.request({
url,
responseType: 'readable',
});
const stream = response.data as IncomingMessage;
const stream = response.body;
stream.socket.setKeepAlive(true);
stream.on('data', (buffer: Buffer) => {
@@ -118,12 +117,12 @@ export class AmcrestCameraClient {
async enableContinousRecording(channel: number) {
for (let i = 0; i < 7; i++) {
const url = `http://${this.ip}/cgi-bin/configManager.cgi?action=setConfig&Record[${channel - 1}].TimeSection[${i}][0]=1 00:00:00-23:59:59`;
const response = await this.digestAuth.request({
httpsAgent: amcrestHttpsAgent,
method: "POST",
const response = await this.request({
url,
});
this.console.log(response.data);
method: 'POST',
responseType: 'text',
},);
this.console.log(response.body);
}
}
}

View File

@@ -6,7 +6,6 @@ import { PassThrough, Readable, Stream } from "stream";
import { OnvifIntercom } from "../../onvif/src/onvif-intercom";
import { RtspProvider, RtspSmartCamera, UrlMediaStreamOptions } from "../../rtsp/src/rtsp";
import { AmcrestCameraClient, AmcrestEvent } from "./amcrest-api";
import { amcrestHttpsAgent } from './probe';
const { mediaManager } = sdk;
@@ -94,12 +93,11 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
for (const element of deviceParameters) {
try {
const response = await this.getClient().digestAuth.request({
httpsAgent: amcrestHttpsAgent,
url: `http://${this.getHttpAddress()}/cgi-bin/magicBox.cgi?action=${element.action}`
const response = await this.getClient().request({
url: `http://${this.getHttpAddress()}/cgi-bin/magicBox.cgi?action=${element.action}`,
responseType: 'text',
});
const result = String(response.data).replace(element.replace, "").trim();
const result = String(response.body).replace(element.replace, "").trim();
deviceInfo[element.parameter] = result;
}
catch (e) {
@@ -147,11 +145,11 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
if (![...params.keys()].length)
return;
const response = await this.getClient().digestAuth.request({
httpsAgent: amcrestHttpsAgent,
url: `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=setConfig&${params}`
const response = await this.getClient().request({
url: `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=setConfig&${params}`,
responseType: 'text',
});
this.console.log('reconfigure result', response.data);
this.console.log('reconfigure result', response.body);
}
getClient() {
@@ -309,9 +307,6 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
}
async takeSmartCameraPicture(option?: PictureOptions): Promise<MediaObject> {
return this.createMediaObject(await this.getClient().jpegSnapshot(), 'image/jpeg');
}
@@ -334,7 +329,6 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
return this.storage.getItem('rtspChannel');
}
createRtspMediaStreamOptions(url: string, index: number) {
const ret = super.createRtspMediaStreamOptions(url, index);
ret.tool = 'scrypted';
@@ -348,12 +342,11 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
this.videoStreamOptions = (async () => {
let mas: string;
try {
const response = await client.digestAuth.request({
const response = await client.request({
url: `http://${this.getHttpAddress()}/cgi-bin/magicBox.cgi?action=getProductDefinition&name=MaxExtraStream`,
responseType: 'text',
httpsAgent: amcrestHttpsAgent,
})
mas = response.data.split('=')[1].trim();
mas = response.body.split('=')[1].trim();
this.storage.setItem('maxExtraStreams', mas.toString());
}
catch (e) {
@@ -366,18 +359,16 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
const vsos = [...Array(maxExtraStreams + 1).keys()].map(subtype => this.createRtspMediaStreamOptions(`rtsp://${this.getRtspAddress()}/cam/realmonitor?channel=${channel}&subtype=${subtype}`, subtype));
try {
const capResponse = await client.digestAuth.request({
const capResponse = await client.request({
url: `http://${this.getHttpAddress()}/cgi-bin/encode.cgi?action=getConfigCaps&channel=0`,
responseType: 'text',
httpsAgent: amcrestHttpsAgent,
});
this.console.log(capResponse.data);
const encodeResponse = await client.digestAuth.request({
this.console.log(capResponse.body);
const encodeResponse = await client.request({
url: `http://${this.getHttpAddress()}/cgi-bin/configManager.cgi?action=getConfig&name=Encode`,
responseType: 'text',
httpsAgent: amcrestHttpsAgent,
});
this.console.log(encodeResponse.data);
this.console.log(encodeResponse.body);
for (let i = 0; i < vsos.length; i++) {
const vso = vsos[i];
@@ -392,9 +383,9 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
encName = `table.Encode[${channel - 1}].ExtraFormat[${i - 1}]`;
}
const videoCodec = findValue(encodeResponse.data, encName, 'Video.Compression')
const videoCodec = findValue(encodeResponse.body, encName, 'Video.Compression')
?.replace('.', '')?.toLowerCase()?.trim();
let audioCodec = findValue(encodeResponse.data, encName, 'Audio.Compression')
let audioCodec = findValue(encodeResponse.body, encName, 'Audio.Compression')
?.replace('.', '')?.toLowerCase()?.trim();
if (audioCodec?.includes('aac'))
audioCodec = 'aac';
@@ -409,18 +400,18 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
vso.audio.codec = audioCodec;
vso.video.codec = videoCodec;
const width = findValue(encodeResponse.data, encName, 'Video.Width');
const height = findValue(encodeResponse.data, encName, 'Video.Height');
const width = findValue(encodeResponse.body, encName, 'Video.Width');
const height = findValue(encodeResponse.body, encName, 'Video.Height');
if (width && height) {
vso.video.width = parseInt(width);
vso.video.height = parseInt(height);
}
const bitrateOptions = findValue(capResponse.data, capName, 'Video.BitRateOptions');
const bitrateOptions = findValue(capResponse.body, capName, 'Video.BitRateOptions');
if (!bitrateOptions)
continue;
const encodeOptions = findValue(encodeResponse.data, encName, 'Video.BitRate');
const encodeOptions = findValue(encodeResponse.body, encName, 'Video.BitRate');
if (!encodeOptions)
continue;
@@ -504,6 +495,8 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
return this.onvifIntercom.startIntercom(media);
}
const doorbellType = this.storage.getItem('doorbellType');
// not sure if this all works, since i don't actually have a doorbell.
// good luck!
const channel = this.getRtspChannel() || '1';
@@ -514,12 +507,29 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
const args = ffmpegInput.inputArguments.slice();
args.unshift('-hide_banner');
args.push(
"-vn",
'-acodec', 'aac',
'-f', 'adts',
'pipe:3',
);
let contentType: string;
if (doorbellType == DAHUA_DOORBELL_TYPE) {
args.push(
"-vn",
'-acodec', 'pcm_alaw',
'-ac', '1',
'-ar', '8000',
'-sample_fmt', 's16',
'-f', 'alaw',
'pipe:3',
);
contentType = 'Audio/G.711A';
}
else {
args.push(
"-vn",
'-acodec', 'aac',
'-f', 'adts',
'pipe:3',
);
contentType = 'Audio/AAC';
}
this.console.log('ffmpeg intercom', args);
@@ -538,16 +548,15 @@ class AmcrestCamera extends RtspSmartCamera implements VideoCameraConfiguration,
// seems the dahua doorbells preferred 1024 chunks. should investigate adts
// parsing and sending multipart chunks instead.
const passthrough = new PassThrough();
this.getClient().digestAuth.request({
method: 'POST',
this.getClient().request({
url,
method: 'POST',
headers: {
'Content-Type': 'Audio/AAC',
'Content-Type': contentType,
'Content-Length': '9999999'
},
httpsAgent: amcrestHttpsAgent,
data: passthrough,
});
responseType: 'readable',
}, passthrough);
try {
while (true) {
@@ -598,7 +607,7 @@ class AmcrestProvider extends RtspProvider {
const username = settings.username?.toString();
const password = settings.password?.toString();
const skipValidate = settings.skipValidate === 'true';
const skipValidate = settings.skipValidate?.toString() === 'true';
let twoWayAudio: string;
if (!skipValidate) {
const api = new AmcrestCameraClient(httpAddress, username, password, this.console);

View File

@@ -1,8 +1,4 @@
import https from 'https';
export const amcrestHttpsAgent = new https.Agent({
rejectUnauthorized: false,
});
import { AuthFetchCredentialState, authHttpFetch } from '@scrypted/common/src/http-auth-fetch';
// appAutoStart=true
// deviceType=IP4M-1041B
@@ -11,17 +7,16 @@ export const amcrestHttpsAgent = new https.Agent({
// serialNumber=12345
// updateSerial=IPC-AW46WN-S2
import AxiosDigestAuth from "@koush/axios-digest-auth";
// updateSerialCloudUpgrade=IPC-AW46WN-.....
export async function getDeviceInfo(digestAuth: AxiosDigestAuth, address: string) {
const response = await digestAuth.request({
httpsAgent: amcrestHttpsAgent,
method: "GET",
responseType: 'text',
export async function getDeviceInfo(credential: AuthFetchCredentialState, address: string) {
const response = await authHttpFetch({
credential,
url: `http://${address}/cgi-bin/magicBox.cgi?action=getSystemInfo`,
rejectUnauthorized: false,
responseType: 'text',
});
const lines = (response.data as string).split('\n');
const lines = response.body.split('\n');
const vals: {
[key: string]: string,
} = {};

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/bticino",
"version": "0.0.11",
"version": "0.0.13",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/bticino",
"version": "0.0.11",
"version": "0.0.13",
"dependencies": {
"@slyoldfox/sip": "^0.0.6-1",
"sdp": "^3.0.3",
@@ -40,7 +40,7 @@
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.2.103",
"version": "0.3.2",
"dev": true,
"license": "ISC",
"dependencies": {

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/bticino",
"version": "0.0.11",
"version": "0.0.13",
"scripts": {
"scrypted-setup-project": "scrypted-setup-project",
"prescrypted-setup-project": "scrypted-package-json",

View File

@@ -0,0 +1,61 @@
import { ScryptedDeviceBase, HttpRequest, HttpResponse, HttpRequestHandler, OnOff } from "@scrypted/sdk";
import { BticinoSipCamera } from "./bticino-camera";
import { VoicemailHandler } from "./bticino-voicemailHandler";
export class BticinoAswmSwitch extends ScryptedDeviceBase implements OnOff, HttpRequestHandler {
private timeout : NodeJS.Timeout
constructor(private camera: BticinoSipCamera, private voicemailHandler : VoicemailHandler) {
super( camera.nativeId + "-aswm-switch")
this.timeout = setTimeout( () => this.syncStatus() , 5000 )
}
turnOff(): Promise<void> {
this.on = false
return this.camera.turnOffAswm()
}
turnOn(): Promise<void> {
this.on = true
return this.camera.turnOnAswm()
}
syncStatus() {
this.on = this.voicemailHandler.isAswmEnabled()
this.timeout = setTimeout( () => this.syncStatus() , 5000 )
}
cancelTimer() {
if( this.timeout ) {
clearTimeout(this.timeout)
}
}
public async onRequest(request: HttpRequest, response: HttpResponse): Promise<void> {
if (request.url.endsWith('/disabled')) {
this.on = false
response.send('Success', {
code: 200,
});
} else if( request.url.endsWith('/enabled') ) {
this.on = true
response.send('Success', {
code: 200,
});
} else if( request.url.endsWith('/enable') ) {
this.turnOn()
response.send('Success', {
code: 200,
});
} else if( request.url.endsWith('/disable') ) {
this.turnOff()
response.send('Success', {
code: 200,
});
} else {
response.send('Unsupported operation', {
code: 400,
});
}
}
}

View File

@@ -1,10 +1,10 @@
import { closeQuiet, createBindZero, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
import { closeQuiet, createBindUdp, createBindZero, listenZeroSingleClient } from '@scrypted/common/src/listen-cluster';
import { sleep } from '@scrypted/common/src/sleep';
import { RtspServer } from '@scrypted/common/src/rtsp-server';
import { addTrackControls } from '@scrypted/common/src/sdp-utils';
import sdk, { BinarySensor, Camera, DeviceProvider, FFmpegInput, HttpRequest, HttpRequestHandler, HttpResponse, Intercom, MediaObject, MediaStreamUrl, PictureOptions, Reboot, ResponseMediaStreamOptions, ScryptedDevice, ScryptedDeviceBase, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoClip, VideoClipOptions, VideoClips } from '@scrypted/sdk';
import { addTrackControls, parseSdp } from '@scrypted/common/src/sdp-utils';
import sdk, { BinarySensor, Camera, DeviceProvider, FFmpegInput, HttpRequest, HttpRequestHandler, HttpResponse, Intercom, MediaObject, MediaStreamUrl, MotionSensor, PictureOptions, Reboot, ResponseMediaStreamOptions, ScryptedDeviceBase, ScryptedMimeTypes, Setting, Settings, SettingValue, VideoCamera, VideoClip, VideoClipOptions, VideoClips } from '@scrypted/sdk';
import { SipCallSession } from '../../sip/src/sip-call-session';
import { RtpDescription } from '../../sip/src/rtp-utils';
import { RtpDescription, getPayloadType, getSequenceNumber, isRtpMessagePayloadType, isStunMessage } from '../../sip/src/rtp-utils';
import { VoicemailHandler } from './bticino-voicemailHandler';
import { CompositeSipMessageHandler } from '../../sip/src/compositeSipMessageHandler';
import { SipHelper } from './sip-helper';
@@ -16,22 +16,22 @@ import { BticinoSipLock } from './bticino-lock';
import { ffmpegLogInitialOutput, safeKillFFmpeg, safePrintFFmpegArguments } from '@scrypted/common/src/media-helpers';
import { PersistentSipManager } from './persistent-sip-manager';
import { InviteHandler } from './bticino-inviteHandler';
import { SipRequest } from '../../sip/src/sip-manager';
import { SipOptions, SipRequest } from '../../sip/src/sip-manager';
import { get } from 'http'
import { ControllerApi } from './c300x-controller-api';
import { BticinoAswmSwitch } from './bticino-aswm-switch';
import { BticinoMuteSwitch } from './bticino-mute-switch';
const STREAM_TIMEOUT = 65000;
const { mediaManager } = sdk;
export class BticinoSipCamera extends ScryptedDeviceBase implements DeviceProvider, Intercom, Camera, VideoCamera, Settings, BinarySensor, HttpRequestHandler, VideoClips, Reboot {
export class BticinoSipCamera extends ScryptedDeviceBase implements MotionSensor, DeviceProvider, Intercom, Camera, VideoCamera, Settings, BinarySensor, HttpRequestHandler, VideoClips, Reboot {
private session: SipCallSession
private remoteRtpDescription: Promise<RtpDescription>
private audioOutForwarder: dgram.Socket
private audioOutProcess: ChildProcess
private currentMedia: FFmpegInput | MediaStreamUrl
private currentMediaMimeType: string
private refreshTimeout: NodeJS.Timeout
public requestHandlers: CompositeSipMessageHandler = new CompositeSipMessageHandler()
public incomingCallRequest : SipRequest
@@ -39,16 +39,21 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements DeviceProvid
private voicemailHandler : VoicemailHandler = new VoicemailHandler(this)
private inviteHandler : InviteHandler = new InviteHandler(this)
private controllerApi : ControllerApi = new ControllerApi(this)
private muteSwitch : BticinoMuteSwitch
private aswmSwitch : BticinoAswmSwitch
private deferredCleanup
private currentMediaObject : Promise<MediaObject>
private lastImageRefresh : number
//TODO: randomize this
private keyAndSalt : string = "/qE7OPGKp9hVGALG2KcvKWyFEZfSSvm7bYVDjT8X"
//private decodedSrtpOptions : SrtpOptions = decodeSrtpOptions( this.keyAndSalt )
private persistentSipManager : PersistentSipManager
public doorbellWebhookUrl : string
public doorbellLockWebhookUrl : string
private cachedImage : Buffer
constructor(nativeId: string, public provider: BticinoSipPlugin) {
super(nativeId)
this.requestHandlers.add( this.voicemailHandler ).add( this.inviteHandler )
this.persistentSipManager = new PersistentSipManager( this );
(async() => {
@@ -63,10 +68,50 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements DeviceProvid
get(`http://${c300x}:8080/reboot?now`, (res) => {
console.log("Reboot API result: " + res.statusCode)
});
}).on('error', (error) => {
this.console.error(error)
reject(error)
} ).end();
})
}
muteRinger(mute : boolean): Promise<void> {
return new Promise<void>( (resolve,reject ) => {
let c300x = SipHelper.getIntercomIp(this)
get(`http://${c300x}:8080/mute?raw=true&enable=` + mute, (res) => {
console.log("Mute API result: " + res.statusCode)
}).on('error', (error) => {
this.console.error(error)
reject(error)
} ).end();
})
}
muteStatus(): Promise<boolean> {
return new Promise<boolean>( (resolve,reject ) => {
let c300x = SipHelper.getIntercomIp(this)
get(`http://${c300x}:8080/mute?status=true&raw=true`, (res) => {
let rawData = '';
res.on('data', (chunk) => { rawData += chunk; })
res.on('error', (error) => this.console.log(error))
res.on('end', () => {
try {
return resolve(JSON.parse(rawData))
} catch (e) {
console.error(e.message);
reject(e.message)
}
})
}).on('error', (error) => {
this.console.error(error)
reject(error)
} ).end();
})
}
getVideoClips(options?: VideoClipOptions): Promise<VideoClip[]> {
return new Promise<VideoClip[]>( (resolve,reject ) => {
let c300x = SipHelper.getIntercomIp(this)
@@ -95,7 +140,10 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements DeviceProvid
console.error(e.message);
}
})
});
}).on('error', (error) => {
this.console.error(error)
reject(error)
} ).end(); ;
});
}
@@ -132,8 +180,34 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements DeviceProvid
} )
}
turnOnAswm() : Promise<void> {
return this.persistentSipManager.enable().then( (sipCall) => {
sipCall.message( "*8*91##" )
} )
}
turnOffAswm() : Promise<void> {
return this.persistentSipManager.enable().then( (sipCall) => {
sipCall.message( "*8*92##" )
} )
}
async takePicture(option?: PictureOptions): Promise<MediaObject> {
throw new Error("The SIP doorbell camera does not provide snapshots. Install the Snapshot Plugin if snapshots are available via an URL.");
const thumbnailCacheTime : number = parseInt( this.storage?.getItem('thumbnailCacheTime') ) * 1000 || 300000
const now = new Date().getTime()
if( !this.lastImageRefresh || this.lastImageRefresh + thumbnailCacheTime < now ) {
// get a proxy object to make sure we pass prebuffer when already watching a stream
let cam : VideoCamera = sdk.systemManager.getDeviceById<VideoCamera>(this.id)
let vs : MediaObject = await cam.getVideoStream()
let buf : Buffer = await mediaManager.convertMediaObjectToBuffer(vs, 'image/jpeg');
this.cachedImage = buf
this.lastImageRefresh = new Date().getTime()
this.console.log(`Camera picture updated and cached: ${this.lastImageRefresh} + cache time: ${thumbnailCacheTime} < ${now}`)
} else {
this.console.log(`Not refreshing camera picture: ${this.lastImageRefresh} + cache time: ${thumbnailCacheTime} < ${now}`)
}
return mediaManager.createMediaObject(this.cachedImage, 'image/jpeg')
}
async getPictureOptions(): Promise<PictureOptions[]> {
@@ -149,8 +223,17 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements DeviceProvid
}
async startIntercom(media: MediaObject): Promise<void> {
if (!this.session)
throw new Error("not in call");
if (!this.session) {
const cleanup = () => {
this.console.log("STARTINTERCOM CLEANUP CALLED: " + this.session )
this.session?.stop()
this.session = undefined
this.deferredCleanup()
this.console.log("STARTINTERCOM CLEANUP ENDED")
}
this.session = await this.callIntercom( cleanup )
}
this.stopIntercom();
@@ -223,27 +306,24 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements DeviceProvid
throw new Error('Please configure from/to/domain settings')
}
if (options?.metadata?.refreshAt) {
if (!this.currentMedia?.mediaStreamOptions)
throw new Error("no stream to refresh");
const currentMedia = this.currentMedia
currentMedia.mediaStreamOptions.refreshAt = Date.now() + STREAM_TIMEOUT;
currentMedia.mediaStreamOptions.metadata = {
refreshAt: currentMedia.mediaStreamOptions.refreshAt
};
this.resetStreamTimeout()
return mediaManager.createMediaObject(currentMedia, this.currentMediaMimeType)
}
this.console.log("Before stopping session")
this.stopSession();
const { clientPromise: playbackPromise, port: playbackPort, url: clientUrl } = await listenZeroSingleClient()
this.console.log("After stopping session")
const playbackUrl = clientUrl
let rebroadcastEnabled = this.interfaces?.includes( "mixin:@scrypted/prebuffer-mixin")
const { clientPromise: playbackPromise, port: playbackPort } = await listenZeroSingleClient()
const playbackUrl = `rtsp://127.0.0.1:${playbackPort}`
this.console.log("PLAYBACKURL: " +playbackUrl)
playbackPromise.then(async (client) => {
client.setKeepAlive(true, 10000)
let sip: SipCallSession
let audioSplitter
let videoSplitter
try {
if( !this.incomingCallRequest ) {
// If this is a "view" call, update the stream endpoint to send it only to "us"
@@ -252,61 +332,37 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements DeviceProvid
}
let rtsp: RtspServer;
const cleanup = () => {
this.console.log("CLEANUP CALLED")
client.destroy();
if (this.session === sip)
this.session = undefined
try {
this.log.d('cleanup(): stopping sip session.')
sip.stop()
sip?.stop()
this.currentMediaObject = undefined
}
catch (e) {
}
audioSplitter?.server?.close()
videoSplitter?.server?.close()
rtsp?.destroy()
this.console.log("CLEANUP ENDED")
this.deferredCleanup = undefined
this.remoteRtpDescription = undefined
}
this.deferredCleanup = cleanup
client.on('close', cleanup)
client.on('error', cleanup)
let sipOptions = SipHelper.sipOptions( this )
sip = await this.persistentSipManager.session( sipOptions );
// Validate this sooner
if( !sip ) return Promise.reject("Cannot create session")
sip.onCallEnded.subscribe(cleanup)
// Call the C300X
this.remoteRtpDescription = sip.callOrAcceptInvite(
( audio ) => {
return [
//TODO: Payload types are hardcoded
`m=audio 65000 RTP/SAVP 110`,
`a=rtpmap:110 speex/8000`,
`a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${this.keyAndSalt}`,
]
}, ( video ) => {
if( false ) {
//TODO: implement later
return [
`m=video 0 RTP/SAVP 0`
]
} else {
return [
//TODO: Payload types are hardcoded
`m=video 65002 RTP/SAVP 96`,
`a=rtpmap:96 H264/90000`,
`a=fmtp:96 profile-level-id=42801F`,
`a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${this.keyAndSalt}`,
'a=recvonly'
]
}
}, this.incomingCallRequest );
this.incomingCallRequest = undefined
if( !rebroadcastEnabled || (rebroadcastEnabled && !this.incomingCallRequest ) ) {
sip = await this.callIntercom( cleanup )
}
//let sdp: string = replacePorts(this.remoteRtpDescription.sdp, 0, 0 )
let sdp : string = [
let sdp : string = [
"v=0",
"m=audio 5000 RTP/AVP 110",
"c=IN IP4 127.0.0.1",
@@ -318,42 +374,141 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements DeviceProvid
//sdp = sdp.replaceAll(/a=crypto\:1.*/g, '')
//sdp = sdp.replaceAll(/RTP\/SAVP/g, 'RTP\/AVP')
//sdp = sdp.replaceAll('\r\n\r\n', '\r\n')
sdp = addTrackControls(sdp)
sdp = sdp.split('\n').filter(line => !line.includes('a=rtcp-mux')).join('\n')
if( sipOptions.debugSip )
this.log.d('SIP: Updated SDP:\n' + sdp);
client.write(sdp)
client.end()
let vseq = 0;
let vseen = 0;
let vlost = 0;
let aseq = 0;
let aseen = 0;
let alost = 0;
sdp = addTrackControls(sdp);
sdp = sdp.split('\n').filter(line => !line.includes('a=rtcp-mux')).join('\n');
this.console.log('proposed sdp', sdp);
this.console.log("================= AUDIOSPLITTER CREATING.... ============" )
audioSplitter = await createBindUdp(5000)
this.console.log("================= AUDIOSPLITTER CREATED ============" )
audioSplitter.server.on('close', () => {
this.console.log("================= CLOSED AUDIOSPLITTER ================")
audioSplitter = undefined
})
this.console.log("================= VIDEOSPLITTER CREATING.... ============" )
videoSplitter = await createBindUdp(5002)
this.console.log("================= VIDEOSPLITTER CREATED.... ============" )
videoSplitter.server.on('close', () => {
this.console.log("================= CLOSED VIDEOSPLITTER ================")
videoSplitter = undefined
})
rtsp = new RtspServer(client, sdp, false);
const parsedSdp = parseSdp(rtsp.sdp);
const videoTrack = parsedSdp.msections.find(msection => msection.type === 'video').control;
const audioTrack = parsedSdp.msections.find(msection => msection.type === 'audio').control;
rtsp.console = this.console;
await rtsp.handlePlayback();
this.session = sip
videoSplitter.server.on('message', (message, rinfo) => {
if ( !isStunMessage(message)) {
const isRtpMessage = isRtpMessagePayloadType(getPayloadType(message));
if (!isRtpMessage)
return;
vseen++;
try {
rtsp.sendTrack(videoTrack, message, !isRtpMessage);
} catch(e ) {
this.console.log(e)
}
const seq = getSequenceNumber(message);
if (seq !== (vseq + 1) % 0x0FFFF)
vlost++;
vseq = seq;
}
});
audioSplitter.server.on('message', (message, rinfo ) => {
if ( !isStunMessage(message)) {
const isRtpMessage = isRtpMessagePayloadType(getPayloadType(message));
if (!isRtpMessage)
return;
aseen++;
try {
rtsp.sendTrack(audioTrack, message, !isRtpMessage);
} catch(e) {
this.console.log(e)
}
const seq = getSequenceNumber(message);
if (seq !== (aseq + 1) % 0x0FFFF)
alost++;
aseq = seq;
}
});
try {
await rtsp.handleTeardown();
this.console.log('rtsp client ended');
} catch (e) {
this.console.log('rtsp client ended ungracefully', e);
} finally {
cleanup();
}
}
catch (e) {
this.console.error(e)
sip?.stop()
throw e;
}
});
this.resetStreamTimeout();
const mediaStreamOptions = Object.assign(this.getSipMediaStreamOptions(), {
refreshAt: Date.now() + STREAM_TIMEOUT,
});
const ffmpegInput: FFmpegInput = {
url: undefined,
container: 'sdp',
mediaStreamOptions,
inputArguments: [
'-f', 'sdp',
'-i', playbackUrl,
],
const mediaStreamUrl: MediaStreamUrl = {
url: playbackUrl,
mediaStreamOptions: this.getSipMediaStreamOptions(),
};
this.currentMedia = ffmpegInput;
this.currentMediaMimeType = ScryptedMimeTypes.FFmpegInput;
return mediaManager.createFFmpegMediaObject(ffmpegInput);
sleep(2500).then( () => this.takePicture() )
this.currentMediaObject = mediaManager.createMediaObject(mediaStreamUrl, ScryptedMimeTypes.MediaStreamUrl);
// Invalidate any cached image and take a picture after some seconds to take into account the opening of the lens
this.lastImageRefresh = undefined
return this.currentMediaObject
}
async callIntercom( cleanup ) : Promise<SipCallSession> {
let sipOptions : SipOptions = SipHelper.sipOptions( this )
let sip : SipCallSession = await this.persistentSipManager.session( sipOptions );
// Validate this sooner
if( !sip ) return Promise.reject("Cannot create session")
sip.onCallEnded.subscribe(cleanup)
// Call the C300X
this.remoteRtpDescription = sip.callOrAcceptInvite(
( audio ) => {
return [
// this SDP is used by the intercom and will send the encrypted packets which we don't care about to the loopback on port 65000 of the intercom
`m=audio 65000 RTP/SAVP 110`,
`a=rtpmap:110 speex/8000`,
`a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${this.keyAndSalt}`,
]
}, ( video ) => {
return [
// this SDP is used by the intercom and will send the encrypted packets which we don't care about to the loopback on port 65000 of the intercom
`m=video 65002 RTP/SAVP 96`,
`a=rtpmap:96 H264/90000`,
`a=fmtp:96 profile-level-id=42801F`,
`a=crypto:1 AES_CM_128_HMAC_SHA1_80 inline:${this.keyAndSalt}`,
'a=recvonly'
]
}, this.incomingCallRequest );
this.incomingCallRequest = undefined
return sip
}
getSipMediaStreamOptions(): ResponseMediaStreamOptions {
@@ -362,13 +517,16 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements DeviceProvid
name: 'SIP',
// this stream is NOT scrypted blessed due to wackiness in the h264 stream.
// tool: "scrypted",
container: 'sdp',
container: 'rtsp',
video: {
codec: 'h264'
},
audio: {
// this is a hint to let homekit, et al, know that it's speex audio and needs transcoding.
codec: 'speex',
},
source: 'cloud', // to disable prebuffering
userConfigurable: false,
userConfigurable: true,
};
}
@@ -378,14 +536,28 @@ export class BticinoSipCamera extends ScryptedDeviceBase implements DeviceProvid
]
}
async getDevice(nativeId: string) : Promise<BticinoSipLock> {
async getDevice(nativeId: string) : Promise<any> {
if( nativeId && nativeId.endsWith('-aswm-switch')) {
this.aswmSwitch = new BticinoAswmSwitch(this, this.voicemailHandler)
return this.aswmSwitch
} else if( nativeId && nativeId.endsWith('-mute-switch') ) {
this.muteSwitch = new BticinoMuteSwitch(this)
return this.muteSwitch
}
return new BticinoSipLock(this)
}
async releaseDevice(id: string, nativeId: string): Promise<void> {
this.voicemailHandler.cancelTimer()
this.persistentSipManager.cancelTimer()
this.controllerApi.cancelTimer()
if( nativeId?.endsWith('-aswm-switch') ) {
this.aswmSwitch.cancelTimer()
} else if( nativeId?.endsWith('mute-switch') ) {
this.muteSwitch.cancelTimer()
} else {
this.stopIntercom()
this.voicemailHandler.cancelTimer()
this.persistentSipManager.cancelTimer()
this.controllerApi.cancelTimer()
}
}
reset() {

View File

@@ -0,0 +1,64 @@
import { ScryptedDeviceBase, HttpRequest, HttpResponse, HttpRequestHandler, OnOff } from "@scrypted/sdk";
import { BticinoSipCamera } from "./bticino-camera";
export class BticinoMuteSwitch extends ScryptedDeviceBase implements OnOff, HttpRequestHandler {
private timeout : NodeJS.Timeout
constructor(private camera: BticinoSipCamera) {
super( camera.nativeId + "-mute-switch");
this.on = false;
this.timeout = setTimeout( () => this.syncStatus() , 5000 )
}
turnOff(): Promise<void> {
this.on = false
return this.camera.muteRinger(false)
}
turnOn(): Promise<void> {
this.on = true
return this.camera.muteRinger(true)
}
syncStatus() {
this.camera.muteStatus().then( (value) => {
this.on = value["status"]
} ).catch( (e) => { this.camera.console.error(e) } ).finally( () => {
this.timeout = setTimeout( () => this.syncStatus() , 60000 )
} )
}
cancelTimer() {
if( this.timeout ) {
clearTimeout(this.timeout)
}
}
public async onRequest(request: HttpRequest, response: HttpResponse): Promise<void> {
if (request.url.endsWith('/disabled')) {
this.on = false
response.send('Success', {
code: 200,
});
} else if( request.url.endsWith('/enabled') ) {
this.on = true
response.send('Success', {
code: 200,
});
} else if( request.url.endsWith('/enable') ) {
this.turnOn()
response.send('Success', {
code: 200,
});
} else if( request.url.endsWith('/disable') ) {
this.turnOff()
response.send('Success', {
code: 200,
});
} else {
response.send('Unsupported operation', {
code: 400,
});
}
}
}

View File

@@ -3,6 +3,7 @@ import { BticinoSipCamera } from "./bticino-camera"
export class VoicemailHandler extends SipRequestHandler {
private timeout : NodeJS.Timeout
private aswmIsEnabled: boolean
constructor( private sipCamera : BticinoSipCamera ) {
super()
@@ -15,14 +16,12 @@ export class VoicemailHandler extends SipRequestHandler {
checkVoicemail() {
if( !this.sipCamera )
return
if( this.isEnabled() ) {
this.sipCamera.console.debug("Checking answering machine, cameraId: " + this.sipCamera.id )
this.sipCamera.getAswmStatus().catch( e => this.sipCamera.console.error(e) )
} else {
this.sipCamera.console.debug("Answering machine check not enabled, cameraId: " + this.sipCamera.id )
}
//TODO: make interval customizable, now every 5 minutes
this.timeout = setTimeout( () => this.checkVoicemail() , 5 * 60 * 1000 )
this.sipCamera.console.debug("Checking answering machine, cameraId: " + this.sipCamera.id )
this.sipCamera.getAswmStatus().catch( e => this.sipCamera.console.error(e) )
//TODO: make interval customizable, now every minute
this.timeout = setTimeout( () => this.checkVoicemail() , 1 * 60 * 1000 )
}
cancelTimer() {
@@ -32,10 +31,11 @@ export class VoicemailHandler extends SipRequestHandler {
}
handle(request: SipRequest) {
if( this.isEnabled() ) {
const lastVoicemailMessageTimestamp : number = Number.parseInt( this.sipCamera.storage.getItem('lastVoicemailMessageTimestamp') ) || -1
const message : string = request.content.toString()
if( message.startsWith('*#8**40*0*0*1176*0*2##') ) {
const lastVoicemailMessageTimestamp : number = Number.parseInt( this.sipCamera.storage.getItem('lastVoicemailMessageTimestamp') ) || -1
const message : string = request.content.toString()
if( message.startsWith('*#8**40*0*0*') || message.startsWith('*#8**40*1*0*') ) {
this.aswmIsEnabled = message.startsWith('*#8**40*1*0*');
if( this.isEnabled() ) {
this.sipCamera.console.debug("Handling incoming answering machine reply")
const messages : string[] = message.split(';')
let lastMessageTimestamp : number = 0
@@ -53,12 +53,12 @@ export class VoicemailHandler extends SipRequestHandler {
}
} )
if( (lastVoicemailMessageTimestamp == null && lastMessageTimestamp > 0) ||
( lastVoicemailMessageTimestamp != null && lastMessageTimestamp > lastVoicemailMessageTimestamp ) ) {
( lastVoicemailMessageTimestamp != null && lastMessageTimestamp > lastVoicemailMessageTimestamp ) ) {
this.sipCamera.log.a(`You have ${countNewMessages} new voicemail messages.`)
this.sipCamera.storage.setItem('lastVoicemailMessageTimestamp', lastMessageTimestamp.toString())
} else {
} else {
this.sipCamera.console.debug("No new messages since: " + lastVoicemailMessageTimestamp + " lastMessage: " + lastMessageTimestamp)
}
}
}
}
}
@@ -66,4 +66,8 @@ export class VoicemailHandler extends SipRequestHandler {
isEnabled() : boolean {
return this.sipCamera?.storage?.getItem('notifyVoicemail')?.toLocaleLowerCase() === 'true' || false
}
isAswmEnabled() : boolean {
return this.aswmIsEnabled
}
}

View File

@@ -99,7 +99,7 @@ export class ControllerApi {
})
}
console.log("Endpoint registration status: " + res.statusCode)
});
}).on('error', (e) => this.sipCamera.console.error(e) );
// The default evict time on the c300x-controller is 5 minutes, so this will certainly be within bounds
this.timeout = setTimeout( () => this.registerEndpoints( false ) , 2 * 60 * 1000 )
@@ -114,7 +114,7 @@ export class ControllerApi {
return new Promise( (resolve, reject) => get(`http://${ipAddress}:8080/register-endpoint?raw=true&updateStreamEndpoint=${sipFrom}`, (res) => {
if( res.statusCode != 200 ) reject( "ERROR: Could not update streaming endpoint, call returned: " + res.statusCode )
else resolve()
} ) );
} ).on('error', (error) => this.sipCamera.console.error(error) ).end() );
}
public cancelTimer() {

View File

@@ -36,7 +36,7 @@ export class BticinoSipPlugin extends ScryptedDeviceBase implements DeviceProvid
const name = settings.newCamera?.toString() === undefined ? "Doorbell" : settings.newCamera?.toString()
await this.updateDevice(nativeId, name)
const device: Device = {
const lockDevice: Device = {
providerNativeId: nativeId,
info: {
//model: `${camera.model} (${camera.data.kind})`,
@@ -49,10 +49,38 @@ export class BticinoSipPlugin extends ScryptedDeviceBase implements DeviceProvid
type: ScryptedDeviceType.Lock,
interfaces: [ScryptedInterface.Lock, ScryptedInterface.HttpRequestHandler],
}
const aswmSwitchDevice: Device = {
providerNativeId: nativeId,
info: {
//model: `${camera.model} (${camera.data.kind})`,
manufacturer: 'BticinoPlugin',
//firmware: camera.data.firmware_version,
//serialNumber: camera.data.device_id
},
nativeId: nativeId + '-aswm-switch',
name: name + ' Voicemail',
type: ScryptedDeviceType.Switch,
interfaces: [ScryptedInterface.OnOff, ScryptedInterface.HttpRequestHandler],
}
const muteSwitchDevice: Device = {
providerNativeId: nativeId,
info: {
//model: `${camera.model} (${camera.data.kind})`,
manufacturer: 'BticinoPlugin',
//firmware: camera.data.firmware_version,
//serialNumber: camera.data.device_id
},
nativeId: nativeId + '-mute-switch',
name: name + ' Muted',
type: ScryptedDeviceType.Switch,
interfaces: [ScryptedInterface.OnOff, ScryptedInterface.HttpRequestHandler],
}
await deviceManager.onDevicesChanged({
providerNativeId: nativeId,
devices: [device],
devices: [lockDevice, aswmSwitchDevice, muteSwitchDevice],
})
let sipCamera : BticinoSipCamera = await this.getDevice(nativeId)
@@ -87,6 +115,7 @@ export class BticinoSipPlugin extends ScryptedDeviceBase implements DeviceProvid
ScryptedInterface.Settings,
ScryptedInterface.Intercom,
ScryptedInterface.BinarySensor,
ScryptedInterface.MotionSensor,
ScryptedDeviceType.DeviceProvider,
ScryptedInterface.HttpRequestHandler,
ScryptedInterface.VideoClips,

View File

@@ -61,7 +61,7 @@ export class SipHelper {
if( !md5 ) {
md5 = crypto.createHash('md5').update( camera.nativeId ).digest("hex")
md5 = md5.substring(0, 8) + '-' + md5.substring(8, 12) + '-' + md5.substring(12,16) + '-' + md5.substring(16, 32)
camera.storage.setItem('md5has', md5)
camera.storage.setItem('md5hash', md5)
}
return md5
}

View File

@@ -35,6 +35,14 @@ export class BticinoStorageSettings {
defaultValue: 600,
placeholder: '600',
},
thumbnailCacheTime: {
title: 'Thumbnail cache time',
type: 'number',
range: [60, 86400],
description: 'How long the snapshot is cached before taking a new one. (in seconds)',
defaultValue: 300,
placeholder: '300',
},
sipdebug: {
title: 'SIP debug logging',
type: 'boolean',

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/cloud",
"version": "0.2.3",
"version": "0.2.4",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "@scrypted/cloud",
"version": "0.2.3",
"version": "0.2.4",
"dependencies": {
"@eneris/push-receiver": "^3.1.4",
"@scrypted/common": "file:../../common",

View File

@@ -54,5 +54,5 @@
"@types/nat-upnp": "^1.1.2",
"@types/node": "^20.4.5"
},
"version": "0.2.3"
"version": "0.2.4"
}

View File

@@ -574,6 +574,8 @@ class ScryptedCloud extends ScryptedDeviceBase implements OauthClient, Settings,
});
const { token_info } = this.storageSettings.values;
if (!token_info)
throw new Error('Scrypted Cloud is not logged in. Skipping home.scrypted.app registration.');
const response = await axios(`https://${SCRYPTED_SERVER}/_punch/register?${q}`, {
headers: {
Authorization: `Bearer ${token_info}`

View File

@@ -10,14 +10,14 @@
"port": 10081,
"request": "attach",
"skipFiles": [
"**/plugin-remote-worker.*",
"**/plugin-console.*",
"<node_internals>/**"
],
"preLaunchTask": "scrypted: deploy+debug",
"sourceMaps": true,
"localRoot": "${workspaceFolder}/out",
"remoteRoot": "/plugin/",
"type": "pwa-node"
"type": "node"
}
]
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/core",
"version": "0.1.149",
"version": "0.3.2",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/core",
"version": "0.1.149",
"version": "0.3.2",
"license": "Apache-2.0",
"dependencies": {
"@scrypted/common": "file:../../common",
@@ -88,7 +88,7 @@
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.2.108",
"version": "0.3.4",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/core",
"version": "0.1.149",
"version": "0.3.2",
"description": "Scrypted Core plugin. Provides the UI, websocket, and engine.io APIs.",
"author": "Scrypted",
"license": "Apache-2.0",

View File

@@ -47,37 +47,45 @@ export class Scheduler {
const day = future.getDay();
if (!days[day])
continue;
source.log.i(`event will fire at ${future.toLocaleString()}`);
return future;
}
source.log.w('event will never fire');
}
const when = reschedule();
if (!when) {
return {
removeListener() {
}
}
}
const delay = when.getTime() - Date.now();
source.log.i(`event will fire in ${Math.round(delay / 60 / 1000)} minutes.`);
let timeout = setTimeout(() => {
reschedule();
let timeout: NodeJS.Timeout = null;
let when: Date = null;
function timerCb() {
timeout = null;
const prevWhen = when;
setupTimer();
callback(ret, {
eventId: undefined,
eventInterface: 'Scheduler',
eventTime: Date.now(),
}, when)
}, delay);
}, prevWhen)
}
function setupTimer() {
when = reschedule();
if (when) {
const delay = when.getTime() - Date.now();
source.log.i(`event will fire in ${Math.round(delay / 60 / 1000)} minutes.`);
timeout = setTimeout(timerCb, delay);
}
}
setupTimer();
return {
removeListener() {
clearTimeout(timeout);
if (timeout) {
clearTimeout(timeout);
}
timeout = null;
when = null;
}
}
}

View File

@@ -1,3 +0,0 @@
export class Timer {
}

View File

@@ -1,4 +1,4 @@
import { tsCompile } from '@scrypted/common/src/eval/scrypted-eval';
import { readFileAsString, tsCompile } from '@scrypted/common/src/eval/scrypted-eval';
import sdk, { DeviceProvider, EngineIOHandler, HttpRequest, HttpRequestHandler, HttpResponse, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting, Settings, SettingValue } from '@scrypted/sdk';
import { StorageSettings } from "@scrypted/sdk/storage-settings";
import fs from 'fs';
@@ -9,14 +9,12 @@ import { AggregateCore, AggregateCoreNativeId } from './aggregate-core';
import { AutomationCore, AutomationCoreNativeId } from './automations-core';
import { LauncherMixin } from './launcher-mixin';
import { MediaCore } from './media-core';
import { ScriptCore, ScriptCoreNativeId } from './script-core';
import { UsersCore, UsersNativeId } from './user';
import { newScript, ScriptCore, ScriptCoreNativeId } from './script-core';
import { TerminalService, TerminalServiceNativeId } from './terminal-service';
import { UsersCore, UsersNativeId } from './user';
const { systemManager, deviceManager, endpointManager } = sdk;
const indexHtml = fs.readFileSync('dist/index.html').toString();
export function getAddresses() {
const addresses: string[] = [];
for (const [iface, nif] of Object.entries(os.networkInterfaces())) {
@@ -61,10 +59,13 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
},
}
});
indexHtml: string;
constructor() {
super();
this.indexHtml = readFileAsString('dist/index.html');
(async () => {
await deviceManager.onDeviceDiscovered(
{
@@ -226,7 +227,7 @@ class ScryptedCore extends ScryptedDeviceBase implements HttpRequestHandler, Eng
const endpoint = await endpointManager.getPublicCloudEndpoint();
const u = new URL(endpoint);
const rewritten = indexHtml
const rewritten = this.indexHtml
.replace('href="manifest.json"', `href="manifest.json${u.search}"`)
.replace('href="img/icons/apple-touch-icon-152x152.png"', `href="img/icons/apple-touch-icon-152x152.png${u.search}"`)
.replace('href="img/icons/safari-pinned-tab.svg"', `href="img/icons/safari-pinned-tab.svg${u.search}"`)
@@ -269,5 +270,6 @@ export default ScryptedCore;
export async function fork() {
return {
tsCompile,
newScript,
}
}
}

View File

@@ -16,7 +16,7 @@ export class MediaCore extends ScryptedDeviceBase implements DeviceProvider, Buf
constructor() {
super(MediaCoreNativeId);
this.fromMimeType = ScryptedMimeTypes.SchemePrefix + 'scrypted-media';
this.fromMimeType = ScryptedMimeTypes.SchemePrefix + 'scrypted-media' + ';converter-weight=2';
this.toMimeType = ScryptedMimeTypes.MediaObject;
(async () => {

View File

@@ -1,33 +1,23 @@
import { Device, DeviceCreator, DeviceCreatorSettings, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, Setting } from "@scrypted/sdk";
import { Script } from "./script";
import sdk from '@scrypted/sdk';
import sdk, { Device, DeviceCreator, DeviceCreatorSettings, DeviceProvider, Readme, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedNativeId, Setting } from '@scrypted/sdk';
import { randomBytes } from "crypto";
import fs from 'fs';
import path from "path/posix";
import { Worker } from "worker_threads";
import { Script } from "./script";
const { deviceManager } = sdk;
export const ScriptCoreNativeId = 'scriptcore';
interface ScriptWorker {
script: Script;
worker: Worker;
}
export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, DeviceCreator, Readme {
scripts = new Map<string, Promise<Script>>();
scripts = new Map<string, ScriptWorker>();
constructor() {
super(ScriptCoreNativeId);
for (const nativeId of deviceManager.getNativeIds()) {
if (nativeId?.startsWith('script:')) {
const script = new Script(nativeId);
this.scripts.set(nativeId, (async () => {
if (script.providedInterfaces.length > 2) {
await script.run();
}
else {
this.reportScript(nativeId);
}
return script;
})());
}
}
}
async getCreateDeviceSettings(): Promise<Setting[]> {
@@ -51,6 +41,10 @@ export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, De
const nativeId = 'script:' + randomBytes(8).toString('hex');
await this.reportScript(nativeId, name?.toString());
const script = new Script(nativeId);
this.scripts.set(nativeId, {
script,
worker: undefined,
});
if (template) {
try {
await script.saveScript({
@@ -65,7 +59,6 @@ export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, De
catch (e) {
}
}
this.scripts.set(nativeId, Promise.resolve(script));
return nativeId;
}
@@ -84,10 +77,75 @@ export class ScriptCore extends ScryptedDeviceBase implements DeviceProvider, De
return await deviceManager.onDeviceDiscovered(device);
}
getDevice(nativeId: string) {
return this.scripts.get(nativeId);
async getDevice(nativeId: string) {
const e = this.scripts.get(nativeId);
if (e) {
if (e.script)
return e.script;
e.worker?.terminate();
this.scripts.delete(nativeId);
}
let script = new Script(nativeId);
let worker: Worker;
const triggerDeviceDiscover = async (name: string, type: ScryptedDeviceType, interfaces: string[]) => {
const e = this.scripts.get(nativeId);
if (e?.script == script)
e.script = undefined;
const device: Device = {
providerNativeId: this.nativeId,
name,
nativeId,
type,
interfaces,
refresh: true,
};
return await deviceManager.onDeviceDiscovered(device);
};
if (script.providedInterfaces.length > 2) {
const fork = sdk.fork<{
newScript: typeof newScript,
}>();
worker = fork.worker;
try {
script = await (await fork.result).newScript(nativeId, triggerDeviceDiscover);
}
catch (e) {
worker.terminate();
throw e;
}
}
worker?.on('exit', () => {
if (this.scripts.get(nativeId)?.worker === worker) {
this.scripts.delete(nativeId);
// notify the system that the device needs to be refreshed.
if (deviceManager.getNativeIds().includes(nativeId)) {
const script = new Script(nativeId);
triggerDeviceDiscover(script.providedName, script.providedType, script.providedInterfaces);
}
}
});
this.scripts.set(nativeId, {
script,
worker,
});
return script;
}
async releaseDevice(id: string, nativeId: string): Promise<void> {
const worker = this.scripts.get(nativeId)?.worker;
this.scripts.delete(nativeId);
worker?.terminate();
}
}
export async function newScript(nativeId: ScryptedNativeId, triggerDeviceDiscover: (name: string, type: ScryptedDeviceType, interfaces: string[]) => Promise<string>) {
const script = new Script(nativeId, triggerDeviceDiscover);
await script.run();
return script;
}

View File

@@ -3,14 +3,11 @@ import { scryptedEval } from "./scrypted-eval";
import { monacoEvalDefaults } from "./monaco";
import { createScriptDevice, ScriptDeviceImpl } from "@scrypted/common/src/eval/scrypted-eval";
import { ScriptCoreNativeId } from "./script-core";
import { PluginAPIProxy } from "../../../server/src/plugin/plugin-api";
const { log, deviceManager, systemManager } = sdk;
const { deviceManager } = sdk;
export class Script extends ScryptedDeviceBase implements Scriptable, Program, ScriptDeviceImpl {
apiProxy: PluginAPIProxy;
constructor(nativeId: string) {
constructor(nativeId: string, public triggerDeviceDiscover?: (name: string, type: ScryptedDeviceType, interfaces: string[]) => Promise<string>) {
super(nativeId);
}
@@ -18,6 +15,8 @@ export class Script extends ScryptedDeviceBase implements Scriptable, Program, S
this.storage.setItem('data', JSON.stringify({
'script.ts': source.script,
}));
this.triggerDeviceDiscover?.(this.providedName, this.providedType, this.providedInterfaces);
}
async loadScripts(): Promise<{ [filename: string]: ScriptSource; }> {
@@ -70,46 +69,38 @@ export class Script extends ScryptedDeviceBase implements Scriptable, Program, S
}
prepareScript() {
this.apiProxy?.removeListeners();
Object.assign(this, createScriptDevice([
ScryptedInterface.Scriptable,
ScryptedInterface.Program,
]));
}
async run(variables?: { [name: string]: any; }): Promise<any> {
async runInternal(script: string, variables?: { [name: string]: any; }): Promise<any> {
this.prepareScript();
try {
const data = JSON.parse(this.storage.getItem('data'));
const { value, defaultExport, apiProxy } = await scryptedEval(this, data['script.ts'], Object.assign({
const { value, defaultExport } = await scryptedEval(this, script, Object.assign({
device: this,
}, variables));
this.apiProxy = apiProxy;
await this.postRunScript(defaultExport);
return value;
}
catch (e) {
this.console.error('error loading script', e);
this.console.error('error evaluating script', e);
throw e;
}
}
async run(variables?: { [name: string]: any; }): Promise<any> {
const data = JSON.parse(this.storage.getItem('data'));
return this.runInternal(data['script.ts'], variables)
}
async eval(source: ScriptSource, variables?: { [name: string]: any }) {
this.prepareScript();
const { value, defaultExport, apiProxy } = await scryptedEval(this, source.script, Object.assign({
device: this,
}, variables));
this.apiProxy = apiProxy;
await this.postRunScript(defaultExport);
return value;
return this.runInternal(source.script, variables);
}
// will be done at runtime

View File

@@ -1,5 +1,5 @@
import { ScryptedDeviceBase, ScryptedNativeId, StreamService } from "@scrypted/sdk";
import { IPty, spawn as ptySpawn } from 'node-pty-prebuilt-multiarch';
import type { IPty, spawn as ptySpawn } from 'node-pty-prebuilt-multiarch';
import { createAsyncQueue } from '@scrypted/common/src/async-queue'
import { ChildProcess, spawn as childSpawn } from "child_process";
@@ -9,8 +9,7 @@ export const TerminalServiceNativeId = 'terminalservice';
class InteractiveTerminal {
cp: IPty
constructor(cmd: string[]) {
const spawn = require('node-pty-prebuilt-multiarch').spawn as typeof ptySpawn;
constructor(cmd: string[], spawn: typeof ptySpawn) {
if (cmd?.length) {
this.cp = spawn(cmd[0], cmd.slice(1), {});
} else {
@@ -150,8 +149,7 @@ export class TerminalService extends ScryptedDeviceBase implements StreamService
}
}
finally {
if (cp)
cp.kill();
cp?.kill();
}
}
@@ -162,37 +160,40 @@ export class TerminalService extends ScryptedDeviceBase implements StreamService
continue;
if (Buffer.isBuffer(message)) {
if (cp)
cp.write(message);
cp?.write(message);
continue;
}
try {
const parsed = JSON.parse(message.toString());
if (parsed.dim) {
if (cp)
cp.resize(parsed.dim.cols, parsed.dim.rows);
cp?.resize(parsed.dim.cols, parsed.dim.rows);
} else if (parsed.eof) {
if (cp)
cp.sendEOF();
cp?.sendEOF();
} else if ("interactive" in parsed && !cp) {
if (parsed.interactive) {
cp = new InteractiveTerminal(parsed.cmd);
try {
const spawn = require('node-pty-prebuilt-multiarch').spawn as typeof ptySpawn;
cp = new InteractiveTerminal(parsed.cmd, spawn);
}
catch (e) {
this.console.error('Error starting pty', e);
queue.end(e);
return;
}
} else {
cp = new NoninteractiveTerminal(parsed.cmd);
}
registerChildListeners();
}
} catch {
if (cp)
cp.write(Buffer.from(message));
cp?.write(Buffer.from(message));
}
}
}
catch (e) {
this.console.log(e);
if (cp)
cp.kill();
cp?.kill();
}
})();

View File

@@ -111,22 +111,22 @@
"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"
"http-auth-utils": "^5.0.1",
"typescript": "^5.3.3"
},
"devDependencies": {
"@types/node": "^16.9.0"
"@types/node": "^20.11.0",
"ts-node": "^10.9.2"
}
},
"../../../sdk": {
"name": "@scrypted/sdk",
"version": "0.2.108",
"version": "0.3.4",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^0.21.4",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
@@ -158,7 +158,7 @@
},
"../../../sdk/types": {
"name": "@scrypted/types",
"version": "0.2.99",
"version": "0.3.4",
"license": "ISC",
"devDependencies": {
"@types/rimraf": "^3.0.2",

View File

@@ -137,7 +137,7 @@ export default {
// cert may need to be reaccepted? Server is down? Go to the
// server root to force the network error to bypass the PWA cache.
if (
e.toString().includes("Network Error") &&
(e.toString().includes("Network Error") || e.toString().includes("Load failed")) &&
window.location.href.startsWith("https:")
) {
window.location = "/";

View File

@@ -173,7 +173,7 @@ export default {
const version = await info.getVersion();
const scryptedEnv = await info.getScryptedEnv();
this.currentVersion = version;
const { updateAvailable } = await checkServerUpdate(version, scryptedEnv.SCRYPTED_INSTALL_ENVIRONMENT);
const { updateAvailable } = await checkServerUpdate(this.$scrypted.mediaManager, version, scryptedEnv.SCRYPTED_INSTALL_ENVIRONMENT);
this.updateAvailable = updateAvailable;
}

View File

@@ -21,27 +21,18 @@
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
v-if="!canUpdate"
small
text
href="https://github.com/koush/scrypted#installation"
>More Information</v-btn
>
<v-btn v-if="!canUpdate" small text href="https://github.com/koush/scrypted#installation">More
Information</v-btn>
<v-dialog v-else v-model="updateAndRestart" width="500">
<template v-slot:activator="{ on }">
<v-btn small text color="red" v-on="on"
>Update and Restart Scrypted</v-btn
>
<v-btn small text color="red" v-on="on">Update and Restart Scrypted</v-btn>
</template>
<v-card color="red" dark>
<v-card-title primary-title>Restart Scrypted</v-card-title>
<v-card-text
>Are you sure you want to restart the Scrypted
service?</v-card-text
>
<v-card-text>Are you sure you want to restart the Scrypted
service?</v-card-text>
<v-card-text>{{ restartStatus }}</v-card-text>
<v-divider></v-divider>
@@ -67,10 +58,11 @@
</v-card>
<v-card class="mt-2" v-if="showRestart">
<v-toolbar
><v-toolbar-title>Server Management</v-toolbar-title></v-toolbar
>
<v-toolbar><v-toolbar-title>Server Management</v-toolbar-title></v-toolbar>
<v-card-actions>
<v-btn text :href="backupUrl" color="info">Backup</v-btn>
<v-btn text color="info" @click="restoreClick">Restore</v-btn>
<input type="file" ref="restoreFile" style="display: none;" @change="restore" />
<v-spacer></v-spacer>
<v-dialog v-model="restart" width="500">
<template v-slot:activator="{ on }">
@@ -80,10 +72,8 @@
<v-card color="red" dark>
<v-card-title primary-title>Restart Scrypted</v-card-title>
<v-card-text
>Are you sure you want to restart the Scrypted
service?</v-card-text
>
<v-card-text>Are you sure you want to restart the Scrypted
service?</v-card-text>
<v-card-text>{{ restartStatus }}</v-card-text>
<v-divider></v-divider>
@@ -103,7 +93,8 @@
<script>
import { checkServerUpdate } from "../plugin/plugin";
import Settings from "../../interfaces/Settings.vue"
import {createSystemSettingsDevice} from './system-settings';
import { createSystemSettingsDevice } from './system-settings';
import { combineBaseUrl, getCurrentBaseUrl } from "../../../../../../packages/client/src";
export default {
components: {
@@ -121,11 +112,35 @@ export default {
showRestart: false,
};
},
computed: {
backupUrl() {
const baseUrl = getCurrentBaseUrl();
return combineBaseUrl(baseUrl, 'web/component/backup');
},
},
mounted() {
this.loadEnv();
this.checkUpdateAvailable();
},
methods: {
async restoreClick() {
const restoreFile = this.$refs.restoreFile;
restoreFile.click();
},
async restore() {
const restoreFile = this.$refs.restoreFile;
const file = restoreFile.files[0];
if (!file)
return;
console.log(file);
const fileBlob = new Blob([file]);
const baseUrl = getCurrentBaseUrl();
const restoreUrl = combineBaseUrl(baseUrl, 'web/component/restore');
await fetch(restoreUrl, {
method: 'POST',
body: fileBlob,
});
},
async checkUpdateAvailable() {
const info = await this.$scrypted.systemManager.getComponent("info");
const version = await info.getVersion();
@@ -141,7 +156,7 @@ export default {
// old scrypted servers dont support this call, or it may be unimplemented
// in which case fall back and determine what the install type is.
const scryptedEnv = await info.getScryptedEnv();
const { updateAvailable } = await checkServerUpdate(version, scryptedEnv.SCRYPTED_INSTALL_ENVIRONMENT);
const { updateAvailable } = await checkServerUpdate(this.$scrypted.mediaManager, version, scryptedEnv.SCRYPTED_INSTALL_ENVIRONMENT);
this.updateAvailable = updateAvailable;
}
},

View File

@@ -42,15 +42,28 @@ export function createSystemSettingsDevice(systemManager: SystemManager): Scrypt
const results = systemSettings.map(async d => {
const settings = await d.getSettings();
for (const setting of settings) {
const subgroup = setting.group;
if (d.pluginId === '@scrypted/core')
setting.group = 'General';
else
setting.group = d.name;
setting.subgroup = subgroup;
setting.key = d.id + ':' + setting.key;
}
return settings;
});
return (await Promise.all(results)).flat();
const ret = (await Promise.all(results)).flat();
ret.sort((a, b) => {
if (a.group === 'General') {
if (b.group === 'General')
return 0;
return -1;
}
if (b.group === 'General')
return 1;
return 0;
});
return ret;
},
async putSetting(key, value) {
const [id, realKey] = key.split(':');

View File

@@ -1,4 +1,4 @@
import { DeviceCreator, Scriptable, ScryptedInterface, ScryptedStatic, SystemManager } from '@scrypted/types';
import { DeviceCreator, MediaObject, Scriptable, ScryptedInterface, ScryptedStatic, SystemManager, MediaManager } from '@scrypted/types';
import axios, { AxiosResponse } from 'axios';
import semver from 'semver';
import { getAllDevices } from '../../common/mixin';
@@ -66,7 +66,7 @@ export async function checkUpdate(npmPackage: string, npmPackageVersion: string)
};
}
export async function checkServerUpdate(version: string, installEnvironment: string): Promise<PluginUpdateCheck> {
export async function checkServerUpdate(mediaManager: MediaManager, version: string, installEnvironment: string): Promise<PluginUpdateCheck> {
const { updateAvailable, updatePublished, versions } = await checkUpdate(
"@scrypted/server",
version
@@ -78,8 +78,9 @@ export async function checkServerUpdate(version: string, installEnvironment: str
// check if there is a new docker image available, using 'latest' tag
// this is done so newer server versions in npm are not immediately
// displayed until a docker image has been published
let response: AxiosResponse<any> = await axios.get("https://corsproxy.io?https://hub.docker.com/v2/namespaces/koush/repositories/scrypted/tags/latest");
const { data } = response;
// due to CORS restrictions, we query this through scrypted server
const mo: MediaObject = await mediaManager.createMediaObjectFromUrl('https://hub.docker.com/v2/namespaces/koush/repositories/scrypted/tags/latest');
const data: any = await mediaManager.convertMediaObjectToJSON(mo, 'application/json');
const imagePublished = new Date(data.last_updated);
console.log(`Latest docker image published ${imagePublished}`);

View File

@@ -169,7 +169,7 @@ export default {
const fs = 20;
const box = `<rect x="${x}" y="${y}" width="${w}" height="${h}" stroke="${s}" stroke-width="2" fill="none" />
const box = `<rect x="${x}" y="${y}" width="${w}" height="${h}" stroke="${s}" stroke-width="1px" fill="none" />
<text x="${x}" y="${y}" font-size="${fs}" dx="0.05em" dy="0.05em" fill="black">${t}</text>
<text x="${x}" y="${y}" font-size="${fs}" fill="white">${t}</text>
`;

View File

@@ -45,7 +45,7 @@ export default {
for (const detection of this.lastDetection.detections || []) {
if (!detection.boundingBox) continue;
const svgScale = this.svgWidth / 1080;
const sw = 6 * svgScale;
const sw = 1;
const s = "red";
const x = detection.boundingBox[0];
const y = detection.boundingBox[1];

View File

@@ -8,7 +8,6 @@
"name": "@scrypted/doorbird",
"version": "0.0.2",
"dependencies": {
"@koush/axios-digest-auth": "^0.8.5",
"doorbird": "^2.1.2"
},
"devDependencies": {
@@ -26,23 +25,24 @@
"dependencies": {
"@scrypted/sdk": "file:../sdk",
"@scrypted/server": "file:../server",
"http-auth-utils": "^3.0.2",
"http-auth-utils": "^5.0.1",
"node-fetch-commonjs": "^3.1.1",
"typescript": "^4.4.3"
"typescript": "^5.3.3"
},
"devDependencies": {
"@types/node": "^16.9.0"
"@types/node": "^20.10.8",
"ts-node": "^10.9.2"
}
},
"../../sdk": {
"name": "@scrypted/sdk",
"version": "0.2.103",
"version": "0.3.4",
"dev": true,
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.18.6",
"adm-zip": "^0.4.13",
"axios": "^0.21.4",
"axios": "^1.6.5",
"babel-loader": "^9.1.0",
"babel-plugin-const-enum": "^1.1.0",
"esbuild": "^0.15.9",
@@ -72,23 +72,6 @@
"typedoc": "^0.23.21"
}
},
"node_modules/@koush/axios-digest-auth": {
"version": "0.8.5",
"resolved": "https://registry.npmjs.org/@koush/axios-digest-auth/-/axios-digest-auth-0.8.5.tgz",
"integrity": "sha512-EZMM0gMJ3hMUD4EuUqSwP6UGt5Vmw2TZtY7Ypec55AnxkExSXM0ySgPtqkAcnL43g1R27yAg/dQL7dRTLMqO3Q==",
"dependencies": {
"auth-header": "^1.0.0",
"axios": "^0.21.4"
}
},
"node_modules/@koush/axios-digest-auth/node_modules/axios": {
"version": "0.21.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.21.4.tgz",
"integrity": "sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==",
"dependencies": {
"follow-redirects": "^1.14.0"
}
},
"node_modules/@scrypted/common": {
"resolved": "../../common",
"link": true
@@ -108,17 +91,12 @@
"resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
},
"node_modules/auth-header": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/auth-header/-/auth-header-1.0.0.tgz",
"integrity": "sha512-CPPazq09YVDUNNVWo4oSPTQmtwIzHusZhQmahCKvIsk0/xH6U3QsMAv3sM+7+Q0B1K2KJ/Q38OND317uXs4NHA=="
},
"node_modules/axios": {
"version": "1.3.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.3.5.tgz",
"integrity": "sha512-glL/PvG/E+xCWwV8S6nCHcrfg1exGx7vxyUIivIA1iL7BIh6bePylCfVHwp6k13ao7SATxB6imau2kqY+I67kw==",
"version": "1.6.5",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.5.tgz",
"integrity": "sha512-Ii012v05KEVuUoFWmMW/UQv9aRIc3ZwkWDcM+h5Il8izZCtRVpDUfwpoFf7eOtajT3QiGR4yDUx7lPqHJULgbg==",
"dependencies": {
"follow-redirects": "^1.15.0",
"follow-redirects": "^1.15.4",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
@@ -199,9 +177,9 @@
}
},
"node_modules/follow-redirects": {
"version": "1.15.2",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
"integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
"version": "1.15.4",
"resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.4.tgz",
"integrity": "sha512-Cr4D/5wlrb0z9dgERpUL3LrmPKVDsETIJhaCMeDfuFYcqa5bldGV6wBsAN6X/vxlXQtFBMrXdXxdL8CbDTGniw==",
"funding": [
{
"type": "individual",

Some files were not shown because too many files have changed in this diff Show More