Compare commits

..

No commits in common. "8135a3877040080b7a8500504d7379801c4f4290" and "cee8d6427c8ac5e8f009f807fe110048c9178ce1" have entirely different histories.

14 changed files with 143 additions and 310 deletions

View File

@ -37,7 +37,7 @@ jobs:
restore-keys: | restore-keys: |
jetkvm-cgo-${{ hashFiles('internal/native/cgo/**/*.c', 'internal/native/cgo/**/*.h', 'internal/native/cgo/**/*.patch', 'internal/native/cgo/**/*.txt', 'internal/native/cgo/**/*.sh', '!internal/native/cgo/build/**') }} jetkvm-cgo-${{ hashFiles('internal/native/cgo/**/*.c', 'internal/native/cgo/**/*.h', 'internal/native/cgo/**/*.patch', 'internal/native/cgo/**/*.txt', 'internal/native/cgo/**/*.sh', '!internal/native/cgo/build/**') }}
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v5
with: with:
node-version: "22" node-version: "22"
cache: "npm" cache: "npm"
@ -63,7 +63,7 @@ jobs:
with: with:
input: "testreport.json" input: "testreport.json"
- name: Upload artifact - name: Upload artifact
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v4
with: with:
name: jetkvm-app name: jetkvm-app
path: | path: |

View File

@ -165,7 +165,7 @@ jobs:
env: env:
CI_HOST: ${{ vars.JETKVM_CI_HOST }} CI_HOST: ${{ vars.JETKVM_CI_HOST }}
- name: Upload logs - name: Upload logs
uses: actions/upload-artifact@v5 uses: actions/upload-artifact@v4
with: with:
name: device-logs name: device-logs
path: | path: |

View File

@ -19,7 +19,7 @@ jobs:
- name: Checkout repository - name: Checkout repository
uses: actions/checkout@v5 uses: actions/checkout@v5
- name: Set up Node.js - name: Set up Node.js
uses: actions/setup-node@v6 uses: actions/setup-node@v5
with: with:
node-version: "22" node-version: "22"
cache: "npm" cache: "npm"

40
go.mod
View File

@ -4,27 +4,26 @@ go 1.24.4
require ( require (
github.com/Masterminds/semver/v3 v3.4.0 github.com/Masterminds/semver/v3 v3.4.0
github.com/beevik/ntp v1.5.0 github.com/beevik/ntp v1.4.3
github.com/coder/websocket v1.8.14 github.com/coder/websocket v1.8.14
github.com/coreos/go-oidc/v3 v3.16.0 github.com/coreos/go-oidc/v3 v3.15.0
github.com/creack/pty v1.1.24 github.com/creack/pty v1.1.24
github.com/erikdubbelboer/gspt v0.0.0-20210805194459-ce36a5128377 github.com/erikdubbelboer/gspt v0.0.0-20210805194459-ce36a5128377
github.com/fsnotify/fsnotify v1.9.0 github.com/fsnotify/fsnotify v1.9.0
github.com/gin-contrib/logger v1.2.6 github.com/gin-contrib/logger v1.2.6
github.com/gin-gonic/gin v1.10.1 github.com/gin-gonic/gin v1.10.1
github.com/go-co-op/gocron/v2 v2.17.0 github.com/go-co-op/gocron/v2 v2.16.6
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/guregu/null/v6 v6.0.0 github.com/guregu/null/v6 v6.0.0
github.com/gwatts/rootcerts v0.0.0-20250901182336-dc5ae18bd79f github.com/gwatts/rootcerts v0.0.0-20250901182336-dc5ae18bd79f
github.com/insomniacslk/dhcp v0.0.0-20250919081422-f80a1952f48e github.com/insomniacslk/dhcp v0.0.0-20250919081422-f80a1952f48e
github.com/mdlayher/ndp v1.1.0
github.com/pion/logging v0.2.4 github.com/pion/logging v0.2.4
github.com/pion/mdns/v2 v2.0.7 github.com/pion/mdns/v2 v2.0.7
github.com/pion/webrtc/v4 v4.1.6 github.com/pion/webrtc/v4 v4.1.4
github.com/pojntfx/go-nbd v0.3.2 github.com/pojntfx/go-nbd v0.3.2
github.com/prometheus/client_golang v1.23.2 github.com/prometheus/client_golang v1.23.2
github.com/prometheus/common v0.67.2 github.com/prometheus/common v0.66.1
github.com/prometheus/procfs v0.19.2 github.com/prometheus/procfs v0.17.0
github.com/psanford/httpreadat v0.1.0 github.com/psanford/httpreadat v0.1.0
github.com/rs/xid v1.6.0 github.com/rs/xid v1.6.0
github.com/rs/zerolog v1.34.0 github.com/rs/zerolog v1.34.0
@ -33,9 +32,9 @@ require (
github.com/vearutop/statigz v1.5.0 github.com/vearutop/statigz v1.5.0
github.com/vishvananda/netlink v1.3.1 github.com/vishvananda/netlink v1.3.1
go.bug.st/serial v1.6.4 go.bug.st/serial v1.6.4
golang.org/x/crypto v0.43.0 golang.org/x/crypto v0.42.0
golang.org/x/net v0.46.0 golang.org/x/net v0.44.0
golang.org/x/sys v0.37.0 golang.org/x/sys v0.36.0
) )
replace github.com/pojntfx/go-nbd v0.3.2 => github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b replace github.com/pojntfx/go-nbd v0.3.2 => github.com/chemhack/go-nbd v0.0.0-20241006125820-59e45f5b1e7b
@ -50,7 +49,7 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect github.com/davecgh/go-spew v1.1.1 // indirect
github.com/gabriel-vasile/mimetype v1.4.9 // indirect github.com/gabriel-vasile/mimetype v1.4.9 // indirect
github.com/gin-contrib/sse v1.1.0 // indirect github.com/gin-contrib/sse v1.1.0 // indirect
github.com/go-jose/go-jose/v4 v4.1.3 // indirect github.com/go-jose/go-jose/v4 v4.1.0 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.27.0 // indirect github.com/go-playground/validator/v10 v10.27.0 // indirect
@ -62,6 +61,7 @@ require (
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mdlayher/ndp v1.1.0 // indirect
github.com/mdlayher/packet v1.1.2 // indirect github.com/mdlayher/packet v1.1.2 // indirect
github.com/mdlayher/socket v0.4.1 // indirect github.com/mdlayher/socket v0.4.1 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
@ -73,15 +73,15 @@ require (
github.com/pion/datachannel v1.5.10 // indirect github.com/pion/datachannel v1.5.10 // indirect
github.com/pion/dtls/v3 v3.0.7 // indirect github.com/pion/dtls/v3 v3.0.7 // indirect
github.com/pion/ice/v4 v4.0.10 // indirect github.com/pion/ice/v4 v4.0.10 // indirect
github.com/pion/interceptor v0.1.41 // indirect github.com/pion/interceptor v0.1.40 // indirect
github.com/pion/randutil v0.1.0 // indirect github.com/pion/randutil v0.1.0 // indirect
github.com/pion/rtcp v1.2.15 // indirect github.com/pion/rtcp v1.2.15 // indirect
github.com/pion/rtp v1.8.23 // indirect github.com/pion/rtp v1.8.22 // indirect
github.com/pion/sctp v1.8.40 // indirect github.com/pion/sctp v1.8.39 // indirect
github.com/pion/sdp/v3 v3.0.16 // indirect github.com/pion/sdp/v3 v3.0.16 // indirect
github.com/pion/srtp/v3 v3.0.8 // indirect github.com/pion/srtp/v3 v3.0.7 // indirect
github.com/pion/stun/v3 v3.0.0 // indirect github.com/pion/stun/v3 v3.0.0 // indirect
github.com/pion/transport/v3 v3.0.8 // indirect github.com/pion/transport/v3 v3.0.7 // indirect
github.com/pion/turn/v4 v4.1.1 // indirect github.com/pion/turn/v4 v4.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_model v0.6.2 // indirect github.com/prometheus/client_model v0.6.2 // indirect
@ -92,11 +92,11 @@ require (
github.com/ugorji/go/codec v1.3.0 // indirect github.com/ugorji/go/codec v1.3.0 // indirect
github.com/vishvananda/netns v0.0.5 // indirect github.com/vishvananda/netns v0.0.5 // indirect
github.com/wlynxg/anet v0.0.5 // indirect github.com/wlynxg/anet v0.0.5 // indirect
go.yaml.in/yaml/v2 v2.4.3 // indirect go.yaml.in/yaml/v2 v2.4.2 // indirect
golang.org/x/arch v0.20.0 // indirect golang.org/x/arch v0.20.0 // indirect
golang.org/x/oauth2 v0.32.0 // indirect golang.org/x/oauth2 v0.30.0 // indirect
golang.org/x/sync v0.17.0 // indirect golang.org/x/sync v0.17.0 // indirect
golang.org/x/text v0.30.0 // indirect golang.org/x/text v0.29.0 // indirect
google.golang.org/protobuf v1.36.10 // indirect google.golang.org/protobuf v1.36.9 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )

80
go.sum
View File

@ -2,8 +2,8 @@ github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1
github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM=
github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA= github.com/andybalholm/brotli v1.1.1 h1:PR2pgnyFznKEugtsUo0xLdDop5SKXd5Qf5ysW+7XdTA=
github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA= github.com/andybalholm/brotli v1.1.1/go.mod h1:05ib4cKhjx3OQYUY22hTVd34Bc8upXjOLL2rKwwZBoA=
github.com/beevik/ntp v1.5.0 h1:y+uj/JjNwlY2JahivxYvtmv4ehfi3h74fAuABB9ZSM4= github.com/beevik/ntp v1.4.3 h1:PlbTvE5NNy4QHmA4Mg57n7mcFTmr1W1j3gcK7L1lqho=
github.com/beevik/ntp v1.5.0/go.mod h1:mJEhBrwT76w9D+IfOEGvuzyuudiW9E52U2BaTrMOYow= github.com/beevik/ntp v1.4.3/go.mod h1:Unr8Zg+2dRn7d8bHFuehIMSvvUYssHMxW3Q5Nx4RW5Q=
github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM=
github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw= github.com/beorn7/perks v1.0.1/go.mod h1:G2ZrVWU2WbWT9wwq4/hrbKbnv/1ERSJQ0ibhJ6rlkpw=
github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE= github.com/bool64/dev v0.2.39 h1:kP8DnMGlWXhGYJEZE/J0l/gVBdbuhoPGL+MJG4QbofE=
@ -20,8 +20,8 @@ github.com/cloudwego/base64x v0.1.6 h1:t11wG9AECkCDk5fMSoxmufanudBtJ+/HemLstXDLI
github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU= github.com/cloudwego/base64x v0.1.6/go.mod h1:OFcloc187FXDaYHvrNIjxSe8ncn0OOM8gEHfghB2IPU=
github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g= github.com/coder/websocket v1.8.14 h1:9L0p0iKiNOibykf283eHkKUHHrpG7f65OE3BhhO7v9g=
github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg= github.com/coder/websocket v1.8.14/go.mod h1:NX3SzP+inril6yawo5CQXx8+fk145lPDC6pumgx0mVg=
github.com/coreos/go-oidc/v3 v3.16.0 h1:qRQUCFstKpXwmEjDQTIbyY/5jF00+asXzSkmkoa/mow= github.com/coreos/go-oidc/v3 v3.15.0 h1:R6Oz8Z4bqWR7VFQ+sPSvZPQv4x8M+sJkDO5ojgwlyAg=
github.com/coreos/go-oidc/v3 v3.16.0/go.mod h1:wqPbKFrVnE90vty060SB40FCJ8fTHTxSwyXJqZH+sI8= github.com/coreos/go-oidc/v3 v3.15.0/go.mod h1:HaZ3szPaZ0e4r6ebqvsLWlk2Tn+aejfmrfah6hnSYEU=
github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc=
github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0= github.com/creack/goselect v0.1.2 h1:2DNy14+JPjRBgPzAd1thbQp4BSIihxcBf0IXhQXDRa0=
github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY= github.com/creack/goselect v0.1.2/go.mod h1:a/NhLweNvqIYMuxcMOuWY516Cimucms3DglDzQP3hKY=
@ -42,10 +42,10 @@ github.com/gin-contrib/sse v1.1.0 h1:n0w2GMuUpWDVp7qSpvze6fAu9iRxJY4Hmj6AmBOU05w
github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM= github.com/gin-contrib/sse v1.1.0/go.mod h1:hxRZ5gVpWMT7Z0B0gSNYqqsSCNIJMjzvm6fqCz9vjwM=
github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ= github.com/gin-gonic/gin v1.10.1 h1:T0ujvqyCSqRopADpgPgiTT63DUQVSfojyME59Ei63pQ=
github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y= github.com/gin-gonic/gin v1.10.1/go.mod h1:4PMNQiOhvDRa013RKVbsiNwoyezlm2rm0uX/T7kzp5Y=
github.com/go-co-op/gocron/v2 v2.17.0 h1:e/oj6fcAM8vOOKZxv2Cgfmjo+s8AXC46po5ZPtaSea4= github.com/go-co-op/gocron/v2 v2.16.6 h1:zI2Ya9sqvuLcgqJgV79LwoJXM8h20Z/drtB7ATbpRWo=
github.com/go-co-op/gocron/v2 v2.17.0/go.mod h1:Zii6he+Zfgy5W9B+JKk/KwejFOW0kZTFvHtwIpR4aBI= github.com/go-co-op/gocron/v2 v2.16.6/go.mod h1:zAfC/GFQ668qHxOVl/D68Jh5Ce7sDqX6TJnSQyRkRBc=
github.com/go-jose/go-jose/v4 v4.1.3 h1:CVLmWDhDVRa6Mi/IgCgaopNosCaHz7zrMeF9MlZRkrs= github.com/go-jose/go-jose/v4 v4.1.0 h1:cYSYxd3pw5zd2FSXk2vGdn9igQU2PS8MuxrCOCl0FdY=
github.com/go-jose/go-jose/v4 v4.1.3/go.mod h1:x4oUasVrzR7071A4TnHLGSPpNOm2a21K9Kf04k1rs08= github.com/go-jose/go-jose/v4 v4.1.0/go.mod h1:GG/vqmYm3Von2nYiB2vGTXzdoNKE5tix5tuc6iAd+sw=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@ -124,8 +124,8 @@ github.com/pion/dtls/v3 v3.0.7 h1:bItXtTYYhZwkPFk4t1n3Kkf5TDrfj6+4wG+CZR8uI9Q=
github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8= github.com/pion/dtls/v3 v3.0.7/go.mod h1:uDlH5VPrgOQIw59irKYkMudSFprY9IEFCqz/eTz16f8=
github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4= github.com/pion/ice/v4 v4.0.10 h1:P59w1iauC/wPk9PdY8Vjl4fOFL5B+USq1+xbDcN6gT4=
github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw= github.com/pion/ice/v4 v4.0.10/go.mod h1:y3M18aPhIxLlcO/4dn9X8LzLLSma84cx6emMSu14FGw=
github.com/pion/interceptor v0.1.41 h1:NpvX3HgWIukTf2yTBVjVGFXtpSpWgXjqz7IIpu7NsOw= github.com/pion/interceptor v0.1.40 h1:e0BjnPcGpr2CFQgKhrQisBU7V3GXK6wrfYrGYaU6Jq4=
github.com/pion/interceptor v0.1.41/go.mod h1:nEt4187unvRXJFyjiw00GKo+kIuXMWQI9K89fsosDLY= github.com/pion/interceptor v0.1.40/go.mod h1:Z6kqH7M/FYirg3frjGJ21VLSRJGBXB/KqaTIrdqnOic=
github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8= github.com/pion/logging v0.2.4 h1:tTew+7cmQ+Mc1pTBLKH2puKsOvhm32dROumOZ655zB8=
github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so= github.com/pion/logging v0.2.4/go.mod h1:DffhXTKYdNZU+KtJ5pyQDjvOAh/GsNSyv1lbkFbe3so=
github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM= github.com/pion/mdns/v2 v2.0.7 h1:c9kM8ewCgjslaAmicYMFQIde2H9/lrZpjBkN8VwoVtM=
@ -134,22 +134,22 @@ github.com/pion/randutil v0.1.0 h1:CFG1UdESneORglEsnimhUjf33Rwjubwj6xfiOXBa3mA=
github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8= github.com/pion/randutil v0.1.0/go.mod h1:XcJrSMMbbMRhASFVOlj/5hQial/Y8oH/HVo7TBZq+j8=
github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo= github.com/pion/rtcp v1.2.15 h1:LZQi2JbdipLOj4eBjK4wlVoQWfrZbh3Q6eHtWtJBZBo=
github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0= github.com/pion/rtcp v1.2.15/go.mod h1:jlGuAjHMEXwMUHK78RgX0UmEJFV4zUKOFHR7OP+D3D0=
github.com/pion/rtp v1.8.23 h1:kxX3bN4nM97DPrVBGq5I/Xcl332HnTHeP1Swx3/MCnU= github.com/pion/rtp v1.8.22 h1:8NCVDDF+uSJmMUkjLJVnIr/HX7gPesyMV1xFt5xozXc=
github.com/pion/rtp v1.8.23/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM= github.com/pion/rtp v1.8.22/go.mod h1:rF5nS1GqbR7H/TCpKwylzeq6yDM+MM6k+On5EgeThEM=
github.com/pion/sctp v1.8.40 h1:bqbgWYOrUhsYItEnRObUYZuzvOMsVplS3oNgzedBlG8= github.com/pion/sctp v1.8.39 h1:PJma40vRHa3UTO3C4MyeJDQ+KIobVYRZQZ0Nt7SjQnE=
github.com/pion/sctp v1.8.40/go.mod h1:SPBBUENXE6ThkEksN5ZavfAhFYll+h+66ZiG6IZQuzo= github.com/pion/sctp v1.8.39/go.mod h1:cNiLdchXra8fHQwmIoqw0MbLLMs+f7uQ+dGMG2gWebE=
github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo= github.com/pion/sdp/v3 v3.0.16 h1:0dKzYO6gTAvuLaAKQkC02eCPjMIi4NuAr/ibAwrGDCo=
github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo= github.com/pion/sdp/v3 v3.0.16/go.mod h1:9tyKzznud3qiweZcD86kS0ff1pGYB3VX+Bcsmkx6IXo=
github.com/pion/srtp/v3 v3.0.8 h1:RjRrjcIeQsilPzxvdaElN0CpuQZdMvcl9VZ5UY9suUM= github.com/pion/srtp/v3 v3.0.7 h1:QUElw0A/FUg3MP8/KNMZB3i0m8F9XeMnTum86F7S4bs=
github.com/pion/srtp/v3 v3.0.8/go.mod h1:2Sq6YnDH7/UDCvkSoHSDNDeyBcFgWL0sAVycVbAsXFg= github.com/pion/srtp/v3 v3.0.7/go.mod h1:qvnHeqbhT7kDdB+OGB05KA/P067G3mm7XBfLaLiaNF0=
github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw= github.com/pion/stun/v3 v3.0.0 h1:4h1gwhWLWuZWOJIJR9s2ferRO+W3zA/b6ijOI6mKzUw=
github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU= github.com/pion/stun/v3 v3.0.0/go.mod h1:HvCN8txt8mwi4FBvS3EmDghW6aQJ24T+y+1TKjB5jyU=
github.com/pion/transport/v3 v3.0.8 h1:oI3myyYnTKUSTthu/NZZ8eu2I5sHbxbUNNFW62olaYc= github.com/pion/transport/v3 v3.0.7 h1:iRbMH05BzSNwhILHoBoAPxoB9xQgOaJk+591KC9P1o0=
github.com/pion/transport/v3 v3.0.8/go.mod h1:+c2eewC5WJQHiAA46fkMMzoYZSuGzA/7E2FPrOYHctQ= github.com/pion/transport/v3 v3.0.7/go.mod h1:YleKiTZ4vqNxVwh77Z0zytYi7rXHl7j6uPLGhhz9rwo=
github.com/pion/turn/v4 v4.1.1 h1:9UnY2HB99tpDyz3cVVZguSxcqkJ1DsTSZ+8TGruh4fc= github.com/pion/turn/v4 v4.1.1 h1:9UnY2HB99tpDyz3cVVZguSxcqkJ1DsTSZ+8TGruh4fc=
github.com/pion/turn/v4 v4.1.1/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8= github.com/pion/turn/v4 v4.1.1/go.mod h1:2123tHk1O++vmjI5VSD0awT50NywDAq5A2NNNU4Jjs8=
github.com/pion/webrtc/v4 v4.1.6 h1:srHH2HwvCGwPba25EYJgUzgLqCQoXl1VCUnrGQMSzUw= github.com/pion/webrtc/v4 v4.1.4 h1:/gK1ACGHXQmtyVVbJFQDxNoODg4eSRiFLB7t9r9pg8M=
github.com/pion/webrtc/v4 v4.1.6/go.mod h1:wKecGRlkl3ox/As/MYghJL+b/cVXMEhoPMJWPuGQFhU= github.com/pion/webrtc/v4 v4.1.4/go.mod h1:Oab9npu1iZtQRMic3K3toYq5zFPvToe/QBw7dMI2ok4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
@ -157,10 +157,10 @@ github.com/prometheus/client_golang v1.23.2 h1:Je96obch5RDVy3FDMndoUsjAhG5Edi49h
github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg= github.com/prometheus/client_golang v1.23.2/go.mod h1:Tb1a6LWHB3/SPIzCoaDXI4I8UHKeFTEQ1YCr+0Gyqmg=
github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk= github.com/prometheus/client_model v0.6.2 h1:oBsgwpGs7iVziMvrGhE53c/GrLUsZdHnqNwqPLxwZyk=
github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE= github.com/prometheus/client_model v0.6.2/go.mod h1:y3m2F6Gdpfy6Ut/GBsUqTWZqCUvMVzSfMLjcu6wAwpE=
github.com/prometheus/common v0.67.2 h1:PcBAckGFTIHt2+L3I33uNRTlKTplNzFctXcWhPyAEN8= github.com/prometheus/common v0.66.1 h1:h5E0h5/Y8niHc5DlaLlWLArTQI7tMrsfQjHV+d9ZoGs=
github.com/prometheus/common v0.67.2/go.mod h1:63W3KZb1JOKgcjlIr64WW/LvFGAqKPj0atm+knVGEko= github.com/prometheus/common v0.66.1/go.mod h1:gcaUsgf3KfRSwHY4dIMXLPV0K/Wg1oZ8+SbZk/HH/dA=
github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= github.com/prometheus/procfs v0.17.0 h1:FuLQ+05u4ZI+SS/w9+BWEM2TXiHKsUQ9TADiRH7DuK0=
github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= github.com/prometheus/procfs v0.17.0/go.mod h1:oPQLaDAMRbA+u8H5Pbfq+dl3VDAvHxMUOVhe0wYB2zw=
github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE= github.com/psanford/httpreadat v0.1.0 h1:VleW1HS2zO7/4c7c7zNl33fO6oYACSagjJIyMIwZLUE=
github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE= github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE=
github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs= github.com/robfig/cron/v3 v3.0.1 h1:WdRxkvbJztn8LMz/QEvLN5sBU+xKpSqwwUO1Pjr4qDs=
@ -202,16 +202,16 @@ go.bug.st/serial v1.6.4 h1:7FmqNPgVp3pu2Jz5PoPtbZ9jJO5gnEnZIvnI1lzve8A=
go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI= go.bug.st/serial v1.6.4/go.mod h1:nofMJxTeNVny/m6+KaafC6vJGj3miwQZ6vW4BZUGJPI=
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE=
go.yaml.in/yaml/v2 v2.4.3 h1:6gvOSjQoTB3vt1l+CU+tSyi/HOjfOjRLJ4YwYZGwRO0= go.yaml.in/yaml/v2 v2.4.2 h1:DzmwEr2rDGHl7lsFgAHxmNz/1NlQ7xLIrlN2h5d1eGI=
go.yaml.in/yaml/v2 v2.4.3/go.mod h1:zSxWcmIDjOzPXpjlTTbAsKokqkDNAVtZO0WOMiT90s8= go.yaml.in/yaml/v2 v2.4.2/go.mod h1:081UH+NErpNdqlCXm3TtEran0rJZGxAYx9hb/ELlsPU=
golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c= golang.org/x/arch v0.20.0 h1:dx1zTU0MAE98U+TQ8BLl7XsJbgze2WnNKF/8tGp/Q6c=
golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk= golang.org/x/arch v0.20.0/go.mod h1:bdwinDaKcfZUGpH09BB7ZmOfhalA8lQdzl62l8gGWsk=
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04= golang.org/x/crypto v0.42.0 h1:chiH31gIWm57EkTXpwnqf8qeuMUi0yekh6mT2AvFlqI=
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0= golang.org/x/crypto v0.42.0/go.mod h1:4+rDnOTJhQCx2q7/j6rAN5XDw8kPjeaXEUR2eL94ix8=
golang.org/x/net v0.46.0 h1:giFlY12I07fugqwPuWJi68oOnpfqFnJIJzaIIm2JVV4= golang.org/x/net v0.44.0 h1:evd8IRDyfNBMBTTY5XRF1vaZlD+EmWx6x8PkhR04H/I=
golang.org/x/net v0.46.0/go.mod h1:Q9BGdFy1y4nkUwiLvT5qtyhAnEHgnQ/zd8PfU6nc210= golang.org/x/net v0.44.0/go.mod h1:ECOoLqd5U3Lhyeyo/QDCEVQ4sNgYsqvCZ722XogGieY=
golang.org/x/oauth2 v0.32.0 h1:jsCblLleRMDrxMN29H3z/k1KliIvpLgCkE6R8FXXNgY= golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI=
golang.org/x/oauth2 v0.32.0/go.mod h1:lzm5WQJQwKZ3nwavOZ3IS5Aulzxi68dUSgRHujetwEA= golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU=
golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug= golang.org/x/sync v0.17.0 h1:l60nONMj9l5drqw6jlhIELNv9I0A4OFgRsG9k2oT9Ug=
golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI=
golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220622161953-175b2fd9d664/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
@ -220,14 +220,14 @@ golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.10.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ= golang.org/x/sys v0.36.0 h1:KVRy2GtZBrk1cBYA7MKu5bEZFxQk4NIDV6RLVcC8o0k=
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.36.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q= golang.org/x/term v0.35.0 h1:bZBVKBudEyhRcajGcNc3jIfWPqV4y/Kt2XcoigOWtDQ=
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss= golang.org/x/term v0.35.0/go.mod h1:TPGtkTLesOwf2DE8CgVYiZinHAOuy5AYUYT1lENIZnA=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k= golang.org/x/text v0.29.0 h1:1neNs90w9YzJ9BocxfsQNHKuAT4pkghyXc4nhZ6sJvk=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM= golang.org/x/text v0.29.0/go.mod h1:7MhJOA9CD2qZyOKYazxdYMF85OwPdEr9jTtBpO7ydH4=
google.golang.org/protobuf v1.36.10 h1:AYd7cD/uASjIL6Q9LiTjz8JLcrh/88q5UObnmY3aOOE= google.golang.org/protobuf v1.36.9 h1:w2gp2mA27hUeUzj9Ex9FBjsBm40zfaDtEWow293U7Iw=
google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= google.golang.org/protobuf v1.36.9/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=

112
ui/package-lock.json generated
View File

@ -31,7 +31,7 @@
"react-hook-form": "^7.65.0", "react-hook-form": "^7.65.0",
"react-hot-toast": "^2.6.0", "react-hot-toast": "^2.6.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-router": "^7.9.5", "react-router": "^7.9.4",
"react-simple-keyboard": "^3.8.131", "react-simple-keyboard": "^3.8.131",
"react-use-websocket": "^4.13.0", "react-use-websocket": "^4.13.0",
"react-xtermjs": "^1.0.10", "react-xtermjs": "^1.0.10",
@ -42,9 +42,9 @@
"zustand": "^4.5.2" "zustand": "^4.5.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.4.1", "@eslint/compat": "^1.4.0",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.39.0", "@eslint/js": "^9.38.0",
"@inlang/cli": "^3.0.12", "@inlang/cli": "^3.0.12",
"@inlang/paraglide-js": "^2.4.0", "@inlang/paraglide-js": "^2.4.0",
"@inlang/plugin-m-function-matcher": "^2.1.0", "@inlang/plugin-m-function-matcher": "^2.1.0",
@ -799,13 +799,13 @@
} }
}, },
"node_modules/@eslint/compat": { "node_modules/@eslint/compat": {
"version": "1.4.1", "version": "1.4.0",
"resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.4.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/compat/-/compat-1.4.0.tgz",
"integrity": "sha512-cfO82V9zxxGBxcQDr1lfaYB7wykTa0b00mGa36FrJl7iTFd0Z2cHfEYuxcBRP/iNijCsWsEkA+jzT8hGYmv33w==", "integrity": "sha512-DEzm5dKeDBPm3r08Ixli/0cmxr8LkRdwxMRUIJBlSCpAwSrvFEJpVBzV+66JhDxiaqKxnRzCXhtiMiczF7Hglg==",
"dev": true, "dev": true,
"license": "Apache-2.0", "license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/core": "^0.17.0" "@eslint/core": "^0.16.0"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -819,19 +819,6 @@
} }
} }
}, },
"node_modules/@eslint/compat/node_modules/@eslint/core": {
"version": "0.17.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.17.0.tgz",
"integrity": "sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@eslint/config-array": { "node_modules/@eslint/config-array": {
"version": "0.21.1", "version": "0.21.1",
"resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz", "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.21.1.tgz",
@ -906,10 +893,9 @@
} }
}, },
"node_modules/@eslint/js": { "node_modules/@eslint/js": {
"version": "9.39.0", "version": "9.38.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.39.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz",
"integrity": "sha512-BIhe0sW91JGPiaF1mOuPy5v8NflqfjIcDNpC+LbW9f609WVRX1rArrhi6Z2ymvrAry9jw+5POTj4t2t62o8Bmw==", "integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==",
"dev": true,
"license": "MIT", "license": "MIT",
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
@ -2212,66 +2198,6 @@
"node": ">=14.0.0" "node": ">=14.0.0"
} }
}, },
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
"version": "1.5.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/wasi-threads": "1.1.0",
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
"version": "1.5.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
"version": "1.1.0",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
"version": "1.0.7",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"@emnapi/core": "^1.5.0",
"@emnapi/runtime": "^1.5.0",
"@tybys/wasm-util": "^0.10.1"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
"version": "0.10.1",
"dev": true,
"inBundle": true,
"license": "MIT",
"optional": true,
"dependencies": {
"tslib": "^2.4.0"
}
},
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
"version": "2.8.1",
"dev": true,
"inBundle": true,
"license": "0BSD",
"optional": true
},
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": { "node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
"version": "4.1.16", "version": "4.1.16",
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.16.tgz", "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.1.16.tgz",
@ -4342,18 +4268,6 @@
"url": "https://opencollective.com/eslint" "url": "https://opencollective.com/eslint"
} }
}, },
"node_modules/eslint/node_modules/@eslint/js": {
"version": "9.38.0",
"resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.38.0.tgz",
"integrity": "sha512-UZ1VpFvXf9J06YG9xQBdnzU+kthors6KjhMAl6f4gH4usHyh31rUf2DLGInT8RFYIReYXNSydgPY0V2LuWgl7A==",
"license": "MIT",
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
},
"funding": {
"url": "https://eslint.org/donate"
}
},
"node_modules/eslint/node_modules/eslint-visitor-keys": { "node_modules/eslint/node_modules/eslint-visitor-keys": {
"version": "4.2.1", "version": "4.2.1",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.1.tgz",
@ -6605,9 +6519,9 @@
} }
}, },
"node_modules/react-router": { "node_modules/react-router": {
"version": "7.9.5", "version": "7.9.4",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.5.tgz", "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.9.4.tgz",
"integrity": "sha512-JmxqrnBZ6E9hWmf02jzNn9Jm3UqyeimyiwzD69NjxGySG6lIz/1LVPsoTCwN7NBX2XjCEa1LIX5EMz1j2b6u6A==", "integrity": "sha512-SD3G8HKviFHg9xj7dNODUKDFgpG4xqD5nhyd0mYoB5iISepuZAvzSr8ywxgxKJ52yRzf/HWtVHc9AWwoTbljvA==",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"cookie": "^1.0.1", "cookie": "^1.0.1",

View File

@ -50,7 +50,7 @@
"react-hook-form": "^7.65.0", "react-hook-form": "^7.65.0",
"react-hot-toast": "^2.6.0", "react-hot-toast": "^2.6.0",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-router": "^7.9.5", "react-router": "^7.9.4",
"react-simple-keyboard": "^3.8.131", "react-simple-keyboard": "^3.8.131",
"react-use-websocket": "^4.13.0", "react-use-websocket": "^4.13.0",
"react-xtermjs": "^1.0.10", "react-xtermjs": "^1.0.10",
@ -61,9 +61,9 @@
"zustand": "^4.5.2" "zustand": "^4.5.2"
}, },
"devDependencies": { "devDependencies": {
"@eslint/compat": "^1.4.1", "@eslint/compat": "^1.4.0",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.39.0", "@eslint/js": "^9.38.0",
"@inlang/cli": "^3.0.12", "@inlang/cli": "^3.0.12",
"@inlang/paraglide-js": "^2.4.0", "@inlang/paraglide-js": "^2.4.0",
"@inlang/plugin-m-function-matcher": "^2.1.0", "@inlang/plugin-m-function-matcher": "^2.1.0",

View File

@ -116,7 +116,7 @@ export interface RTCState {
peerConnection: RTCPeerConnection | null; peerConnection: RTCPeerConnection | null;
setPeerConnection: (pc: RTCState["peerConnection"]) => void; setPeerConnection: (pc: RTCState["peerConnection"]) => void;
setRpcDataChannel: (channel: RTCDataChannel | null) => void; setRpcDataChannel: (channel: RTCDataChannel) => void;
rpcDataChannel: RTCDataChannel | null; rpcDataChannel: RTCDataChannel | null;
hidRpcDisabled: boolean; hidRpcDisabled: boolean;
@ -178,42 +178,41 @@ export const useRTCStore = create<RTCState>(set => ({
setPeerConnection: (pc: RTCState["peerConnection"]) => set({ peerConnection: pc }), setPeerConnection: (pc: RTCState["peerConnection"]) => set({ peerConnection: pc }),
rpcDataChannel: null, rpcDataChannel: null,
setRpcDataChannel: channel => set({ rpcDataChannel: channel }), setRpcDataChannel: (channel: RTCDataChannel) => set({ rpcDataChannel: channel }),
hidRpcDisabled: false, hidRpcDisabled: false,
setHidRpcDisabled: disabled => set({ hidRpcDisabled: disabled }), setHidRpcDisabled: (disabled: boolean) => set({ hidRpcDisabled: disabled }),
rpcHidProtocolVersion: null, rpcHidProtocolVersion: null,
setRpcHidProtocolVersion: version => set({ rpcHidProtocolVersion: version }), setRpcHidProtocolVersion: (version: number | null) => set({ rpcHidProtocolVersion: version }),
rpcHidChannel: null, rpcHidChannel: null,
setRpcHidChannel: channel => set({ rpcHidChannel: channel }), setRpcHidChannel: (channel: RTCDataChannel) => set({ rpcHidChannel: channel }),
rpcHidUnreliableChannel: null, rpcHidUnreliableChannel: null,
setRpcHidUnreliableChannel: channel => set({ rpcHidUnreliableChannel: channel }), setRpcHidUnreliableChannel: (channel: RTCDataChannel) => set({ rpcHidUnreliableChannel: channel }),
rpcHidUnreliableNonOrderedChannel: null, rpcHidUnreliableNonOrderedChannel: null,
setRpcHidUnreliableNonOrderedChannel: channel => setRpcHidUnreliableNonOrderedChannel: (channel: RTCDataChannel) => set({ rpcHidUnreliableNonOrderedChannel: channel }),
set({ rpcHidUnreliableNonOrderedChannel: channel }),
transceiver: null, transceiver: null,
setTransceiver: transceiver => set({ transceiver }), setTransceiver: (transceiver: RTCRtpTransceiver) => set({ transceiver }),
peerConnectionState: null, peerConnectionState: null,
setPeerConnectionState: state => set({ peerConnectionState: state }), setPeerConnectionState: (state: RTCPeerConnectionState) => set({ peerConnectionState: state }),
mediaStream: null, mediaStream: null,
setMediaStream: stream => set({ mediaStream: stream }), setMediaStream: (stream: MediaStream) => set({ mediaStream: stream }),
videoStreamStats: null, videoStreamStats: null,
appendVideoStreamStats: stats => set({ videoStreamStats: stats }), appendVideoStreamStats: (stats: RTCInboundRtpStreamStats) => set({ videoStreamStats: stats }),
videoStreamStatsHistory: new Map(), videoStreamStatsHistory: new Map(),
isTurnServerInUse: false, isTurnServerInUse: false,
setTurnServerInUse: inUse => set({ isTurnServerInUse: inUse }), setTurnServerInUse: (inUse: boolean) => set({ isTurnServerInUse: inUse }),
inboundRtpStats: new Map(), inboundRtpStats: new Map(),
appendInboundRtpStats: stats => { appendInboundRtpStats: (stats: RTCInboundRtpStreamStats) => {
set(prevState => ({ set(prevState => ({
inboundRtpStats: appendStatToMap(stats, prevState.inboundRtpStats), inboundRtpStats: appendStatToMap(stats, prevState.inboundRtpStats),
})); }));
@ -221,7 +220,7 @@ export const useRTCStore = create<RTCState>(set => ({
clearInboundRtpStats: () => set({ inboundRtpStats: new Map() }), clearInboundRtpStats: () => set({ inboundRtpStats: new Map() }),
candidatePairStats: new Map(), candidatePairStats: new Map(),
appendCandidatePairStats: stats => { appendCandidatePairStats: (stats: RTCIceCandidatePairStats) => {
set(prevState => ({ set(prevState => ({
candidatePairStats: appendStatToMap(stats, prevState.candidatePairStats), candidatePairStats: appendStatToMap(stats, prevState.candidatePairStats),
})); }));
@ -229,21 +228,21 @@ export const useRTCStore = create<RTCState>(set => ({
clearCandidatePairStats: () => set({ candidatePairStats: new Map() }), clearCandidatePairStats: () => set({ candidatePairStats: new Map() }),
localCandidateStats: new Map(), localCandidateStats: new Map(),
appendLocalCandidateStats: stats => { appendLocalCandidateStats: (stats: RTCIceCandidateStats) => {
set(prevState => ({ set(prevState => ({
localCandidateStats: appendStatToMap(stats, prevState.localCandidateStats), localCandidateStats: appendStatToMap(stats, prevState.localCandidateStats),
})); }));
}, },
remoteCandidateStats: new Map(), remoteCandidateStats: new Map(),
appendRemoteCandidateStats: stats => { appendRemoteCandidateStats: (stats: RTCIceCandidateStats) => {
set(prevState => ({ set(prevState => ({
remoteCandidateStats: appendStatToMap(stats, prevState.remoteCandidateStats), remoteCandidateStats: appendStatToMap(stats, prevState.remoteCandidateStats),
})); }));
}, },
diskDataChannelStats: new Map(), diskDataChannelStats: new Map(),
appendDiskDataChannelStats: stats => { appendDiskDataChannelStats: (stats: RTCDataChannelStats) => {
set(prevState => ({ set(prevState => ({
diskDataChannelStats: appendStatToMap(stats, prevState.diskDataChannelStats), diskDataChannelStats: appendStatToMap(stats, prevState.diskDataChannelStats),
})); }));
@ -251,7 +250,7 @@ export const useRTCStore = create<RTCState>(set => ({
// Add these new properties to the store implementation // Add these new properties to the store implementation
terminalChannel: null, terminalChannel: null,
setTerminalChannel: channel => set({ terminalChannel: channel }), setTerminalChannel: (channel: RTCDataChannel) => set({ terminalChannel: channel }),
})); }));
export interface MouseMove { export interface MouseMove {
@ -271,20 +270,12 @@ export interface MouseState {
export const useMouseStore = create<MouseState>(set => ({ export const useMouseStore = create<MouseState>(set => ({
mouseX: 0, mouseX: 0,
mouseY: 0, mouseY: 0,
setMouseMove: move => set({ mouseMove: move }), setMouseMove: (move?: MouseMove) => set({ mouseMove: move }),
setMousePosition: (x, y) => set({ mouseX: x, mouseY: y }), setMousePosition: (x: number, y: number) => set({ mouseX: x, mouseY: y }),
})); }));
export type HdmiStates = export type HdmiStates = "ready" | "no_signal" | "no_lock" | "out_of_range" | "connecting";
| "ready" export type HdmiErrorStates = Extract<VideoState["hdmiState"], "no_signal" | "no_lock" | "out_of_range">
| "no_signal"
| "no_lock"
| "out_of_range"
| "connecting";
export type HdmiErrorStates = Extract<
VideoState["hdmiState"],
"no_signal" | "no_lock" | "out_of_range"
>;
export interface HdmiState { export interface HdmiState {
ready: boolean; ready: boolean;
@ -299,7 +290,10 @@ export interface VideoState {
setClientSize: (width: number, height: number) => void; setClientSize: (width: number, height: number) => void;
setSize: (width: number, height: number) => void; setSize: (width: number, height: number) => void;
hdmiState: HdmiStates; hdmiState: HdmiStates;
setHdmiState: (state: { ready: boolean; error?: HdmiErrorStates }) => void; setHdmiState: (state: {
ready: boolean;
error?: HdmiErrorStates;
}) => void;
} }
export const useVideoStore = create<VideoState>(set => ({ export const useVideoStore = create<VideoState>(set => ({
@ -310,8 +304,7 @@ export const useVideoStore = create<VideoState>(set => ({
clientHeight: 0, clientHeight: 0,
// The video element's client size // The video element's client size
setClientSize: (clientWidth: number, clientHeight: number) => setClientSize: (clientWidth: number, clientHeight: number) => set({ clientWidth, clientHeight }),
set({ clientWidth, clientHeight }),
// Resolution // Resolution
setSize: (width: number, height: number) => set({ width, height }), setSize: (width: number, height: number) => set({ width, height }),
@ -458,15 +451,13 @@ export interface MountMediaState {
export const useMountMediaStore = create<MountMediaState>(set => ({ export const useMountMediaStore = create<MountMediaState>(set => ({
remoteVirtualMediaState: null, remoteVirtualMediaState: null,
setRemoteVirtualMediaState: (state: MountMediaState["remoteVirtualMediaState"]) => setRemoteVirtualMediaState: (state: MountMediaState["remoteVirtualMediaState"]) => set({ remoteVirtualMediaState: state }),
set({ remoteVirtualMediaState: state }),
modalView: "mode", modalView: "mode",
setModalView: (view: MountMediaState["modalView"]) => set({ modalView: view }), setModalView: (view: MountMediaState["modalView"]) => set({ modalView: view }),
isMountMediaDialogOpen: false, isMountMediaDialogOpen: false,
setIsMountMediaDialogOpen: (isOpen: MountMediaState["isMountMediaDialogOpen"]) => setIsMountMediaDialogOpen: (isOpen: MountMediaState["isMountMediaDialogOpen"]) => set({ isMountMediaDialogOpen: isOpen }),
set({ isMountMediaDialogOpen: isOpen }),
uploadedFiles: [], uploadedFiles: [],
addUploadedFile: (file: { name: string; size: string; uploadedAt: string }) => addUploadedFile: (file: { name: string; size: string; uploadedAt: string }) =>
@ -483,7 +474,7 @@ export interface KeyboardLedState {
compose: boolean; compose: boolean;
kana: boolean; kana: boolean;
shift: boolean; // Optional, as not all keyboards have a shift LED shift: boolean; // Optional, as not all keyboards have a shift LED
} };
export const hidKeyBufferSize = 6; export const hidKeyBufferSize = 6;
export const hidErrorRollOver = 0x01; export const hidErrorRollOver = 0x01;
@ -518,23 +509,14 @@ export interface HidState {
} }
export const useHidStore = create<HidState>(set => ({ export const useHidStore = create<HidState>(set => ({
keyboardLedState: { keyboardLedState: { num_lock: false, caps_lock: false, scroll_lock: false, compose: false, kana: false, shift: false } as KeyboardLedState,
num_lock: false, setKeyboardLedState: (ledState: KeyboardLedState): void => set({ keyboardLedState: ledState }),
caps_lock: false,
scroll_lock: false,
compose: false,
kana: false,
shift: false,
} as KeyboardLedState,
setKeyboardLedState: (ledState: KeyboardLedState): void =>
set({ keyboardLedState: ledState }),
keysDownState: { modifier: 0, keys: [0, 0, 0, 0, 0, 0] } as KeysDownState, keysDownState: { modifier: 0, keys: [0, 0, 0, 0, 0, 0] } as KeysDownState,
setKeysDownState: (state: KeysDownState): void => set({ keysDownState: state }), setKeysDownState: (state: KeysDownState): void => set({ keysDownState: state }),
isVirtualKeyboardEnabled: false, isVirtualKeyboardEnabled: false,
setVirtualKeyboardEnabled: (enabled: boolean): void => setVirtualKeyboardEnabled: (enabled: boolean): void => set({ isVirtualKeyboardEnabled: enabled }),
set({ isVirtualKeyboardEnabled: enabled }),
isPasteInProgress: false, isPasteInProgress: false,
setPasteModeEnabled: (enabled: boolean): void => set({ isPasteInProgress: enabled }), setPasteModeEnabled: (enabled: boolean): void => set({ isPasteInProgress: enabled }),
@ -586,7 +568,7 @@ export interface OtaState {
systemUpdateProgress: number; systemUpdateProgress: number;
systemUpdatedAt: string | null; systemUpdatedAt: string | null;
} };
export interface UpdateState { export interface UpdateState {
isUpdatePending: boolean; isUpdatePending: boolean;
@ -598,7 +580,7 @@ export interface UpdateState {
otaState: OtaState; otaState: OtaState;
setOtaState: (state: OtaState) => void; setOtaState: (state: OtaState) => void;
modalView: UpdateModalViews; modalView: UpdateModalViews
setModalView: (view: UpdateModalViews) => void; setModalView: (view: UpdateModalViews) => void;
updateErrorMessage: string | null; updateErrorMessage: string | null;
@ -638,11 +620,12 @@ export const useUpdateStore = create<UpdateState>(set => ({
setModalView: (view: UpdateModalViews) => set({ modalView: view }), setModalView: (view: UpdateModalViews) => set({ modalView: view }),
updateErrorMessage: null, updateErrorMessage: null,
setUpdateErrorMessage: (errorMessage: string) => setUpdateErrorMessage: (errorMessage: string) => set({ updateErrorMessage: errorMessage }),
set({ updateErrorMessage: errorMessage }),
})); }));
export type UsbConfigModalViews = "updateUsbConfig" | "updateUsbConfigSuccess"; export type UsbConfigModalViews =
| "updateUsbConfig"
| "updateUsbConfigSuccess";
export interface UsbConfigModalState { export interface UsbConfigModalState {
modalView: UsbConfigModalViews; modalView: UsbConfigModalViews;
@ -1007,5 +990,5 @@ export const useMacrosStore = create<MacrosState>((set, get) => ({
} finally { } finally {
set({ loading: false }); set({ loading: false });
} }
}, }
})); }));

View File

@ -12,7 +12,6 @@ import { TextAreaWithLabel } from "@components/TextArea";
import { isOnDevice } from "@/main"; import { isOnDevice } from "@/main";
import notifications from "@/notifications"; import notifications from "@/notifications";
import { m } from "@localizations/messages.js"; import { m } from "@localizations/messages.js";
import { sleep } from "@/utils";
export default function SettingsAdvancedRoute() { export default function SettingsAdvancedRoute() {
const { send } = useJsonRpc(); const { send } = useJsonRpc();
@ -312,10 +311,8 @@ export default function SettingsAdvancedRoute() {
size="SM" size="SM"
theme="light" theme="light"
text={m.advanced_reset_config_button()} text={m.advanced_reset_config_button()}
onClick={async () => { onClick={() => {
handleResetConfig(); handleResetConfig();
// Add 2s delay between resetting the configuration and calling reload() to prevent reload from interrupting the RPC call to reset things.
await sleep(2000);
window.location.reload(); window.location.reload();
}} }}
/> />

View File

@ -4,16 +4,13 @@ import { useNavigate } from "react-router";
import { useJsonRpc } from "@hooks/useJsonRpc"; import { useJsonRpc } from "@hooks/useJsonRpc";
import { Button } from "@components/Button"; import { Button } from "@components/Button";
import { m } from "@localizations/messages.js"; import { m } from "@localizations/messages.js";
import { sleep } from "@/utils";
export default function SettingsGeneralRebootRoute() { export default function SettingsGeneralRebootRoute() {
const navigate = useNavigate(); const navigate = useNavigate();
const { send } = useJsonRpc(); const { send } = useJsonRpc();
const onClose = useCallback(async () => { const onClose = useCallback(() => {
navigate(".."); // back to the devices.$id.settings page navigate(".."); // back to the devices.$id.settings page
// Add 1s delay between navigation and calling reload() to prevent reload from interrupting the navigation.
await sleep(1000);
window.location.reload(); // force a full reload to ensure the current device/cloud UI version is loaded window.location.reload(); // force a full reload to ensure the current device/cloud UI version is loaded
}, [navigate]); }, [navigate]);

View File

@ -21,10 +21,8 @@ export default function SettingsGeneralUpdateRoute() {
const { setModalView, otaState } = useUpdateStore(); const { setModalView, otaState } = useUpdateStore();
const { send } = useJsonRpc(); const { send } = useJsonRpc();
const onClose = useCallback(async () => { const onClose = useCallback(() => {
navigate(".."); // back to the devices.$id.settings page navigate(".."); // back to the devices.$id.settings page
// Add 1s delay between navigation and calling reload() to prevent reload from interrupting the navigation.
await sleep(1000);
window.location.reload(); // force a full reload to ensure the current device/cloud UI version is loaded window.location.reload(); // force a full reload to ensure the current device/cloud UI version is loaded
}, [navigate]); }, [navigate]);

View File

@ -557,9 +557,8 @@ export default function KvmIdRoute() {
clearCandidatePairStats(); clearCandidatePairStats();
setSidebarView(null); setSidebarView(null);
setPeerConnection(null); setPeerConnection(null);
setRpcDataChannel(null);
}; };
}, [clearCandidatePairStats, clearInboundRtpStats, setPeerConnection, setSidebarView, setRpcDataChannel]); }, [clearCandidatePairStats, clearInboundRtpStats, setPeerConnection, setSidebarView]);
// TURN server usage detection // TURN server usage detection
useEffect(() => { useEffect(() => {

View File

@ -24,47 +24,17 @@ export interface JsonRpcCallResponse<T = unknown> {
let rpcCallCounter = 0; let rpcCallCounter = 0;
// Helper: wait for RTC data channel to be ready // Helper: wait for RTC data channel to be ready
// This waits indefinitely for the channel to be ready, only aborting via the signal
// Throws if the channel instance changed while waiting (stale connection detected)
async function waitForRtcReady(signal: AbortSignal): Promise<RTCDataChannel> { async function waitForRtcReady(signal: AbortSignal): Promise<RTCDataChannel> {
const pollInterval = 100; const pollInterval = 100;
let lastSeenChannel: RTCDataChannel | null = null;
while (!signal.aborted) { while (!signal.aborted) {
const state = useRTCStore.getState(); const state = useRTCStore.getState();
const currentChannel = state.rpcDataChannel; if (state.rpcDataChannel?.readyState === "open") {
return state.rpcDataChannel;
// Channel instance changed (new connection replaced old one)
if (lastSeenChannel && currentChannel && lastSeenChannel !== currentChannel) {
console.debug("[waitForRtcReady] Channel instance changed, aborting wait");
throw new Error("RTC connection changed while waiting for readiness");
} }
// Channel was removed from store (connection closed)
if (lastSeenChannel && !currentChannel) {
console.debug("[waitForRtcReady] Channel was removed from store, aborting wait");
throw new Error("RTC connection was closed while waiting for readiness");
}
// No channel yet, keep waiting
if (!currentChannel) {
await sleep(pollInterval);
continue;
}
// Track this channel instance
lastSeenChannel = currentChannel;
// Channel is ready!
if (currentChannel.readyState === "open") {
return currentChannel;
}
await sleep(pollInterval); await sleep(pollInterval);
} }
// Signal was aborted for some reason
console.debug("[waitForRtcReady] Aborted via signal");
throw new Error("RTC readiness check aborted"); throw new Error("RTC readiness check aborted");
} }
@ -127,26 +97,25 @@ export async function callJsonRpc<T = unknown>(
const timeout = options.attemptTimeoutMs || 5000; const timeout = options.attemptTimeoutMs || 5000;
for (let attempt = 0; attempt < maxAttempts; attempt++) { for (let attempt = 0; attempt < maxAttempts; attempt++) {
const abortController = new AbortController();
const timeoutId = setTimeout(() => abortController.abort(), timeout);
// Exponential backoff for retries that starts at 500ms up to a maximum of 10 seconds // Exponential backoff for retries that starts at 500ms up to a maximum of 10 seconds
const backoffMs = Math.min(500 * Math.pow(2, attempt), 10000); const backoffMs = Math.min(500 * Math.pow(2, attempt), 10000);
let timeoutId: ReturnType<typeof setTimeout> | null = null;
try { try {
// Wait for RTC readiness without timeout - this allows time for WebRTC to connect // Wait for RTC readiness
const readyAbortController = new AbortController(); const rpcDataChannel = await waitForRtcReady(abortController.signal);
const rpcDataChannel = await waitForRtcReady(readyAbortController.signal);
// Now apply timeout only to the actual RPC request/response
const rpcAbortController = new AbortController();
timeoutId = setTimeout(() => rpcAbortController.abort(), timeout);
// Send RPC request and wait for response // Send RPC request and wait for response
const response = await sendRpcRequest<T>( const response = await sendRpcRequest<T>(
rpcDataChannel, rpcDataChannel,
options, options,
rpcAbortController.signal, abortController.signal,
); );
clearTimeout(timeoutId);
// Retry on error if attempts remain // Retry on error if attempts remain
if (response.error && attempt < maxAttempts - 1) { if (response.error && attempt < maxAttempts - 1) {
await sleep(backoffMs); await sleep(backoffMs);
@ -155,6 +124,8 @@ export async function callJsonRpc<T = unknown>(
return response; return response;
} catch (error) { } catch (error) {
clearTimeout(timeoutId);
// Retry on timeout/error if attempts remain // Retry on timeout/error if attempts remain
if (attempt < maxAttempts - 1) { if (attempt < maxAttempts - 1) {
await sleep(backoffMs); await sleep(backoffMs);
@ -164,10 +135,6 @@ export async function callJsonRpc<T = unknown>(
throw error instanceof Error throw error instanceof Error
? error ? error
: new Error(`JSON-RPC call failed after ${timeout}ms`); : new Error(`JSON-RPC call failed after ${timeout}ms`);
} finally {
if (timeoutId !== null) {
clearTimeout(timeoutId);
}
} }
} }

24
web.go
View File

@ -8,7 +8,6 @@ import (
"errors" "errors"
"fmt" "fmt"
"io/fs" "io/fs"
"net"
"net/http" "net/http"
"net/http/pprof" "net/http/pprof"
"path/filepath" "path/filepath"
@ -185,8 +184,6 @@ func setupRouter() *gin.Engine {
protected.PUT("/auth/password-local", handleUpdatePassword) protected.PUT("/auth/password-local", handleUpdatePassword)
protected.DELETE("/auth/local-password", handleDeletePassword) protected.DELETE("/auth/local-password", handleDeletePassword)
protected.POST("/storage/upload", handleUploadHttp) protected.POST("/storage/upload", handleUploadHttp)
protected.POST("/device/send-wol/:mac-addr", handleSendWOLMagicPacket)
} }
// Catch-all route for SPA // Catch-all route for SPA
@ -344,6 +341,7 @@ func handleWebRTCSignalWsMessages(
l.Trace().Msg("sending ping frame") l.Trace().Msg("sending ping frame")
err := wsCon.Ping(runCtx) err := wsCon.Ping(runCtx)
if err != nil { if err != nil {
l.Warn().Str("error", err.Error()).Msg("websocket ping error") l.Warn().Str("error", err.Error()).Msg("websocket ping error")
cancelRun() cancelRun()
@ -809,23 +807,3 @@ func handleSetup(c *gin.Context) {
c.JSON(http.StatusOK, gin.H{"message": "Device setup completed successfully"}) c.JSON(http.StatusOK, gin.H{"message": "Device setup completed successfully"})
} }
func handleSendWOLMagicPacket(c *gin.Context) {
inputMacAddr := c.Param("mac-addr")
macAddr, err := net.ParseMAC(inputMacAddr)
if err != nil {
logger.Warn().Err(err).Str("inputMacAddr", inputMacAddr).Msg("Invalid MAC address provided")
c.String(http.StatusBadRequest, "Invalid mac address provided")
return
}
macAddrString := macAddr.String()
err = rpcSendWOLMagicPacket(macAddrString)
if err != nil {
logger.Warn().Err(err).Str("macAddrString", macAddrString).Msg("Failed to send WOL magic packet")
c.String(http.StatusInternalServerError, "Failed to send WOL to %s: %v", macAddrString, err)
return
}
c.String(http.StatusOK, "WOL sent to %s ", macAddr)
}