mirror of
https://github.com/netbirdio/netbird.git
synced 2026-05-08 17:59:56 +00:00
Compare commits
1 Commits
dns-skip-f
...
wasm-webso
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
fbd74d3867 |
@@ -17,6 +17,7 @@ import (
|
|||||||
"github.com/netbirdio/netbird/client/wasm/internal/http"
|
"github.com/netbirdio/netbird/client/wasm/internal/http"
|
||||||
"github.com/netbirdio/netbird/client/wasm/internal/rdp"
|
"github.com/netbirdio/netbird/client/wasm/internal/rdp"
|
||||||
"github.com/netbirdio/netbird/client/wasm/internal/ssh"
|
"github.com/netbirdio/netbird/client/wasm/internal/ssh"
|
||||||
|
nbwebsocket "github.com/netbirdio/netbird/client/wasm/internal/websocket"
|
||||||
"github.com/netbirdio/netbird/util"
|
"github.com/netbirdio/netbird/util"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ const (
|
|||||||
pingTimeout = 10 * time.Second
|
pingTimeout = 10 * time.Second
|
||||||
defaultLogLevel = "warn"
|
defaultLogLevel = "warn"
|
||||||
defaultSSHDetectionTimeout = 20 * time.Second
|
defaultSSHDetectionTimeout = 20 * time.Second
|
||||||
|
dialWebSocketTimeout = 30 * time.Second
|
||||||
|
|
||||||
icmpEchoRequest = 8
|
icmpEchoRequest = 8
|
||||||
icmpCodeEcho = 0
|
icmpCodeEcho = 0
|
||||||
@@ -516,6 +518,7 @@ func createClientObject(client *netbird.Client) js.Value {
|
|||||||
obj["createSSHConnection"] = createSSHMethod(client)
|
obj["createSSHConnection"] = createSSHMethod(client)
|
||||||
obj["proxyRequest"] = createProxyRequestMethod(client)
|
obj["proxyRequest"] = createProxyRequestMethod(client)
|
||||||
obj["createRDPProxy"] = createRDPProxyMethod(client)
|
obj["createRDPProxy"] = createRDPProxyMethod(client)
|
||||||
|
obj["dialWebSocket"] = createDialWebSocketMethod(client)
|
||||||
obj["status"] = createStatusMethod(client)
|
obj["status"] = createStatusMethod(client)
|
||||||
obj["statusSummary"] = createStatusSummaryMethod(client)
|
obj["statusSummary"] = createStatusSummaryMethod(client)
|
||||||
obj["statusDetail"] = createStatusDetailMethod(client)
|
obj["statusDetail"] = createStatusDetailMethod(client)
|
||||||
@@ -525,6 +528,74 @@ func createClientObject(client *netbird.Client) js.Value {
|
|||||||
return js.ValueOf(obj)
|
return js.ValueOf(obj)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func createDialWebSocketMethod(client *netbird.Client) js.Func {
|
||||||
|
return js.FuncOf(func(_ js.Value, args []js.Value) any {
|
||||||
|
url, protocols, timeout, errVal := parseDialWebSocketArgs(args)
|
||||||
|
if !errVal.IsUndefined() {
|
||||||
|
return errVal
|
||||||
|
}
|
||||||
|
|
||||||
|
return createPromise(func(resolve, reject js.Value) {
|
||||||
|
ctx, cancel := context.WithTimeout(context.Background(), timeout)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
conn, err := nbwebsocket.Dial(ctx, client, url, protocols)
|
||||||
|
if err != nil {
|
||||||
|
reject.Invoke(js.ValueOf(fmt.Sprintf("dial websocket: %v", err)))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
resolve.Invoke(nbwebsocket.NewJSInterface(conn))
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseDialWebSocketArgs(args []js.Value) (url string, protocols []string, timeout time.Duration, errVal js.Value) {
|
||||||
|
if len(args) < 1 || args[0].Type() != js.TypeString {
|
||||||
|
return "", nil, 0, js.ValueOf("error: dialWebSocket requires a URL string argument")
|
||||||
|
}
|
||||||
|
url = args[0].String()
|
||||||
|
|
||||||
|
if len(args) >= 2 && !args[1].IsNull() && !args[1].IsUndefined() {
|
||||||
|
arr, err := jsStringArray(args[1])
|
||||||
|
if err != nil {
|
||||||
|
return "", nil, 0, js.ValueOf(fmt.Sprintf("error: protocols: %v", err))
|
||||||
|
}
|
||||||
|
protocols = arr
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout = dialWebSocketTimeout
|
||||||
|
if len(args) >= 3 && !args[2].IsNull() && !args[2].IsUndefined() {
|
||||||
|
if args[2].Type() != js.TypeNumber {
|
||||||
|
return "", nil, 0, js.ValueOf("error: timeoutMs must be a number")
|
||||||
|
}
|
||||||
|
timeoutMs := args[2].Int()
|
||||||
|
if timeoutMs <= 0 {
|
||||||
|
return "", nil, 0, js.ValueOf("error: timeout must be positive")
|
||||||
|
}
|
||||||
|
timeout = time.Duration(timeoutMs) * time.Millisecond
|
||||||
|
}
|
||||||
|
|
||||||
|
return url, protocols, timeout, js.Undefined()
|
||||||
|
}
|
||||||
|
|
||||||
|
// jsStringArray converts a JS array of strings to a Go []string.
|
||||||
|
func jsStringArray(v js.Value) ([]string, error) {
|
||||||
|
if !v.InstanceOf(js.Global().Get("Array")) {
|
||||||
|
return nil, fmt.Errorf("expected array")
|
||||||
|
}
|
||||||
|
n := v.Length()
|
||||||
|
out := make([]string, n)
|
||||||
|
for i := 0; i < n; i++ {
|
||||||
|
el := v.Index(i)
|
||||||
|
if el.Type() != js.TypeString {
|
||||||
|
return nil, fmt.Errorf("element %d is not a string", i)
|
||||||
|
}
|
||||||
|
out[i] = el.String()
|
||||||
|
}
|
||||||
|
return out, nil
|
||||||
|
}
|
||||||
|
|
||||||
// netBirdClientConstructor acts as a JavaScript constructor function
|
// netBirdClientConstructor acts as a JavaScript constructor function
|
||||||
func netBirdClientConstructor(_ js.Value, args []js.Value) any {
|
func netBirdClientConstructor(_ js.Value, args []js.Value) any {
|
||||||
return js.Global().Get("Promise").New(js.FuncOf(func(_ js.Value, promiseArgs []js.Value) any {
|
return js.Global().Get("Promise").New(js.FuncOf(func(_ js.Value, promiseArgs []js.Value) any {
|
||||||
|
|||||||
304
client/wasm/internal/websocket/websocket.go
Normal file
304
client/wasm/internal/websocket/websocket.go
Normal file
@@ -0,0 +1,304 @@
|
|||||||
|
//go:build js
|
||||||
|
|
||||||
|
package websocket
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"net"
|
||||||
|
"sync"
|
||||||
|
"syscall/js"
|
||||||
|
|
||||||
|
"github.com/gobwas/ws"
|
||||||
|
"github.com/gobwas/ws/wsutil"
|
||||||
|
netbird "github.com/netbirdio/netbird/client/embed"
|
||||||
|
log "github.com/sirupsen/logrus"
|
||||||
|
)
|
||||||
|
|
||||||
|
type closeError struct {
|
||||||
|
code uint16
|
||||||
|
reason string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *closeError) Error() string {
|
||||||
|
return fmt.Sprintf("websocket closed: %d %s", e.code, e.reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
// bufferedConn fronts a net.Conn with a reader that serves any bytes buffered
|
||||||
|
// during the WebSocket handshake before falling through to the raw conn.
|
||||||
|
type bufferedConn struct {
|
||||||
|
net.Conn
|
||||||
|
r io.Reader
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *bufferedConn) Read(p []byte) (int, error) { return c.r.Read(p) }
|
||||||
|
|
||||||
|
// Conn wraps a WebSocket connection over a NetBird TCP connection.
|
||||||
|
type Conn struct {
|
||||||
|
conn net.Conn
|
||||||
|
mu sync.Mutex
|
||||||
|
closed chan struct{}
|
||||||
|
closeOnce sync.Once
|
||||||
|
closeErr error
|
||||||
|
}
|
||||||
|
|
||||||
|
// Dial establishes a WebSocket connection to the given URL through the NetBird network.
|
||||||
|
// Optional protocols are sent via the Sec-WebSocket-Protocol header.
|
||||||
|
func Dial(ctx context.Context, client *netbird.Client, rawURL string, protocols []string) (*Conn, error) {
|
||||||
|
d := ws.Dialer{
|
||||||
|
NetDial: client.Dial,
|
||||||
|
Protocols: protocols,
|
||||||
|
}
|
||||||
|
|
||||||
|
conn, br, _, err := d.Dial(ctx, rawURL)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("websocket dial: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// br is non-nil when the server pushed frames alongside the handshake
|
||||||
|
// response; those bytes live in the bufio.Reader and must be drained
|
||||||
|
// before reading from conn, otherwise we'd skip the first frames.
|
||||||
|
if br != nil {
|
||||||
|
if br.Buffered() > 0 {
|
||||||
|
conn = &bufferedConn{Conn: conn, r: io.MultiReader(br, conn)}
|
||||||
|
} else {
|
||||||
|
ws.PutReader(br)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return &Conn{
|
||||||
|
conn: conn,
|
||||||
|
closed: make(chan struct{}),
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadMessage reads the next WebSocket message, handling control frames automatically.
|
||||||
|
func (c *Conn) ReadMessage() (ws.OpCode, []byte, error) {
|
||||||
|
for {
|
||||||
|
msgs, err := wsutil.ReadServerMessage(c.conn, nil)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, msg := range msgs {
|
||||||
|
if msg.OpCode.IsControl() {
|
||||||
|
if err := c.handleControl(msg); err != nil {
|
||||||
|
return 0, nil, err
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
return msg.OpCode, msg.Payload, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Conn) handleControl(msg wsutil.Message) error {
|
||||||
|
switch msg.OpCode {
|
||||||
|
case ws.OpPing:
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
return wsutil.WriteClientMessage(c.conn, ws.OpPong, msg.Payload)
|
||||||
|
case ws.OpClose:
|
||||||
|
code, reason := parseClosePayload(msg.Payload)
|
||||||
|
return &closeError{code: code, reason: reason}
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteText sends a text WebSocket message.
|
||||||
|
func (c *Conn) WriteText(data []byte) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
return wsutil.WriteClientMessage(c.conn, ws.OpText, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// WriteBinary sends a binary WebSocket message.
|
||||||
|
func (c *Conn) WriteBinary(data []byte) error {
|
||||||
|
c.mu.Lock()
|
||||||
|
defer c.mu.Unlock()
|
||||||
|
return wsutil.WriteClientMessage(c.conn, ws.OpBinary, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Close sends a close frame with StatusNormalClosure and closes the underlying connection.
|
||||||
|
func (c *Conn) Close() error {
|
||||||
|
return c.closeWith(ws.StatusNormalClosure, "")
|
||||||
|
}
|
||||||
|
|
||||||
|
// closeWith sends a close frame with the given code/reason and closes the underlying connection.
|
||||||
|
// Used to echo the server's code when responding to a server-initiated close per RFC 6455 §5.5.1.
|
||||||
|
func (c *Conn) closeWith(code ws.StatusCode, reason string) error {
|
||||||
|
var first bool
|
||||||
|
c.closeOnce.Do(func() {
|
||||||
|
first = true
|
||||||
|
close(c.closed)
|
||||||
|
|
||||||
|
c.mu.Lock()
|
||||||
|
_ = wsutil.WriteClientMessage(c.conn, ws.OpClose, ws.NewCloseFrameBody(code, reason))
|
||||||
|
c.mu.Unlock()
|
||||||
|
|
||||||
|
c.closeErr = c.conn.Close()
|
||||||
|
})
|
||||||
|
|
||||||
|
if !first {
|
||||||
|
return net.ErrClosed
|
||||||
|
}
|
||||||
|
return c.closeErr
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewJSInterface creates a JavaScript object wrapping the WebSocket connection.
|
||||||
|
// It exposes: send(string|Uint8Array), close(), and callback properties
|
||||||
|
// onmessage, onclose, onerror.
|
||||||
|
//
|
||||||
|
// Callback properties may be set from the JS thread while the read loop
|
||||||
|
// goroutine reads them. In WASM this is safe because Go and JS share a
|
||||||
|
// single thread, but the design would need synchronization on
|
||||||
|
// multi-threaded runtimes.
|
||||||
|
func NewJSInterface(conn *Conn) js.Value {
|
||||||
|
obj := js.Global().Get("Object").Call("create", js.Null())
|
||||||
|
|
||||||
|
sendFunc := js.FuncOf(func(_ js.Value, args []js.Value) any {
|
||||||
|
if len(args) < 1 {
|
||||||
|
log.Errorf("websocket send requires a data argument")
|
||||||
|
return js.ValueOf(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := args[0]
|
||||||
|
switch data.Type() {
|
||||||
|
case js.TypeString:
|
||||||
|
if err := conn.WriteText([]byte(data.String())); err != nil {
|
||||||
|
log.Errorf("failed to send websocket text: %v", err)
|
||||||
|
return js.ValueOf(false)
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
buf, err := jsToBytes(data)
|
||||||
|
if err != nil {
|
||||||
|
log.Errorf("failed to convert js value to bytes: %v", err)
|
||||||
|
return js.ValueOf(false)
|
||||||
|
}
|
||||||
|
if err := conn.WriteBinary(buf); err != nil {
|
||||||
|
log.Errorf("failed to send websocket binary: %v", err)
|
||||||
|
return js.ValueOf(false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return js.ValueOf(true)
|
||||||
|
})
|
||||||
|
obj.Set("send", sendFunc)
|
||||||
|
|
||||||
|
closeFunc := js.FuncOf(func(_ js.Value, _ []js.Value) any {
|
||||||
|
if err := conn.Close(); err != nil {
|
||||||
|
log.Debugf("failed to close websocket: %v", err)
|
||||||
|
}
|
||||||
|
return js.Undefined()
|
||||||
|
})
|
||||||
|
obj.Set("close", closeFunc)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
defer func() {
|
||||||
|
if err := conn.Close(); err != nil && !errors.Is(err, net.ErrClosed) {
|
||||||
|
log.Debugf("close websocket on readLoop exit: %v", err)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
readLoop(conn, obj)
|
||||||
|
// Undefining before Release turns post-close JS calls into TypeError
|
||||||
|
// instead of a silent "call to released function".
|
||||||
|
obj.Set("send", js.Undefined())
|
||||||
|
obj.Set("close", js.Undefined())
|
||||||
|
sendFunc.Release()
|
||||||
|
closeFunc.Release()
|
||||||
|
}()
|
||||||
|
|
||||||
|
return obj
|
||||||
|
}
|
||||||
|
|
||||||
|
func jsToBytes(data js.Value) ([]byte, error) {
|
||||||
|
var uint8Array js.Value
|
||||||
|
switch {
|
||||||
|
case data.InstanceOf(js.Global().Get("Uint8Array")):
|
||||||
|
uint8Array = data
|
||||||
|
case data.InstanceOf(js.Global().Get("ArrayBuffer")):
|
||||||
|
uint8Array = js.Global().Get("Uint8Array").New(data)
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("send: unsupported data type, use string, Uint8Array, or ArrayBuffer")
|
||||||
|
}
|
||||||
|
|
||||||
|
buf := make([]byte, uint8Array.Get("length").Int())
|
||||||
|
js.CopyBytesToGo(buf, uint8Array)
|
||||||
|
return buf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLoop(conn *Conn, obj js.Value) {
|
||||||
|
var ce *closeError
|
||||||
|
defer func() { invokeOnClose(obj, ce) }()
|
||||||
|
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case <-conn.closed:
|
||||||
|
return
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
|
||||||
|
op, payload, err := conn.ReadMessage()
|
||||||
|
if err != nil {
|
||||||
|
ce = handleReadError(conn, obj, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
dispatchMessage(obj, op, payload)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func handleReadError(conn *Conn, obj js.Value, err error) *closeError {
|
||||||
|
var ce *closeError
|
||||||
|
if errors.As(err, &ce) {
|
||||||
|
if cerr := conn.closeWith(ws.StatusCode(ce.code), ce.reason); cerr != nil {
|
||||||
|
log.Debugf("failed to close websocket after server close frame: %v", cerr)
|
||||||
|
}
|
||||||
|
return ce
|
||||||
|
}
|
||||||
|
if errors.Is(err, io.EOF) || errors.Is(err, net.ErrClosed) {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if onerror := obj.Get("onerror"); onerror.Truthy() {
|
||||||
|
onerror.Invoke(js.ValueOf(err.Error()))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeOnClose(obj js.Value, ce *closeError) {
|
||||||
|
onclose := obj.Get("onclose")
|
||||||
|
if !onclose.Truthy() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if ce != nil {
|
||||||
|
onclose.Invoke(js.ValueOf(int(ce.code)), js.ValueOf(ce.reason))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
onclose.Invoke()
|
||||||
|
}
|
||||||
|
|
||||||
|
func dispatchMessage(obj js.Value, op ws.OpCode, payload []byte) {
|
||||||
|
onmessage := obj.Get("onmessage")
|
||||||
|
if !onmessage.Truthy() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
switch op {
|
||||||
|
case ws.OpText:
|
||||||
|
onmessage.Invoke(js.ValueOf(string(payload)))
|
||||||
|
case ws.OpBinary:
|
||||||
|
uint8Array := js.Global().Get("Uint8Array").New(len(payload))
|
||||||
|
js.CopyBytesToJS(uint8Array, payload)
|
||||||
|
onmessage.Invoke(uint8Array)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseClosePayload(payload []byte) (uint16, string) {
|
||||||
|
if len(payload) < 2 {
|
||||||
|
return 1005, "" // RFC 6455: No Status Rcvd
|
||||||
|
}
|
||||||
|
code := binary.BigEndian.Uint16(payload[:2])
|
||||||
|
return code, string(payload[2:])
|
||||||
|
}
|
||||||
3
go.mod
3
go.mod
@@ -52,6 +52,7 @@ require (
|
|||||||
github.com/fsnotify/fsnotify v1.9.0
|
github.com/fsnotify/fsnotify v1.9.0
|
||||||
github.com/gliderlabs/ssh v0.3.8
|
github.com/gliderlabs/ssh v0.3.8
|
||||||
github.com/go-jose/go-jose/v4 v4.1.3
|
github.com/go-jose/go-jose/v4 v4.1.3
|
||||||
|
github.com/gobwas/ws v1.4.0
|
||||||
github.com/godbus/dbus/v5 v5.1.0
|
github.com/godbus/dbus/v5 v5.1.0
|
||||||
github.com/golang-jwt/jwt/v5 v5.3.0
|
github.com/golang-jwt/jwt/v5 v5.3.0
|
||||||
github.com/golang/mock v1.6.0
|
github.com/golang/mock v1.6.0
|
||||||
@@ -204,6 +205,8 @@ require (
|
|||||||
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
github.com/go-sql-driver/mysql v1.9.3 // indirect
|
||||||
github.com/go-text/render v0.2.0 // indirect
|
github.com/go-text/render v0.2.0 // indirect
|
||||||
github.com/go-text/typesetting v0.2.1 // indirect
|
github.com/go-text/typesetting v0.2.1 // indirect
|
||||||
|
github.com/gobwas/httphead v0.1.0 // indirect
|
||||||
|
github.com/gobwas/pool v0.2.1 // indirect
|
||||||
github.com/goccy/go-yaml v1.18.0 // indirect
|
github.com/goccy/go-yaml v1.18.0 // indirect
|
||||||
github.com/gogo/protobuf v1.3.2 // indirect
|
github.com/gogo/protobuf v1.3.2 // indirect
|
||||||
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
github.com/golang-jwt/jwt/v4 v4.5.2 // indirect
|
||||||
|
|||||||
7
go.sum
7
go.sum
@@ -233,6 +233,12 @@ github.com/go-text/typesetting v0.2.1 h1:x0jMOGyO3d1qFAPI0j4GSsh7M0Q3Ypjzr4+CEVg
|
|||||||
github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
|
github.com/go-text/typesetting v0.2.1/go.mod h1:mTOxEwasOFpAMBjEQDhdWRckoLLeI/+qrQeBCTGEt6M=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066 h1:qCuYC+94v2xrb1PoS4NIDe7DGYtLnU2wWiQe9a1B1c0=
|
||||||
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
github.com/go-text/typesetting-utils v0.0.0-20241103174707-87a29e9e6066/go.mod h1:DDxDdQEnB70R8owOx3LVpEFvpMK9eeH1o2r0yZhFI9o=
|
||||||
|
github.com/gobwas/httphead v0.1.0 h1:exrUm0f4YX0L7EBwZHuCF4GDp8aJfVeBrlLQrs6NqWU=
|
||||||
|
github.com/gobwas/httphead v0.1.0/go.mod h1:O/RXo79gxV8G+RqlR/otEwx4Q36zl9rqC5u12GKvMCM=
|
||||||
|
github.com/gobwas/pool v0.2.1 h1:xfeeEhW7pwmX8nuLVlqbzVc7udMDrwetjEv+TZIz1og=
|
||||||
|
github.com/gobwas/pool v0.2.1/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw=
|
||||||
|
github.com/gobwas/ws v1.4.0 h1:CTaoG1tojrh4ucGPcoJFiAQUAsEWekEWvLy7GsVNqGs=
|
||||||
|
github.com/gobwas/ws v1.4.0/go.mod h1:G3gNqMNtPppf5XUz7O4shetPpcZ1VJ7zt18dlUeakrc=
|
||||||
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
github.com/goccy/go-yaml v1.18.0 h1:8W7wMFS12Pcas7KU+VVkaiCng+kG8QiFeFwzFb+rwuw=
|
||||||
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
github.com/goccy/go-yaml v1.18.0/go.mod h1:XBurs7gK8ATbW4ZPGKgcbrY1Br56PdM69F7LkFRi1kA=
|
||||||
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
|
||||||
@@ -787,6 +793,7 @@ golang.org/x/sys v0.0.0-20220908164124-27713097b956/go.mod h1:oPkhp1MJrh7nUepCBc
|
|||||||
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.7.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
golang.org/x/sys v0.8.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=
|
||||||
|
|||||||
Reference in New Issue
Block a user