diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000..f5e40cb Binary files /dev/null and b/.DS_Store differ diff --git a/README.md b/README.md index 83617e0..c71c096 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,48 @@ -# lpra -Long polling remote access +# LPRA — LongPolling Remote Access + +Лёгкий self-hosted сервис для доступа к Linux-машинам **без белого IP** (за NAT). +Агент на клиенте сам исходяще подключается к серверу по HTTP long-polling; админ +в веб-панели видит список машин и открывает по каждой **root-терминал в браузере**. + +``` +браузер ──WS──> [nginx :8445 TLS] ──> [Go server 127.0.0.1:8091] <──long-poll── агент (root, за NAT) + │ + SQLite +``` + +## Компоненты +- **Сервер** (`cmd/server`) — админ-панель + точка встречи для агентов. За nginx (TLS). +- **Агент** (`cmd/agent`) — Linux-бинарь, systemd-сервис от root, PTY по запросу. +- **install.sh** — интерактивный установщик, отдаётся сервером как статика. + +## Сборка +```sh +./build.sh +``` +Получаются `bin/lpra-server` и `dist/agent-linux-{amd64,arm64}`. + +## Запуск сервера +```sh +./bin/lpra-server -addr 127.0.0.1:8091 -dist ./dist -db ./lpra.db +``` +Поставьте перед ним nginx по `deploy/nginx.conf.example` (TLS, long-poll/WS). +Локально без TLS можно добавить `-insecure-cookies`. + +Первый запуск: откройте `/setup` — мастер создаст администратора. Дальше `/settings`: +адрес хоста (по умолчанию `https://REDACTED:8445`), enroll-секрет и готовая +команда установки. + +## Установка агента на клиент +Одной командой (под root): +```sh +curl -fsSL https://REDACTED:8445/install.sh | sudo HOST=https://REDACTED:8445 ENROLL_SECRET=<секрет> sh +``` +Или интерактивно (спросит секрет, а если агент уже стоит — предложит удалить): +```sh +curl -fsSL https://REDACTED:8445/install.sh | sudo sh +``` + +## Безопасность +- Пароли админов — bcrypt; сессии в HttpOnly/Secure/SameSite куках; CSRF на формах; rate-limit логина. +- Агенты: общий enroll-секрет → индивидуальный токен (хранится хешем, сверка constant-time). +- Открытие терминала пишется в таблицу `audit` (терминал даёт root-доступ). diff --git a/VERSION.txt b/VERSION.txt new file mode 100644 index 0000000..8acdd82 --- /dev/null +++ b/VERSION.txt @@ -0,0 +1 @@ +0.0.1 diff --git a/bin/agent-darwin-amd64 b/bin/agent-darwin-amd64 new file mode 100755 index 0000000..248463c Binary files /dev/null and b/bin/agent-darwin-amd64 differ diff --git a/bin/agent-darwin-arm64 b/bin/agent-darwin-arm64 new file mode 100755 index 0000000..57c22d9 Binary files /dev/null and b/bin/agent-darwin-arm64 differ diff --git a/bin/lpra-server b/bin/lpra-server new file mode 100755 index 0000000..70a7bd6 Binary files /dev/null and b/bin/lpra-server differ diff --git a/bin/lpra-server-linux-amd64 b/bin/lpra-server-linux-amd64 new file mode 100755 index 0000000..f3843ec Binary files /dev/null and b/bin/lpra-server-linux-amd64 differ diff --git a/bin/lpra-server-linux-arm64 b/bin/lpra-server-linux-arm64 new file mode 100755 index 0000000..4df7f71 Binary files /dev/null and b/bin/lpra-server-linux-arm64 differ diff --git a/build.sh b/build.sh new file mode 100755 index 0000000..5841025 --- /dev/null +++ b/build.sh @@ -0,0 +1,47 @@ +#!/bin/sh +# Build the LPRA server (for the local host OS *and* Linux) and cross-compile +# the Linux agent binaries served at /download/. +set -eu + +cd "$(dirname "$0")" + +VERSION="${VERSION:-$(cat VERSION.txt 2>/dev/null || echo 0.0.0)}" +PKG="github.com/x-grk/longpolling-remote-access" +LDFLAGS="-s -w -X $PKG/internal/server.Version=$VERSION -X $PKG/internal/agent.Version=$VERSION" +echo ">> version $VERSION" + +mkdir -p dist bin + +echo ">> building server (local: $(go env GOOS)/$(go env GOARCH))" +go build -trimpath -ldflags "$LDFLAGS" -o bin/lpra-server ./cmd/server + +echo ">> building server linux/amd64" +GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" \ + -o bin/lpra-server-linux-amd64 ./cmd/server + +echo ">> building server linux/arm64" +GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" \ + -o bin/lpra-server-linux-arm64 ./cmd/server + +echo ">> building agent linux/amd64" +GOOS=linux GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" \ + -o dist/agent-linux-amd64 ./cmd/agent + +echo ">> building agent linux/arm64" +GOOS=linux GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" \ + -o dist/agent-linux-arm64 ./cmd/agent + +echo ">> building agent darwin/arm64 + amd64" +GOOS=darwin GOARCH=arm64 CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" \ + -o bin/agent-darwin-arm64 ./cmd/agent +GOOS=darwin GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags "$LDFLAGS" \ + -o bin/agent-darwin-amd64 ./cmd/agent + +echo +echo "Done." +echo " server (local): bin/lpra-server" +echo " server (Linux): bin/lpra-server-linux-amd64, bin/lpra-server-linux-arm64" +echo " agents (Linux): dist/agent-linux-amd64, dist/agent-linux-arm64" +echo +echo "On the Linux host copy the matching server binary to /opt/lpra/lpra-server," +echo "e.g.: cp bin/lpra-server-linux-amd64 /opt/lpra/lpra-server" diff --git a/cmd/.DS_Store b/cmd/.DS_Store new file mode 100644 index 0000000..293ea1c Binary files /dev/null and b/cmd/.DS_Store differ diff --git a/cmd/agent/main.go b/cmd/agent/main.go new file mode 100644 index 0000000..87c56bf --- /dev/null +++ b/cmd/agent/main.go @@ -0,0 +1,64 @@ +// Command agent is the Linux client installed on machines without a public IP. +// It runs as a root systemd service, dials the server over HTTP long-polling, +// and opens a root PTY when an admin requests a terminal in the web panel. +package main + +import ( + "context" + "log" + "net" + "net/url" + "os" + "os/signal" + "syscall" + + "github.com/x-grk/longpolling-remote-access/internal/agent" +) + +func main() { + host := envOr("HOST", "https://REDACTED:8445") + cfg := agent.Config{ + Host: host, + EnrollSecret: os.Getenv("ENROLL_SECRET"), + Name: os.Getenv("AGENT_NAME"), + StatePath: envOr("STATE_PATH", "/etc/lpagent/state.json"), + TunnelAddr: tunnelAddr(host), + } + if cfg.EnrollSecret == "" { + // Only required for the very first run (before enrollment). + if _, err := os.Stat(cfg.StatePath); os.IsNotExist(err) { + log.Fatal("ENROLL_SECRET is required for first enrollment") + } + } + + ctx, stop := signal.NotifyContext(context.Background(), syscall.SIGINT, syscall.SIGTERM) + defer stop() + + if err := agent.New(cfg).Run(ctx); err != nil { + log.Fatalf("agent: %v", err) + } +} + +func envOr(key, def string) string { + if v := os.Getenv(key); v != "" { + return v + } + return def +} + +// tunnelAddr derives the direct-tunnel address (host:8446) from the server URL. +// TUNNEL_ADDR overrides it entirely; TUNNEL_PORT overrides just the port. +// Set TUNNEL_ADDR=off to disable the direct tunnel. +func tunnelAddr(hostURL string) string { + if v := os.Getenv("TUNNEL_ADDR"); v != "" { + if v == "off" { + return "" + } + return v + } + u, err := url.Parse(hostURL) + if err != nil || u.Hostname() == "" { + return "" + } + return net.JoinHostPort(u.Hostname(), envOr("TUNNEL_PORT", "8446")) +} diff --git a/cmd/server/main.go b/cmd/server/main.go new file mode 100644 index 0000000..85a9958 --- /dev/null +++ b/cmd/server/main.go @@ -0,0 +1,62 @@ +// Command server is the host: it serves the admin web panel and acts as the +// rendezvous point that NAT'd agents dial into over HTTP long-polling. +// +// TLS is terminated by nginx in front of this process, so it listens on plain +// HTTP on localhost by default (see deploy/nginx.conf.example). +package main + +import ( + "flag" + "log" + "net/http" + "os" + "time" + + "github.com/x-grk/longpolling-remote-access/internal/server" +) + +func main() { + addr := flag.String("addr", envOr("LPRA_ADDR", "127.0.0.1:8091"), "listen address (behind nginx)") + dbPath := flag.String("db", envOr("LPRA_DB", "lpra.db"), "path to SQLite database") + distDir := flag.String("dist", envOr("LPRA_DIST", "dist"), "directory with agent binaries served at /download/") + tunnelAddr := flag.String("tunnel", envOr("LPRA_TUNNEL_ADDR", ":8446"), "listen address for the direct TCP+UDP forward tunnel (empty disables)") + insecureCookies := flag.Bool("insecure-cookies", envOr("LPRA_INSECURE_COOKIES", "") == "1", + "do not mark cookies Secure (plain-HTTP local testing only)") + flag.Parse() + + db, err := server.OpenDB(*dbPath) + if err != nil { + log.Fatalf("open db: %v", err) + } + defer db.Close() + + srv, err := server.New(server.Options{ + DB: db, + DistDir: *distDir, + SecureCookies: !*insecureCookies, + TunnelAddr: *tunnelAddr, + }) + if err != nil { + log.Fatalf("init server: %v", err) + } + + httpSrv := &http.Server{ + Addr: *addr, + Handler: srv.Handler(), + // No ReadTimeout/WriteTimeout: long-poll connections are intentionally + // long-lived. nginx bounds them instead. + ReadHeaderTimeout: 15 * time.Second, + IdleTimeout: 130 * time.Second, + } + log.Printf("lpra server listening on %s (db=%s dist=%s)", *addr, *dbPath, *distDir) + if err := httpSrv.ListenAndServe(); err != nil { + log.Fatalf("serve: %v", err) + } +} + +func envOr(key, def string) string { + if v := os.Getenv(key); v != "" { + return v + } + return def +} diff --git a/deploy/lpra-server.service b/deploy/lpra-server.service new file mode 100644 index 0000000..a6769e7 --- /dev/null +++ b/deploy/lpra-server.service @@ -0,0 +1,58 @@ +# systemd unit for the LPRA host (server). +# +# Unlike the agent, the server does NOT need root: it listens on a high local +# port (127.0.0.1:8091) behind nginx, so run it as an unprivileged user. +# +# Install: +# sudo useradd --system --home /opt/lpra --shell /usr/sbin/nologin lpra +# sudo mkdir -p /opt/lpra /var/lib/lpra +# sudo cp bin/lpra-server /opt/lpra/ +# sudo cp -r dist /opt/lpra/dist # agent binaries served at /download/ +# sudo chown -R lpra:lpra /opt/lpra /var/lib/lpra +# sudo cp deploy/lpra-server.service /etc/systemd/system/ +# sudo systemctl daemon-reload && sudo systemctl enable --now lpra-server +# +# Config overrides can go in /etc/lpra-server.env (see EnvironmentFile below), +# e.g. LPRA_ADDR=127.0.0.1:8091 +# +# Direct port forwarding (tcp/udp modes) uses port 8446 on the host — open it in +# the firewall: ufw allow 8446/tcp ; ufw allow 8446/udp +# (The "http" forward mode and terminal/files go through nginx :8445 and need +# nothing extra.) Override with LPRA_TUNNEL_ADDR, or set it empty to disable. + +[Unit] +Description=LPRA remote access host (admin panel + agent rendezvous) +After=network-online.target +Wants=network-online.target + +[Service] +Type=simple +User=lpra +Group=lpra +WorkingDirectory=/opt/lpra + +# Optional environment overrides; "-" means the file is not required. +EnvironmentFile=-/etc/lpra-server.env + +ExecStart=/opt/lpra/lpra-server \ + -addr 127.0.0.1:8091 \ + -db /var/lib/lpra/lpra.db \ + -dist /opt/lpra/dist + +Restart=always +RestartSec=3 + +# --- hardening --- +NoNewPrivileges=true +ProtectSystem=strict +ProtectHome=true +PrivateTmp=true +PrivateDevices=true +ProtectKernelTunables=true +ProtectControlGroups=true +RestrictSUIDSGID=true +# The server only needs to write its SQLite database here. +ReadWritePaths=/var/lib/lpra + +[Install] +WantedBy=multi-user.target diff --git a/deploy/nginx.conf.example b/deploy/nginx.conf.example new file mode 100644 index 0000000..ad9b658 --- /dev/null +++ b/deploy/nginx.conf.example @@ -0,0 +1,42 @@ +# nginx reverse proxy for the LPRA server. +# TLS is terminated here; the Go server listens on 127.0.0.1:8091. +# +# Put this in /etc/nginx/sites-available/lp_agent and symlink it into +# sites-enabled, then: sudo nginx -t && sudo systemctl reload nginx +# +# Everything (agent + browser terminal) is plain HTTP long-polling, so NO +# WebSocket config is needed. The two settings that matter: +# - proxy_buffering off -> deliver long-poll responses immediately +# - proxy_read_timeout 120s -> longer than the 25s long-poll hold +# +# NOTE: proxy_pass MUST start with "http://". A bare "proxy_pass 127.0.0.1:8091;" +# causes nginx error: [emerg] invalid URL prefix. + +server { + listen 8445 ssl; + server_name REDACTED; + + ssl_certificate /etc/letsencrypt/live/REDACTED/fullchain.pem; # managed by Certbot + ssl_certificate_key /etc/letsencrypt/live/REDACTED/privkey.pem; # managed by Certbot + include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot + ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot + + # Agent binaries can be large; allow the download. + client_max_body_size 64m; + + location / { + proxy_pass http://127.0.0.1:8091; + + proxy_http_version 1.1; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + # Long-poll behaviour: no buffering, generous read timeout. + proxy_buffering off; + proxy_cache off; + proxy_read_timeout 120s; + proxy_send_timeout 120s; + } +} diff --git a/dist/agent-linux-amd64 b/dist/agent-linux-amd64 new file mode 100755 index 0000000..78f8433 Binary files /dev/null and b/dist/agent-linux-amd64 differ diff --git a/dist/agent-linux-arm64 b/dist/agent-linux-arm64 new file mode 100755 index 0000000..db3c125 Binary files /dev/null and b/dist/agent-linux-arm64 differ diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..711c290 --- /dev/null +++ b/go.mod @@ -0,0 +1,20 @@ +module github.com/x-grk/longpolling-remote-access + +go 1.26 + +require ( + github.com/creack/pty v1.1.21 + golang.org/x/crypto v0.31.0 + modernc.org/sqlite v1.24.0 +) + +require ( + github.com/dustin/go-humanize v1.0.1 // indirect + github.com/google/uuid v1.6.0 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect + golang.org/x/sys v0.28.0 // indirect + modernc.org/libc v1.22.5 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.5.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..dbcce56 --- /dev/null +++ b/go.sum @@ -0,0 +1,28 @@ +github.com/creack/pty v1.1.21 h1:1/QdRyBaHHJP61QkWMXlOIBfsgdDeeKfK8SYVUWJKf0= +github.com/creack/pty v1.1.21/go.mod h1:MOBLtS5ELjhRRrroQr9kyvTxUAFNvYEK993ew/Vr4O4= +github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= +github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= +github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= +github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE= +github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +golang.org/x/crypto v0.28.0 h1:GBDwsMXVQi34v5CCYUm2jkJvu4cbtru2U4TN2PSyQnw= +golang.org/x/crypto v0.28.0/go.mod h1:rmgy+3RHxRZMyY0jjAJShp2zgEdOqj2AO7U0pYmeQ7U= +golang.org/x/crypto v0.31.0 h1:ihbySMvVjLAeSH1IbfcRTkD/iNscyz8rGzjF/E5hV6U= +golang.org/x/crypto v0.31.0/go.mod h1:kDsLvtWBEx7MV9tJOj9bnXsPbxwJQ6csT/x4KIN4Ssk= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.28.0 h1:Fksou7UEQUWlKvIdsqzJmUmCX3cZuD2+P3XyyzwMhlA= +golang.org/x/sys v0.28.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +modernc.org/libc v1.22.5 h1:91BNch/e5B0uPbJFgqbxXuOnxBQjlS//icfQEGmvyjE= +modernc.org/libc v1.22.5/go.mod h1:jj+Z7dTNX8fBScMVNRAYZ/jF91K8fdT2hYMThc3YjBY= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds= +modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/sqlite v1.24.0 h1:EsClRIWHGhLTCX44p+Ri/JLD+vFGo0QGjasg2/F9TlI= +modernc.org/sqlite v1.24.0/go.mod h1:OrDj17Mggn6MhE+iPbBNf7RGKODDE9NFT0f3EwDzJqk= diff --git a/internal/.DS_Store b/internal/.DS_Store new file mode 100644 index 0000000..413f7ee Binary files /dev/null and b/internal/.DS_Store differ diff --git a/internal/agent/client.go b/internal/agent/client.go new file mode 100644 index 0000000..eb9ea07 --- /dev/null +++ b/internal/agent/client.go @@ -0,0 +1,207 @@ +// Package agent implements the Linux client that dials home to the server over +// HTTP long-polling and exposes a root PTY on demand. +package agent + +import ( + "bytes" + "context" + "crypto/tls" + "encoding/base64" + "encoding/json" + "fmt" + "io" + "log" + "net" + "net/http" + "os" + "path/filepath" + "runtime" + "time" + + "github.com/x-grk/longpolling-remote-access/internal/proto" +) + +// Version is the agent build version, injected at compile time via -ldflags +// (-X .../internal/agent.Version=...). +var Version = "dev" + +// Config is the agent's runtime configuration (from env / config.env). +type Config struct { + Host string // e.g. https://REDACTED:8445 + EnrollSecret string + Name string // optional friendly name + StatePath string // where the enrolled id+token are persisted + TunnelAddr string // host:port for the direct forward tunnel (e.g. REDACTED:8446); empty disables +} + +type state struct { + AgentID string `json:"agent_id"` + Token string `json:"token"` +} + +// Agent is a running client. +type Agent struct { + cfg Config + http *http.Client + state state + pollCount int +} + +// New builds an agent. It does not perform any network I/O. +func New(cfg Config) *Agent { + // Tuned transport for low-latency forwarding: keep-alive connection reuse + // (no TCP/TLS handshake per chunk) and a generous idle pool so many + // concurrent streams don't fight over 2 connections. Plain HTTP/1.1 keeps + // each stream on its own connection for predictable parallelism. + tr := &http.Transport{ + Proxy: http.ProxyFromEnvironment, + DialContext: (&net.Dialer{ + Timeout: 10 * time.Second, + KeepAlive: 30 * time.Second, + }).DialContext, + ForceAttemptHTTP2: false, + TLSNextProto: map[string]func(string, *tls.Conn) http.RoundTripper{}, + MaxIdleConns: 100, + MaxIdleConnsPerHost: 32, + IdleConnTimeout: 90 * time.Second, + TLSHandshakeTimeout: 10 * time.Second, + ExpectContinueTimeout: time.Second, + } + return &Agent{ + cfg: cfg, + // Timeout must exceed the server's long-poll hold (25s) plus slack. + http: &http.Client{Timeout: 60 * time.Second, Transport: tr}, + } +} + +// Run enrolls if needed and then loops the control poll forever, reconnecting +// on error with a short backoff. It returns only on a fatal config error. +func (a *Agent) Run(ctx context.Context) error { + if err := a.loadState(); err != nil { + return err + } + if a.state.AgentID == "" || a.state.Token == "" { + if err := a.enroll(ctx); err != nil { + return fmt.Errorf("enroll: %w", err) + } + } + log.Printf("agent %s connected to %s", a.state.AgentID, a.cfg.Host) + + // Direct forward tunnel (low-latency TCP mux + UDP) on 8446, if configured. + if a.cfg.TunnelAddr != "" { + go a.runTunnel(ctx) + go a.runUDP(ctx) + } + + backoff := time.Second + for ctx.Err() == nil { + if err := a.pollOnce(ctx); err != nil { + log.Printf("poll: %v (retry in %s)", err, backoff) + select { + case <-time.After(backoff): + case <-ctx.Done(): + return nil + } + if backoff < 15*time.Second { + backoff *= 2 + } + continue + } + backoff = time.Second + } + return nil +} + +func (a *Agent) enroll(ctx context.Context) error { + host, _ := os.Hostname() + body, _ := json.Marshal(proto.EnrollRequest{ + Name: a.cfg.Name, + Hostname: host, + OS: runtime.GOOS, + Arch: runtime.GOARCH, + Version: Version, + }) + req, _ := http.NewRequestWithContext(ctx, http.MethodPost, a.cfg.Host+"/api/agent/enroll", bytes.NewReader(body)) + req.Header.Set("X-Enroll-Secret", a.cfg.EnrollSecret) + req.Header.Set("Content-Type", "application/json") + resp, err := a.http.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + b, _ := io.ReadAll(io.LimitReader(resp.Body, 512)) + return fmt.Errorf("server said %s: %s", resp.Status, bytes.TrimSpace(b)) + } + var er proto.EnrollResponse + if err := json.NewDecoder(resp.Body).Decode(&er); err != nil { + return err + } + a.state = state{AgentID: er.AgentID, Token: er.Token} + return a.saveState() +} + +// pollOnce performs one control long-poll and dispatches any instruction. +func (a *Agent) pollOnce(ctx context.Context) error { + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, a.cfg.Host+"/api/agent/poll", nil) + a.authHeaders(req) + // Report the version occasionally (not every poll), so the panel can flag + // outdated agents without extra traffic. + a.pollCount++ + if a.pollCount%20 == 1 { + req.Header.Set("X-Agent-Version", Version) + } + resp, err := a.http.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusUnauthorized { + return fmt.Errorf("unauthorized (token rejected)") + } + if resp.StatusCode != http.StatusOK { + return fmt.Errorf("status %s", resp.Status) + } + var msg proto.ControlMessage + if err := json.NewDecoder(resp.Body).Decode(&msg); err != nil { + return err + } + // Dispatch in the background so we keep polling for more work. + switch msg.Type { + case proto.CtrlOpenTerminal: + go a.runTerminal(ctx, msg.SessionID) + case proto.CtrlOpenForward: + go a.runForward(ctx, msg.SessionID, msg.Port) + case proto.CtrlTask: + go a.runTask(ctx, msg.TaskID) + } + return nil +} + +func (a *Agent) authHeaders(req *http.Request) { + req.Header.Set("X-Agent-Id", a.state.AgentID) + req.Header.Set("Authorization", "Bearer "+a.state.Token) +} + +// --- state persistence --- + +func (a *Agent) loadState() error { + b, err := os.ReadFile(a.cfg.StatePath) + if os.IsNotExist(err) { + return nil + } + if err != nil { + return err + } + return json.Unmarshal(b, &a.state) +} + +func (a *Agent) saveState() error { + if err := os.MkdirAll(filepath.Dir(a.cfg.StatePath), 0o700); err != nil { + return err + } + b, _ := json.MarshalIndent(a.state, "", " ") + return os.WriteFile(a.cfg.StatePath, b, 0o600) +} + +var b64 = base64.StdEncoding diff --git a/internal/agent/fs.go b/internal/agent/fs.go new file mode 100644 index 0000000..22a807e --- /dev/null +++ b/internal/agent/fs.go @@ -0,0 +1,224 @@ +package agent + +import ( + "archive/zip" + "encoding/base64" + "encoding/json" + "errors" + "fmt" + "io" + "os" + "os/exec" + "path/filepath" + "sort" + "strings" + "unicode/utf8" + + "github.com/x-grk/longpolling-remote-access/internal/proto" +) + +// fsReadCap bounds how many bytes a view/read returns (5 MiB). +const fsReadCap = 5 * 1024 * 1024 + +// fsDownloadCap bounds a browser download (kept under the 64 MiB result/body +// limit once base64-encoded). +const fsDownloadCap = 40 * 1024 * 1024 + +// dispatchFS runs one native file-manager operation. +func dispatchFS(env proto.TaskEnvelope) (any, error) { + switch env.Type { + case proto.TaskFSList: + var a proto.FSPathArgs + _ = json.Unmarshal(env.Args, &a) + return fsList(a.Path) + case proto.TaskFSRead: + var a proto.FSPathArgs + _ = json.Unmarshal(env.Args, &a) + return fsReadCapped(a.Path, fsReadCap) + case proto.TaskFSDownload: + var a proto.FSPathArgs + _ = json.Unmarshal(env.Args, &a) + return fsReadCapped(a.Path, fsDownloadCap) + case proto.TaskFSWrite: + var a proto.FSWriteArgs + _ = json.Unmarshal(env.Args, &a) + return fsWrite(a.Path, a.Data) + case proto.TaskFSMkdir: + var a proto.FSPathArgs + _ = json.Unmarshal(env.Args, &a) + return map[string]string{"path": a.Path}, os.MkdirAll(a.Path, 0o755) + case proto.TaskFSDelete: + var a proto.FSPathArgs + _ = json.Unmarshal(env.Args, &a) + return map[string]string{"path": a.Path}, os.Remove(a.Path) + case proto.TaskFSDelTree: + var a proto.FSPathArgs + _ = json.Unmarshal(env.Args, &a) + if a.Path == "" || a.Path == "/" { + return nil, errors.New("отказ: удаление корня запрещено") + } + return map[string]string{"path": a.Path}, os.RemoveAll(a.Path) + case proto.TaskFSMove: + var a proto.FSMoveArgs + _ = json.Unmarshal(env.Args, &a) + return map[string]string{"src": a.Src, "dst": a.Dst}, os.Rename(a.Src, a.Dst) + case proto.TaskFSUnzip: + var a proto.FSUnzipArgs + _ = json.Unmarshal(env.Args, &a) + return fsUnzip(a.Path, a.Dest) + case proto.TaskFSZip: + var a proto.FSZipArgs + _ = json.Unmarshal(env.Args, &a) + return fsZip(a.Paths, a.Dest) + default: + return nil, fmt.Errorf("неизвестная операция %q", env.Type) + } +} + +func fsList(path string) (proto.FSListResult, error) { + if path == "" { + path = "/" + } + abs, _ := filepath.Abs(path) + entries, err := os.ReadDir(abs) + if err != nil { + return proto.FSListResult{}, err + } + out := make([]proto.FSEntry, 0, len(entries)) + for _, e := range entries { + info, err := e.Info() + if err != nil { + continue + } + out = append(out, proto.FSEntry{ + Name: e.Name(), + IsDir: e.IsDir(), + Size: info.Size(), + Mode: info.Mode().String(), + MTime: info.ModTime().Unix(), + }) + } + // Directories first, then alphabetical. + sort.Slice(out, func(i, j int) bool { + if out[i].IsDir != out[j].IsDir { + return out[i].IsDir + } + return strings.ToLower(out[i].Name) < strings.ToLower(out[j].Name) + }) + return proto.FSListResult{Path: abs, Entries: out}, nil +} + +func fsReadCapped(path string, cap int) (proto.FSReadResult, error) { + info, err := os.Stat(path) + if err != nil { + return proto.FSReadResult{}, err + } + if info.IsDir() { + return proto.FSReadResult{}, errors.New("это каталог") + } + f, err := os.Open(path) + if err != nil { + return proto.FSReadResult{}, err + } + defer f.Close() + buf := make([]byte, cap+1) + n, _ := io.ReadFull(f, buf) + truncated := n > cap + if truncated { + n = cap + } + data := buf[:n] + binary := !utf8.Valid(data) || containsNUL(data) + return proto.FSReadResult{ + Path: path, + Size: info.Size(), + Content: base64.StdEncoding.EncodeToString(data), + Truncated: truncated, + Binary: binary, + }, nil +} + +func containsNUL(b []byte) bool { + for _, c := range b { + if c == 0 { + return true + } + } + return false +} + +func fsWrite(path, dataB64 string) (any, error) { + data, err := base64.StdEncoding.DecodeString(dataB64) + if err != nil { + return nil, err + } + if err := os.WriteFile(path, data, 0o644); err != nil { + return nil, err + } + return map[string]any{"path": path, "size": len(data)}, nil +} + +func fsUnzip(path, dest string) (any, error) { + if dest == "" { + dest = filepath.Dir(path) + } + bin, err := exec.LookPath("unzip") + if err != nil { + return nil, errors.New("unzip не установлен на клиенте") + } + out, err := exec.Command(bin, "-o", path, "-d", dest).CombinedOutput() + if err != nil { + return nil, fmt.Errorf("unzip: %v: %s", err, strings.TrimSpace(string(out))) + } + return map[string]string{"dest": dest}, nil +} + +// fsZip creates dest as a zip archive of the given paths (files and dirs). +func fsZip(paths []string, dest string) (any, error) { + if dest == "" { + return nil, errors.New("не указан путь архива") + } + if len(paths) == 0 { + return nil, errors.New("не выбраны файлы") + } + f, err := os.Create(dest) + if err != nil { + return nil, err + } + defer f.Close() + zw := zip.NewWriter(f) + defer zw.Close() + + for _, p := range paths { + parent := filepath.Dir(p) + err := filepath.Walk(p, func(fp string, info os.FileInfo, err error) error { + if err != nil { + return err + } + rel, err := filepath.Rel(parent, fp) + if err != nil { + return err + } + rel = filepath.ToSlash(rel) + if info.IsDir() { + _, err := zw.Create(rel + "/") + return err + } + w, err := zw.Create(rel) + if err != nil { + return err + } + src, err := os.Open(fp) + if err != nil { + return err + } + defer src.Close() + _, err = io.Copy(w, src) + return err + }) + if err != nil { + return nil, err + } + } + return map[string]string{"dest": dest}, nil +} diff --git a/internal/agent/session.go b/internal/agent/session.go new file mode 100644 index 0000000..101343f --- /dev/null +++ b/internal/agent/session.go @@ -0,0 +1,131 @@ +package agent + +import ( + "bytes" + "context" + "encoding/json" + "io" + "log" + "net" + "net/http" + "strconv" + "time" + + "github.com/x-grk/longpolling-remote-access/internal/proto" +) + +// runTerminal spawns a PTY and bridges it to the session. +func (a *Agent) runTerminal(parent context.Context, sid string) { + term, err := StartPTY() + if err != nil { + log.Printf("term %s: start pty: %v", sid, err) + a.postOut(context.Background(), sid, proto.OutMessage{Exit: true}) + return + } + log.Printf("term %s: shell started", sid) + a.bridgeStream(parent, sid, term, term.Resize) + log.Printf("term %s: ended", sid) +} + +// runForward dials 127.0.0.1:port on the client and bridges it to the session, +// giving the host a reverse TCP tunnel over the same long-poll transport. +func (a *Agent) runForward(parent context.Context, sid string, port int) { + d := net.Dialer{Timeout: 10 * time.Second} + conn, err := d.DialContext(parent, "tcp", net.JoinHostPort("127.0.0.1", strconv.Itoa(port))) + if err != nil { + log.Printf("forward %s: dial :%d: %v", sid, port, err) + a.postOut(context.Background(), sid, proto.OutMessage{Exit: true}) + return + } + // TCP_NODELAY (default on in Go) keeps latency low for small packets. + if tc, ok := conn.(*net.TCPConn); ok { + _ = tc.SetNoDelay(true) + } + log.Printf("forward %s: connected to :%d", sid, port) + a.bridgeStream(parent, sid, conn, nil) +} + +// bridgeStream wires a local byte stream (PTY or socket) to a hub session: +// server->agent frames are written to rw, and rw output is streamed back. +func (a *Agent) bridgeStream(parent context.Context, sid string, rw io.ReadWriteCloser, onResize func(cols, rows uint16)) { + ctx, cancel := context.WithCancel(parent) + defer cancel() + defer rw.Close() + + // Downstream: pull input frames and write them to the stream. + go func() { + defer cancel() + for ctx.Err() == nil { + if !a.pollIn(ctx, sid, rw, onResize) { + return + } + } + }() + + // Upstream: stream output until the stream (or session) ends. + buf := make([]byte, 32*1024) + for { + n, err := rw.Read(buf) + if n > 0 { + if !a.postOut(ctx, sid, proto.OutMessage{Data: b64.EncodeToString(buf[:n])}) { + return + } + } + if err != nil { + a.postOut(ctx, sid, proto.OutMessage{Exit: true}) + return + } + } +} + +// pollIn performs one downstream long-poll and applies the frames. +// Returns false when the session is gone and the loop should stop. +func (a *Agent) pollIn(ctx context.Context, sid string, w io.Writer, onResize func(cols, rows uint16)) bool { + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, a.cfg.Host+"/api/agent/term/"+sid+"/in", nil) + a.authHeaders(req) + resp, err := a.http.Do(req) + if err != nil { + return ctx.Err() == nil + } + defer resp.Body.Close() + if resp.StatusCode == http.StatusGone || resp.StatusCode == http.StatusUnauthorized { + return false + } + if resp.StatusCode != http.StatusOK { + return true + } + var batch proto.InBatch + if err := json.NewDecoder(resp.Body).Decode(&batch); err != nil { + return true + } + for _, f := range batch.Frames { + switch f.Type { + case proto.FrameStdin: + if data, err := b64.DecodeString(f.Data); err == nil { + _, _ = w.Write(data) + } + case proto.FrameResize: + if onResize != nil { + onResize(f.Cols, f.Rows) + } + case proto.FrameClose: + return false + } + } + return true +} + +// postOut sends one upstream message. Returns false if the session is gone. +func (a *Agent) postOut(ctx context.Context, sid string, msg proto.OutMessage) bool { + body, _ := json.Marshal(msg) + req, _ := http.NewRequestWithContext(ctx, http.MethodPost, a.cfg.Host+"/api/agent/term/"+sid+"/out", bytes.NewReader(body)) + a.authHeaders(req) + req.Header.Set("Content-Type", "application/json") + resp, err := a.http.Do(req) + if err != nil { + return ctx.Err() == nil + } + defer resp.Body.Close() + _, _ = io.Copy(io.Discard, resp.Body) // drain so the connection is reused + return resp.StatusCode == http.StatusNoContent +} diff --git a/internal/agent/task.go b/internal/agent/task.go new file mode 100644 index 0000000..c7f67fc --- /dev/null +++ b/internal/agent/task.go @@ -0,0 +1,97 @@ +package agent + +import ( + "bytes" + "context" + "encoding/json" + "io" + "net/http" + + "github.com/x-grk/longpolling-remote-access/internal/proto" +) + +// runTask fetches one task, executes it, and posts the result back. +func (a *Agent) runTask(ctx context.Context, taskID string) { + env, err := a.fetchTask(ctx, taskID) + if err != nil { + return + } + var res proto.TaskResult + if env.Type == proto.TaskSysUpdate { + res = a.doUpdate() + } else { + res = a.execTask(env) + } + a.postResult(ctx, taskID, res) +} + +func (a *Agent) fetchTask(ctx context.Context, taskID string) (proto.TaskEnvelope, error) { + var env proto.TaskEnvelope + req, _ := http.NewRequestWithContext(ctx, http.MethodGet, a.cfg.Host+"/api/agent/task/"+taskID, nil) + a.authHeaders(req) + resp, err := a.http.Do(req) + if err != nil { + return env, err + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return env, errStatus(resp.StatusCode) + } + err = json.NewDecoder(resp.Body).Decode(&env) + return env, err +} + +func (a *Agent) postResult(ctx context.Context, taskID string, res proto.TaskResult) { + body, _ := json.Marshal(res) + req, _ := http.NewRequestWithContext(ctx, http.MethodPost, a.cfg.Host+"/api/agent/task/"+taskID+"/result", bytes.NewReader(body)) + a.authHeaders(req) + req.Header.Set("Content-Type", "application/json") + resp, err := a.http.Do(req) + if err != nil { + return + } + io.Copy(io.Discard, resp.Body) + resp.Body.Close() +} + +// execTask runs a single file-manager operation natively and returns a result. +func (a *Agent) execTask(env proto.TaskEnvelope) proto.TaskResult { + data, err := dispatchFS(env) + if err != nil { + return proto.TaskResult{Error: err.Error()} + } + raw, err := json.Marshal(data) + if err != nil { + return proto.TaskResult{Error: err.Error()} + } + return proto.TaskResult{OK: true, Data: raw} +} + +type statusErr int + +func (e statusErr) Error() string { return "server status " + itoa(int(e)) } +func errStatus(code int) error { return statusErr(code) } + +func itoa(n int) string { return itoa64(int64(n)) } + +func itoa64(n int64) string { + if n == 0 { + return "0" + } + neg := n < 0 + if neg { + n = -n + } + var b [24]byte + i := len(b) + for n > 0 { + i-- + b[i] = byte('0' + n%10) + n /= 10 + } + if neg { + i-- + b[i] = '-' + } + return string(b[i:]) +} diff --git a/internal/agent/terminal.go b/internal/agent/terminal.go new file mode 100644 index 0000000..b6aefd8 --- /dev/null +++ b/internal/agent/terminal.go @@ -0,0 +1,54 @@ +package agent + +import ( + "os" + "os/exec" + + "github.com/creack/pty" +) + +// PTY is a pseudo-terminal running an interactive login shell. +type PTY struct { + cmd *exec.Cmd + f *os.File +} + +// StartPTY launches the shell attached to a new PTY. The agent runs as root +// (systemd User=root), so this is a root shell by design. +func StartPTY() (*PTY, error) { + shell := "/bin/bash" + if _, err := os.Stat(shell); err != nil { + shell = "/bin/sh" + } + cmd := exec.Command(shell, "-l") + cmd.Env = append(os.Environ(), "TERM=xterm-256color") + f, err := pty.Start(cmd) + if err != nil { + return nil, err + } + return &PTY{cmd: cmd, f: f}, nil +} + +// Read reads terminal output. +func (p *PTY) Read(b []byte) (int, error) { return p.f.Read(b) } + +// Write writes keystrokes to the terminal. +func (p *PTY) Write(b []byte) (int, error) { return p.f.Write(b) } + +// Resize changes the terminal window size. +func (p *PTY) Resize(cols, rows uint16) { + if cols == 0 || rows == 0 { + return + } + _ = pty.Setsize(p.f, &pty.Winsize{Rows: rows, Cols: cols}) +} + +// Close terminates the shell and releases the PTY. +func (p *PTY) Close() error { + err := p.f.Close() + if p.cmd.Process != nil { + _ = p.cmd.Process.Kill() + } + _ = p.cmd.Wait() + return err +} diff --git a/internal/agent/tunnel.go b/internal/agent/tunnel.go new file mode 100644 index 0000000..e5dd121 --- /dev/null +++ b/internal/agent/tunnel.go @@ -0,0 +1,374 @@ +package agent + +import ( + "context" + "encoding/binary" + "fmt" + "log" + "net" + "strings" + "sync" + "time" + + "github.com/x-grk/longpolling-remote-access/internal/tunnel" +) + +// --- direct TCP mux tunnel --- + +// runTunnel keeps a persistent connection to the host's 8446 tunnel, dialing +// client-side ports on demand for "tcp" forward rules. +func (a *Agent) runTunnel(ctx context.Context) { + backoff := time.Second + for ctx.Err() == nil { + if err := a.tunnelOnce(ctx); err != nil { + select { + case <-time.After(backoff): + case <-ctx.Done(): + return + } + if backoff < 15*time.Second { + backoff *= 2 + } + continue + } + backoff = time.Second + } +} + +func (a *Agent) tunnelOnce(ctx context.Context) error { + d := net.Dialer{Timeout: 10 * time.Second} + conn, err := d.DialContext(ctx, "tcp", a.cfg.TunnelAddr) + if err != nil { + return err + } + defer conn.Close() + if tc, ok := conn.(*net.TCPConn); ok { + _ = tc.SetNoDelay(true) + } + + // Handshake: server sends "LPRA-TUN1 ", we reply " ". + _ = conn.SetDeadline(time.Now().Add(10 * time.Second)) + line, err := readLineConn(conn, 512) + if err != nil { + return err + } + f := strings.Fields(line) + if len(f) != 2 || f[0] != "LPRA-TUN1" { + return fmt.Errorf("bad tunnel greeting") + } + nonce, err := hexDecode(f[1]) + if err != nil { + return err + } + key := tunnel.AuthKey(a.state.Token) + if _, err := fmt.Fprintf(conn, "%s %s\n", a.state.AgentID, tunnel.Sign(key, nonce)); err != nil { + return err + } + _ = conn.SetDeadline(time.Time{}) + log.Printf("tunnel: connected to %s", a.cfg.TunnelAddr) + + tc := &tunClient{conn: conn, streams: map[uint32]*agentStream{}} + return tc.readLoop() +} + +type tunClient struct { + conn net.Conn + wmu sync.Mutex + mu sync.Mutex + streams map[uint32]*agentStream +} + +// agentStream buffers downstream data in a channel so bytes that arrive before +// the client-side dial completes are not lost (the OPEN/DATA race). +type agentStream struct { + in chan []byte + closed chan struct{} + once sync.Once +} + +func (s *agentStream) close() { s.once.Do(func() { close(s.closed) }) } + +func (t *tunClient) write(f tunnel.Frame) error { + t.wmu.Lock() + defer t.wmu.Unlock() + return tunnel.WriteFrame(t.conn, f) +} + +func (t *tunClient) readLoop() error { + for { + f, err := tunnel.ReadFrame(t.conn) + if err != nil { + t.shutdown() + return err + } + switch f.Type { + case tunnel.TypeOpen: + if len(f.Payload) >= 2 { + port := int(f.Payload[0])<<8 | int(f.Payload[1]) + t.mu.Lock() + s := &agentStream{in: make(chan []byte, 256), closed: make(chan struct{})} + t.streams[f.Stream] = s + t.mu.Unlock() + go t.openStream(f.Stream, port, s) + } + case tunnel.TypeData: + t.mu.Lock() + s := t.streams[f.Stream] + t.mu.Unlock() + if s != nil { + select { + case s.in <- f.Payload: + case <-s.closed: + } + } + case tunnel.TypeClose: + t.closeStream(f.Stream) + } + } +} + +func (t *tunClient) openStream(id uint32, port int, s *agentStream) { + conn, err := net.DialTimeout("tcp", net.JoinHostPort("127.0.0.1", itoa(port)), 10*time.Second) + if err != nil { + _ = t.write(tunnel.Frame{Type: tunnel.TypeClose, Stream: id}) + t.closeStream(id) + return + } + if tc, ok := conn.(*net.TCPConn); ok { + _ = tc.SetNoDelay(true) + } + defer conn.Close() + + // downstream buffer -> local socket + go func() { + for { + select { + case b := <-s.in: + if _, err := conn.Write(b); err != nil { + s.close() + return + } + case <-s.closed: + return + } + } + }() + + // local socket -> upstream frames + buf := make([]byte, 32*1024) + for { + n, err := conn.Read(buf) + if n > 0 { + if t.write(tunnel.Frame{Type: tunnel.TypeData, Stream: id, Payload: append([]byte(nil), buf[:n]...)}) != nil { + break + } + } + if err != nil { + break + } + } + _ = t.write(tunnel.Frame{Type: tunnel.TypeClose, Stream: id}) + t.closeStream(id) +} + +func (t *tunClient) closeStream(id uint32) { + t.mu.Lock() + s := t.streams[id] + delete(t.streams, id) + t.mu.Unlock() + if s != nil { + s.close() + } +} + +func (t *tunClient) shutdown() { + t.mu.Lock() + streams := t.streams + t.streams = map[uint32]*agentStream{} + t.mu.Unlock() + for _, s := range streams { + s.close() + } + t.conn.Close() +} + +// --- direct UDP forwarding --- + +// runUDP maintains the agent's UDP endpoint on the host's 8446 channel and +// relays datagrams to/from client-side UDP ports for "udp" forward rules. +func (a *Agent) runUDP(ctx context.Context) { + backoff := time.Second + for ctx.Err() == nil { + if err := a.udpOnce(ctx); err != nil { + select { + case <-time.After(backoff): + case <-ctx.Done(): + return + } + if backoff < 15*time.Second { + backoff *= 2 + } + continue + } + backoff = time.Second + } +} + +func (a *Agent) udpOnce(ctx context.Context) error { + raddr, err := net.ResolveUDPAddr("udp", a.cfg.TunnelAddr) + if err != nil { + return err + } + conn, err := net.DialUDP("udp", nil, raddr) + if err != nil { + return err + } + defer conn.Close() + + key := tunnel.AuthKey(a.state.Token) + register := func() { + m := time.Now().Unix() / 60 + sig := tunnel.Sign(key, []byte(a.state.AgentID+"|"+itoa64(m))) + _, _ = conn.Write([]byte("R" + a.state.AgentID + " " + sig)) + } + register() + + // Keepalive registration so the host keeps our NAT mapping fresh. + regCtx, cancel := context.WithCancel(ctx) + defer cancel() + go func() { + t := time.NewTicker(20 * time.Second) + defer t.Stop() + for { + select { + case <-regCtx.Done(): + return + case <-t.C: + register() + } + } + }() + + uc := &udpClient{server: conn, dials: map[uint32]*net.UDPConn{}} + defer uc.shutdown() + + buf := make([]byte, 65535) + for { + n, err := conn.Read(buf) + if err != nil { + return err + } + if n < 7 || buf[0] != 'D' { + continue + } + sid := binary.BigEndian.Uint32(buf[1:5]) + port := int(binary.BigEndian.Uint16(buf[5:7])) + uc.send(sid, port, append([]byte(nil), buf[7:n]...)) + } +} + +type udpClient struct { + server *net.UDPConn // connection back to host:8446 + mu sync.Mutex + dials map[uint32]*net.UDPConn // sid -> client-side socket +} + +// send forwards a datagram to 127.0.0.1:port and ensures replies are relayed. +func (u *udpClient) send(sid uint32, port int, data []byte) { + u.mu.Lock() + c := u.dials[sid] + u.mu.Unlock() + if c == nil { + raddr, err := net.ResolveUDPAddr("udp", net.JoinHostPort("127.0.0.1", itoa(port))) + if err != nil { + return + } + c, err = net.DialUDP("udp", nil, raddr) + if err != nil { + return + } + u.mu.Lock() + u.dials[sid] = c + u.mu.Unlock() + go u.replyLoop(sid, c) + } + _, _ = c.Write(data) +} + +func (u *udpClient) replyLoop(sid uint32, c *net.UDPConn) { + buf := make([]byte, 65535) + hdr := make([]byte, 7) + hdr[0] = 'D' + binary.BigEndian.PutUint32(hdr[1:5], sid) + for { + _ = c.SetReadDeadline(time.Now().Add(90 * time.Second)) + n, err := c.Read(buf) + if err != nil { + u.mu.Lock() + delete(u.dials, sid) + u.mu.Unlock() + c.Close() + return + } + out := append(append([]byte(nil), hdr...), buf[:n]...) + _, _ = u.server.Write(out) + } +} + +func (u *udpClient) shutdown() { + u.mu.Lock() + dials := u.dials + u.dials = map[uint32]*net.UDPConn{} + u.mu.Unlock() + for _, c := range dials { + c.Close() + } +} + +// --- small helpers --- + +func readLineConn(c net.Conn, max int) (string, error) { + var b []byte + one := make([]byte, 1) + for len(b) < max { + n, err := c.Read(one) + if n > 0 { + if one[0] == '\n' { + return string(b), nil + } + b = append(b, one[0]) + } + if err != nil { + return "", err + } + } + return string(b), nil +} + +func hexDecode(s string) ([]byte, error) { + out := make([]byte, len(s)/2) + for i := 0; i < len(out); i++ { + hi, err := hexVal(s[i*2]) + if err != nil { + return nil, err + } + lo, err := hexVal(s[i*2+1]) + if err != nil { + return nil, err + } + out[i] = hi<<4 | lo + } + return out, nil +} + +func hexVal(c byte) (byte, error) { + switch { + case c >= '0' && c <= '9': + return c - '0', nil + case c >= 'a' && c <= 'f': + return c - 'a' + 10, nil + case c >= 'A' && c <= 'F': + return c - 'A' + 10, nil + } + return 0, fmt.Errorf("bad hex") +} diff --git a/internal/agent/update.go b/internal/agent/update.go new file mode 100644 index 0000000..2e29c6e --- /dev/null +++ b/internal/agent/update.go @@ -0,0 +1,76 @@ +package agent + +import ( + "context" + "encoding/json" + "fmt" + "io" + "net/http" + "os" + "os/exec" + "path/filepath" + "runtime" + "time" + + "github.com/x-grk/longpolling-remote-access/internal/proto" +) + +// doUpdate downloads the latest agent binary for this platform, atomically +// replaces the running executable, and schedules a service restart. It never +// uninstalls — only updates. +func (a *Agent) doUpdate() proto.TaskResult { + exe, err := os.Executable() + if err != nil { + return proto.TaskResult{Error: "не удалось определить путь бинарника: " + err.Error()} + } + exe, _ = filepath.EvalSymlinks(exe) + + url := fmt.Sprintf("%s/download/agent-%s-%s", a.cfg.Host, runtime.GOOS, runtime.GOARCH) + req, _ := http.NewRequestWithContext(context.Background(), http.MethodGet, url, nil) + resp, err := a.http.Do(req) + if err != nil { + return proto.TaskResult{Error: "скачивание: " + err.Error()} + } + defer resp.Body.Close() + if resp.StatusCode != http.StatusOK { + return proto.TaskResult{Error: "скачивание: сервер вернул " + resp.Status} + } + + tmp := exe + ".new" + f, err := os.OpenFile(tmp, os.O_CREATE|os.O_WRONLY|os.O_TRUNC, 0o755) + if err != nil { + return proto.TaskResult{Error: "создание файла: " + err.Error()} + } + if _, err := io.Copy(f, resp.Body); err != nil { + f.Close() + os.Remove(tmp) + return proto.TaskResult{Error: "запись: " + err.Error()} + } + f.Close() + + // Atomic replace: renaming over a running executable is allowed on Linux; + // the live process keeps the old inode until it restarts. + if err := os.Rename(tmp, exe); err != nil { + os.Remove(tmp) + return proto.TaskResult{Error: "замена бинарника: " + err.Error()} + } + + // Restart shortly, after the result has been reported. + go func() { + time.Sleep(1500 * time.Millisecond) + restartSelf() + }() + + data, _ := json.Marshal(map[string]string{"status": "обновлено, перезапуск"}) + return proto.TaskResult{OK: true, Data: data} +} + +// restartSelf restarts the agent so the new binary takes effect. +func restartSelf() { + if path, err := exec.LookPath("systemctl"); err == nil { + _ = exec.Command(path, "restart", "lpagent").Start() + return + } + // No systemd (e.g. launchd/manual run): exit so the supervisor restarts us. + os.Exit(0) +} diff --git a/internal/proto/proto.go b/internal/proto/proto.go new file mode 100644 index 0000000..88aaf66 --- /dev/null +++ b/internal/proto/proto.go @@ -0,0 +1,146 @@ +// Package proto describes the wire messages shared by the server and the agent. +// +// All agent<->server traffic is plain JSON over HTTP(S) long-polling so it +// survives NAT and restrictive egress firewalls: the agent only ever makes +// outbound requests. +package proto + +import "encoding/json" + +// Control poll message types (server -> agent, returned from /api/agent/poll). +const ( + // CtrlIdle is returned when the long-poll times out with nothing to do. + CtrlIdle = "idle" + // CtrlOpenTerminal tells the agent to spawn a PTY for the session id. + CtrlOpenTerminal = "open_terminal" + // CtrlOpenForward tells the agent to dial 127.0.0.1:Port and bridge it to + // the session id (reverse TCP port-forward). + CtrlOpenForward = "open_forward" + // CtrlTask tells the agent to fetch and run the one-shot task TaskID. + CtrlTask = "task" +) + +// EnrollRequest is sent once by a freshly installed agent. +type EnrollRequest struct { + Name string `json:"name"` + Hostname string `json:"hostname"` + OS string `json:"os"` + Arch string `json:"arch"` + Version string `json:"version"` +} + +// EnrollResponse carries the identity the agent persists and uses from then on. +type EnrollResponse struct { + AgentID string `json:"agent_id"` + Token string `json:"token"` +} + +// ControlMessage is the body returned by the control long-poll. +type ControlMessage struct { + Type string `json:"type"` + SessionID string `json:"session_id,omitempty"` + Port int `json:"port,omitempty"` // CtrlOpenForward: client-side port to dial + TaskID string `json:"task_id,omitempty"` // CtrlTask +} + +// Terminal / stream frame types on the session "in" channel (server -> agent). +// Reused for both PTY terminals and raw TCP port-forward streams. +const ( + FrameStdin = "stdin" // base64 bytes for the PTY / socket + FrameResize = "resize" // PTY window size (terminal only) + FrameClose = "close" // tear the stream down +) + +// InFrame is one element of the stream downstream batch (server -> agent). +type InFrame struct { + Type string `json:"type"` + Data string `json:"data,omitempty"` // base64 bytes + Cols uint16 `json:"cols,omitempty"` + Rows uint16 `json:"rows,omitempty"` +} + +// InBatch is the JSON body returned by GET /api/agent/term/{sid}/in. +type InBatch struct { + Frames []InFrame `json:"frames"` +} + +// OutMessage is POSTed by the agent to /api/agent/term/{sid}/out. +type OutMessage struct { + Data string `json:"data,omitempty"` // base64 bytes + Exit bool `json:"exit,omitempty"` +} + +// --- one-shot tasks (file manager, native in the agent) --- + +const ( + TaskFSList = "fs.list" // list a directory + TaskFSRead = "fs.read" // read file content (for view/edit) + TaskFSWrite = "fs.write" // write/overwrite file (edit save, create, upload) + TaskFSMkdir = "fs.mkdir" // create directory + TaskFSDelete = "fs.delete" // delete a single file or empty dir + TaskFSDelTree = "fs.deltree" // recursively delete a directory + TaskFSUnzip = "fs.unzip" // unzip an archive (needs `unzip` on the client) + TaskFSZip = "fs.zip" // zip selected paths (native archive/zip) + TaskFSMove = "fs.move" // move/rename + TaskFSDownload = "fs.download" // read a whole file (larger cap) for browser download + + TaskSysUpdate = "sys.update" // download the latest agent binary and restart +) + +// TaskEnvelope is what the agent fetches for a task id. +type TaskEnvelope struct { + Type string `json:"type"` + Args json.RawMessage `json:"args"` +} + +// TaskResult is POSTed back by the agent. +type TaskResult struct { + OK bool `json:"ok"` + Error string `json:"error,omitempty"` + Data json.RawMessage `json:"data,omitempty"` +} + +// fs task argument shapes. +type FSPathArgs struct { + Path string `json:"path"` +} +type FSWriteArgs struct { + Path string `json:"path"` + Data string `json:"data"` // base64 content +} +type FSUnzipArgs struct { + Path string `json:"path"` + Dest string `json:"dest"` +} +type FSZipArgs struct { + Paths []string `json:"paths"` + Dest string `json:"dest"` +} +type FSMoveArgs struct { + Src string `json:"src"` + Dst string `json:"dst"` +} + +// FSEntry is one directory entry. +type FSEntry struct { + Name string `json:"name"` + IsDir bool `json:"is_dir"` + Size int64 `json:"size"` + Mode string `json:"mode"` + MTime int64 `json:"mtime"` +} + +// FSListResult is returned for TaskFSList. +type FSListResult struct { + Path string `json:"path"` + Entries []FSEntry `json:"entries"` +} + +// FSReadResult is returned for TaskFSRead. +type FSReadResult struct { + Path string `json:"path"` + Size int64 `json:"size"` + Content string `json:"content"` // base64 + Truncated bool `json:"truncated"` + Binary bool `json:"binary"` +} diff --git a/internal/server/.DS_Store b/internal/server/.DS_Store new file mode 100644 index 0000000..5eb9e26 Binary files /dev/null and b/internal/server/.DS_Store differ diff --git a/internal/server/agents.go b/internal/server/agents.go new file mode 100644 index 0000000..44a7427 --- /dev/null +++ b/internal/server/agents.go @@ -0,0 +1,62 @@ +package server + +import ( + "net/http" + "strings" +) + +// authenticateAgent resolves the agent identified by the Bearer token on the +// request, verifying the token against the stored hash in constant time. +// Returns nil if authentication fails. +func (s *Server) authenticateAgent(r *http.Request) *Agent { + id := r.Header.Get("X-Agent-Id") + token := bearerToken(r) + if id == "" || token == "" { + return nil + } + a, err := s.db.GetAgent(id) + if err != nil || a == nil { + return nil + } + if !constantTimeEqual(a.TokenHash, hashToken(token)) { + return nil + } + return a +} + +func bearerToken(r *http.Request) string { + h := r.Header.Get("Authorization") + if strings.HasPrefix(h, "Bearer ") { + return strings.TrimPrefix(h, "Bearer ") + } + return "" +} + +// enroll creates a new agent record for a valid enroll secret and returns the +// generated id plus a one-time plaintext token (only the hash is stored). +func (s *Server) enroll(name, hostname, os, arch, version string) (id, token string, err error) { + id = randToken()[:16] + token = randToken() + a := Agent{ + ID: id, + Name: firstNonEmpty(name, hostname, id), + Hostname: hostname, + OS: os, + Arch: arch, + Version: version, + TokenHash: hashToken(token), + } + if err = s.db.CreateAgent(a); err != nil { + return "", "", err + } + return id, token, nil +} + +func firstNonEmpty(vals ...string) string { + for _, v := range vals { + if strings.TrimSpace(v) != "" { + return v + } + } + return "" +} diff --git a/internal/server/auth.go b/internal/server/auth.go new file mode 100644 index 0000000..8eb20ed --- /dev/null +++ b/internal/server/auth.go @@ -0,0 +1,150 @@ +package server + +import ( + "crypto/sha256" + "crypto/subtle" + "net/http" + "sync" + "time" + + "golang.org/x/crypto/bcrypt" +) + +const ( + sessionCookie = "lpra_session" + csrfCookie = "lpra_csrf" + sessionTTL = 12 * time.Hour +) + +// hashToken returns a hex SHA-256 of a bearer token, for at-rest storage of +// per-agent tokens (we never need the plaintext back, only to compare). +func hashToken(token string) string { + sum := sha256.Sum256([]byte(token)) + return string(hexEncode(sum[:])) +} + +func hexEncode(b []byte) []byte { + const hextable = "0123456789abcdef" + out := make([]byte, len(b)*2) + for i, v := range b { + out[i*2] = hextable[v>>4] + out[i*2+1] = hextable[v&0x0f] + } + return out +} + +// constantTimeEqual compares two strings without leaking length-independent timing. +func constantTimeEqual(a, b string) bool { + return subtle.ConstantTimeCompare([]byte(a), []byte(b)) == 1 +} + +func hashPassword(pw string) (string, error) { + h, err := bcrypt.GenerateFromPassword([]byte(pw), bcrypt.DefaultCost) + return string(h), err +} + +func checkPassword(hash, pw string) bool { + return bcrypt.CompareHashAndPassword([]byte(hash), []byte(pw)) == nil +} + +// loginLimiter throttles failed logins per client IP to blunt brute force. +type loginLimiter struct { + mu sync.Mutex + fail map[string]*attempt +} + +type attempt struct { + count int + until time.Time +} + +func newLoginLimiter() *loginLimiter { return &loginLimiter{fail: map[string]*attempt{}} } + +// blocked reports whether this IP is currently in a lockout window. +func (l *loginLimiter) blocked(ip string) bool { + l.mu.Lock() + defer l.mu.Unlock() + a := l.fail[ip] + return a != nil && time.Now().Before(a.until) +} + +func (l *loginLimiter) fin(ip string, ok bool) { + l.mu.Lock() + defer l.mu.Unlock() + if ok { + delete(l.fail, ip) + return + } + a := l.fail[ip] + if a == nil { + a = &attempt{} + l.fail[ip] = a + } + a.count++ + // Exponential-ish backoff after 5 failures, capped at 5 minutes. + if a.count >= 5 { + d := time.Duration(a.count-4) * 15 * time.Second + if d > 5*time.Minute { + d = 5 * time.Minute + } + a.until = time.Now().Add(d) + } +} + +// currentAdmin returns the logged-in admin for a request, or nil. +func (s *Server) currentAdmin(r *http.Request) *Admin { + c, err := r.Cookie(sessionCookie) + if err != nil { + return nil + } + a, err := s.db.SessionAdmin(c.Value) + if err != nil { + return nil + } + return a +} + +// setSession issues a session cookie and a paired CSRF cookie. +func (s *Server) setSession(w http.ResponseWriter, adminID int64) error { + sid := randToken() + if err := s.db.CreateSession(sid, adminID, sessionTTL); err != nil { + return err + } + secure := s.secureCookies + http.SetCookie(w, &http.Cookie{ + Name: sessionCookie, Value: sid, Path: "/", + HttpOnly: true, Secure: secure, SameSite: http.SameSiteLaxMode, + MaxAge: int(sessionTTL.Seconds()), + }) + // CSRF token is readable by templates (not HttpOnly) and echoed back in forms. + http.SetCookie(w, &http.Cookie{ + Name: csrfCookie, Value: randToken(), Path: "/", + Secure: secure, SameSite: http.SameSiteLaxMode, + MaxAge: int(sessionTTL.Seconds()), + }) + return nil +} + +func (s *Server) clearSession(w http.ResponseWriter, r *http.Request) { + if c, err := r.Cookie(sessionCookie); err == nil { + _ = s.db.DeleteSession(c.Value) + } + for _, name := range []string{sessionCookie, csrfCookie} { + http.SetCookie(w, &http.Cookie{Name: name, Value: "", Path: "/", MaxAge: -1}) + } +} + +// csrfToken reads the CSRF cookie value for embedding in a form. +func csrfToken(r *http.Request) string { + if c, err := r.Cookie(csrfCookie); err == nil { + return c.Value + } + return "" +} + +// checkCSRF verifies the submitted token matches the cookie (double-submit). +func checkCSRF(r *http.Request) bool { + want := csrfToken(r) + got := r.FormValue("csrf") + return want != "" && constantTimeEqual(want, got) +} diff --git a/internal/server/config.go b/internal/server/config.go new file mode 100644 index 0000000..5cacfeb --- /dev/null +++ b/internal/server/config.go @@ -0,0 +1,117 @@ +package server + +import ( + "crypto/rand" + "encoding/hex" +) + +// Config keys stored in the config table. +const ( + cfgHostAddress = "host_address" + cfgEnrollSecret = "enroll_secret" + cfgSetupDone = "setup_done" + cfgInstallCode = "install_code" + cfgUfwEnabled = "ufw_enabled" +) + +// DefaultHostAddress is the public URL agents dial by default. It is baked into +// the rendered install.sh and can be changed from the settings page. +const DefaultHostAddress = "https://REDACTED:8445" + +// Version is the server build version, injected at compile time via -ldflags +// (-X .../internal/server.Version=...). It is the newest version agents update to. +var Version = "dev" + +// ensureDefaults seeds the host address and a random enroll secret on first boot. +func (db *DB) ensureDefaults() error { + host, err := db.GetConfig(cfgHostAddress) + if err != nil { + return err + } + if host == "" { + if err := db.SetConfig(cfgHostAddress, DefaultHostAddress); err != nil { + return err + } + } + secret, err := db.GetConfig(cfgEnrollSecret) + if err != nil { + return err + } + if secret == "" { + if err := db.SetConfig(cfgEnrollSecret, randToken()); err != nil { + return err + } + } + code, err := db.GetConfig(cfgInstallCode) + if err != nil { + return err + } + if code == "" { + if err := db.SetConfig(cfgInstallCode, shortCode()); err != nil { + return err + } + } + return nil +} + +func (db *DB) HostAddress() string { + v, _ := db.GetConfig(cfgHostAddress) + if v == "" { + return DefaultHostAddress + } + return v +} + +func (db *DB) EnrollSecret() string { + v, _ := db.GetConfig(cfgEnrollSecret) + return v +} + +// InstallCode returns the short code used for the /i/ install one-liner. +func (db *DB) InstallCode() string { + v, _ := db.GetConfig(cfgInstallCode) + return v +} + +// UfwEnabled reports whether panel-driven ufw integration is on (default yes). +func (db *DB) UfwEnabled() bool { + v, _ := db.GetConfig(cfgUfwEnabled) + return v != "0" +} + +func (db *DB) SetUfwEnabled(on bool) error { + if on { + return db.SetConfig(cfgUfwEnabled, "1") + } + return db.SetConfig(cfgUfwEnabled, "0") +} + +func (db *DB) SetupDone() bool { + v, _ := db.GetConfig(cfgSetupDone) + return v == "1" +} + +func (db *DB) MarkSetupDone() error { return db.SetConfig(cfgSetupDone, "1") } + +// randToken returns 32 bytes of cryptographic randomness as hex (64 chars). +func randToken() string { + b := make([]byte, 32) + if _, err := rand.Read(b); err != nil { + panic("crypto/rand failed: " + err.Error()) + } + return hex.EncodeToString(b) +} + +// shortCode returns a short, URL-safe, typo-resistant code for install links. +// It avoids ambiguous characters (0/O, 1/l/I) so it is easy to read and type. +func shortCode() string { + const alphabet = "abcdefghjkmnpqrstuvwxyz23456789" + b := make([]byte, 7) + if _, err := rand.Read(b); err != nil { + panic("crypto/rand failed: " + err.Error()) + } + for i := range b { + b[i] = alphabet[int(b[i])%len(alphabet)] + } + return string(b) +} diff --git a/internal/server/db.go b/internal/server/db.go new file mode 100644 index 0000000..3e46c03 --- /dev/null +++ b/internal/server/db.go @@ -0,0 +1,452 @@ +package server + +import ( + "database/sql" + "fmt" + "strings" + "time" + + _ "modernc.org/sqlite" +) + +// DB wraps the SQLite connection and all queries used by the server. +type DB struct { + sql *sql.DB +} + +// OpenDB opens (creating if needed) the SQLite database at path and runs +// migrations. The pure-Go driver ("sqlite") needs no CGO. +func OpenDB(path string) (*DB, error) { + // _pragma busy_timeout avoids "database is locked" under the concurrent + // long-poll load; foreign_keys keeps the schema honest. + dsn := fmt.Sprintf("file:%s?_pragma=busy_timeout(5000)&_pragma=foreign_keys(1)&_pragma=journal_mode(WAL)", path) + sdb, err := sql.Open("sqlite", dsn) + if err != nil { + return nil, err + } + // SQLite writes serialize; a single connection avoids lock churn while the + // busy_timeout still covers the long-poll readers. + sdb.SetMaxOpenConns(1) + if err := sdb.Ping(); err != nil { + return nil, err + } + db := &DB{sql: sdb} + if err := db.migrate(); err != nil { + return nil, err + } + return db, nil +} + +func (db *DB) Close() error { return db.sql.Close() } + +func (db *DB) migrate() error { + const schema = ` +CREATE TABLE IF NOT EXISTS config ( + key TEXT PRIMARY KEY, + value TEXT NOT NULL +); +CREATE TABLE IF NOT EXISTS admins ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + username TEXT UNIQUE NOT NULL, + pass_hash TEXT NOT NULL, + created_at INTEGER NOT NULL +); +CREATE TABLE IF NOT EXISTS agents ( + id TEXT PRIMARY KEY, + name TEXT NOT NULL, + hostname TEXT NOT NULL, + os TEXT NOT NULL, + arch TEXT NOT NULL, + token_hash TEXT NOT NULL, + version TEXT NOT NULL DEFAULT '', + enrolled_at INTEGER NOT NULL, + last_seen INTEGER NOT NULL +); +CREATE TABLE IF NOT EXISTS admin_sessions ( + id TEXT PRIMARY KEY, + admin_id INTEGER NOT NULL REFERENCES admins(id) ON DELETE CASCADE, + created_at INTEGER NOT NULL, + expires_at INTEGER NOT NULL +); +CREATE TABLE IF NOT EXISTS audit ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + admin_id INTEGER, + agent_id TEXT, + action TEXT NOT NULL, + at INTEGER NOT NULL +); +CREATE TABLE IF NOT EXISTS ufw_rules ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + port INTEGER NOT NULL, + proto TEXT NOT NULL, + rule_id INTEGER NOT NULL DEFAULT 0, + created_at INTEGER NOT NULL, + UNIQUE(port, proto) +); +CREATE TABLE IF NOT EXISTS forward_rules ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + agent_id TEXT NOT NULL, + client_port INTEGER NOT NULL, + host_port INTEGER NOT NULL, + listen_addr TEXT NOT NULL DEFAULT '0.0.0.0', + proto TEXT NOT NULL DEFAULT 'http', + created_at INTEGER NOT NULL +);` + if _, err := db.sql.Exec(schema); err != nil { + return err + } + // Add columns to databases created before they existed. SQLite has no + // "IF NOT EXISTS" for columns, so ignore the "duplicate column" error. + for _, alter := range []string{ + `ALTER TABLE forward_rules ADD COLUMN proto TEXT NOT NULL DEFAULT 'http'`, + `ALTER TABLE agents ADD COLUMN version TEXT NOT NULL DEFAULT ''`, + `ALTER TABLE ufw_rules ADD COLUMN rule_id INTEGER NOT NULL DEFAULT 0`, + } { + if _, err := db.sql.Exec(alter); err != nil && !strings.Contains(err.Error(), "duplicate column") { + return err + } + } + return nil +} + +// --- config kv --- + +func (db *DB) GetConfig(key string) (string, error) { + var v string + err := db.sql.QueryRow(`SELECT value FROM config WHERE key = ?`, key).Scan(&v) + if err == sql.ErrNoRows { + return "", nil + } + return v, err +} + +func (db *DB) SetConfig(key, value string) error { + _, err := db.sql.Exec( + `INSERT INTO config(key, value) VALUES(?, ?) + ON CONFLICT(key) DO UPDATE SET value = excluded.value`, key, value) + return err +} + +// --- admins --- + +func (db *DB) CountAdmins() (int, error) { + var n int + err := db.sql.QueryRow(`SELECT COUNT(*) FROM admins`).Scan(&n) + return n, err +} + +func (db *DB) CreateAdmin(username, passHash string) error { + _, err := db.sql.Exec( + `INSERT INTO admins(username, pass_hash, created_at) VALUES(?, ?, ?)`, + username, passHash, time.Now().Unix()) + return err +} + +// Admin is one row of the admins table. +type Admin struct { + ID int64 + Username string + PassHash string +} + +func (db *DB) AdminByUsername(username string) (*Admin, error) { + a := &Admin{} + err := db.sql.QueryRow( + `SELECT id, username, pass_hash FROM admins WHERE username = ?`, username). + Scan(&a.ID, &a.Username, &a.PassHash) + if err == sql.ErrNoRows { + return nil, nil + } + return a, err +} + +func (db *DB) ListAdmins() ([]Admin, error) { + rows, err := db.sql.Query(`SELECT id, username FROM admins ORDER BY username`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []Admin + for rows.Next() { + var a Admin + if err := rows.Scan(&a.ID, &a.Username); err != nil { + return nil, err + } + out = append(out, a) + } + return out, rows.Err() +} + +func (db *DB) DeleteAdmin(id int64) error { + _, err := db.sql.Exec(`DELETE FROM admins WHERE id = ?`, id) + return err +} + +// --- admin sessions --- + +func (db *DB) CreateSession(id string, adminID int64, ttl time.Duration) error { + now := time.Now() + _, err := db.sql.Exec( + `INSERT INTO admin_sessions(id, admin_id, created_at, expires_at) VALUES(?, ?, ?, ?)`, + id, adminID, now.Unix(), now.Add(ttl).Unix()) + return err +} + +// SessionAdmin returns the admin for a live (non-expired) session, or nil. +func (db *DB) SessionAdmin(sessionID string) (*Admin, error) { + a := &Admin{} + err := db.sql.QueryRow( + `SELECT a.id, a.username, a.pass_hash + FROM admin_sessions s JOIN admins a ON a.id = s.admin_id + WHERE s.id = ? AND s.expires_at > ?`, sessionID, time.Now().Unix()). + Scan(&a.ID, &a.Username, &a.PassHash) + if err == sql.ErrNoRows { + return nil, nil + } + return a, err +} + +func (db *DB) DeleteSession(id string) error { + _, err := db.sql.Exec(`DELETE FROM admin_sessions WHERE id = ?`, id) + return err +} + +// --- agents --- + +// Agent is one row of the agents table. +type Agent struct { + ID string + Name string + Hostname string + OS string + Arch string + TokenHash string + Version string + LastSeen time.Time +} + +// Online reports whether the agent polled recently enough to be considered up. +func (a Agent) Online() bool { + return time.Since(a.LastSeen) < 45*time.Second +} + +func (db *DB) CreateAgent(a Agent) error { + now := time.Now().Unix() + _, err := db.sql.Exec( + `INSERT INTO agents(id, name, hostname, os, arch, token_hash, version, enrolled_at, last_seen) + VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)`, + a.ID, a.Name, a.Hostname, a.OS, a.Arch, a.TokenHash, a.Version, now, now) + return err +} + +func (db *DB) GetAgent(id string) (*Agent, error) { + a := &Agent{} + var last int64 + err := db.sql.QueryRow( + `SELECT id, name, hostname, os, arch, token_hash, version, last_seen FROM agents WHERE id = ?`, id). + Scan(&a.ID, &a.Name, &a.Hostname, &a.OS, &a.Arch, &a.TokenHash, &a.Version, &last) + if err == sql.ErrNoRows { + return nil, nil + } + if err != nil { + return nil, err + } + a.LastSeen = time.Unix(last, 0) + return a, nil +} + +func (db *DB) ListAgents() ([]Agent, error) { + rows, err := db.sql.Query( + `SELECT id, name, hostname, os, arch, version, last_seen FROM agents ORDER BY name`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []Agent + for rows.Next() { + var a Agent + var last int64 + if err := rows.Scan(&a.ID, &a.Name, &a.Hostname, &a.OS, &a.Arch, &a.Version, &last); err != nil { + return nil, err + } + a.LastSeen = time.Unix(last, 0) + out = append(out, a) + } + return out, rows.Err() +} + +func (db *DB) TouchAgent(id string) error { + _, err := db.sql.Exec(`UPDATE agents SET last_seen = ? WHERE id = ?`, time.Now().Unix(), id) + return err +} + +// TouchAgentVersion updates last_seen and the reported version together. +func (db *DB) TouchAgentVersion(id, version string) error { + _, err := db.sql.Exec(`UPDATE agents SET last_seen = ?, version = ? WHERE id = ?`, + time.Now().Unix(), version, id) + return err +} + +func (db *DB) DeleteAgent(id string) error { + _, err := db.sql.Exec(`DELETE FROM agents WHERE id = ?`, id) + return err +} + +func (db *DB) RenameAgent(id, name string) error { + _, err := db.sql.Exec(`UPDATE agents SET name = ? WHERE id = ?`, name, id) + return err +} + +// --- forward rules --- + +// ForwardRule maps a port on the agent's localhost to a port on the host. +// Proto is "http" (long-poll), "tcp" (direct mux) or "udp" (direct). +type ForwardRule struct { + ID int64 + AgentID string + ClientPort int + HostPort int + ListenAddr string + Proto string +} + +func (db *DB) CreateForwardRule(r ForwardRule) (int64, error) { + if r.Proto == "" { + r.Proto = "http" + } + res, err := db.sql.Exec( + `INSERT INTO forward_rules(agent_id, client_port, host_port, listen_addr, proto, created_at) + VALUES(?, ?, ?, ?, ?, ?)`, + r.AgentID, r.ClientPort, r.HostPort, r.ListenAddr, r.Proto, time.Now().Unix()) + if err != nil { + return 0, err + } + return res.LastInsertId() +} + +func (db *DB) ListForwardRules() ([]ForwardRule, error) { + rows, err := db.sql.Query( + `SELECT id, agent_id, client_port, host_port, listen_addr, proto FROM forward_rules ORDER BY id`) + if err != nil { + return nil, err + } + defer rows.Close() + var out []ForwardRule + for rows.Next() { + var r ForwardRule + if err := rows.Scan(&r.ID, &r.AgentID, &r.ClientPort, &r.HostPort, &r.ListenAddr, &r.Proto); err != nil { + return nil, err + } + out = append(out, r) + } + return out, rows.Err() +} + +func (db *DB) ListForwardRulesByAgent(agentID string) ([]ForwardRule, error) { + all, err := db.ListForwardRules() + if err != nil { + return nil, err + } + var out []ForwardRule + for _, r := range all { + if r.AgentID == agentID { + out = append(out, r) + } + } + return out, nil +} + +func (db *DB) GetForwardRule(id int64) (*ForwardRule, error) { + r := &ForwardRule{} + err := db.sql.QueryRow( + `SELECT id, agent_id, client_port, host_port, listen_addr, proto FROM forward_rules WHERE id = ?`, id). + Scan(&r.ID, &r.AgentID, &r.ClientPort, &r.HostPort, &r.ListenAddr, &r.Proto) + if err == sql.ErrNoRows { + return nil, nil + } + return r, err +} + +func (db *DB) DeleteForwardRule(id int64) error { + _, err := db.sql.Exec(`DELETE FROM forward_rules WHERE id = ?`, id) + return err +} + +// --- ufw rules created by the panel --- + +type UfwRule struct { + ID int64 + Port int + Proto string + RuleID int64 // bound forward rule id, or 0 for shared/manual (e.g. tunnel 8446) +} + +func (db *DB) AddUfwRule(port int, proto string, ruleID int64) error { + _, err := db.sql.Exec( + `INSERT OR IGNORE INTO ufw_rules(port, proto, rule_id, created_at) VALUES(?, ?, ?, ?)`, + port, proto, ruleID, time.Now().Unix()) + return err +} + +func (db *DB) HasUfwRule(port int, proto string) bool { + var n int + _ = db.sql.QueryRow(`SELECT COUNT(*) FROM ufw_rules WHERE port = ? AND proto = ?`, port, proto).Scan(&n) + return n > 0 +} + +func (db *DB) scanUfwRules(query string, args ...any) ([]UfwRule, error) { + rows, err := db.sql.Query(query, args...) + if err != nil { + return nil, err + } + defer rows.Close() + var out []UfwRule + for rows.Next() { + var r UfwRule + if err := rows.Scan(&r.ID, &r.Port, &r.Proto, &r.RuleID); err != nil { + return nil, err + } + out = append(out, r) + } + return out, rows.Err() +} + +func (db *DB) ListUfwRules() ([]UfwRule, error) { + return db.scanUfwRules(`SELECT id, port, proto, rule_id FROM ufw_rules ORDER BY port, proto`) +} + +// ListUfwRulesByRule returns ufw rules bound to a specific forward rule. +func (db *DB) ListUfwRulesByRule(ruleID int64) ([]UfwRule, error) { + return db.scanUfwRules(`SELECT id, port, proto, rule_id FROM ufw_rules WHERE rule_id = ?`, ruleID) +} + +func (db *DB) GetUfwRule(id int64) (*UfwRule, error) { + r := &UfwRule{} + err := db.sql.QueryRow(`SELECT id, port, proto, rule_id FROM ufw_rules WHERE id = ?`, id). + Scan(&r.ID, &r.Port, &r.Proto, &r.RuleID) + if err == sql.ErrNoRows { + return nil, nil + } + return r, err +} + +func (db *DB) DeleteUfwRule(id int64) error { + _, err := db.sql.Exec(`DELETE FROM ufw_rules WHERE id = ?`, id) + return err +} + +// HostPortUsed reports whether any forward rule already uses this host port. +func (db *DB) HostPortUsed(hostPort int) bool { + var n int + _ = db.sql.QueryRow(`SELECT COUNT(*) FROM forward_rules WHERE host_port = ?`, hostPort).Scan(&n) + return n > 0 +} + +// --- audit --- + +func (db *DB) Audit(adminID int64, agentID, action string) { + // Best-effort: an audit failure must not block the operation. + _, _ = db.sql.Exec( + `INSERT INTO audit(admin_id, agent_id, action, at) VALUES(?, ?, ?, ?)`, + adminID, agentID, action, time.Now().Unix()) +} diff --git a/internal/server/forward.go b/internal/server/forward.go new file mode 100644 index 0000000..cc0670e --- /dev/null +++ b/internal/server/forward.go @@ -0,0 +1,149 @@ +package server + +import ( + "encoding/base64" + "fmt" + "log" + "net" + "sync" + + "github.com/x-grk/longpolling-remote-access/internal/proto" +) + +// forwardManager owns the host-side listeners for port-forward rules. It +// supports three transports: +// - "http": tunneled over the HTTP long-poll channel (works anywhere nginx +// reaches, highest latency); +// - "tcp": direct TCP multiplexed over the 8446 tunnel (low latency); +// - "udp": direct UDP over the 8446 channel. +type forwardManager struct { + hub *Hub + tun *tunnelHub + udp *udpRelay + + mu sync.Mutex + lst map[int64]net.Listener // TCP listeners for http/tcp rules +} + +func newForwardManager(hub *Hub, tun *tunnelHub, udp *udpRelay) *forwardManager { + return &forwardManager{hub: hub, tun: tun, udp: udp, lst: map[int64]net.Listener{}} +} + +// open starts the host-side listener(s) for a rule. +func (m *forwardManager) open(rule ForwardRule) error { + m.close(rule.ID) + switch rule.Proto { + case "udp": + if err := m.udp.openRulePort(rule); err != nil { + return err + } + log.Printf("forward[udp]: %s:%d -> agent %s :%d", rule.ListenAddr, rule.HostPort, rule.AgentID, rule.ClientPort) + return nil + default: // "http" and "tcp" both listen TCP on the host port + addr := fmt.Sprintf("%s:%d", rule.ListenAddr, rule.HostPort) + ln, err := net.Listen("tcp", addr) + if err != nil { + return err + } + m.mu.Lock() + m.lst[rule.ID] = ln + m.mu.Unlock() + log.Printf("forward[%s]: %s -> agent %s :%d", rule.Proto, addr, rule.AgentID, rule.ClientPort) + go m.acceptTCP(rule, ln) + return nil + } +} + +func (m *forwardManager) close(ruleID int64) { + m.mu.Lock() + ln := m.lst[ruleID] + delete(m.lst, ruleID) + m.mu.Unlock() + if ln != nil { + _ = ln.Close() + } + m.udp.closeRulePort(ruleID) +} + +func (m *forwardManager) closeAll() { + m.mu.Lock() + lst := m.lst + m.lst = map[int64]net.Listener{} + m.mu.Unlock() + for _, ln := range lst { + _ = ln.Close() + } +} + +func (m *forwardManager) acceptTCP(rule ForwardRule, ln net.Listener) { + for { + conn, err := ln.Accept() + if err != nil { + return + } + if tc, ok := conn.(*net.TCPConn); ok { + _ = tc.SetNoDelay(true) + } + if rule.Proto == "tcp" { + go m.bridgeDirect(rule, conn) + } else { + go m.bridgeHTTP(rule, conn) + } + } +} + +// bridgeDirect wires a connection to the agent's low-latency 8446 tunnel. +func (m *forwardManager) bridgeDirect(rule ForwardRule, conn net.Conn) { + at := m.tun.get(rule.AgentID) + if at == nil { + // No direct tunnel (agent offline or 8446 unreachable). + conn.Close() + return + } + at.openStream(rule.ClientPort, conn) +} + +// bridgeHTTP wires a connection through the HTTP long-poll hub session. +func (m *forwardManager) bridgeHTTP(rule ForwardRule, conn net.Conn) { + sess := m.hub.CreateForward(rule.AgentID, rule.ClientPort) + defer m.hub.CloseSession(sess.ID) + defer conn.Close() + + go func() { + defer m.hub.CloseSession(sess.ID) + buf := make([]byte, 32*1024) + for { + n, err := conn.Read(buf) + if n > 0 { + sess.PushIn(proto.InFrame{ + Type: proto.FrameStdin, + Data: base64.StdEncoding.EncodeToString(buf[:n]), + }) + } + if err != nil { + sess.PushIn(proto.InFrame{Type: proto.FrameClose}) + return + } + } + }() + + for { + select { + case <-sess.Done(): + return + case out, ok := <-sess.Out(): + if !ok || out.Exit { + return + } + if out.Data != "" { + b, err := base64.StdEncoding.DecodeString(out.Data) + if err != nil { + return + } + if _, err := conn.Write(b); err != nil { + return + } + } + } + } +} diff --git a/internal/server/handlers_agent.go b/internal/server/handlers_agent.go new file mode 100644 index 0000000..c38dbba --- /dev/null +++ b/internal/server/handlers_agent.go @@ -0,0 +1,127 @@ +package server + +import ( + "encoding/json" + "net/http" + "time" + + "github.com/x-grk/longpolling-remote-access/internal/proto" +) + +// Long-poll hold window. Must stay below nginx proxy_read_timeout (120s). +const pollHold = 25 * time.Second + +func writeJSON(w http.ResponseWriter, v any) { + w.Header().Set("Content-Type", "application/json") + _ = json.NewEncoder(w).Encode(v) +} + +// POST /api/agent/enroll — exchange the shared enroll secret for a per-agent id+token. +func (s *Server) handleEnroll(w http.ResponseWriter, r *http.Request) { + secret := r.Header.Get("X-Enroll-Secret") + if !constantTimeEqual(secret, s.db.EnrollSecret()) { + http.Error(w, "forbidden", http.StatusForbidden) + return + } + var req proto.EnrollRequest + if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<16)).Decode(&req); err != nil { + http.Error(w, "bad request", http.StatusBadRequest) + return + } + id, token, err := s.enroll(req.Name, req.Hostname, req.OS, req.Arch, req.Version) + if err != nil { + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + writeJSON(w, proto.EnrollResponse{AgentID: id, Token: token}) +} + +// GET /api/agent/poll — control channel. Held until a job is queued or timeout. +func (s *Server) handlePoll(w http.ResponseWriter, r *http.Request) { + a := s.authenticateAgent(r) + if a == nil { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + // The agent sends its version occasionally; record it when present. + if v := r.Header.Get("X-Agent-Version"); v != "" && v != a.Version { + _ = s.db.TouchAgentVersion(a.ID, v) + } else { + _ = s.db.TouchAgent(a.ID) + } + writeJSON(w, s.hub.WaitControl(a.ID, pollHold)) +} + +// GET /api/agent/task/{id} — the agent fetches the task to run. +func (s *Server) handleTaskFetch(w http.ResponseWriter, r *http.Request) { + if s.authenticateAgent(r) == nil { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + env, ok := s.hub.GetTask(r.PathValue("id")) + if !ok { + http.Error(w, "no task", http.StatusGone) + return + } + writeJSON(w, env) +} + +// POST /api/agent/task/{id}/result — the agent returns the task result. +func (s *Server) handleTaskResult(w http.ResponseWriter, r *http.Request) { + if s.authenticateAgent(r) == nil { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + var res proto.TaskResult + if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 64<<20)).Decode(&res); err != nil { + http.Error(w, "bad request", http.StatusBadRequest) + return + } + s.hub.CompleteTask(r.PathValue("id"), res) + w.WriteHeader(http.StatusNoContent) +} + +// GET /api/agent/term/{sid}/in — downstream (panel -> agent) long-poll. +func (s *Server) handleTermIn(w http.ResponseWriter, r *http.Request) { + if s.authenticateAgent(r) == nil { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + sess := s.hub.Session(r.PathValue("sid")) + if sess == nil { + http.Error(w, "no session", http.StatusGone) + return + } + frames, alive := sess.PollIn(pollHold) + if !alive { + http.Error(w, "closed", http.StatusGone) + return + } + writeJSON(w, proto.InBatch{Frames: frames}) +} + +// POST /api/agent/term/{sid}/out — upstream (agent PTY -> panel). +func (s *Server) handleTermOut(w http.ResponseWriter, r *http.Request) { + if s.authenticateAgent(r) == nil { + http.Error(w, "unauthorized", http.StatusUnauthorized) + return + } + sess := s.hub.Session(r.PathValue("sid")) + if sess == nil { + http.Error(w, "no session", http.StatusGone) + return + } + var msg proto.OutMessage + if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&msg); err != nil { + http.Error(w, "bad request", http.StatusBadRequest) + return + } + if !sess.PushOut(msg) { + http.Error(w, "closed", http.StatusGone) + return + } + if msg.Exit { + s.hub.CloseSession(sess.ID) + } + w.WriteHeader(http.StatusNoContent) +} diff --git a/internal/server/handlers_panel.go b/internal/server/handlers_panel.go new file mode 100644 index 0000000..989b72c --- /dev/null +++ b/internal/server/handlers_panel.go @@ -0,0 +1,337 @@ +package server + +import ( + "fmt" + "log" + "net/http" + "strconv" + "strings" + "time" + + "github.com/x-grk/longpolling-remote-access/internal/proto" +) + +// --- first-run setup --- + +func (s *Server) handleSetupForm(w http.ResponseWriter, r *http.Request) { + if s.db.SetupDone() { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + s.render(w, "setup.html", map[string]any{}) +} + +func (s *Server) handleSetupSubmit(w http.ResponseWriter, r *http.Request) { + if s.db.SetupDone() { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + // Guard against a race where two requests create the first admin. + if n, _ := s.db.CountAdmins(); n > 0 { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + username := strings.TrimSpace(r.FormValue("username")) + pw := r.FormValue("password") + pw2 := r.FormValue("password2") + if username == "" || len(pw) < 8 || pw != pw2 { + s.render(w, "setup.html", map[string]any{ + "Error": "Введите имя и пароль (мин. 8 символов), пароли должны совпадать.", + }) + return + } + hash, err := hashPassword(pw) + if err != nil { + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + if err := s.db.CreateAdmin(username, hash); err != nil { + s.render(w, "setup.html", map[string]any{"Error": "Не удалось создать админа: " + err.Error()}) + return + } + _ = s.db.MarkSetupDone() + admin, _ := s.db.AdminByUsername(username) + if admin != nil { + _ = s.setSession(w, admin.ID) + } + http.Redirect(w, r, "/", http.StatusSeeOther) +} + +// --- login / logout --- + +func (s *Server) handleLoginForm(w http.ResponseWriter, r *http.Request) { + if !s.db.SetupDone() { + http.Redirect(w, r, "/setup", http.StatusSeeOther) + return + } + if s.currentAdmin(r) != nil { + http.Redirect(w, r, "/", http.StatusSeeOther) + return + } + s.render(w, "login.html", map[string]any{}) +} + +func (s *Server) handleLoginSubmit(w http.ResponseWriter, r *http.Request) { + ip := clientIP(r) + if s.limiter.blocked(ip) { + s.render(w, "login.html", map[string]any{"Error": "Слишком много попыток. Подождите немного."}) + return + } + username := strings.TrimSpace(r.FormValue("username")) + pw := r.FormValue("password") + admin, _ := s.db.AdminByUsername(username) + if admin == nil || !checkPassword(admin.PassHash, pw) { + s.limiter.fin(ip, false) + s.render(w, "login.html", map[string]any{"Error": "Неверный логин или пароль."}) + return + } + s.limiter.fin(ip, true) + if err := s.setSession(w, admin.ID); err != nil { + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + http.Redirect(w, r, "/", http.StatusSeeOther) +} + +func (s *Server) handleLogout(w http.ResponseWriter, r *http.Request) { + s.clearSession(w, r) + http.Redirect(w, r, "/login", http.StatusSeeOther) +} + +// --- dashboard --- + +func (s *Server) handleDashboard(w http.ResponseWriter, r *http.Request) { + agents, err := s.db.ListAgents() + if err != nil { + http.Error(w, "internal error", http.StatusInternalServerError) + return + } + type row struct { + Agent + Online bool + Seen string + } + rows := make([]row, 0, len(agents)) + for _, a := range agents { + rows = append(rows, row{Agent: a, Online: a.Online(), Seen: humanSince(a.LastSeen)}) + } + s.render(w, "dashboard.html", map[string]any{ + "Admin": s.currentAdmin(r), + "Agents": rows, + "CSRF": csrfToken(r), + }) +} + +func (s *Server) handleTerminalPage(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + a, err := s.db.GetAgent(id) + if err != nil || a == nil { + http.NotFound(w, r) + return + } + s.render(w, "terminal.html", map[string]any{ + "Agent": a, + "CSRF": csrfToken(r), + }) +} + +func (s *Server) handleAgentDelete(w http.ResponseWriter, r *http.Request) { + if !checkCSRF(r) { + http.Error(w, "bad csrf", http.StatusForbidden) + return + } + id := r.PathValue("id") + _ = s.db.DeleteAgent(id) + http.Redirect(w, r, "/", http.StatusSeeOther) +} + +func (s *Server) handleAgentEdit(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + a, err := s.db.GetAgent(id) + if err != nil || a == nil { + http.NotFound(w, r) + return + } + s.render(w, "edit.html", map[string]any{ + "Agent": a, + "CSRF": csrfToken(r), + "ServerVersion": Version, + "Outdated": a.Version != "" && a.Version != Version, + "Updated": r.URL.Query().Get("updated") == "1", + }) +} + +// handleAgentUpdate asks the agent to download the latest binary and restart. +func (s *Server) handleAgentUpdate(w http.ResponseWriter, r *http.Request) { + if !checkCSRF(r) { + http.Error(w, "bad csrf", http.StatusForbidden) + return + } + id := r.PathValue("id") + a, err := s.db.GetAgent(id) + if err != nil || a == nil { + http.NotFound(w, r) + return + } + if !a.Online() { + http.Redirect(w, r, "/agents/"+id+"/edit?updated=0", http.StatusSeeOther) + return + } + taskID, ch := s.hub.CreateTask(id, proto.TaskSysUpdate, nil) + defer s.hub.DiscardTask(taskID) + select { + case <-ch: + case <-time.After(30 * time.Second): + } + http.Redirect(w, r, "/agents/"+id+"/edit?updated=1", http.StatusSeeOther) +} + +func (s *Server) handleAgentRename(w http.ResponseWriter, r *http.Request) { + if !checkCSRF(r) { + http.Error(w, "bad csrf", http.StatusForbidden) + return + } + id := r.PathValue("id") + name := strings.TrimSpace(r.FormValue("name")) + if name != "" { + _ = s.db.RenameAgent(id, name) + } + http.Redirect(w, r, "/", http.StatusSeeOther) +} + +// --- settings --- + +func (s *Server) handleSettings(w http.ResponseWriter, r *http.Request) { + admins, _ := s.db.ListAdmins() + host := s.db.HostAddress() + secret := s.db.EnrollSecret() + code := s.db.InstallCode() + ufwInstalled, ufwActive, ufwRunnable := s.ufw.status() + ufwRules, _ := s.db.ListUfwRules() + s.render(w, "settings.html", map[string]any{ + "Admin": s.currentAdmin(r), + "Host": host, + "Secret": secret, + "Admins": admins, + "CSRF": csrfToken(r), + "InstallShort": fmt.Sprintf("wget -qO- %s/i/%s | sudo sh", host, code), + "InstallCurl": fmt.Sprintf("curl -fsSL %s/install.sh | sudo HOST=%s ENROLL_SECRET=%s sh", host, host, secret), + "InstallWget": fmt.Sprintf("wget -qO- %s/install.sh | sudo HOST=%s ENROLL_SECRET=%s sh", host, host, secret), + "ServerVersion": Version, + "UfwInstalled": ufwInstalled, + "UfwActive": ufwActive, + "UfwRunnable": ufwRunnable, + "UfwEnabled": s.db.UfwEnabled(), + "UfwRules": ufwRules, + }) +} + +func (s *Server) handleSetHost(w http.ResponseWriter, r *http.Request) { + if !checkCSRF(r) { + http.Error(w, "bad csrf", http.StatusForbidden) + return + } + host := strings.TrimRight(strings.TrimSpace(r.FormValue("host")), "/") + if host != "" { + _ = s.db.SetConfig(cfgHostAddress, host) + } + http.Redirect(w, r, "/settings", http.StatusSeeOther) +} + +func (s *Server) handleRotateSecret(w http.ResponseWriter, r *http.Request) { + if !checkCSRF(r) { + http.Error(w, "bad csrf", http.StatusForbidden) + return + } + _ = s.db.SetConfig(cfgEnrollSecret, randToken()) + // Rotate the short install code too so old /i/ links stop working. + _ = s.db.SetConfig(cfgInstallCode, shortCode()) + http.Redirect(w, r, "/settings", http.StatusSeeOther) +} + +func (s *Server) handleAddAdmin(w http.ResponseWriter, r *http.Request) { + if !checkCSRF(r) { + http.Error(w, "bad csrf", http.StatusForbidden) + return + } + username := strings.TrimSpace(r.FormValue("username")) + pw := r.FormValue("password") + if username != "" && len(pw) >= 8 { + if hash, err := hashPassword(pw); err == nil { + _ = s.db.CreateAdmin(username, hash) + } + } + http.Redirect(w, r, "/settings", http.StatusSeeOther) +} + +func (s *Server) handleDeleteAdmin(w http.ResponseWriter, r *http.Request) { + if !checkCSRF(r) { + http.Error(w, "bad csrf", http.StatusForbidden) + return + } + id, _ := strconv.ParseInt(r.FormValue("id"), 10, 64) + // Never delete the last admin. + if n, _ := s.db.CountAdmins(); n > 1 { + _ = s.db.DeleteAdmin(id) + } + http.Redirect(w, r, "/settings", http.StatusSeeOther) +} + +func (s *Server) handleUfwToggle(w http.ResponseWriter, r *http.Request) { + if !checkCSRF(r) { + http.Error(w, "bad csrf", http.StatusForbidden) + return + } + _ = s.db.SetUfwEnabled(r.FormValue("enabled") == "1") + http.Redirect(w, r, "/settings", http.StatusSeeOther) +} + +func (s *Server) handleUfwRevoke(w http.ResponseWriter, r *http.Request) { + if !checkCSRF(r) { + http.Error(w, "bad csrf", http.StatusForbidden) + return + } + id, _ := strconv.ParseInt(r.FormValue("id"), 10, 64) + s.ufw.revoke(id) + http.Redirect(w, r, "/settings", http.StatusSeeOther) +} + +// --- install script + downloads --- + +// handleInstallScript serves the generic installer; the secret is NOT baked in, +// so it must be supplied via env or the interactive prompt. +func (s *Server) handleInstallScript(w http.ResponseWriter, r *http.Request) { + s.renderInstall(w, "") +} + +// handleShortInstall serves the installer behind the short /i/ link with +// HOST and ENROLL_SECRET already embedded, so the one-liner is just +// +// wget -qO- https://host/i/ | sudo sh +func (s *Server) handleShortInstall(w http.ResponseWriter, r *http.Request) { + code := r.PathValue("code") + want := s.db.InstallCode() + if want == "" || !constantTimeEqual(code, want) { + http.NotFound(w, r) + return + } + s.renderInstall(w, s.db.EnrollSecret()) +} + +// renderInstall writes install.sh with the current host and an optional secret. +func (s *Server) renderInstall(w http.ResponseWriter, secret string) { + w.Header().Set("Content-Type", "text/x-shellscript; charset=utf-8") + if err := s.installTmpl.Execute(w, map[string]any{ + "Host": s.db.HostAddress(), + "Secret": secret, + }); err != nil { + log.Printf("install.sh render: %v", err) + http.Error(w, "internal error", http.StatusInternalServerError) + } +} + +func (s *Server) downloadHandler() http.Handler { + // Agent binaries carry no secrets and are intentionally public so the + // install one-liner works before the machine is enrolled. + return http.FileServer(http.Dir(s.distDir)) +} diff --git a/internal/server/handlers_panel_fs.go b/internal/server/handlers_panel_fs.go new file mode 100644 index 0000000..fb407b6 --- /dev/null +++ b/internal/server/handlers_panel_fs.go @@ -0,0 +1,116 @@ +package server + +import ( + "encoding/base64" + "encoding/json" + "io" + "net/http" + "path" + "time" + + "github.com/x-grk/longpolling-remote-access/internal/proto" +) + +// fsOps maps the URL op segment to a proto task type. +var fsOps = map[string]string{ + "list": proto.TaskFSList, + "read": proto.TaskFSRead, + "write": proto.TaskFSWrite, + "mkdir": proto.TaskFSMkdir, + "delete": proto.TaskFSDelete, + "deltree": proto.TaskFSDelTree, + "unzip": proto.TaskFSUnzip, + "zip": proto.TaskFSZip, + "move": proto.TaskFSMove, +} + +// fsTaskTimeout bounds how long we wait for an agent to answer a file op. +const fsTaskTimeout = 60 * time.Second + +func (s *Server) handleFilesPage(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + a, err := s.db.GetAgent(id) + if err != nil || a == nil { + http.NotFound(w, r) + return + } + s.render(w, "files.html", map[string]any{"Agent": a}) +} + +// POST /api/panel/agents/{id}/fs/{op} — run a file-manager operation on the +// agent and return its result. The request body is the op's JSON args and is +// forwarded verbatim to the agent. +func (s *Server) handleFSOp(w http.ResponseWriter, r *http.Request) { + agentID := r.PathValue("id") + op := r.PathValue("op") + taskType, ok := fsOps[op] + if !ok { + http.Error(w, "unknown op", http.StatusBadRequest) + return + } + a, err := s.db.GetAgent(agentID) + if err != nil || a == nil { + http.NotFound(w, r) + return + } + if !a.Online() { + writeJSON(w, proto.TaskResult{Error: "машина офлайн"}) + return + } + args, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 64<<20)) + if err != nil { + http.Error(w, "bad request", http.StatusBadRequest) + return + } + + taskID, resultCh := s.hub.CreateTask(agentID, taskType, args) + defer s.hub.DiscardTask(taskID) + + select { + case res := <-resultCh: + writeJSON(w, res) + case <-time.After(fsTaskTimeout): + writeJSON(w, proto.TaskResult{Error: "таймаут ожидания агента"}) + } +} + +// GET /api/panel/agents/{id}/fs/download?path=... — stream a file as a download. +func (s *Server) handleFSDownload(w http.ResponseWriter, r *http.Request) { + agentID := r.PathValue("id") + p := r.URL.Query().Get("path") + a, err := s.db.GetAgent(agentID) + if err != nil || a == nil { + http.NotFound(w, r) + return + } + if !a.Online() { + http.Error(w, "машина офлайн", http.StatusServiceUnavailable) + return + } + args, _ := json.Marshal(proto.FSPathArgs{Path: p}) + taskID, ch := s.hub.CreateTask(agentID, proto.TaskFSDownload, args) + defer s.hub.DiscardTask(taskID) + + select { + case res := <-ch: + if !res.OK { + http.Error(w, res.Error, http.StatusBadGateway) + return + } + var rr proto.FSReadResult + if err := json.Unmarshal(res.Data, &rr); err != nil { + http.Error(w, "bad result", http.StatusBadGateway) + return + } + data, _ := base64.StdEncoding.DecodeString(rr.Content) + name := path.Base(p) + w.Header().Set("Content-Type", "application/octet-stream") + w.Header().Set("Content-Disposition", "attachment; filename=\""+name+"\"") + if rr.Truncated { + w.Header().Set("X-Truncated", "1") + } + _, _ = w.Write(data) + case <-time.After(fsTaskTimeout): + http.Error(w, "таймаут ожидания агента", http.StatusGatewayTimeout) + } +} diff --git a/internal/server/handlers_panel_fwd.go b/internal/server/handlers_panel_fwd.go new file mode 100644 index 0000000..c098cc6 --- /dev/null +++ b/internal/server/handlers_panel_fwd.go @@ -0,0 +1,101 @@ +package server + +import ( + "log" + "net/http" + "strconv" + "strings" +) + +func (s *Server) handleForwardsPage(w http.ResponseWriter, r *http.Request) { + id := r.PathValue("id") + a, err := s.db.GetAgent(id) + if err != nil || a == nil { + http.NotFound(w, r) + return + } + rules, _ := s.db.ListForwardRulesByAgent(id) + errMsg := map[string]string{ + "busy": "Этот порт хоста уже занят другим правилом.", + "port": "Некорректный номер порта.", + "listen": "Не удалось занять порт на хосте (возможно, уже используется).", + "db": "Ошибка базы данных.", + }[r.URL.Query().Get("err")] + s.render(w, "forwards.html", map[string]any{ + "Agent": a, + "Rules": rules, + "CSRF": csrfToken(r), + "Error": errMsg, + }) +} + +// POST /agents/{id}/forwards/add +func (s *Server) handleForwardAdd(w http.ResponseWriter, r *http.Request) { + if !checkCSRF(r) { + http.Error(w, "bad csrf", http.StatusForbidden) + return + } + id := r.PathValue("id") + a, err := s.db.GetAgent(id) + if err != nil || a == nil { + http.NotFound(w, r) + return + } + clientPort, _ := strconv.Atoi(strings.TrimSpace(r.FormValue("client_port"))) + hostPort, _ := strconv.Atoi(strings.TrimSpace(r.FormValue("host_port"))) + listenAddr := strings.TrimSpace(r.FormValue("listen_addr")) + if listenAddr == "" { + listenAddr = "0.0.0.0" + } + protoKind := strings.TrimSpace(r.FormValue("proto")) + switch protoKind { + case "http", "tcp", "udp": + default: + protoKind = "http" + } + if clientPort < 1 || clientPort > 65535 || hostPort < 1 || hostPort > 65535 { + http.Redirect(w, r, "/agents/"+id+"/forwards?err=port", http.StatusSeeOther) + return + } + // Reject a host port already bound by another forward rule. + if s.db.HostPortUsed(hostPort) { + http.Redirect(w, r, "/agents/"+id+"/forwards?err=busy", http.StatusSeeOther) + return + } + rule := ForwardRule{AgentID: id, ClientPort: clientPort, HostPort: hostPort, ListenAddr: listenAddr, Proto: protoKind} + ruleID, err := s.db.CreateForwardRule(rule) + if err != nil { + http.Redirect(w, r, "/agents/"+id+"/forwards?err=db", http.StatusSeeOther) + return + } + rule.ID = ruleID + if err := s.fwd.open(rule); err != nil { + // Roll back the rule if the port can't be bound. + _ = s.db.DeleteForwardRule(ruleID) + log.Printf("forward open %s:%d: %v", listenAddr, hostPort, err) + http.Redirect(w, r, "/agents/"+id+"/forwards?err=listen", http.StatusSeeOther) + return + } + // Open the port in ufw too (if integration is enabled and ufw is active). + s.ufw.allowForward(rule) + http.Redirect(w, r, "/agents/"+id+"/forwards", http.StatusSeeOther) +} + +// POST /forwards/{rid}/delete +func (s *Server) handleForwardDelete(w http.ResponseWriter, r *http.Request) { + if !checkCSRF(r) { + http.Error(w, "bad csrf", http.StatusForbidden) + return + } + rid, _ := strconv.ParseInt(r.PathValue("rid"), 10, 64) + rule, _ := s.db.GetForwardRule(rid) + s.fwd.close(rid) + _ = s.db.DeleteForwardRule(rid) + // Remove the ufw rule bound to this forward rule (shared 8446 stays). + s.ufw.revokeForRule(rid) + dest := "/" + if rule != nil { + dest = "/agents/" + rule.AgentID + "/forwards" + } + http.Redirect(w, r, dest, http.StatusSeeOther) +} diff --git a/internal/server/handlers_panel_term.go b/internal/server/handlers_panel_term.go new file mode 100644 index 0000000..559f808 --- /dev/null +++ b/internal/server/handlers_panel_term.go @@ -0,0 +1,79 @@ +package server + +import ( + "encoding/json" + "log" + "net/http" + + "github.com/x-grk/longpolling-remote-access/internal/proto" +) + +// The browser terminal talks to the server over plain HTTP long-polling (the +// same transport the agent uses), so it works through any reverse proxy without +// WebSocket upgrade support. + +// POST /api/panel/term/{agentID}/open — create a terminal session. +func (s *Server) handlePanelOpen(w http.ResponseWriter, r *http.Request) { + agentID := r.PathValue("agentID") + agent, err := s.db.GetAgent(agentID) + if err != nil || agent == nil { + log.Printf("panel open: agent %q not found (err=%v)", agentID, err) + http.NotFound(w, r) + return + } + sess := s.hub.CreateSession(agentID) + if admin := s.currentAdmin(r); admin != nil { + s.db.Audit(admin.ID, agentID, "open_terminal") + } + writeJSON(w, map[string]string{"session_id": sess.ID}) +} + +// GET /api/panel/term/{sid}/out — long-poll for PTY output. +func (s *Server) handlePanelOut(w http.ResponseWriter, r *http.Request) { + sess := s.hub.Session(r.PathValue("sid")) + if sess == nil { + writeJSON(w, map[string]any{"closed": true}) + return + } + msgs, alive := sess.PollOut(pollHold) + if !alive { + writeJSON(w, map[string]any{"closed": true}) + return + } + writeJSON(w, map[string]any{"frames": msgs}) +} + +// panelInMsg is what the browser posts for keystrokes/resize. +type panelInMsg struct { + T string `json:"t"` // "i" input, "r" resize + D string `json:"d,omitempty"` + Cols uint16 `json:"cols,omitempty"` + Rows uint16 `json:"rows,omitempty"` +} + +// POST /api/panel/term/{sid}/in — send keystrokes/resize to the agent PTY. +func (s *Server) handlePanelIn(w http.ResponseWriter, r *http.Request) { + sess := s.hub.Session(r.PathValue("sid")) + if sess == nil { + http.Error(w, "closed", http.StatusGone) + return + } + var m panelInMsg + if err := json.NewDecoder(http.MaxBytesReader(w, r.Body, 1<<20)).Decode(&m); err != nil { + http.Error(w, "bad request", http.StatusBadRequest) + return + } + switch m.T { + case "i": + sess.PushIn(proto.InFrame{Type: proto.FrameStdin, Data: m.D}) + case "r": + sess.PushIn(proto.InFrame{Type: proto.FrameResize, Cols: m.Cols, Rows: m.Rows}) + } + w.WriteHeader(http.StatusNoContent) +} + +// POST /api/panel/term/{sid}/close — tear the session down. +func (s *Server) handlePanelClose(w http.ResponseWriter, r *http.Request) { + s.hub.CloseSession(r.PathValue("sid")) + w.WriteHeader(http.StatusNoContent) +} diff --git a/internal/server/hub.go b/internal/server/hub.go new file mode 100644 index 0000000..0ae1cb4 --- /dev/null +++ b/internal/server/hub.go @@ -0,0 +1,258 @@ +package server + +import ( + "sync" + "time" + + "github.com/x-grk/longpolling-remote-access/internal/proto" +) + +// Hub is the in-memory broker between agents (HTTP long-polling) and the panel. +// It carries three kinds of work to an agent through its single control poll: +// terminal sessions, port-forward streams, and one-shot tasks (file manager). +// Nothing here is persisted. +type Hub struct { + mu sync.Mutex + + // jobs holds control messages queued for an agent but not yet delivered via + // its control poll. + jobs map[string][]proto.ControlMessage + // ctrlWaiters wakes a blocked control poll the moment a job is enqueued. + ctrlWaiters map[string]chan struct{} + + sessions map[string]*Session + tasks map[string]*taskEntry +} + +// taskEntry is a pending one-shot task awaiting the agent's result. +type taskEntry struct { + env proto.TaskEnvelope + result chan proto.TaskResult +} + +// Session is one live byte stream between the panel and an agent (a PTY terminal +// or a raw TCP forward — the hub treats both identically). +type Session struct { + ID string + AgentID string + + mu sync.Mutex + inBuf []proto.InFrame + inSignal chan struct{} // size-1; pulsed when inBuf gains data + + out chan proto.OutMessage // agent -> panel + + closed chan struct{} + closeOnce sync.Once +} + +func NewHub() *Hub { + return &Hub{ + jobs: map[string][]proto.ControlMessage{}, + ctrlWaiters: map[string]chan struct{}{}, + sessions: map[string]*Session{}, + tasks: map[string]*taskEntry{}, + } +} + +// enqueueJob appends a control message for an agent and wakes its poll. +func (h *Hub) enqueueJob(agentID string, msg proto.ControlMessage) { + h.mu.Lock() + h.jobs[agentID] = append(h.jobs[agentID], msg) + if w := h.ctrlWaiters[agentID]; w != nil { + select { + case w <- struct{}{}: + default: + } + } + h.mu.Unlock() +} + +func (h *Hub) newSession(agentID string) *Session { + s := &Session{ + ID: randToken()[:16], + AgentID: agentID, + inSignal: make(chan struct{}, 1), + out: make(chan proto.OutMessage, 1024), + closed: make(chan struct{}), + } + h.mu.Lock() + h.sessions[s.ID] = s + h.mu.Unlock() + return s +} + +// CreateSession registers a terminal session and queues it for the agent. +func (h *Hub) CreateSession(agentID string) *Session { + s := h.newSession(agentID) + h.enqueueJob(agentID, proto.ControlMessage{Type: proto.CtrlOpenTerminal, SessionID: s.ID}) + return s +} + +// CreateForward registers a port-forward stream (agent dials 127.0.0.1:port). +func (h *Hub) CreateForward(agentID string, port int) *Session { + s := h.newSession(agentID) + h.enqueueJob(agentID, proto.ControlMessage{Type: proto.CtrlOpenForward, SessionID: s.ID, Port: port}) + return s +} + +// WaitControl blocks until a job is queued for agentID or timeout elapses. +func (h *Hub) WaitControl(agentID string, timeout time.Duration) proto.ControlMessage { + deadline := time.After(timeout) + for { + h.mu.Lock() + if q := h.jobs[agentID]; len(q) > 0 { + msg := q[0] + h.jobs[agentID] = q[1:] + h.mu.Unlock() + return msg + } + w := h.ctrlWaiters[agentID] + if w == nil { + w = make(chan struct{}, 1) + h.ctrlWaiters[agentID] = w + } + h.mu.Unlock() + + select { + case <-w: + case <-deadline: + return proto.ControlMessage{Type: proto.CtrlIdle} + } + } +} + +func (h *Hub) Session(id string) *Session { + h.mu.Lock() + defer h.mu.Unlock() + return h.sessions[id] +} + +func (h *Hub) CloseSession(id string) { + h.mu.Lock() + s := h.sessions[id] + delete(h.sessions, id) + h.mu.Unlock() + if s != nil { + s.close() + } +} + +// --- one-shot tasks --- + +// CreateTask queues a task and returns its id plus a channel for the result. +func (h *Hub) CreateTask(agentID, typ string, args []byte) (string, chan proto.TaskResult) { + id := randToken()[:16] + entry := &taskEntry{ + env: proto.TaskEnvelope{Type: typ, Args: args}, + result: make(chan proto.TaskResult, 1), + } + h.mu.Lock() + h.tasks[id] = entry + h.mu.Unlock() + h.enqueueJob(agentID, proto.ControlMessage{Type: proto.CtrlTask, TaskID: id}) + return id, entry.result +} + +// GetTask returns the envelope the agent should execute. +func (h *Hub) GetTask(id string) (proto.TaskEnvelope, bool) { + h.mu.Lock() + defer h.mu.Unlock() + e := h.tasks[id] + if e == nil { + return proto.TaskEnvelope{}, false + } + return e.env, true +} + +// CompleteTask delivers a result to the waiting panel handler. +func (h *Hub) CompleteTask(id string, res proto.TaskResult) { + h.mu.Lock() + e := h.tasks[id] + h.mu.Unlock() + if e != nil { + select { + case e.result <- res: + default: + } + } +} + +// DiscardTask drops a task from the registry. +func (h *Hub) DiscardTask(id string) { + h.mu.Lock() + delete(h.tasks, id) + h.mu.Unlock() +} + +func (s *Session) close() { s.closeOnce.Do(func() { close(s.closed) }) } + +// Done returns a channel closed when the session ends. +func (s *Session) Done() <-chan struct{} { return s.closed } + +// PushIn appends a downstream frame (panel -> agent) and wakes the agent poll. +func (s *Session) PushIn(f proto.InFrame) { + s.mu.Lock() + s.inBuf = append(s.inBuf, f) + s.mu.Unlock() + select { + case s.inSignal <- struct{}{}: + default: + } +} + +// PollIn drains queued downstream frames, blocking up to timeout for the first. +func (s *Session) PollIn(timeout time.Duration) ([]proto.InFrame, bool) { + deadline := time.After(timeout) + for { + s.mu.Lock() + if len(s.inBuf) > 0 { + b := s.inBuf + s.inBuf = nil + s.mu.Unlock() + return b, true + } + s.mu.Unlock() + + select { + case <-s.inSignal: + case <-deadline: + return nil, true + case <-s.closed: + return nil, false + } + } +} + +// PushOut delivers agent output to the panel side. Returns false if closed. +func (s *Session) PushOut(m proto.OutMessage) bool { + select { + case s.out <- m: + return true + case <-s.closed: + return false + } +} + +func (s *Session) Out() <-chan proto.OutMessage { return s.out } + +// PollOut drains queued upstream messages, blocking up to timeout for the first. +func (s *Session) PollOut(timeout time.Duration) ([]proto.OutMessage, bool) { + var batch []proto.OutMessage + select { + case m := <-s.out: + batch = append(batch, m) + case <-time.After(timeout): + return nil, true + case <-s.closed: + return nil, false + } + for { + select { + case m := <-s.out: + batch = append(batch, m) + default: + return batch, true + } + } +} diff --git a/internal/server/server.go b/internal/server/server.go new file mode 100644 index 0000000..30ad1f0 --- /dev/null +++ b/internal/server/server.go @@ -0,0 +1,232 @@ +package server + +import ( + "embed" + "html/template" + "io/fs" + "log" + "net" + "net/http" + "strconv" + "strings" + texttemplate "text/template" +) + +//go:embed web +var webFS embed.FS + +// Options configures a Server. +type Options struct { + DB *DB + // DistDir holds the compiled agent binaries served under /download/. + DistDir string + // SecureCookies marks session cookies Secure. True in production (behind + // nginx TLS); set false only for plain-HTTP local testing. + SecureCookies bool + // TunnelAddr is the listen address for the direct TCP+UDP forward tunnel + // (default ":8446"). Empty disables direct forwarding. + TunnelAddr string +} + +// Server is the HTTP host: admin panel + agent rendezvous. +type Server struct { + db *DB + hub *Hub + fwd *forwardManager + ufw *ufwManager + tmpl *template.Template + limiter *loginLimiter + distDir string + secureCookies bool + installTmpl *texttemplate.Template +} + +// New builds the server, parsing embedded templates. +func New(opts Options) (*Server, error) { + if err := opts.DB.ensureDefaults(); err != nil { + return nil, err + } + tmpl, err := template.ParseFS(webFS, "web/templates/*.html") + if err != nil { + return nil, err + } + installRaw, err := webFS.ReadFile("web/install.sh.tmpl") + if err != nil { + return nil, err + } + installTmpl, err := texttemplate.New("install").Parse(string(installRaw)) + if err != nil { + return nil, err + } + hub := NewHub() + tun := newTunnelHub(opts.DB) + udp := newUDPRelay(opts.DB) + s := &Server{ + db: opts.DB, + hub: hub, + fwd: newForwardManager(hub, tun, udp), + ufw: newUfwManager(opts.DB, portFromAddr(opts.TunnelAddr)), + tmpl: tmpl, + limiter: newLoginLimiter(), + distDir: opts.DistDir, + secureCookies: opts.SecureCookies, + installTmpl: installTmpl, + } + // Start the direct forward tunnel (TCP mux + UDP) on 8446. + if opts.TunnelAddr != "" { + if err := tun.listen(opts.TunnelAddr); err != nil { + log.Printf("tunnel tcp listen %s: %v", opts.TunnelAddr, err) + } + if err := udp.listen(opts.TunnelAddr); err != nil { + log.Printf("tunnel udp listen %s: %v", opts.TunnelAddr, err) + } + } + s.restoreForwards() + return s, nil +} + +// restoreForwards re-opens the host listeners for all saved forward rules. +func (s *Server) restoreForwards() { + rules, err := s.db.ListForwardRules() + if err != nil { + log.Printf("restore forwards: %v", err) + return + } + for _, r := range rules { + if err := s.fwd.open(r); err != nil { + log.Printf("restore forward rule %d (%s:%d): %v", r.ID, r.ListenAddr, r.HostPort, err) + } + } +} + +// Handler returns the root http.Handler with all routes mounted. +func (s *Server) Handler() http.Handler { + mux := http.NewServeMux() + + // Static assets (xterm.js etc.) from the embedded FS. + staticFS, _ := fs.Sub(webFS, "web/static") + mux.Handle("GET /static/", http.StripPrefix("/static/", http.FileServer(http.FS(staticFS)))) + + // Public: install script + agent binary downloads. + mux.HandleFunc("GET /install.sh", s.handleInstallScript) + mux.HandleFunc("GET /i/{code}", s.handleShortInstall) + mux.Handle("GET /download/", http.StripPrefix("/download/", s.downloadHandler())) + + // First-run setup wizard. + mux.HandleFunc("GET /setup", s.handleSetupForm) + mux.HandleFunc("POST /setup", s.handleSetupSubmit) + + // Auth. + mux.HandleFunc("GET /login", s.handleLoginForm) + mux.HandleFunc("POST /login", s.handleLoginSubmit) + mux.HandleFunc("POST /logout", s.handleLogout) + + // Panel (admin-only). + mux.Handle("GET /{$}", s.requireAdmin(s.handleDashboard)) + mux.Handle("GET /agents/{id}/terminal", s.requireAdmin(s.handleTerminalPage)) + mux.Handle("GET /agents/{id}/files", s.requireAdmin(s.handleFilesPage)) + mux.Handle("GET /agents/{id}/forwards", s.requireAdmin(s.handleForwardsPage)) + mux.Handle("GET /agents/{id}/edit", s.requireAdmin(s.handleAgentEdit)) + mux.Handle("POST /agents/{id}/rename", s.requireAdmin(s.handleAgentRename)) + mux.Handle("POST /agents/{id}/update", s.requireAdmin(s.handleAgentUpdate)) + mux.Handle("POST /agents/{id}/delete", s.requireAdmin(s.handleAgentDelete)) + mux.Handle("GET /settings", s.requireAdmin(s.handleSettings)) + mux.Handle("POST /settings/host", s.requireAdmin(s.handleSetHost)) + mux.Handle("POST /settings/secret/rotate", s.requireAdmin(s.handleRotateSecret)) + mux.Handle("POST /settings/admins/add", s.requireAdmin(s.handleAddAdmin)) + mux.Handle("POST /settings/admins/delete", s.requireAdmin(s.handleDeleteAdmin)) + mux.Handle("POST /settings/ufw/toggle", s.requireAdmin(s.handleUfwToggle)) + mux.Handle("POST /settings/ufw/revoke", s.requireAdmin(s.handleUfwRevoke)) + + // Panel terminal over HTTP long-polling (admin-only). + mux.Handle("POST /api/panel/term/{agentID}/open", s.requireAdmin(s.handlePanelOpen)) + mux.Handle("GET /api/panel/term/{sid}/out", s.requireAdmin(s.handlePanelOut)) + mux.Handle("POST /api/panel/term/{sid}/in", s.requireAdmin(s.handlePanelIn)) + mux.Handle("POST /api/panel/term/{sid}/close", s.requireAdmin(s.handlePanelClose)) + + // Panel file manager (admin-only). + mux.Handle("GET /api/panel/agents/{id}/fs/download", s.requireAdmin(s.handleFSDownload)) + mux.Handle("POST /api/panel/agents/{id}/fs/{op}", s.requireAdmin(s.handleFSOp)) + + // Panel port forwarding (admin-only). + mux.Handle("POST /agents/{id}/forwards/add", s.requireAdmin(s.handleForwardAdd)) + mux.Handle("POST /forwards/{rid}/delete", s.requireAdmin(s.handleForwardDelete)) + + // Agent API (token/secret auth inside handlers). + mux.HandleFunc("POST /api/agent/enroll", s.handleEnroll) + mux.HandleFunc("GET /api/agent/poll", s.handlePoll) + mux.HandleFunc("GET /api/agent/term/{sid}/in", s.handleTermIn) + mux.HandleFunc("POST /api/agent/term/{sid}/out", s.handleTermOut) + mux.HandleFunc("GET /api/agent/task/{id}", s.handleTaskFetch) + mux.HandleFunc("POST /api/agent/task/{id}/result", s.handleTaskResult) + + return s.logRequests(mux) +} + +// requireAdmin is middleware that redirects anonymous users appropriately. +func (s *Server) requireAdmin(h http.HandlerFunc) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + if !s.db.SetupDone() { + http.Redirect(w, r, "/setup", http.StatusSeeOther) + return + } + admin := s.currentAdmin(r) + if admin == nil { + http.Redirect(w, r, "/login", http.StatusSeeOther) + return + } + h(w, r) + }) +} + +func (s *Server) logRequests(next http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // Keep terminal/poll paths quiet; they are frequent and long-lived. + if !strings.Contains(r.URL.Path, "/api/agent/") && !strings.Contains(r.URL.Path, "/api/panel/") { + log.Printf("%s %s %s", clientIP(r), r.Method, r.URL.Path) + } + next.ServeHTTP(w, r) + }) +} + +func (s *Server) render(w http.ResponseWriter, name string, data map[string]any) { + w.Header().Set("Content-Type", "text/html; charset=utf-8") + // Panel pages carry inline JS that changes between releases; never let a + // browser or proxy serve a stale copy. + w.Header().Set("Cache-Control", "no-store") + if err := s.tmpl.ExecuteTemplate(w, name, data); err != nil { + log.Printf("template %s: %v", name, err) + http.Error(w, "internal error", http.StatusInternalServerError) + } +} + +// portFromAddr extracts the numeric port from a "host:port" or ":port" address. +func portFromAddr(addr string) int { + if addr == "" { + return 0 + } + _, p, err := net.SplitHostPort(addr) + if err != nil { + return 0 + } + n, _ := strconv.Atoi(p) + return n +} + +func clientIP(r *http.Request) string { + // nginx sets X-Forwarded-For / X-Real-IP; trust the first hop. + if v := r.Header.Get("X-Real-IP"); v != "" { + return v + } + if v := r.Header.Get("X-Forwarded-For"); v != "" { + if i := strings.IndexByte(v, ','); i >= 0 { + return strings.TrimSpace(v[:i]) + } + return strings.TrimSpace(v) + } + host := r.RemoteAddr + if i := strings.LastIndexByte(host, ':'); i >= 0 { + host = host[:i] + } + return host +} diff --git a/internal/server/tunnelserver.go b/internal/server/tunnelserver.go new file mode 100644 index 0000000..a45d37e --- /dev/null +++ b/internal/server/tunnelserver.go @@ -0,0 +1,241 @@ +package server + +import ( + "crypto/rand" + "encoding/hex" + "fmt" + "io" + "log" + "net" + "strings" + "sync" + "time" + + "github.com/x-grk/longpolling-remote-access/internal/tunnel" +) + +// readLine reads a single '\n'-terminated line, up to max bytes, one byte at a +// time so it never buffers past the delimiter into following binary frames. +func readLine(r io.Reader, max int) (string, error) { + var b []byte + buf := make([]byte, 1) + for len(b) < max { + n, err := r.Read(buf) + if n > 0 { + if buf[0] == '\n' { + return string(b), nil + } + b = append(b, buf[0]) + } + if err != nil { + return "", err + } + } + return string(b), nil +} + +// tunnelHub tracks the live direct-tunnel connection for each agent and +// multiplexes forwarded TCP streams over it. +type tunnelHub struct { + db *DB + mu sync.Mutex + agents map[string]*agentTunnel +} + +func newTunnelHub(db *DB) *tunnelHub { + return &tunnelHub{db: db, agents: map[string]*agentTunnel{}} +} + +// agentTunnel is one authenticated direct tunnel to an agent. +type agentTunnel struct { + agentID string + conn net.Conn + wmu sync.Mutex // serialize frame writes + + mu sync.Mutex + nextID uint32 + streams map[uint32]net.Conn +} + +// listen runs the 8446 TCP tunnel acceptor until the listener closes. +func (h *tunnelHub) listen(addr string) error { + ln, err := net.Listen("tcp", addr) + if err != nil { + return err + } + log.Printf("tunnel: listening on %s (direct TCP forwarding)", addr) + go func() { + for { + conn, err := ln.Accept() + if err != nil { + return + } + go h.handleConn(conn) + } + }() + return nil +} + +func (h *tunnelHub) handleConn(conn net.Conn) { + if tc, ok := conn.(*net.TCPConn); ok { + _ = tc.SetNoDelay(true) + } + agentID, err := h.authenticate(conn) + if err != nil { + conn.Close() + return + } + at := &agentTunnel{agentID: agentID, conn: conn, streams: map[uint32]net.Conn{}} + + h.mu.Lock() + if old := h.agents[agentID]; old != nil { + old.conn.Close() + } + h.agents[agentID] = at + h.mu.Unlock() + log.Printf("tunnel: agent %s connected", agentID) + + go at.keepalive() + at.readLoop() + + h.mu.Lock() + if h.agents[agentID] == at { + delete(h.agents, agentID) + } + h.mu.Unlock() + at.shutdown() + log.Printf("tunnel: agent %s disconnected", agentID) +} + +// authenticate performs the nonce/HMAC handshake and returns the agent id. +func (h *tunnelHub) authenticate(conn net.Conn) (string, error) { + _ = conn.SetDeadline(time.Now().Add(10 * time.Second)) + nonce := make([]byte, 32) + if _, err := rand.Read(nonce); err != nil { + return "", err + } + if _, err := fmt.Fprintf(conn, "LPRA-TUN1 %s\n", hex.EncodeToString(nonce)); err != nil { + return "", err + } + line, err := readLine(conn, 512) + if err != nil { + return "", err + } + parts := strings.Fields(strings.TrimSpace(line)) + if len(parts) != 2 { + return "", fmt.Errorf("bad handshake") + } + agentID, sig := parts[0], parts[1] + agent, err := h.db.GetAgent(agentID) + if err != nil || agent == nil { + return "", fmt.Errorf("unknown agent") + } + key, err := tunnel.KeyFromHash(agent.TokenHash) + if err != nil { + return "", err + } + if !tunnel.EqualSig(sig, tunnel.Sign(key, nonce)) { + return "", fmt.Errorf("bad signature") + } + _ = conn.SetDeadline(time.Time{}) // clear + return agentID, nil +} + +func (at *agentTunnel) writeFrame(f tunnel.Frame) error { + at.wmu.Lock() + defer at.wmu.Unlock() + return tunnel.WriteFrame(at.conn, f) +} + +func (at *agentTunnel) keepalive() { + t := time.NewTicker(20 * time.Second) + defer t.Stop() + for range t.C { + if at.writeFrame(tunnel.Frame{Type: tunnel.TypePing}) != nil { + return + } + } +} + +// readLoop demultiplexes frames from the agent to their streams. +func (at *agentTunnel) readLoop() { + for { + f, err := tunnel.ReadFrame(at.conn) + if err != nil { + return + } + switch f.Type { + case tunnel.TypeData: + at.mu.Lock() + c := at.streams[f.Stream] + at.mu.Unlock() + if c != nil { + _, _ = c.Write(f.Payload) + } + case tunnel.TypeClose: + at.closeStream(f.Stream) + } + } +} + +// openStream wires a freshly accepted user connection to a new agent stream +// targeting 127.0.0.1:port on the client. +func (at *agentTunnel) openStream(port int, userConn net.Conn) { + at.mu.Lock() + at.nextID++ + id := at.nextID + at.streams[id] = userConn + at.mu.Unlock() + + // Tell the agent to dial the client-side port. + if at.writeFrame(tunnel.Frame{Type: tunnel.TypeOpen, Stream: id, Payload: []byte{byte(port >> 8), byte(port)}}) != nil { + at.closeStream(id) + return + } + + // user -> agent + go func() { + buf := make([]byte, 32*1024) + for { + n, err := userConn.Read(buf) + if n > 0 { + if at.writeFrame(tunnel.Frame{Type: tunnel.TypeData, Stream: id, Payload: append([]byte(nil), buf[:n]...)}) != nil { + break + } + } + if err != nil { + break + } + } + _ = at.writeFrame(tunnel.Frame{Type: tunnel.TypeClose, Stream: id}) + at.closeStream(id) + }() +} + +func (at *agentTunnel) closeStream(id uint32) { + at.mu.Lock() + c := at.streams[id] + delete(at.streams, id) + at.mu.Unlock() + if c != nil { + c.Close() + } +} + +func (at *agentTunnel) shutdown() { + at.mu.Lock() + conns := at.streams + at.streams = map[uint32]net.Conn{} + at.mu.Unlock() + for _, c := range conns { + c.Close() + } + at.conn.Close() +} + +// get returns the live tunnel for an agent, or nil. +func (h *tunnelHub) get(agentID string) *agentTunnel { + h.mu.Lock() + defer h.mu.Unlock() + return h.agents[agentID] +} diff --git a/internal/server/udp.go b/internal/server/udp.go new file mode 100644 index 0000000..bf7b986 --- /dev/null +++ b/internal/server/udp.go @@ -0,0 +1,246 @@ +package server + +import ( + "encoding/binary" + "log" + "net" + "strings" + "sync" + "time" + + "github.com/x-grk/longpolling-remote-access/internal/tunnel" +) + +// udpRelay carries UDP forwards over the direct 8446 UDP channel. The agent +// registers its UDP return address; end-user datagrams arriving on a rule's +// host port are relayed to the agent and replies are sent back. +// +// Datagram framing on the 8446 control socket: +// +// registration (agent->server): "R " +// data (both): 'D' | sid(4) | port(2) | payload +type udpRelay struct { + db *DB + mu sync.Mutex + ctrl *net.UDPConn + + agentAddr map[string]*net.UDPAddr // agentID -> last registered addr + sessions map[uint32]*udpSession + nextSID uint32 + rulePorts map[int64]*net.UDPConn // ruleID -> per-rule listener +} + +type udpSession struct { + id uint32 + agentID string + ruleConn *net.UDPConn + userAddr *net.UDPAddr + clientPort int + last time.Time +} + +func newUDPRelay(db *DB) *udpRelay { + return &udpRelay{ + db: db, + agentAddr: map[string]*net.UDPAddr{}, + sessions: map[uint32]*udpSession{}, + rulePorts: map[int64]*net.UDPConn{}, + } +} + +// listen opens the shared 8446 UDP control socket. +func (u *udpRelay) listen(addr string) error { + ua, err := net.ResolveUDPAddr("udp", addr) + if err != nil { + return err + } + c, err := net.ListenUDP("udp", ua) + if err != nil { + return err + } + u.ctrl = c + log.Printf("tunnel: listening on %s/udp (direct UDP forwarding)", addr) + go u.ctrlLoop() + go u.gc() + return nil +} + +func (u *udpRelay) ctrlLoop() { + buf := make([]byte, 65535) + for { + n, src, err := u.ctrl.ReadFromUDP(buf) + if err != nil { + return + } + if n == 0 { + continue + } + switch buf[0] { + case 'R': + u.handleReg(string(buf[1:n]), src) + case 'D': + u.handleAgentData(buf[1:n]) + } + } +} + +// handleReg validates "agentID sig" and records the agent's UDP addr. +// sig = HMAC(key, "agentID|minute") accepting the current or previous minute. +func (u *udpRelay) handleReg(line string, src *net.UDPAddr) { + f := strings.Fields(strings.TrimSpace(line)) + if len(f) != 2 { + return + } + agentID, sig := f[0], f[1] + agent, err := u.db.GetAgent(agentID) + if err != nil || agent == nil { + return + } + key, err := tunnel.KeyFromHash(agent.TokenHash) + if err != nil { + return + } + now := time.Now().Unix() / 60 + ok := false + for _, m := range []int64{now, now - 1} { + msg := []byte(agentID + "|" + itoa64(m)) + if tunnel.EqualSig(sig, tunnel.Sign(key, msg)) { + ok = true + break + } + } + if !ok { + return + } + u.mu.Lock() + u.agentAddr[agentID] = src + u.mu.Unlock() +} + +// handleAgentData routes a reply datagram from an agent back to the end user. +func (u *udpRelay) handleAgentData(b []byte) { + if len(b) < 6 { + return + } + sid := binary.BigEndian.Uint32(b[0:4]) + payload := b[6:] + u.mu.Lock() + s := u.sessions[sid] + if s != nil { + s.last = time.Now() + } + u.mu.Unlock() + if s != nil { + _, _ = s.ruleConn.WriteToUDP(payload, s.userAddr) + } +} + +// openRulePort starts the per-rule UDP listener on the host port. +func (u *udpRelay) openRulePort(rule ForwardRule) error { + ua, err := net.ResolveUDPAddr("udp", rule.ListenAddr+":"+itoa(rule.HostPort)) + if err != nil { + return err + } + c, err := net.ListenUDP("udp", ua) + if err != nil { + return err + } + u.mu.Lock() + u.rulePorts[rule.ID] = c + u.mu.Unlock() + go u.rulePortLoop(rule, c) + return nil +} + +func (u *udpRelay) closeRulePort(ruleID int64) { + u.mu.Lock() + c := u.rulePorts[ruleID] + delete(u.rulePorts, ruleID) + u.mu.Unlock() + if c != nil { + c.Close() + } +} + +func (u *udpRelay) rulePortLoop(rule ForwardRule, c *net.UDPConn) { + buf := make([]byte, 65535) + for { + n, userAddr, err := c.ReadFromUDP(buf) + if err != nil { + return + } + u.mu.Lock() + aAddr := u.agentAddr[rule.AgentID] + // Find existing session for this (rule, user). + var sess *udpSession + for _, s := range u.sessions { + if s.ruleConn == c && udpAddrEqual(s.userAddr, userAddr) { + sess = s + break + } + } + if sess == nil { + u.nextSID++ + sess = &udpSession{ + id: u.nextSID, agentID: rule.AgentID, ruleConn: c, + userAddr: userAddr, clientPort: rule.ClientPort, + } + u.sessions[sess.id] = sess + } + sess.last = time.Now() + u.mu.Unlock() + + if aAddr == nil { + continue // agent has not registered its UDP endpoint yet + } + // 'D' | sid(4) | clientPort(2) | payload + out := make([]byte, 7+n) + out[0] = 'D' + binary.BigEndian.PutUint32(out[1:5], sess.id) + binary.BigEndian.PutUint16(out[5:7], uint16(rule.ClientPort)) + copy(out[7:], buf[:n]) + _, _ = u.ctrl.WriteToUDP(out, aAddr) + } +} + +func (u *udpRelay) gc() { + t := time.NewTicker(30 * time.Second) + defer t.Stop() + for range t.C { + cut := time.Now().Add(-90 * time.Second) + u.mu.Lock() + for id, s := range u.sessions { + if s.last.Before(cut) { + delete(u.sessions, id) + } + } + u.mu.Unlock() + } +} + +func udpAddrEqual(a, b *net.UDPAddr) bool { + return a != nil && b != nil && a.Port == b.Port && a.IP.Equal(b.IP) +} + +func itoa(n int) string { return itoa64(int64(n)) } +func itoa64(n int64) string { + if n == 0 { + return "0" + } + neg := n < 0 + if neg { + n = -n + } + var b [24]byte + i := len(b) + for n > 0 { + i-- + b[i] = byte('0' + n%10) + n /= 10 + } + if neg { + i-- + b[i] = '-' + } + return string(b[i:]) +} diff --git a/internal/server/ufw.go b/internal/server/ufw.go new file mode 100644 index 0000000..622e397 --- /dev/null +++ b/internal/server/ufw.go @@ -0,0 +1,119 @@ +package server + +import ( + "fmt" + "log" + "os/exec" + "strings" +) + +// ufwManager integrates with the host firewall (ufw) when it is installed and +// active. Opening a forward port in the panel also opens it in ufw; the created +// rules are tracked so they can be listed and revoked from Settings. +// +// ufw requires root, so this only works when the server process can run ufw +// (e.g. running as root). When it cannot, integration is simply inactive. +type ufwManager struct { + db *DB + tunnelPort int +} + +func newUfwManager(db *DB, tunnelPort int) *ufwManager { + return &ufwManager{db: db, tunnelPort: tunnelPort} +} + +// status reports whether ufw is installed, active, and runnable by this process. +func (u *ufwManager) status() (installed, active, runnable bool) { + path, err := exec.LookPath("ufw") + if err != nil { + return false, false, false + } + installed = true + out, err := exec.Command(path, "status").CombinedOutput() + if err != nil { + // Typically a permission error (not root) -> not runnable. + return true, false, false + } + runnable = true + active = strings.Contains(string(out), "Status: active") + return installed, active, runnable +} + +// available reports whether we can and should drive ufw now. +func (u *ufwManager) available() bool { + if !u.db.UfwEnabled() { + return false + } + installed, active, runnable := u.status() + return installed && active && runnable +} + +// allow opens port/proto in ufw (if integration is available) and records it, +// bound to the given forward rule id (0 = shared/manual). +func (u *ufwManager) allow(port int, proto string, ruleID int64) { + if !u.available() { + return + } + if u.db.HasUfwRule(port, proto) { + return + } + spec := fmt.Sprintf("%d/%s", port, proto) + if out, err := exec.Command("ufw", "allow", spec).CombinedOutput(); err != nil { + log.Printf("ufw allow %s: %v: %s", spec, err, strings.TrimSpace(string(out))) + return + } + _ = u.db.AddUfwRule(port, proto, ruleID) + log.Printf("ufw: allowed %s", spec) +} + +// revokeRule deletes one tracked ufw rule from ufw and the tracking table. +func (u *ufwManager) revokeRule(r UfwRule) { + spec := fmt.Sprintf("%d/%s", r.Port, r.Proto) + if out, err := exec.Command("ufw", "delete", "allow", spec).CombinedOutput(); err != nil { + log.Printf("ufw delete allow %s: %v: %s", spec, err, strings.TrimSpace(string(out))) + } else { + log.Printf("ufw: revoked %s", spec) + } + _ = u.db.DeleteUfwRule(r.ID) +} + +// revoke removes a ufw rule by its tracking id (manual revoke from Settings). +func (u *ufwManager) revoke(id int64) { + r, err := u.db.GetUfwRule(id) + if err != nil || r == nil { + return + } + u.revokeRule(*r) +} + +// revokeForRule removes the ufw rules bound to a specific forward rule. The +// shared tunnel port (rule_id 0) is left alone, since other forwards use it. +func (u *ufwManager) revokeForRule(ruleID int64) { + rules, err := u.db.ListUfwRulesByRule(ruleID) + if err != nil { + return + } + for _, r := range rules { + u.revokeRule(r) + } +} + +// allowForward opens the host ports a forward rule needs: its host port (bound +// to the rule so it is revoked with it) plus the shared 8446 tunnel port for +// direct tcp/udp modes (bound to 0 — shared across rules). +func (u *ufwManager) allowForward(rule ForwardRule) { + switch rule.Proto { + case "udp": + u.allow(rule.HostPort, "udp", rule.ID) + if u.tunnelPort > 0 { + u.allow(u.tunnelPort, "udp", 0) + } + case "tcp": + u.allow(rule.HostPort, "tcp", rule.ID) + if u.tunnelPort > 0 { + u.allow(u.tunnelPort, "tcp", 0) + } + default: // http long-poll listener is TCP + u.allow(rule.HostPort, "tcp", rule.ID) + } +} diff --git a/internal/server/util.go b/internal/server/util.go new file mode 100644 index 0000000..b1ed8bb --- /dev/null +++ b/internal/server/util.go @@ -0,0 +1,26 @@ +package server + +import ( + "fmt" + "time" +) + +// humanSince renders a coarse "time ago" label for the dashboard. +func humanSince(t time.Time) string { + if t.IsZero() { + return "никогда" + } + d := time.Since(t) + switch { + case d < 10*time.Second: + return "только что" + case d < time.Minute: + return fmt.Sprintf("%d сек назад", int(d.Seconds())) + case d < time.Hour: + return fmt.Sprintf("%d мин назад", int(d.Minutes())) + case d < 24*time.Hour: + return fmt.Sprintf("%d ч назад", int(d.Hours())) + default: + return fmt.Sprintf("%d дн назад", int(d.Hours()/24)) + } +} diff --git a/internal/server/web/.DS_Store b/internal/server/web/.DS_Store new file mode 100644 index 0000000..9f699be Binary files /dev/null and b/internal/server/web/.DS_Store differ diff --git a/internal/server/web/install.sh.tmpl b/internal/server/web/install.sh.tmpl new file mode 100644 index 0000000..4c5371c --- /dev/null +++ b/internal/server/web/install.sh.tmpl @@ -0,0 +1,165 @@ +#!/bin/sh +# LPRA agent installer. +# Usage (non-interactive): +# curl -fsSL {{.Host}}/install.sh | sudo HOST={{.Host}} ENROLL_SECRET=xxxx sh +# Usage (interactive, will ask for the secret): +# curl -fsSL {{.Host}}/install.sh | sudo sh +set -eu + +HOST="${HOST:-{{.Host}}}" +{{if .Secret}}ENROLL_SECRET="${ENROLL_SECRET:-{{.Secret}}}" +{{end}} +BIN_PATH="/usr/local/bin/lpagent" +CONF_DIR="/etc/lpagent" +CONF_FILE="$CONF_DIR/config.env" +STATE_FILE="$CONF_DIR/state.json" +SERVICE="/etc/systemd/system/lpagent.service" + +red() { printf '\033[31m%s\033[0m\n' "$1"; } +green() { printf '\033[32m%s\033[0m\n' "$1"; } +bold() { printf '\033[1m%s\033[0m\n' "$1"; } + +# Read a line from the real terminal even when the script is piped into `sh`. +ask() { # ask VARNAME "prompt" + _v="" + if [ -r /dev/tty ]; then + printf '%s' "$2" > /dev/tty + read -r _v < /dev/tty || true + fi + eval "$1=\$_v" +} +confirm() { # confirm "prompt" -> returns 0 on yes + if [ ! -r /dev/tty ]; then return 1; fi + printf '%s [y/N]: ' "$1" > /dev/tty + read -r _a < /dev/tty || _a="" + case "$_a" in y|Y|yes|YES) return 0 ;; *) return 1 ;; esac +} + +# --- root check ------------------------------------------------------------- +if [ "$(id -u)" -ne 0 ]; then + red "Нужны права root." + echo "Запустите через sudo, например:" + echo " curl -fsSL $HOST/install.sh | sudo sh" + exit 1 +fi + +# --- systemd check ---------------------------------------------------------- +if ! command -v systemctl >/dev/null 2>&1; then + red "systemctl не найден: поддерживаются только системы с systemd." + exit 1 +fi + +uninstall() { + bold "Удаление агента LPRA…" + systemctl stop lpagent 2>/dev/null || true + systemctl disable lpagent 2>/dev/null || true + rm -f "$SERVICE" + systemctl daemon-reload 2>/dev/null || true + rm -f "$BIN_PATH" + rm -rf "$CONF_DIR" + green "Агент удалён." +} + +# --- existing installation -------------------------------------------------- +if [ -f "$SERVICE" ] || [ -f "$BIN_PATH" ]; then + bold "Агент LPRA уже установлен на этой машине." + if confirm "Удалить его сейчас?"; then + uninstall + if confirm "Установить заново?"; then + : # fall through to install + else + exit 0 + fi + else + if confirm "Переустановить / обновить?"; then + : # fall through, keep existing state.json (не будет повторной регистрации) + else + echo "Ничего не делаю." + exit 0 + fi + fi +fi + +# --- architecture ----------------------------------------------------------- +case "$(uname -m)" in + x86_64|amd64) ARCH=amd64 ;; + aarch64|arm64) ARCH=arm64 ;; + *) red "Неподдерживаемая архитектура: $(uname -m)"; exit 1 ;; +esac + +# --- machine name ----------------------------------------------------------- +# Default: -<4 random chars>. Only used at the first enrollment. +if [ -z "${AGENT_NAME:-}" ]; then + SUFFIX="$(head -c16 /dev/urandom 2>/dev/null | od -An -tx1 2>/dev/null | tr -d ' \n' | cut -c1-4)" + [ -z "$SUFFIX" ] && SUFFIX="$(date +%s | tail -c 5)" + AGENT_NAME="$(hostname 2>/dev/null || echo host)-$SUFFIX" +fi + +# --- enroll secret ---------------------------------------------------------- +# Needed only for the very first enrollment; skipped if state.json already exists. +if [ -z "${ENROLL_SECRET:-}" ] && [ ! -f "$STATE_FILE" ]; then + ask ENROLL_SECRET "Введите enroll-секрет (из панели, раздел Настройки): " + if [ -z "${ENROLL_SECRET:-}" ]; then + red "Enroll-секрет обязателен для первой установки." + exit 1 + fi +fi + +# --- downloader ------------------------------------------------------------- +download() { # download URL DEST + if command -v curl >/dev/null 2>&1; then + curl -fSL -o "$2" "$1" + elif command -v wget >/dev/null 2>&1; then + wget -qO "$2" "$1" + else + red "Нужен curl или wget."; exit 1 + fi +} + +bold "Скачивание агента ($ARCH) с $HOST…" +TMP="$(mktemp)" +download "$HOST/download/agent-linux-$ARCH" "$TMP" +install -m 0755 "$TMP" "$BIN_PATH" +rm -f "$TMP" +green "Бинарник установлен: $BIN_PATH" + +# --- config ----------------------------------------------------------------- +mkdir -p "$CONF_DIR" +chmod 700 "$CONF_DIR" +umask 077 +cat > "$CONF_FILE" < "$SERVICE" </dev/null 2>&1 || true +systemctl restart lpagent + +sleep 1 +bold "Статус сервиса:" +systemctl --no-pager --full status lpagent || true +echo +green "Готово. Машина должна появиться в панели: $HOST" diff --git a/internal/server/web/static/addon-fit.js b/internal/server/web/static/addon-fit.js new file mode 100644 index 0000000..7cfd9de --- /dev/null +++ b/internal/server/web/static/addon-fit.js @@ -0,0 +1,2 @@ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.FitAddon=t():e.FitAddon=t()}(self,(()=>(()=>{"use strict";var e={};return(()=>{var t=e;Object.defineProperty(t,"__esModule",{value:!0}),t.FitAddon=void 0,t.FitAddon=class{activate(e){this._terminal=e}dispose(){}fit(){const e=this.proposeDimensions();if(!e||!this._terminal||isNaN(e.cols)||isNaN(e.rows))return;const t=this._terminal._core;this._terminal.rows===e.rows&&this._terminal.cols===e.cols||(t._renderService.clear(),this._terminal.resize(e.cols,e.rows))}proposeDimensions(){if(!this._terminal)return;if(!this._terminal.element||!this._terminal.element.parentElement)return;const e=this._terminal._core,t=e._renderService.dimensions;if(0===t.css.cell.width||0===t.css.cell.height)return;const r=0===this._terminal.options.scrollback?0:e.viewport.scrollBarWidth,i=window.getComputedStyle(this._terminal.element.parentElement),o=parseInt(i.getPropertyValue("height")),s=Math.max(0,parseInt(i.getPropertyValue("width"))),n=window.getComputedStyle(this._terminal.element),l=o-(parseInt(n.getPropertyValue("padding-top"))+parseInt(n.getPropertyValue("padding-bottom"))),a=s-(parseInt(n.getPropertyValue("padding-right"))+parseInt(n.getPropertyValue("padding-left")))-r;return{cols:Math.max(2,Math.floor(a/t.css.cell.width)),rows:Math.max(1,Math.floor(l/t.css.cell.height))}}}})(),e})())); +//# sourceMappingURL=xterm-addon-fit.js.map \ No newline at end of file diff --git a/internal/server/web/static/app.css b/internal/server/web/static/app.css new file mode 100644 index 0000000..06830e4 --- /dev/null +++ b/internal/server/web/static/app.css @@ -0,0 +1,72 @@ +:root { + --bg: #0f1216; + --panel: #171c23; + --border: #262d38; + --text: #e6e9ef; + --muted: #8b95a5; + --accent: #4f9dff; + --accent-hover: #3b86e6; + --danger: #ff5c5c; + --ok: #37c871; +} +* { box-sizing: border-box; } +body { + margin: 0; + background: var(--bg); + color: var(--text); + font: 15px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; +} +a { color: var(--accent); text-decoration: none; } +a:hover { text-decoration: underline; } +header.topbar { + display: flex; align-items: center; justify-content: space-between; + padding: 14px 24px; border-bottom: 1px solid var(--border); background: var(--panel); +} +header.topbar .brand { font-weight: 600; } +header.topbar nav a { margin-left: 18px; color: var(--muted); } +header.topbar nav a:hover { color: var(--text); } +.wrap { max-width: 960px; margin: 0 auto; padding: 24px 16px; } +.center { max-width: 420px; margin: 8vh auto; padding: 0 16px; } +.card { + background: var(--panel); border: 1px solid var(--border); + border-radius: 10px; padding: 24px; margin-bottom: 20px; +} +h1 { font-size: 22px; margin: 0 0 18px; } +h2 { font-size: 17px; margin: 0 0 14px; } +label { display: block; margin: 12px 0 4px; color: var(--muted); font-size: 13px; } +input[type=text], input[type=password] { + width: 100%; padding: 10px 12px; border-radius: 8px; + border: 1px solid var(--border); background: #0c0f13; color: var(--text); font-size: 14px; +} +button, .btn { + display: inline-block; cursor: pointer; border: 0; border-radius: 8px; + padding: 10px 16px; font-size: 14px; font-weight: 500; + background: var(--accent); color: #fff; +} +button:hover, .btn:hover { background: var(--accent-hover); text-decoration: none; } +.btn.ghost { background: transparent; border: 1px solid var(--border); color: var(--text); } +.btn.danger { background: var(--danger); } +.btn.small { padding: 6px 10px; font-size: 13px; } +.btn.icon { width: 30px; height: 30px; padding: 0; display: inline-flex; + align-items: center; justify-content: center; font-size: 14px; line-height: 1; text-decoration: none; } +.err { background: #3a1d1d; border: 1px solid #6b2b2b; color: #ffb3b3; padding: 10px 12px; border-radius: 8px; margin-bottom: 14px; font-size: 14px; } +table { width: 100%; border-collapse: collapse; } +th, td { text-align: left; padding: 10px 8px; border-bottom: 1px solid var(--border); font-size: 14px; } +th { color: var(--muted); font-weight: 500; } +.dot { display: inline-block; width: 9px; height: 9px; border-radius: 50%; margin-right: 6px; } +.dot.on { background: var(--ok); } +.dot.off { background: #555d6a; } +.muted { color: var(--muted); } +code, pre { + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + background: #0c0f13; border: 1px solid var(--border); border-radius: 8px; +} +code { padding: 2px 6px; font-size: 13px; } +pre { padding: 12px; overflow-x: auto; font-size: 13px; white-space: pre-wrap; word-break: break-all; } +.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; } +.spacer { flex: 1; } +#terminal { width: 100%; height: calc(100vh - 150px); } +@media (max-width: 600px) { + header.topbar { padding: 12px 16px; } + header.topbar nav a { margin-left: 12px; } +} diff --git a/internal/server/web/static/xterm.css b/internal/server/web/static/xterm.css new file mode 100644 index 0000000..74acc26 --- /dev/null +++ b/internal/server/web/static/xterm.css @@ -0,0 +1,209 @@ +/** + * Copyright (c) 2014 The xterm.js authors. All rights reserved. + * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * https://github.com/chjj/term.js + * @license MIT + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + * + * Originally forked from (with the author's permission): + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + * The original design remains. The terminal itself + * has been extended to include xterm CSI codes, among + * other features. + */ + +/** + * Default styles for xterm.js + */ + +.xterm { + cursor: text; + position: relative; + user-select: none; + -ms-user-select: none; + -webkit-user-select: none; +} + +.xterm.focus, +.xterm:focus { + outline: none; +} + +.xterm .xterm-helpers { + position: absolute; + top: 0; + /** + * The z-index of the helpers must be higher than the canvases in order for + * IMEs to appear on top. + */ + z-index: 5; +} + +.xterm .xterm-helper-textarea { + padding: 0; + border: 0; + margin: 0; + /* Move textarea out of the screen to the far left, so that the cursor is not visible */ + position: absolute; + opacity: 0; + left: -9999em; + top: 0; + width: 0; + height: 0; + z-index: -5; + /** Prevent wrapping so the IME appears against the textarea at the correct position */ + white-space: nowrap; + overflow: hidden; + resize: none; +} + +.xterm .composition-view { + /* TODO: Composition position got messed up somewhere */ + background: #000; + color: #FFF; + display: none; + position: absolute; + white-space: nowrap; + z-index: 1; +} + +.xterm .composition-view.active { + display: block; +} + +.xterm .xterm-viewport { + /* On OS X this is required in order for the scroll bar to appear fully opaque */ + background-color: #000; + overflow-y: scroll; + cursor: default; + position: absolute; + right: 0; + left: 0; + top: 0; + bottom: 0; +} + +.xterm .xterm-screen { + position: relative; +} + +.xterm .xterm-screen canvas { + position: absolute; + left: 0; + top: 0; +} + +.xterm .xterm-scroll-area { + visibility: hidden; +} + +.xterm-char-measure-element { + display: inline-block; + visibility: hidden; + position: absolute; + top: 0; + left: -9999em; + line-height: normal; +} + +.xterm.enable-mouse-events { + /* When mouse events are enabled (eg. tmux), revert to the standard pointer cursor */ + cursor: default; +} + +.xterm.xterm-cursor-pointer, +.xterm .xterm-cursor-pointer { + cursor: pointer; +} + +.xterm.column-select.focus { + /* Column selection mode */ + cursor: crosshair; +} + +.xterm .xterm-accessibility, +.xterm .xterm-message { + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + z-index: 10; + color: transparent; + pointer-events: none; +} + +.xterm .live-region { + position: absolute; + left: -9999px; + width: 1px; + height: 1px; + overflow: hidden; +} + +.xterm-dim { + /* Dim should not apply to background, so the opacity of the foreground color is applied + * explicitly in the generated class and reset to 1 here */ + opacity: 1 !important; +} + +.xterm-underline-1 { text-decoration: underline; } +.xterm-underline-2 { text-decoration: double underline; } +.xterm-underline-3 { text-decoration: wavy underline; } +.xterm-underline-4 { text-decoration: dotted underline; } +.xterm-underline-5 { text-decoration: dashed underline; } + +.xterm-overline { + text-decoration: overline; +} + +.xterm-overline.xterm-underline-1 { text-decoration: overline underline; } +.xterm-overline.xterm-underline-2 { text-decoration: overline double underline; } +.xterm-overline.xterm-underline-3 { text-decoration: overline wavy underline; } +.xterm-overline.xterm-underline-4 { text-decoration: overline dotted underline; } +.xterm-overline.xterm-underline-5 { text-decoration: overline dashed underline; } + +.xterm-strikethrough { + text-decoration: line-through; +} + +.xterm-screen .xterm-decoration-container .xterm-decoration { + z-index: 6; + position: absolute; +} + +.xterm-screen .xterm-decoration-container .xterm-decoration.xterm-decoration-top-layer { + z-index: 7; +} + +.xterm-decoration-overview-ruler { + z-index: 8; + position: absolute; + top: 0; + right: 0; + pointer-events: none; +} + +.xterm-decoration-top { + z-index: 2; + position: relative; +} diff --git a/internal/server/web/static/xterm.js b/internal/server/web/static/xterm.js new file mode 100644 index 0000000..a68eae6 --- /dev/null +++ b/internal/server/web/static/xterm.js @@ -0,0 +1,2 @@ +!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i=t();for(var s in i)("object"==typeof exports?exports:e)[s]=i[s]}}(self,(()=>(()=>{"use strict";var e={4567:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.AccessibilityManager=void 0;const n=i(9042),o=i(6114),a=i(9924),h=i(844),c=i(5596),l=i(4725),d=i(3656);let _=t.AccessibilityManager=class extends h.Disposable{constructor(e,t){super(),this._terminal=e,this._renderService=t,this._liveRegionLineCount=0,this._charsToConsume=[],this._charsToAnnounce="",this._accessibilityContainer=document.createElement("div"),this._accessibilityContainer.classList.add("xterm-accessibility"),this._rowContainer=document.createElement("div"),this._rowContainer.setAttribute("role","list"),this._rowContainer.classList.add("xterm-accessibility-tree"),this._rowElements=[];for(let e=0;ethis._handleBoundaryFocus(e,0),this._bottomBoundaryFocusListener=e=>this._handleBoundaryFocus(e,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions(),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=document.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this.register(new a.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this.register(this._terminal.onResize((e=>this._handleResize(e.rows)))),this.register(this._terminal.onRender((e=>this._refreshRows(e.start,e.end)))),this.register(this._terminal.onScroll((()=>this._refreshRows()))),this.register(this._terminal.onA11yChar((e=>this._handleChar(e)))),this.register(this._terminal.onLineFeed((()=>this._handleChar("\n")))),this.register(this._terminal.onA11yTab((e=>this._handleTab(e)))),this.register(this._terminal.onKey((e=>this._handleKey(e.key)))),this.register(this._terminal.onBlur((()=>this._clearLiveRegion()))),this.register(this._renderService.onDimensionsChange((()=>this._refreshRowsDimensions()))),this._screenDprMonitor=new c.ScreenDprMonitor(window),this.register(this._screenDprMonitor),this._screenDprMonitor.setListener((()=>this._refreshRowsDimensions())),this.register((0,d.addDisposableDomListener)(window,"resize",(()=>this._refreshRowsDimensions()))),this._refreshRows(),this.register((0,h.toDisposable)((()=>{this._accessibilityContainer.remove(),this._rowElements.length=0})))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,"\n"===e&&(this._liveRegionLineCount++,21===this._liveRegionLineCount&&(this._liveRegion.textContent+=n.tooMuchOutput)),o.isMac&&this._liveRegion.textContent&&this._liveRegion.textContent.length>0&&!this._liveRegion.parentNode&&setTimeout((()=>{this._accessibilityContainer.appendChild(this._liveRegion)}),0))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0,o.isMac&&this._liveRegion.remove()}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){const i=this._terminal.buffer,s=i.lines.length.toString();for(let r=e;r<=t;r++){const e=i.translateBufferLineToString(i.ydisp+r,!0),t=(i.ydisp+r+1).toString(),n=this._rowElements[r];n&&(0===e.length?n.innerText=" ":n.textContent=e,n.setAttribute("aria-posinset",t),n.setAttribute("aria-setsize",s))}this._announceCharacters()}_announceCharacters(){0!==this._charsToAnnounce.length&&(this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){const i=e.target,s=this._rowElements[0===t?1:this._rowElements.length-2];if(i.getAttribute("aria-posinset")===(0===t?"1":`${this._terminal.buffer.lines.length}`))return;if(e.relatedTarget!==s)return;let r,n;if(0===t?(r=i,n=this._rowElements.pop(),this._rowContainer.removeChild(n)):(r=this._rowElements.shift(),n=i,this._rowContainer.removeChild(r)),r.removeEventListener("focus",this._topBoundaryFocusListener),n.removeEventListener("focus",this._bottomBoundaryFocusListener),0===t){const e=this._createAccessibilityTreeNode();this._rowElements.unshift(e),this._rowContainer.insertAdjacentElement("afterbegin",e)}else{const e=this._createAccessibilityTreeNode();this._rowElements.push(e),this._rowContainer.appendChild(e)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(0===t?-1:1),this._rowElements[0===t?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let e=this._rowContainer.children.length;ee;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){const e=document.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){this._accessibilityContainer.style.width=`${this._renderService.dimensions.css.canvas.width}px`,this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;e{function i(e){return e.replace(/\r?\n/g,"\r")}function s(e,t){return t?"[200~"+e+"[201~":e}function r(e,t,r,n){e=s(e=i(e),r.decPrivateModes.bracketedPasteMode&&!0!==n.rawOptions.ignoreBracketedPasteMode),r.triggerDataEvent(e,!0),t.value=""}function n(e,t,i){const s=i.getBoundingClientRect(),r=e.clientX-s.left-10,n=e.clientY-s.top-10;t.style.width="20px",t.style.height="20px",t.style.left=`${r}px`,t.style.top=`${n}px`,t.style.zIndex="1000",t.focus()}Object.defineProperty(t,"__esModule",{value:!0}),t.rightClickHandler=t.moveTextAreaUnderMouseCursor=t.paste=t.handlePasteEvent=t.copyHandler=t.bracketTextForPaste=t.prepareTextForTerminal=void 0,t.prepareTextForTerminal=i,t.bracketTextForPaste=s,t.copyHandler=function(e,t){e.clipboardData&&e.clipboardData.setData("text/plain",t.selectionText),e.preventDefault()},t.handlePasteEvent=function(e,t,i,s){e.stopPropagation(),e.clipboardData&&r(e.clipboardData.getData("text/plain"),t,i,s)},t.paste=r,t.moveTextAreaUnderMouseCursor=n,t.rightClickHandler=function(e,t,i,s,r){n(e,t,i),r&&s.rightClickSelect(e),t.value=s.selectionText,t.select()}},7239:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ColorContrastCache=void 0;const s=i(1505);t.ColorContrastCache=class{constructor(){this._color=new s.TwoKeyMap,this._css=new s.TwoKeyMap}setCss(e,t,i){this._css.set(e,t,i)}getCss(e,t){return this._css.get(e,t)}setColor(e,t,i){this._color.set(e,t,i)}getColor(e,t){return this._color.get(e,t)}clear(){this._color.clear(),this._css.clear()}}},3656:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.addDisposableDomListener=void 0,t.addDisposableDomListener=function(e,t,i,s){e.addEventListener(t,i,s);let r=!1;return{dispose:()=>{r||(r=!0,e.removeEventListener(t,i,s))}}}},6465:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Linkifier2=void 0;const n=i(3656),o=i(8460),a=i(844),h=i(2585);let c=t.Linkifier2=class extends a.Disposable{get currentLink(){return this._currentLink}constructor(e){super(),this._bufferService=e,this._linkProviders=[],this._linkCacheDisposables=[],this._isMouseOut=!0,this._wasResized=!1,this._activeLine=-1,this._onShowLinkUnderline=this.register(new o.EventEmitter),this.onShowLinkUnderline=this._onShowLinkUnderline.event,this._onHideLinkUnderline=this.register(new o.EventEmitter),this.onHideLinkUnderline=this._onHideLinkUnderline.event,this.register((0,a.getDisposeArrayDisposable)(this._linkCacheDisposables)),this.register((0,a.toDisposable)((()=>{this._lastMouseEvent=void 0}))),this.register(this._bufferService.onResize((()=>{this._clearCurrentLink(),this._wasResized=!0})))}registerLinkProvider(e){return this._linkProviders.push(e),{dispose:()=>{const t=this._linkProviders.indexOf(e);-1!==t&&this._linkProviders.splice(t,1)}}}attachToDom(e,t,i){this._element=e,this._mouseService=t,this._renderService=i,this.register((0,n.addDisposableDomListener)(this._element,"mouseleave",(()=>{this._isMouseOut=!0,this._clearCurrentLink()}))),this.register((0,n.addDisposableDomListener)(this._element,"mousemove",this._handleMouseMove.bind(this))),this.register((0,n.addDisposableDomListener)(this._element,"mousedown",this._handleMouseDown.bind(this))),this.register((0,n.addDisposableDomListener)(this._element,"mouseup",this._handleMouseUp.bind(this)))}_handleMouseMove(e){if(this._lastMouseEvent=e,!this._element||!this._mouseService)return;const t=this._positionFromMouseEvent(e,this._element,this._mouseService);if(!t)return;this._isMouseOut=!1;const i=e.composedPath();for(let e=0;e{null==e||e.forEach((e=>{e.link.dispose&&e.link.dispose()}))})),this._activeProviderReplies=new Map,this._activeLine=e.y);let r=!1;for(const[i,n]of this._linkProviders.entries())t?(null===(s=this._activeProviderReplies)||void 0===s?void 0:s.get(i))&&(r=this._checkLinkProviderResult(i,e,r)):n.provideLinks(e.y,(t=>{var s,n;if(this._isMouseOut)return;const o=null==t?void 0:t.map((e=>({link:e})));null===(s=this._activeProviderReplies)||void 0===s||s.set(i,o),r=this._checkLinkProviderResult(i,e,r),(null===(n=this._activeProviderReplies)||void 0===n?void 0:n.size)===this._linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)}))}_removeIntersectingLinks(e,t){const i=new Set;for(let s=0;se?this._bufferService.cols:s.link.range.end.x;for(let e=n;e<=o;e++){if(i.has(e)){r.splice(t--,1);break}i.add(e)}}}}_checkLinkProviderResult(e,t,i){var s;if(!this._activeProviderReplies)return i;const r=this._activeProviderReplies.get(e);let n=!1;for(let t=0;tthis._linkAtPosition(e.link,t)));e&&(i=!0,this._handleNewLink(e))}if(this._activeProviderReplies.size===this._linkProviders.length&&!i)for(let e=0;ethis._linkAtPosition(e.link,t)));if(r){i=!0,this._handleNewLink(r);break}}return i}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._element||!this._mouseService||!this._currentLink)return;const t=this._positionFromMouseEvent(e,this._element,this._mouseService);t&&this._mouseDownLink===this._currentLink&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){this._element&&this._currentLink&&this._lastMouseEvent&&(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,(0,a.disposeArray)(this._linkCacheDisposables))}_handleNewLink(e){if(!this._element||!this._lastMouseEvent||!this._mouseService)return;const t=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:void 0===e.link.decorations||e.link.decorations.underline,pointerCursor:void 0===e.link.decorations||e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>{var e,t;return null===(t=null===(e=this._currentLink)||void 0===e?void 0:e.state)||void 0===t?void 0:t.decorations.pointerCursor},set:e=>{var t,i;(null===(t=this._currentLink)||void 0===t?void 0:t.state)&&this._currentLink.state.decorations.pointerCursor!==e&&(this._currentLink.state.decorations.pointerCursor=e,this._currentLink.state.isHovered&&(null===(i=this._element)||void 0===i||i.classList.toggle("xterm-cursor-pointer",e)))}},underline:{get:()=>{var e,t;return null===(t=null===(e=this._currentLink)||void 0===e?void 0:e.state)||void 0===t?void 0:t.decorations.underline},set:t=>{var i,s,r;(null===(i=this._currentLink)||void 0===i?void 0:i.state)&&(null===(r=null===(s=this._currentLink)||void 0===s?void 0:s.state)||void 0===r?void 0:r.decorations.underline)!==t&&(this._currentLink.state.decorations.underline=t,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,t))}}}),this._renderService&&this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange((e=>{if(!this._currentLink)return;const t=0===e.start?0:e.start+1+this._bufferService.buffer.ydisp,i=this._bufferService.buffer.ydisp+1+e.end;if(this._currentLink.link.range.start.y>=t&&this._currentLink.link.range.end.y<=i&&(this._clearCurrentLink(t,i),this._lastMouseEvent&&this._element)){const e=this._positionFromMouseEvent(this._lastMouseEvent,this._element,this._mouseService);e&&this._askForLink(e,!1)}}))))}_linkHover(e,t,i){var s;(null===(s=this._currentLink)||void 0===s?void 0:s.state)&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(i,t.text)}_fireUnderlineEvent(e,t){const i=e.range,s=this._bufferService.buffer.ydisp,r=this._createLinkUnderlineEvent(i.start.x-1,i.start.y-s-1,i.end.x,i.end.y-s-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(r)}_linkLeave(e,t,i){var s;(null===(s=this._currentLink)||void 0===s?void 0:s.state)&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(i,t.text)}_linkAtPosition(e,t){const i=e.range.start.y*this._bufferService.cols+e.range.start.x,s=e.range.end.y*this._bufferService.cols+e.range.end.x,r=t.y*this._bufferService.cols+t.x;return i<=r&&r<=s}_positionFromMouseEvent(e,t,i){const s=i.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(s)return{x:s[0],y:s[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,i,s,r){return{x1:e,y1:t,x2:i,y2:s,cols:this._bufferService.cols,fg:r}}};t.Linkifier2=c=s([r(0,h.IBufferService)],c)},9042:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.tooMuchOutput=t.promptLabel=void 0,t.promptLabel="Terminal input",t.tooMuchOutput="Too much output to announce, navigate to rows manually to read"},3730:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OscLinkProvider=void 0;const n=i(511),o=i(2585);let a=t.OscLinkProvider=class{constructor(e,t,i){this._bufferService=e,this._optionsService=t,this._oscLinkService=i}provideLinks(e,t){var i;const s=this._bufferService.buffer.lines.get(e-1);if(!s)return void t(void 0);const r=[],o=this._optionsService.rawOptions.linkHandler,a=new n.CellData,c=s.getTrimmedLength();let l=-1,d=-1,_=!1;for(let t=0;to?o.activate(e,t,i):h(0,t),hover:(e,t)=>{var s;return null===(s=null==o?void 0:o.hover)||void 0===s?void 0:s.call(o,e,t,i)},leave:(e,t)=>{var s;return null===(s=null==o?void 0:o.leave)||void 0===s?void 0:s.call(o,e,t,i)}})}_=!1,a.hasExtendedAttrs()&&a.extended.urlId?(d=t,l=a.extended.urlId):(d=-1,l=-1)}}t(r)}};function h(e,t){if(confirm(`Do you want to navigate to ${t}?\n\nWARNING: This link could potentially be dangerous`)){const e=window.open();if(e){try{e.opener=null}catch(e){}e.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}}t.OscLinkProvider=a=s([r(0,o.IBufferService),r(1,o.IOptionsService),r(2,o.IOscLinkService)],a)},6193:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.RenderDebouncer=void 0,t.RenderDebouncer=class{constructor(e,t){this._parentWindow=e,this._renderCallback=t,this._refreshCallbacks=[]}dispose(){this._animationFrame&&(this._parentWindow.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}addRefreshCallback(e){return this._refreshCallbacks.push(e),this._animationFrame||(this._animationFrame=this._parentWindow.requestAnimationFrame((()=>this._innerRefresh()))),this._animationFrame}refresh(e,t,i){this._rowCount=i,e=void 0!==e?e:0,t=void 0!==t?t:this._rowCount-1,this._rowStart=void 0!==this._rowStart?Math.min(this._rowStart,e):e,this._rowEnd=void 0!==this._rowEnd?Math.max(this._rowEnd,t):t,this._animationFrame||(this._animationFrame=this._parentWindow.requestAnimationFrame((()=>this._innerRefresh())))}_innerRefresh(){if(this._animationFrame=void 0,void 0===this._rowStart||void 0===this._rowEnd||void 0===this._rowCount)return void this._runRefreshCallbacks();const e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(const e of this._refreshCallbacks)e(0);this._refreshCallbacks=[]}}},5596:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ScreenDprMonitor=void 0;const s=i(844);class r extends s.Disposable{constructor(e){super(),this._parentWindow=e,this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this.register((0,s.toDisposable)((()=>{this.clearListener()})))}setListener(e){this._listener&&this.clearListener(),this._listener=e,this._outerListener=()=>{this._listener&&(this._listener(this._parentWindow.devicePixelRatio,this._currentDevicePixelRatio),this._updateDpr())},this._updateDpr()}_updateDpr(){var e;this._outerListener&&(null===(e=this._resolutionMediaMatchList)||void 0===e||e.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){this._resolutionMediaMatchList&&this._listener&&this._outerListener&&(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._listener=void 0,this._outerListener=void 0)}}t.ScreenDprMonitor=r},3236:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Terminal=void 0;const s=i(3614),r=i(3656),n=i(6465),o=i(9042),a=i(3730),h=i(1680),c=i(3107),l=i(5744),d=i(2950),_=i(1296),u=i(428),f=i(4269),v=i(5114),p=i(8934),g=i(3230),m=i(9312),S=i(4725),C=i(6731),b=i(8055),y=i(8969),w=i(8460),E=i(844),k=i(6114),L=i(8437),D=i(2584),R=i(7399),x=i(5941),A=i(9074),B=i(2585),T=i(5435),M=i(4567),O="undefined"!=typeof window?window.document:null;class P extends y.CoreTerminal{get onFocus(){return this._onFocus.event}get onBlur(){return this._onBlur.event}get onA11yChar(){return this._onA11yCharEmitter.event}get onA11yTab(){return this._onA11yTabEmitter.event}get onWillOpen(){return this._onWillOpen.event}constructor(e={}){super(e),this.browser=k,this._keyDownHandled=!1,this._keyDownSeen=!1,this._keyPressHandled=!1,this._unprocessedDeadKey=!1,this._accessibilityManager=this.register(new E.MutableDisposable),this._onCursorMove=this.register(new w.EventEmitter),this.onCursorMove=this._onCursorMove.event,this._onKey=this.register(new w.EventEmitter),this.onKey=this._onKey.event,this._onRender=this.register(new w.EventEmitter),this.onRender=this._onRender.event,this._onSelectionChange=this.register(new w.EventEmitter),this.onSelectionChange=this._onSelectionChange.event,this._onTitleChange=this.register(new w.EventEmitter),this.onTitleChange=this._onTitleChange.event,this._onBell=this.register(new w.EventEmitter),this.onBell=this._onBell.event,this._onFocus=this.register(new w.EventEmitter),this._onBlur=this.register(new w.EventEmitter),this._onA11yCharEmitter=this.register(new w.EventEmitter),this._onA11yTabEmitter=this.register(new w.EventEmitter),this._onWillOpen=this.register(new w.EventEmitter),this._setup(),this.linkifier2=this.register(this._instantiationService.createInstance(n.Linkifier2)),this.linkifier2.registerLinkProvider(this._instantiationService.createInstance(a.OscLinkProvider)),this._decorationService=this._instantiationService.createInstance(A.DecorationService),this._instantiationService.setService(B.IDecorationService,this._decorationService),this.register(this._inputHandler.onRequestBell((()=>this._onBell.fire()))),this.register(this._inputHandler.onRequestRefreshRows(((e,t)=>this.refresh(e,t)))),this.register(this._inputHandler.onRequestSendFocus((()=>this._reportFocus()))),this.register(this._inputHandler.onRequestReset((()=>this.reset()))),this.register(this._inputHandler.onRequestWindowsOptionsReport((e=>this._reportWindowsOptions(e)))),this.register(this._inputHandler.onColor((e=>this._handleColorEvent(e)))),this.register((0,w.forwardEvent)(this._inputHandler.onCursorMove,this._onCursorMove)),this.register((0,w.forwardEvent)(this._inputHandler.onTitleChange,this._onTitleChange)),this.register((0,w.forwardEvent)(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this.register((0,w.forwardEvent)(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this.register(this._bufferService.onResize((e=>this._afterResize(e.cols,e.rows)))),this.register((0,E.toDisposable)((()=>{var e,t;this._customKeyEventHandler=void 0,null===(t=null===(e=this.element)||void 0===e?void 0:e.parentNode)||void 0===t||t.removeChild(this.element)})))}_handleColorEvent(e){if(this._themeService)for(const t of e){let e,i="";switch(t.index){case 256:e="foreground",i="10";break;case 257:e="background",i="11";break;case 258:e="cursor",i="12";break;default:e="ansi",i="4;"+t.index}switch(t.type){case 0:const s=b.color.toColorRGB("ansi"===e?this._themeService.colors.ansi[t.index]:this._themeService.colors[e]);this.coreService.triggerDataEvent(`${D.C0.ESC}]${i};${(0,x.toRgbString)(s)}${D.C1_ESCAPED.ST}`);break;case 1:if("ansi"===e)this._themeService.modifyColors((e=>e.ansi[t.index]=b.rgba.toColor(...t.color)));else{const i=e;this._themeService.modifyColors((e=>e[i]=b.rgba.toColor(...t.color)))}break;case 2:this._themeService.restoreColor(t.index)}}}_setup(){super._setup(),this._customKeyEventHandler=void 0}get buffer(){return this.buffers.active}focus(){this.textarea&&this.textarea.focus({preventScroll:!0})}_handleScreenReaderModeOptionChange(e){e?!this._accessibilityManager.value&&this._renderService&&(this._accessibilityManager.value=this._instantiationService.createInstance(M.AccessibilityManager,this)):this._accessibilityManager.clear()}_handleTextAreaFocus(e){this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(D.C0.ESC+"[I"),this.updateCursorStyle(e),this.element.classList.add("focus"),this._showCursor(),this._onFocus.fire()}blur(){var e;return null===(e=this.textarea)||void 0===e?void 0:e.blur()}_handleTextAreaBlur(){this.textarea.value="",this.refresh(this.buffer.y,this.buffer.y),this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent(D.C0.ESC+"[O"),this.element.classList.remove("focus"),this._onBlur.fire()}_syncTextArea(){if(!this.textarea||!this.buffer.isCursorInViewport||this._compositionHelper.isComposing||!this._renderService)return;const e=this.buffer.ybase+this.buffer.y,t=this.buffer.lines.get(e);if(!t)return;const i=Math.min(this.buffer.x,this.cols-1),s=this._renderService.dimensions.css.cell.height,r=t.getWidth(i),n=this._renderService.dimensions.css.cell.width*r,o=this.buffer.y*this._renderService.dimensions.css.cell.height,a=i*this._renderService.dimensions.css.cell.width;this.textarea.style.left=a+"px",this.textarea.style.top=o+"px",this.textarea.style.width=n+"px",this.textarea.style.height=s+"px",this.textarea.style.lineHeight=s+"px",this.textarea.style.zIndex="-5"}_initGlobal(){this._bindKeys(),this.register((0,r.addDisposableDomListener)(this.element,"copy",(e=>{this.hasSelection()&&(0,s.copyHandler)(e,this._selectionService)})));const e=e=>(0,s.handlePasteEvent)(e,this.textarea,this.coreService,this.optionsService);this.register((0,r.addDisposableDomListener)(this.textarea,"paste",e)),this.register((0,r.addDisposableDomListener)(this.element,"paste",e)),k.isFirefox?this.register((0,r.addDisposableDomListener)(this.element,"mousedown",(e=>{2===e.button&&(0,s.rightClickHandler)(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)}))):this.register((0,r.addDisposableDomListener)(this.element,"contextmenu",(e=>{(0,s.rightClickHandler)(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)}))),k.isLinux&&this.register((0,r.addDisposableDomListener)(this.element,"auxclick",(e=>{1===e.button&&(0,s.moveTextAreaUnderMouseCursor)(e,this.textarea,this.screenElement)})))}_bindKeys(){this.register((0,r.addDisposableDomListener)(this.textarea,"keyup",(e=>this._keyUp(e)),!0)),this.register((0,r.addDisposableDomListener)(this.textarea,"keydown",(e=>this._keyDown(e)),!0)),this.register((0,r.addDisposableDomListener)(this.textarea,"keypress",(e=>this._keyPress(e)),!0)),this.register((0,r.addDisposableDomListener)(this.textarea,"compositionstart",(()=>this._compositionHelper.compositionstart()))),this.register((0,r.addDisposableDomListener)(this.textarea,"compositionupdate",(e=>this._compositionHelper.compositionupdate(e)))),this.register((0,r.addDisposableDomListener)(this.textarea,"compositionend",(()=>this._compositionHelper.compositionend()))),this.register((0,r.addDisposableDomListener)(this.textarea,"input",(e=>this._inputEvent(e)),!0)),this.register(this.onRender((()=>this._compositionHelper.updateCompositionElements())))}open(e){var t;if(!e)throw new Error("Terminal requires a parent element.");e.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this._document=e.ownerDocument,this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),e.appendChild(this.element);const i=O.createDocumentFragment();this._viewportElement=O.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),i.appendChild(this._viewportElement),this._viewportScrollArea=O.createElement("div"),this._viewportScrollArea.classList.add("xterm-scroll-area"),this._viewportElement.appendChild(this._viewportScrollArea),this.screenElement=O.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._helperContainer=O.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),i.appendChild(this.screenElement),this.textarea=O.createElement("textarea"),this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",o.promptLabel),k.isChromeOS||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._coreBrowserService=this._instantiationService.createInstance(v.CoreBrowserService,this.textarea,null!==(t=this._document.defaultView)&&void 0!==t?t:window),this._instantiationService.setService(S.ICoreBrowserService,this._coreBrowserService),this.register((0,r.addDisposableDomListener)(this.textarea,"focus",(e=>this._handleTextAreaFocus(e)))),this.register((0,r.addDisposableDomListener)(this.textarea,"blur",(()=>this._handleTextAreaBlur()))),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(u.CharSizeService,this._document,this._helperContainer),this._instantiationService.setService(S.ICharSizeService,this._charSizeService),this._themeService=this._instantiationService.createInstance(C.ThemeService),this._instantiationService.setService(S.IThemeService,this._themeService),this._characterJoinerService=this._instantiationService.createInstance(f.CharacterJoinerService),this._instantiationService.setService(S.ICharacterJoinerService,this._characterJoinerService),this._renderService=this.register(this._instantiationService.createInstance(g.RenderService,this.rows,this.screenElement)),this._instantiationService.setService(S.IRenderService,this._renderService),this.register(this._renderService.onRenderedViewportChange((e=>this._onRender.fire(e)))),this.onResize((e=>this._renderService.resize(e.cols,e.rows))),this._compositionView=O.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(d.CompositionHelper,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this.element.appendChild(i);try{this._onWillOpen.fire(this.element)}catch(e){}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._mouseService=this._instantiationService.createInstance(p.MouseService),this._instantiationService.setService(S.IMouseService,this._mouseService),this.viewport=this._instantiationService.createInstance(h.Viewport,this._viewportElement,this._viewportScrollArea),this.viewport.onRequestScrollLines((e=>this.scrollLines(e.amount,e.suppressScrollEvent,1))),this.register(this._inputHandler.onRequestSyncScrollBar((()=>this.viewport.syncScrollArea()))),this.register(this.viewport),this.register(this.onCursorMove((()=>{this._renderService.handleCursorMove(),this._syncTextArea()}))),this.register(this.onResize((()=>this._renderService.handleResize(this.cols,this.rows)))),this.register(this.onBlur((()=>this._renderService.handleBlur()))),this.register(this.onFocus((()=>this._renderService.handleFocus()))),this.register(this._renderService.onDimensionsChange((()=>this.viewport.syncScrollArea()))),this._selectionService=this.register(this._instantiationService.createInstance(m.SelectionService,this.element,this.screenElement,this.linkifier2)),this._instantiationService.setService(S.ISelectionService,this._selectionService),this.register(this._selectionService.onRequestScrollLines((e=>this.scrollLines(e.amount,e.suppressScrollEvent)))),this.register(this._selectionService.onSelectionChange((()=>this._onSelectionChange.fire()))),this.register(this._selectionService.onRequestRedraw((e=>this._renderService.handleSelectionChanged(e.start,e.end,e.columnSelectMode)))),this.register(this._selectionService.onLinuxMouseSelection((e=>{this.textarea.value=e,this.textarea.focus(),this.textarea.select()}))),this.register(this._onScroll.event((e=>{this.viewport.syncScrollArea(),this._selectionService.refresh()}))),this.register((0,r.addDisposableDomListener)(this._viewportElement,"scroll",(()=>this._selectionService.refresh()))),this.linkifier2.attachToDom(this.screenElement,this._mouseService,this._renderService),this.register(this._instantiationService.createInstance(c.BufferDecorationRenderer,this.screenElement)),this.register((0,r.addDisposableDomListener)(this.element,"mousedown",(e=>this._selectionService.handleMouseDown(e)))),this.coreMouseService.areMouseEventsActive?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):this._selectionService.enable(),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(M.AccessibilityManager,this)),this.register(this.optionsService.onSpecificOptionChange("screenReaderMode",(e=>this._handleScreenReaderModeOptionChange(e)))),this.options.overviewRulerWidth&&(this._overviewRulerRenderer=this.register(this._instantiationService.createInstance(l.OverviewRulerRenderer,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("overviewRulerWidth",(e=>{!this._overviewRulerRenderer&&e&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this.register(this._instantiationService.createInstance(l.OverviewRulerRenderer,this._viewportElement,this.screenElement)))})),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this.bindMouse()}_createRenderer(){return this._instantiationService.createInstance(_.DomRenderer,this.element,this.screenElement,this._viewportElement,this.linkifier2)}bindMouse(){const e=this,t=this.element;function i(t){const i=e._mouseService.getMouseReportCoords(t,e.screenElement);if(!i)return!1;let s,r;switch(t.overrideType||t.type){case"mousemove":r=32,void 0===t.buttons?(s=3,void 0!==t.button&&(s=t.button<3?t.button:3)):s=1&t.buttons?0:4&t.buttons?1:2&t.buttons?2:3;break;case"mouseup":r=0,s=t.button<3?t.button:3;break;case"mousedown":r=1,s=t.button<3?t.button:3;break;case"wheel":if(0===e.viewport.getLinesScrolled(t))return!1;r=t.deltaY<0?0:1,s=4;break;default:return!1}return!(void 0===r||void 0===s||s>4)&&e.coreMouseService.triggerMouseEvent({col:i.col,row:i.row,x:i.x,y:i.y,button:s,action:r,ctrl:t.ctrlKey,alt:t.altKey,shift:t.shiftKey})}const s={mouseup:null,wheel:null,mousedrag:null,mousemove:null},n={mouseup:e=>(i(e),e.buttons||(this._document.removeEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.removeEventListener("mousemove",s.mousedrag)),this.cancel(e)),wheel:e=>(i(e),this.cancel(e,!0)),mousedrag:e=>{e.buttons&&i(e)},mousemove:e=>{e.buttons||i(e)}};this.register(this.coreMouseService.onProtocolChange((e=>{e?("debug"===this.optionsService.rawOptions.logLevel&&this._logService.debug("Binding to mouse events:",this.coreMouseService.explainEvents(e)),this.element.classList.add("enable-mouse-events"),this._selectionService.disable()):(this._logService.debug("Unbinding from mouse events."),this.element.classList.remove("enable-mouse-events"),this._selectionService.enable()),8&e?s.mousemove||(t.addEventListener("mousemove",n.mousemove),s.mousemove=n.mousemove):(t.removeEventListener("mousemove",s.mousemove),s.mousemove=null),16&e?s.wheel||(t.addEventListener("wheel",n.wheel,{passive:!1}),s.wheel=n.wheel):(t.removeEventListener("wheel",s.wheel),s.wheel=null),2&e?s.mouseup||(t.addEventListener("mouseup",n.mouseup),s.mouseup=n.mouseup):(this._document.removeEventListener("mouseup",s.mouseup),t.removeEventListener("mouseup",s.mouseup),s.mouseup=null),4&e?s.mousedrag||(s.mousedrag=n.mousedrag):(this._document.removeEventListener("mousemove",s.mousedrag),s.mousedrag=null)}))),this.coreMouseService.activeProtocol=this.coreMouseService.activeProtocol,this.register((0,r.addDisposableDomListener)(t,"mousedown",(e=>{if(e.preventDefault(),this.focus(),this.coreMouseService.areMouseEventsActive&&!this._selectionService.shouldForceSelection(e))return i(e),s.mouseup&&this._document.addEventListener("mouseup",s.mouseup),s.mousedrag&&this._document.addEventListener("mousemove",s.mousedrag),this.cancel(e)}))),this.register((0,r.addDisposableDomListener)(t,"wheel",(e=>{if(!s.wheel){if(!this.buffer.hasScrollback){const t=this.viewport.getLinesScrolled(e);if(0===t)return;const i=D.C0.ESC+(this.coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(e.deltaY<0?"A":"B");let s="";for(let e=0;e{if(!this.coreMouseService.areMouseEventsActive)return this.viewport.handleTouchStart(e),this.cancel(e)}),{passive:!0})),this.register((0,r.addDisposableDomListener)(t,"touchmove",(e=>{if(!this.coreMouseService.areMouseEventsActive)return this.viewport.handleTouchMove(e)?void 0:this.cancel(e)}),{passive:!1}))}refresh(e,t){var i;null===(i=this._renderService)||void 0===i||i.refreshRows(e,t)}updateCursorStyle(e){var t;(null===(t=this._selectionService)||void 0===t?void 0:t.shouldColumnSelect(e))?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t,i=0){var s;1===i?(super.scrollLines(e,t,i),this.refresh(0,this.rows-1)):null===(s=this.viewport)||void 0===s||s.scrollLines(e)}paste(e){(0,s.paste)(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}registerLinkProvider(e){return this.linkifier2.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");const t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return!!this._selectionService&&this._selectionService.hasSelection}select(e,t,i){this._selectionService.setSelection(e,t,i)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(this._selectionService&&this._selectionService.hasSelection)return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){var e;null===(e=this._selectionService)||void 0===e||e.clearSelection()}selectAll(){var e;null===(e=this._selectionService)||void 0===e||e.selectAll()}selectLines(e,t){var i;null===(i=this._selectionService)||void 0===i||i.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;const t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(),!1;t||"Dead"!==e.key&&"AltGraph"!==e.key||(this._unprocessedDeadKey=!0);const i=(0,R.evaluateKeyboardEvent)(e,this.coreService.decPrivateModes.applicationCursorKeys,this.browser.isMac,this.options.macOptionIsMeta);if(this.updateCursorStyle(e),3===i.type||2===i.type){const t=this.rows-1;return this.scrollLines(2===i.type?-t:t),this.cancel(e,!0)}return 1===i.type&&this.selectAll(),!!this._isThirdLevelShift(this.browser,e)||(i.cancel&&this.cancel(e,!0),!i.key||!!(e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&1===e.key.length&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)||(this._unprocessedDeadKey?(this._unprocessedDeadKey=!1,!0):(i.key!==D.C0.ETX&&i.key!==D.C0.CR||(this.textarea.value=""),this._onKey.fire({key:i.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(i.key,!0),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey?this.cancel(e,!0):void(this._keyDownHandled=!0))))}_isThirdLevelShift(e,t){const i=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState("AltGraph");return"keypress"===t.type?i:i&&(!t.keyCode||t.keyCode>47)}_keyUp(e){this._keyDownSeen=!1,this._customKeyEventHandler&&!1===this._customKeyEventHandler(e)||(function(e){return 16===e.keyCode||17===e.keyCode||18===e.keyCode}(e)||this.focus(),this.updateCursorStyle(e),this._keyPressHandled=!1)}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled)return!1;if(this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;if(this.cancel(e),e.charCode)t=e.charCode;else if(null===e.which||void 0===e.which)t=e.keyCode;else{if(0===e.which||0===e.charCode)return!1;t=e.which}return!(!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)||(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,0))}_inputEvent(e){if(e.data&&"insertText"===e.inputType&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;const t=e.data;return this.coreService.triggerDataEvent(t,!0),this.cancel(e),!0}return!1}resize(e,t){e!==this.cols||t!==this.rows?super.resize(e,t):this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure()}_afterResize(e,t){var i,s;null===(i=this._charSizeService)||void 0===i||i.measure(),null===(s=this.viewport)||void 0===s||s.syncScrollArea(!0)}clear(){var e;if(0!==this.buffer.ybase||0!==this.buffer.y){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;e{Object.defineProperty(t,"__esModule",{value:!0}),t.TimeBasedDebouncer=void 0,t.TimeBasedDebouncer=class{constructor(e,t=1e3){this._renderCallback=e,this._debounceThresholdMS=t,this._lastRefreshMs=0,this._additionalRefreshRequested=!1}dispose(){this._refreshTimeoutID&&clearTimeout(this._refreshTimeoutID)}refresh(e,t,i){this._rowCount=i,e=void 0!==e?e:0,t=void 0!==t?t:this._rowCount-1,this._rowStart=void 0!==this._rowStart?Math.min(this._rowStart,e):e,this._rowEnd=void 0!==this._rowEnd?Math.max(this._rowEnd,t):t;const s=Date.now();if(s-this._lastRefreshMs>=this._debounceThresholdMS)this._lastRefreshMs=s,this._innerRefresh();else if(!this._additionalRefreshRequested){const e=s-this._lastRefreshMs,t=this._debounceThresholdMS-e;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout((()=>{this._lastRefreshMs=Date.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0}),t)}}_innerRefresh(){if(void 0===this._rowStart||void 0===this._rowEnd||void 0===this._rowCount)return;const e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t)}}},1680:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Viewport=void 0;const n=i(3656),o=i(4725),a=i(8460),h=i(844),c=i(2585);let l=t.Viewport=class extends h.Disposable{constructor(e,t,i,s,r,o,h,c){super(),this._viewportElement=e,this._scrollArea=t,this._bufferService=i,this._optionsService=s,this._charSizeService=r,this._renderService=o,this._coreBrowserService=h,this.scrollBarWidth=0,this._currentRowHeight=0,this._currentDeviceCellHeight=0,this._lastRecordedBufferLength=0,this._lastRecordedViewportHeight=0,this._lastRecordedBufferHeight=0,this._lastTouchY=0,this._lastScrollTop=0,this._wheelPartialScroll=0,this._refreshAnimationFrame=null,this._ignoreNextScrollEvent=!1,this._smoothScrollState={startTime:0,origin:-1,target:-1},this._onRequestScrollLines=this.register(new a.EventEmitter),this.onRequestScrollLines=this._onRequestScrollLines.event,this.scrollBarWidth=this._viewportElement.offsetWidth-this._scrollArea.offsetWidth||15,this.register((0,n.addDisposableDomListener)(this._viewportElement,"scroll",this._handleScroll.bind(this))),this._activeBuffer=this._bufferService.buffer,this.register(this._bufferService.buffers.onBufferActivate((e=>this._activeBuffer=e.activeBuffer))),this._renderDimensions=this._renderService.dimensions,this.register(this._renderService.onDimensionsChange((e=>this._renderDimensions=e))),this._handleThemeChange(c.colors),this.register(c.onChangeColors((e=>this._handleThemeChange(e)))),this.register(this._optionsService.onSpecificOptionChange("scrollback",(()=>this.syncScrollArea()))),setTimeout((()=>this.syncScrollArea()))}_handleThemeChange(e){this._viewportElement.style.backgroundColor=e.background.css}reset(){this._currentRowHeight=0,this._currentDeviceCellHeight=0,this._lastRecordedBufferLength=0,this._lastRecordedViewportHeight=0,this._lastRecordedBufferHeight=0,this._lastTouchY=0,this._lastScrollTop=0,this._coreBrowserService.window.requestAnimationFrame((()=>this.syncScrollArea()))}_refresh(e){if(e)return this._innerRefresh(),void(null!==this._refreshAnimationFrame&&this._coreBrowserService.window.cancelAnimationFrame(this._refreshAnimationFrame));null===this._refreshAnimationFrame&&(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>this._innerRefresh())))}_innerRefresh(){if(this._charSizeService.height>0){this._currentRowHeight=this._renderService.dimensions.device.cell.height/this._coreBrowserService.dpr,this._currentDeviceCellHeight=this._renderService.dimensions.device.cell.height,this._lastRecordedViewportHeight=this._viewportElement.offsetHeight;const e=Math.round(this._currentRowHeight*this._lastRecordedBufferLength)+(this._lastRecordedViewportHeight-this._renderService.dimensions.css.canvas.height);this._lastRecordedBufferHeight!==e&&(this._lastRecordedBufferHeight=e,this._scrollArea.style.height=this._lastRecordedBufferHeight+"px")}const e=this._bufferService.buffer.ydisp*this._currentRowHeight;this._viewportElement.scrollTop!==e&&(this._ignoreNextScrollEvent=!0,this._viewportElement.scrollTop=e),this._refreshAnimationFrame=null}syncScrollArea(e=!1){if(this._lastRecordedBufferLength!==this._bufferService.buffer.lines.length)return this._lastRecordedBufferLength=this._bufferService.buffer.lines.length,void this._refresh(e);this._lastRecordedViewportHeight===this._renderService.dimensions.css.canvas.height&&this._lastScrollTop===this._activeBuffer.ydisp*this._currentRowHeight&&this._renderDimensions.device.cell.height===this._currentDeviceCellHeight||this._refresh(e)}_handleScroll(e){if(this._lastScrollTop=this._viewportElement.scrollTop,!this._viewportElement.offsetParent)return;if(this._ignoreNextScrollEvent)return this._ignoreNextScrollEvent=!1,void this._onRequestScrollLines.fire({amount:0,suppressScrollEvent:!0});const t=Math.round(this._lastScrollTop/this._currentRowHeight)-this._bufferService.buffer.ydisp;this._onRequestScrollLines.fire({amount:t,suppressScrollEvent:!0})}_smoothScroll(){if(this._isDisposed||-1===this._smoothScrollState.origin||-1===this._smoothScrollState.target)return;const e=this._smoothScrollPercent();this._viewportElement.scrollTop=this._smoothScrollState.origin+Math.round(e*(this._smoothScrollState.target-this._smoothScrollState.origin)),e<1?this._coreBrowserService.window.requestAnimationFrame((()=>this._smoothScroll())):this._clearSmoothScrollState()}_smoothScrollPercent(){return this._optionsService.rawOptions.smoothScrollDuration&&this._smoothScrollState.startTime?Math.max(Math.min((Date.now()-this._smoothScrollState.startTime)/this._optionsService.rawOptions.smoothScrollDuration,1),0):1}_clearSmoothScrollState(){this._smoothScrollState.startTime=0,this._smoothScrollState.origin=-1,this._smoothScrollState.target=-1}_bubbleScroll(e,t){const i=this._viewportElement.scrollTop+this._lastRecordedViewportHeight;return!(t<0&&0!==this._viewportElement.scrollTop||t>0&&i0&&(s=e),r=""}}return{bufferElements:n,cursorElement:s}}getLinesScrolled(e){if(0===e.deltaY||e.shiftKey)return 0;let t=this._applyScrollModifier(e.deltaY,e);return e.deltaMode===WheelEvent.DOM_DELTA_PIXEL?(t/=this._currentRowHeight+0,this._wheelPartialScroll+=t,t=Math.floor(Math.abs(this._wheelPartialScroll))*(this._wheelPartialScroll>0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(t*=this._bufferService.rows),t}_applyScrollModifier(e,t){const i=this._optionsService.rawOptions.fastScrollModifier;return"alt"===i&&t.altKey||"ctrl"===i&&t.ctrlKey||"shift"===i&&t.shiftKey?e*this._optionsService.rawOptions.fastScrollSensitivity*this._optionsService.rawOptions.scrollSensitivity:e*this._optionsService.rawOptions.scrollSensitivity}handleTouchStart(e){this._lastTouchY=e.touches[0].pageY}handleTouchMove(e){const t=this._lastTouchY-e.touches[0].pageY;return this._lastTouchY=e.touches[0].pageY,0!==t&&(this._viewportElement.scrollTop+=t,this._bubbleScroll(e,t))}};t.Viewport=l=s([r(2,c.IBufferService),r(3,c.IOptionsService),r(4,o.ICharSizeService),r(5,o.IRenderService),r(6,o.ICoreBrowserService),r(7,o.IThemeService)],l)},3107:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.BufferDecorationRenderer=void 0;const n=i(3656),o=i(4725),a=i(844),h=i(2585);let c=t.BufferDecorationRenderer=class extends a.Disposable{constructor(e,t,i,s){super(),this._screenElement=e,this._bufferService=t,this._decorationService=i,this._renderService=s,this._decorationElements=new Map,this._altBufferIsActive=!1,this._dimensionsChanged=!1,this._container=document.createElement("div"),this._container.classList.add("xterm-decoration-container"),this._screenElement.appendChild(this._container),this.register(this._renderService.onRenderedViewportChange((()=>this._doRefreshDecorations()))),this.register(this._renderService.onDimensionsChange((()=>{this._dimensionsChanged=!0,this._queueRefresh()}))),this.register((0,n.addDisposableDomListener)(window,"resize",(()=>this._queueRefresh()))),this.register(this._bufferService.buffers.onBufferActivate((()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt}))),this.register(this._decorationService.onDecorationRegistered((()=>this._queueRefresh()))),this.register(this._decorationService.onDecorationRemoved((e=>this._removeDecoration(e)))),this.register((0,a.toDisposable)((()=>{this._container.remove(),this._decorationElements.clear()})))}_queueRefresh(){void 0===this._animationFrame&&(this._animationFrame=this._renderService.addRefreshCallback((()=>{this._doRefreshDecorations(),this._animationFrame=void 0})))}_doRefreshDecorations(){for(const e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){var t,i;const s=document.createElement("div");s.classList.add("xterm-decoration"),s.classList.toggle("xterm-decoration-top-layer","top"===(null===(t=null==e?void 0:e.options)||void 0===t?void 0:t.layer)),s.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,s.style.height=(e.options.height||1)*this._renderService.dimensions.css.cell.height+"px",s.style.top=(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height+"px",s.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;const r=null!==(i=e.options.x)&&void 0!==i?i:0;return r&&r>this._bufferService.cols&&(s.style.display="none"),this._refreshXPosition(e,s),s}_refreshStyle(e){const t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display="none",e.onRenderEmitter.fire(e.element));else{let i=this._decorationElements.get(e);i||(i=this._createElement(e),e.element=i,this._decorationElements.set(e,i),this._container.appendChild(i),e.onDispose((()=>{this._decorationElements.delete(e),i.remove()}))),i.style.top=t*this._renderService.dimensions.css.cell.height+"px",i.style.display=this._altBufferIsActive?"none":"block",e.onRenderEmitter.fire(i)}}_refreshXPosition(e,t=e.element){var i;if(!t)return;const s=null!==(i=e.options.x)&&void 0!==i?i:0;"right"===(e.options.anchor||"left")?t.style.right=s?s*this._renderService.dimensions.css.cell.width+"px":"":t.style.left=s?s*this._renderService.dimensions.css.cell.width+"px":""}_removeDecoration(e){var t;null===(t=this._decorationElements.get(e))||void 0===t||t.remove(),this._decorationElements.delete(e),e.dispose()}};t.BufferDecorationRenderer=c=s([r(1,h.IBufferService),r(2,h.IDecorationService),r(3,o.IRenderService)],c)},5871:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ColorZoneStore=void 0,t.ColorZoneStore=class{constructor(){this._zones=[],this._zonePool=[],this._zonePoolIndex=0,this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(e){if(e.options.overviewRulerOptions){for(const t of this._zones)if(t.color===e.options.overviewRulerOptions.color&&t.position===e.options.overviewRulerOptions.position){if(this._lineIntersectsZone(t,e.marker.line))return;if(this._lineAdjacentToZone(t,e.marker.line,e.options.overviewRulerOptions.position))return void this._addLineToZone(t,e.marker.line)}if(this._zonePoolIndex=e.startBufferLine&&t<=e.endBufferLine}_lineAdjacentToZone(e,t,i){return t>=e.startBufferLine-this._linePadding[i||"full"]&&t<=e.endBufferLine+this._linePadding[i||"full"]}_addLineToZone(e,t){e.startBufferLine=Math.min(e.startBufferLine,t),e.endBufferLine=Math.max(e.endBufferLine,t)}}},5744:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OverviewRulerRenderer=void 0;const n=i(5871),o=i(3656),a=i(4725),h=i(844),c=i(2585),l={full:0,left:0,center:0,right:0},d={full:0,left:0,center:0,right:0},_={full:0,left:0,center:0,right:0};let u=t.OverviewRulerRenderer=class extends h.Disposable{get _width(){return this._optionsService.options.overviewRulerWidth||0}constructor(e,t,i,s,r,o,a){var c;super(),this._viewportElement=e,this._screenElement=t,this._bufferService=i,this._decorationService=s,this._renderService=r,this._optionsService=o,this._coreBrowseService=a,this._colorZoneStore=new n.ColorZoneStore,this._shouldUpdateDimensions=!0,this._shouldUpdateAnchor=!0,this._lastKnownBufferLength=0,this._canvas=document.createElement("canvas"),this._canvas.classList.add("xterm-decoration-overview-ruler"),this._refreshCanvasDimensions(),null===(c=this._viewportElement.parentElement)||void 0===c||c.insertBefore(this._canvas,this._viewportElement);const l=this._canvas.getContext("2d");if(!l)throw new Error("Ctx cannot be null");this._ctx=l,this._registerDecorationListeners(),this._registerBufferChangeListeners(),this._registerDimensionChangeListeners(),this.register((0,h.toDisposable)((()=>{var e;null===(e=this._canvas)||void 0===e||e.remove()})))}_registerDecorationListeners(){this.register(this._decorationService.onDecorationRegistered((()=>this._queueRefresh(void 0,!0)))),this.register(this._decorationService.onDecorationRemoved((()=>this._queueRefresh(void 0,!0))))}_registerBufferChangeListeners(){this.register(this._renderService.onRenderedViewportChange((()=>this._queueRefresh()))),this.register(this._bufferService.buffers.onBufferActivate((()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?"none":"block"}))),this.register(this._bufferService.onScroll((()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())})))}_registerDimensionChangeListeners(){this.register(this._renderService.onRender((()=>{this._containerHeight&&this._containerHeight===this._screenElement.clientHeight||(this._queueRefresh(!0),this._containerHeight=this._screenElement.clientHeight)}))),this.register(this._optionsService.onSpecificOptionChange("overviewRulerWidth",(()=>this._queueRefresh(!0)))),this.register((0,o.addDisposableDomListener)(this._coreBrowseService.window,"resize",(()=>this._queueRefresh(!0)))),this._queueRefresh(!0)}_refreshDrawConstants(){const e=Math.floor(this._canvas.width/3),t=Math.ceil(this._canvas.width/3);d.full=this._canvas.width,d.left=e,d.center=t,d.right=e,this._refreshDrawHeightConstants(),_.full=0,_.left=0,_.center=d.left,_.right=d.left+d.center}_refreshDrawHeightConstants(){l.full=Math.round(2*this._coreBrowseService.dpr);const e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowseService.dpr);l.left=t,l.center=t,l.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowseService.dpr),this._canvas.style.height=`${this._screenElement.clientHeight}px`,this._canvas.height=Math.round(this._screenElement.clientHeight*this._coreBrowseService.dpr),this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(const e of this._decorationService.decorations)this._colorZoneStore.addDecoration(e);this._ctx.lineWidth=1;const e=this._colorZoneStore.zones;for(const t of e)"full"!==t.position&&this._renderColorZone(t);for(const t of e)"full"===t.position&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect(_[e.position||"full"],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-l[e.position||"full"]/2),d[e.position||"full"],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+l[e.position||"full"]))}_queueRefresh(e,t){this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,void 0===this._animationFrame&&(this._animationFrame=this._coreBrowseService.window.requestAnimationFrame((()=>{this._refreshDecorations(),this._animationFrame=void 0})))}};t.OverviewRulerRenderer=u=s([r(2,c.IBufferService),r(3,c.IDecorationService),r(4,a.IRenderService),r(5,c.IOptionsService),r(6,a.ICoreBrowserService)],u)},2950:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CompositionHelper=void 0;const n=i(4725),o=i(2585),a=i(2584);let h=t.CompositionHelper=class{get isComposing(){return this._isComposing}constructor(e,t,i,s,r,n){this._textarea=e,this._compositionView=t,this._bufferService=i,this._optionsService=s,this._coreService=r,this._renderService=n,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._dataAlreadySent=""}compositionstart(){this._isComposing=!0,this._compositionPosition.start=this._textarea.value.length,this._compositionView.textContent="",this._dataAlreadySent="",this._compositionView.classList.add("active")}compositionupdate(e){this._compositionView.textContent=e.data,this.updateCompositionElements(),setTimeout((()=>{this._compositionPosition.end=this._textarea.value.length}),0)}compositionend(){this._finalizeComposition(!0)}keydown(e){if(this._isComposing||this._isSendingComposition){if(229===e.keyCode)return!1;if(16===e.keyCode||17===e.keyCode||18===e.keyCode)return!1;this._finalizeComposition(!1)}return 229!==e.keyCode||(this._handleAnyTextareaChanges(),!1)}_finalizeComposition(e){if(this._compositionView.classList.remove("active"),this._isComposing=!1,e){const e={start:this._compositionPosition.start,end:this._compositionPosition.end};this._isSendingComposition=!0,setTimeout((()=>{if(this._isSendingComposition){let t;this._isSendingComposition=!1,e.start+=this._dataAlreadySent.length,t=this._isComposing?this._textarea.value.substring(e.start,e.end):this._textarea.value.substring(e.start),t.length>0&&this._coreService.triggerDataEvent(t,!0)}}),0)}else{this._isSendingComposition=!1;const e=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(e,!0)}}_handleAnyTextareaChanges(){const e=this._textarea.value;setTimeout((()=>{if(!this._isComposing){const t=this._textarea.value,i=t.replace(e,"");this._dataAlreadySent=i,t.length>e.length?this._coreService.triggerDataEvent(i,!0):t.lengththis.updateCompositionElements(!0)),0)}}};t.CompositionHelper=h=s([r(2,o.IBufferService),r(3,o.IOptionsService),r(4,o.ICoreService),r(5,n.IRenderService)],h)},9806:(e,t)=>{function i(e,t,i){const s=i.getBoundingClientRect(),r=e.getComputedStyle(i),n=parseInt(r.getPropertyValue("padding-left")),o=parseInt(r.getPropertyValue("padding-top"));return[t.clientX-s.left-n,t.clientY-s.top-o]}Object.defineProperty(t,"__esModule",{value:!0}),t.getCoords=t.getCoordsRelativeToElement=void 0,t.getCoordsRelativeToElement=i,t.getCoords=function(e,t,s,r,n,o,a,h,c){if(!o)return;const l=i(e,t,s);return l?(l[0]=Math.ceil((l[0]+(c?a/2:0))/a),l[1]=Math.ceil(l[1]/h),l[0]=Math.min(Math.max(l[0],1),r+(c?1:0)),l[1]=Math.min(Math.max(l[1],1),n),l):void 0}},9504:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.moveToCellSequence=void 0;const s=i(2584);function r(e,t,i,s){const r=e-n(e,i),a=t-n(t,i),l=Math.abs(r-a)-function(e,t,i){let s=0;const r=e-n(e,i),a=t-n(t,i);for(let n=0;n=0&&et?"A":"B"}function a(e,t,i,s,r,n){let o=e,a=t,h="";for(;o!==i||a!==s;)o+=r?1:-1,r&&o>n.cols-1?(h+=n.buffer.translateBufferLineToString(a,!1,e,o),o=0,e=0,a++):!r&&o<0&&(h+=n.buffer.translateBufferLineToString(a,!1,0,e+1),o=n.cols-1,e=o,a--);return h+n.buffer.translateBufferLineToString(a,!1,e,o)}function h(e,t){const i=t?"O":"[";return s.C0.ESC+i+e}function c(e,t){e=Math.floor(e);let i="";for(let s=0;s0?s-n(s,o):t;const _=s,u=function(e,t,i,s,o,a){let h;return h=r(i,s,o,a).length>0?s-n(s,o):t,e=i&&he?"D":"C",c(Math.abs(o-e),h(d,s));d=l>t?"D":"C";const _=Math.abs(l-t);return c(function(e,t){return t.cols-e}(l>t?e:o,i)+(_-1)*i.cols+1+((l>t?o:e)-1),h(d,s))}},1296:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DomRenderer=void 0;const n=i(3787),o=i(2550),a=i(2223),h=i(6171),c=i(4725),l=i(8055),d=i(8460),_=i(844),u=i(2585),f="xterm-dom-renderer-owner-",v="xterm-rows",p="xterm-fg-",g="xterm-bg-",m="xterm-focus",S="xterm-selection";let C=1,b=t.DomRenderer=class extends _.Disposable{constructor(e,t,i,s,r,a,c,l,u,p){super(),this._element=e,this._screenElement=t,this._viewportElement=i,this._linkifier2=s,this._charSizeService=a,this._optionsService=c,this._bufferService=l,this._coreBrowserService=u,this._themeService=p,this._terminalClass=C++,this._rowElements=[],this.onRequestRedraw=this.register(new d.EventEmitter).event,this._rowContainer=document.createElement("div"),this._rowContainer.classList.add(v),this._rowContainer.style.lineHeight="normal",this._rowContainer.setAttribute("aria-hidden","true"),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=document.createElement("div"),this._selectionContainer.classList.add(S),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=(0,h.createRenderDimensions)(),this._updateDimensions(),this.register(this._optionsService.onOptionChange((()=>this._handleOptionsChanged()))),this.register(this._themeService.onChangeColors((e=>this._injectCss(e)))),this._injectCss(this._themeService.colors),this._rowFactory=r.createInstance(n.DomRendererRowFactory,document),this._element.classList.add(f+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this.register(this._linkifier2.onShowLinkUnderline((e=>this._handleLinkHover(e)))),this.register(this._linkifier2.onHideLinkUnderline((e=>this._handleLinkLeave(e)))),this.register((0,_.toDisposable)((()=>{this._element.classList.remove(f+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()}))),this._widthCache=new o.WidthCache(document),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){const e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(const e of this._rowElements)e.style.width=`${this.dimensions.css.canvas.width}px`,e.style.height=`${this.dimensions.css.cell.height}px`,e.style.lineHeight=`${this.dimensions.css.cell.height}px`,e.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));const t=`${this._terminalSelector} .${v} span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=document.createElement("style"),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .${v} { color: ${e.foreground.css}; font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`;t+=`${this._terminalSelector} .${v} .xterm-dim { color: ${l.color.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}`,t+="@keyframes blink_box_shadow_"+this._terminalClass+" { 50% { border-bottom-style: hidden; }}",t+="@keyframes blink_block_"+this._terminalClass+" { 0% {"+` background-color: ${e.cursor.css};`+` color: ${e.cursorAccent.css}; } 50% { background-color: inherit;`+` color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .${v}.${m} .xterm-cursor.xterm-cursor-blink:not(.xterm-cursor-block) { animation: blink_box_shadow_`+this._terminalClass+" 1s step-end infinite;}"+`${this._terminalSelector} .${v}.${m} .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: blink_block_`+this._terminalClass+" 1s step-end infinite;}"+`${this._terminalSelector} .${v} .xterm-cursor.xterm-cursor-block {`+` background-color: ${e.cursor.css};`+` color: ${e.cursorAccent.css};}`+`${this._terminalSelector} .${v} .xterm-cursor.xterm-cursor-outline {`+` outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}`+`${this._terminalSelector} .${v} .xterm-cursor.xterm-cursor-bar {`+` box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}`+`${this._terminalSelector} .${v} .xterm-cursor.xterm-cursor-underline {`+` border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .${S} { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .${S} div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .${S} div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(const[i,s]of e.ansi.entries())t+=`${this._terminalSelector} .${p}${i} { color: ${s.css}; }${this._terminalSelector} .${p}${i}.xterm-dim { color: ${l.color.multiplyOpacity(s,.5).css}; }${this._terminalSelector} .${g}${i} { background-color: ${s.css}; }`;t+=`${this._terminalSelector} .${p}${a.INVERTED_DEFAULT_COLOR} { color: ${l.color.opaque(e.background).css}; }${this._terminalSelector} .${p}${a.INVERTED_DEFAULT_COLOR}.xterm-dim { color: ${l.color.multiplyOpacity(l.color.opaque(e.background),.5).css}; }${this._terminalSelector} .${g}${a.INVERTED_DEFAULT_COLOR} { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){const e=this.dimensions.css.cell.width-this._widthCache.get("W",!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let e=this._rowElements.length;e<=t;e++){const e=document.createElement("div");this._rowContainer.appendChild(e),this._rowElements.push(e)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop())}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions()}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove(m)}handleFocus(){this._rowContainer.classList.add(m),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleSelectionChanged(e,t,i){if(this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,i),this.renderRows(0,this._bufferService.rows-1),!e||!t)return;const s=e[1]-this._bufferService.buffer.ydisp,r=t[1]-this._bufferService.buffer.ydisp,n=Math.max(s,0),o=Math.min(r,this._bufferService.rows-1);if(n>=this._bufferService.rows||o<0)return;const a=document.createDocumentFragment();if(i){const i=e[0]>t[0];a.appendChild(this._createSelectionElement(n,i?t[0]:e[0],i?e[0]:t[0],o-n+1))}else{const i=s===n?e[0]:0,h=n===r?t[0]:this._bufferService.cols;a.appendChild(this._createSelectionElement(n,i,h));const c=o-n-1;if(a.appendChild(this._createSelectionElement(n+1,0,this._bufferService.cols,c)),n!==o){const e=r===o?t[0]:this._bufferService.cols;a.appendChild(this._createSelectionElement(o,0,e))}}this._selectionContainer.appendChild(a)}_createSelectionElement(e,t,i,s=1){const r=document.createElement("div");return r.style.height=s*this.dimensions.css.cell.height+"px",r.style.top=e*this.dimensions.css.cell.height+"px",r.style.left=t*this.dimensions.css.cell.width+"px",r.style.width=this.dimensions.css.cell.width*(i-t)+"px",r}handleCursorMove(){}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(const e of this._rowElements)e.replaceChildren()}renderRows(e,t){const i=this._bufferService.buffer,s=i.ybase+i.y,r=Math.min(i.x,this._bufferService.cols-1),n=this._optionsService.rawOptions.cursorBlink,o=this._optionsService.rawOptions.cursorStyle,a=this._optionsService.rawOptions.cursorInactiveStyle;for(let h=e;h<=t;h++){const e=h+i.ydisp,t=this._rowElements[h],c=i.lines.get(e);if(!t||!c)break;t.replaceChildren(...this._rowFactory.createRow(c,e,e===s,o,a,r,n,this.dimensions.css.cell.width,this._widthCache,-1,-1))}}get _terminalSelector(){return`.${f}${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,i,s,r,n){i<0&&(e=0),s<0&&(t=0);const o=this._bufferService.rows-1;i=Math.max(Math.min(i,o),0),s=Math.max(Math.min(s,o),0),r=Math.min(r,this._bufferService.cols);const a=this._bufferService.buffer,h=a.ybase+a.y,c=Math.min(a.x,r-1),l=this._optionsService.rawOptions.cursorBlink,d=this._optionsService.rawOptions.cursorStyle,_=this._optionsService.rawOptions.cursorInactiveStyle;for(let o=i;o<=s;++o){const u=o+a.ydisp,f=this._rowElements[o],v=a.lines.get(u);if(!f||!v)break;f.replaceChildren(...this._rowFactory.createRow(v,u,u===h,d,_,c,l,this.dimensions.css.cell.width,this._widthCache,n?o===i?e:0:-1,n?(o===s?t:r)-1:-1))}}};t.DomRenderer=b=s([r(4,u.IInstantiationService),r(5,c.ICharSizeService),r(6,u.IOptionsService),r(7,u.IBufferService),r(8,c.ICoreBrowserService),r(9,c.IThemeService)],b)},3787:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DomRendererRowFactory=void 0;const n=i(2223),o=i(643),a=i(511),h=i(2585),c=i(8055),l=i(4725),d=i(4269),_=i(6171),u=i(3734);let f=t.DomRendererRowFactory=class{constructor(e,t,i,s,r,n,o){this._document=e,this._characterJoinerService=t,this._optionsService=i,this._coreBrowserService=s,this._coreService=r,this._decorationService=n,this._themeService=o,this._workCell=new a.CellData,this._columnSelectMode=!1,this.defaultSpacing=0}handleSelectionChanged(e,t,i){this._selectionStart=e,this._selectionEnd=t,this._columnSelectMode=i}createRow(e,t,i,s,r,a,h,l,_,f,p){const g=[],m=this._characterJoinerService.getJoinedCharacters(t),S=this._themeService.colors;let C,b=e.getNoBgTrimmedLength();i&&b0&&M===m[0][0]){O=!0;const t=m.shift();I=new d.JoinedCellData(this._workCell,e.translateToString(!0,t[0],t[1]),t[1]-t[0]),P=t[1]-1,b=I.getWidth()}const H=this._isCellInSelection(M,t),F=i&&M===a,W=T&&M>=f&&M<=p;let U=!1;this._decorationService.forEachDecorationAtCell(M,t,void 0,(e=>{U=!0}));let N=I.getChars()||o.WHITESPACE_CELL_CHAR;if(" "===N&&(I.isUnderline()||I.isOverline())&&(N=" "),A=b*l-_.get(N,I.isBold(),I.isItalic()),C){if(y&&(H&&x||!H&&!x&&I.bg===E)&&(H&&x&&S.selectionForeground||I.fg===k)&&I.extended.ext===L&&W===D&&A===R&&!F&&!O&&!U){w+=N,y++;continue}y&&(C.textContent=w),C=this._document.createElement("span"),y=0,w=""}else C=this._document.createElement("span");if(E=I.bg,k=I.fg,L=I.extended.ext,D=W,R=A,x=H,O&&a>=M&&a<=P&&(a=M),!this._coreService.isCursorHidden&&F)if(B.push("xterm-cursor"),this._coreBrowserService.isFocused)h&&B.push("xterm-cursor-blink"),B.push("bar"===s?"xterm-cursor-bar":"underline"===s?"xterm-cursor-underline":"xterm-cursor-block");else if(r)switch(r){case"outline":B.push("xterm-cursor-outline");break;case"block":B.push("xterm-cursor-block");break;case"bar":B.push("xterm-cursor-bar");break;case"underline":B.push("xterm-cursor-underline")}if(I.isBold()&&B.push("xterm-bold"),I.isItalic()&&B.push("xterm-italic"),I.isDim()&&B.push("xterm-dim"),w=I.isInvisible()?o.WHITESPACE_CELL_CHAR:I.getChars()||o.WHITESPACE_CELL_CHAR,I.isUnderline()&&(B.push(`xterm-underline-${I.extended.underlineStyle}`)," "===w&&(w=" "),!I.isUnderlineColorDefault()))if(I.isUnderlineColorRGB())C.style.textDecorationColor=`rgb(${u.AttributeData.toColorRGB(I.getUnderlineColor()).join(",")})`;else{let e=I.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&I.isBold()&&e<8&&(e+=8),C.style.textDecorationColor=S.ansi[e].css}I.isOverline()&&(B.push("xterm-overline")," "===w&&(w=" ")),I.isStrikethrough()&&B.push("xterm-strikethrough"),W&&(C.style.textDecoration="underline");let $=I.getFgColor(),j=I.getFgColorMode(),z=I.getBgColor(),K=I.getBgColorMode();const q=!!I.isInverse();if(q){const e=$;$=z,z=e;const t=j;j=K,K=t}let V,G,X,J=!1;switch(this._decorationService.forEachDecorationAtCell(M,t,void 0,(e=>{"top"!==e.options.layer&&J||(e.backgroundColorRGB&&(K=50331648,z=e.backgroundColorRGB.rgba>>8&16777215,V=e.backgroundColorRGB),e.foregroundColorRGB&&(j=50331648,$=e.foregroundColorRGB.rgba>>8&16777215,G=e.foregroundColorRGB),J="top"===e.options.layer)})),!J&&H&&(V=this._coreBrowserService.isFocused?S.selectionBackgroundOpaque:S.selectionInactiveBackgroundOpaque,z=V.rgba>>8&16777215,K=50331648,J=!0,S.selectionForeground&&(j=50331648,$=S.selectionForeground.rgba>>8&16777215,G=S.selectionForeground)),J&&B.push("xterm-decoration-top"),K){case 16777216:case 33554432:X=S.ansi[z],B.push(`xterm-bg-${z}`);break;case 50331648:X=c.rgba.toColor(z>>16,z>>8&255,255&z),this._addStyle(C,`background-color:#${v((z>>>0).toString(16),"0",6)}`);break;default:q?(X=S.foreground,B.push(`xterm-bg-${n.INVERTED_DEFAULT_COLOR}`)):X=S.background}switch(V||I.isDim()&&(V=c.color.multiplyOpacity(X,.5)),j){case 16777216:case 33554432:I.isBold()&&$<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&($+=8),this._applyMinimumContrast(C,X,S.ansi[$],I,V,void 0)||B.push(`xterm-fg-${$}`);break;case 50331648:const e=c.rgba.toColor($>>16&255,$>>8&255,255&$);this._applyMinimumContrast(C,X,e,I,V,G)||this._addStyle(C,`color:#${v($.toString(16),"0",6)}`);break;default:this._applyMinimumContrast(C,X,S.foreground,I,V,void 0)||q&&B.push(`xterm-fg-${n.INVERTED_DEFAULT_COLOR}`)}B.length&&(C.className=B.join(" "),B.length=0),F||O||U?C.textContent=w:y++,A!==this.defaultSpacing&&(C.style.letterSpacing=`${A}px`),g.push(C),M=P}return C&&y&&(C.textContent=w),g}_applyMinimumContrast(e,t,i,s,r,n){if(1===this._optionsService.rawOptions.minimumContrastRatio||(0,_.excludeFromContrastRatioDemands)(s.getCode()))return!1;const o=this._getContrastCache(s);let a;if(r||n||(a=o.getColor(t.rgba,i.rgba)),void 0===a){const e=this._optionsService.rawOptions.minimumContrastRatio/(s.isDim()?2:1);a=c.color.ensureContrastRatio(r||t,n||i,e),o.setColor((r||t).rgba,(n||i).rgba,null!=a?a:null)}return!!a&&(this._addStyle(e,`color:${a.css}`),!0)}_getContrastCache(e){return e.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(e,t){e.setAttribute("style",`${e.getAttribute("style")||""}${t};`)}_isCellInSelection(e,t){const i=this._selectionStart,s=this._selectionEnd;return!(!i||!s)&&(this._columnSelectMode?i[0]<=s[0]?e>=i[0]&&t>=i[1]&&e=i[1]&&e>=s[0]&&t<=s[1]:t>i[1]&&t=i[0]&&e=i[0])}};function v(e,t,i){for(;e.length{Object.defineProperty(t,"__esModule",{value:!0}),t.WidthCache=void 0,t.WidthCache=class{constructor(e){this._flat=new Float32Array(256),this._font="",this._fontSize=0,this._weight="normal",this._weightBold="bold",this._measureElements=[],this._container=e.createElement("div"),this._container.style.position="absolute",this._container.style.top="-50000px",this._container.style.width="50000px",this._container.style.whiteSpace="pre",this._container.style.fontKerning="none";const t=e.createElement("span"),i=e.createElement("span");i.style.fontWeight="bold";const s=e.createElement("span");s.style.fontStyle="italic";const r=e.createElement("span");r.style.fontWeight="bold",r.style.fontStyle="italic",this._measureElements=[t,i,s,r],this._container.appendChild(t),this._container.appendChild(i),this._container.appendChild(s),this._container.appendChild(r),e.body.appendChild(this._container),this.clear()}dispose(){this._container.remove(),this._measureElements.length=0,this._holey=void 0}clear(){this._flat.fill(-9999),this._holey=new Map}setFont(e,t,i,s){e===this._font&&t===this._fontSize&&i===this._weight&&s===this._weightBold||(this._font=e,this._fontSize=t,this._weight=i,this._weightBold=s,this._container.style.fontFamily=this._font,this._container.style.fontSize=`${this._fontSize}px`,this._measureElements[0].style.fontWeight=`${i}`,this._measureElements[1].style.fontWeight=`${s}`,this._measureElements[2].style.fontWeight=`${i}`,this._measureElements[3].style.fontWeight=`${s}`,this.clear())}get(e,t,i){let s=0;if(!t&&!i&&1===e.length&&(s=e.charCodeAt(0))<256)return-9999!==this._flat[s]?this._flat[s]:this._flat[s]=this._measure(e,0);let r=e;t&&(r+="B"),i&&(r+="I");let n=this._holey.get(r);if(void 0===n){let s=0;t&&(s|=1),i&&(s|=2),n=this._measure(e,s),this._holey.set(r,n)}return n}_measure(e,t){const i=this._measureElements[t];return i.textContent=e.repeat(32),i.offsetWidth/32}}},2223:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.TEXT_BASELINE=t.DIM_OPACITY=t.INVERTED_DEFAULT_COLOR=void 0;const s=i(6114);t.INVERTED_DEFAULT_COLOR=257,t.DIM_OPACITY=.5,t.TEXT_BASELINE=s.isFirefox||s.isLegacyEdge?"bottom":"ideographic"},6171:(e,t)=>{function i(e){return 57508<=e&&e<=57558}Object.defineProperty(t,"__esModule",{value:!0}),t.createRenderDimensions=t.excludeFromContrastRatioDemands=t.isRestrictedPowerlineGlyph=t.isPowerlineGlyph=t.throwIfFalsy=void 0,t.throwIfFalsy=function(e){if(!e)throw new Error("value must not be falsy");return e},t.isPowerlineGlyph=i,t.isRestrictedPowerlineGlyph=function(e){return 57520<=e&&e<=57527},t.excludeFromContrastRatioDemands=function(e){return i(e)||function(e){return 9472<=e&&e<=9631}(e)},t.createRenderDimensions=function(){return{css:{canvas:{width:0,height:0},cell:{width:0,height:0}},device:{canvas:{width:0,height:0},cell:{width:0,height:0},char:{width:0,height:0,left:0,top:0}}}}},456:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionModel=void 0,t.SelectionModel=class{constructor(e){this._bufferService=e,this.isSelectAllActive=!1,this.selectionStartLength=0}clearSelection(){this.selectionStart=void 0,this.selectionEnd=void 0,this.isSelectAllActive=!1,this.selectionStartLength=0}get finalSelectionStart(){return this.isSelectAllActive?[0,0]:this.selectionEnd&&this.selectionStart&&this.areSelectionValuesReversed()?this.selectionEnd:this.selectionStart}get finalSelectionEnd(){if(this.isSelectAllActive)return[this._bufferService.cols,this._bufferService.buffer.ybase+this._bufferService.rows-1];if(this.selectionStart){if(!this.selectionEnd||this.areSelectionValuesReversed()){const e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?e%this._bufferService.cols==0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)-1]:[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){const e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[Math.max(e,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){const e=this.selectionStart,t=this.selectionEnd;return!(!e||!t)&&(e[1]>t[1]||e[1]===t[1]&&e[0]>t[0])}handleTrim(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):(this.selectionStart&&this.selectionStart[1]<0&&(this.selectionStart[1]=0),!1)}}},428:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CharSizeService=void 0;const n=i(2585),o=i(8460),a=i(844);let h=t.CharSizeService=class extends a.Disposable{get hasValidSize(){return this.width>0&&this.height>0}constructor(e,t,i){super(),this._optionsService=i,this.width=0,this.height=0,this._onCharSizeChange=this.register(new o.EventEmitter),this.onCharSizeChange=this._onCharSizeChange.event,this._measureStrategy=new c(e,t,this._optionsService),this.register(this._optionsService.onMultipleOptionChange(["fontFamily","fontSize"],(()=>this.measure())))}measure(){const e=this._measureStrategy.measure();e.width===this.width&&e.height===this.height||(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};t.CharSizeService=h=s([r(2,n.IOptionsService)],h);class c{constructor(e,t,i){this._document=e,this._parentElement=t,this._optionsService=i,this._result={width:0,height:0},this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W".repeat(32),this._measureElement.setAttribute("aria-hidden","true"),this._measureElement.style.whiteSpace="pre",this._measureElement.style.fontKerning="none",this._parentElement.appendChild(this._measureElement)}measure(){this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`;const e={height:Number(this._measureElement.offsetHeight),width:Number(this._measureElement.offsetWidth)};return 0!==e.width&&0!==e.height&&(this._result.width=e.width/32,this._result.height=Math.ceil(e.height)),this._result}}},4269:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CharacterJoinerService=t.JoinedCellData=void 0;const n=i(3734),o=i(643),a=i(511),h=i(2585);class c extends n.AttributeData{constructor(e,t,i){super(),this.content=0,this.combinedData="",this.fg=e.fg,this.bg=e.bg,this.combinedData=t,this._width=i}isCombined(){return 2097152}getWidth(){return this._width}getChars(){return this.combinedData}getCode(){return 2097151}setFromCharData(e){throw new Error("not implemented")}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}}t.JoinedCellData=c;let l=t.CharacterJoinerService=class e{constructor(e){this._bufferService=e,this._characterJoiners=[],this._nextCharacterJoinerId=0,this._workCell=new a.CellData}register(e){const t={id:this._nextCharacterJoinerId++,handler:e};return this._characterJoiners.push(t),t.id}deregister(e){for(let t=0;t1){const e=this._getJoinedRanges(s,a,n,t,r);for(let t=0;t1){const e=this._getJoinedRanges(s,a,n,t,r);for(let t=0;t{Object.defineProperty(t,"__esModule",{value:!0}),t.CoreBrowserService=void 0,t.CoreBrowserService=class{constructor(e,t){this._textarea=e,this.window=t,this._isFocused=!1,this._cachedIsFocused=void 0,this._textarea.addEventListener("focus",(()=>this._isFocused=!0)),this._textarea.addEventListener("blur",(()=>this._isFocused=!1))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return void 0===this._cachedIsFocused&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask((()=>this._cachedIsFocused=void 0))),this._cachedIsFocused}}},8934:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.MouseService=void 0;const n=i(4725),o=i(9806);let a=t.MouseService=class{constructor(e,t){this._renderService=e,this._charSizeService=t}getCoords(e,t,i,s,r){return(0,o.getCoords)(window,e,t,i,s,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,r)}getMouseReportCoords(e,t){const i=(0,o.getCoordsRelativeToElement)(window,e,t);if(this._charSizeService.hasValidSize)return i[0]=Math.min(Math.max(i[0],0),this._renderService.dimensions.css.canvas.width-1),i[1]=Math.min(Math.max(i[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(i[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(i[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(i[0]),y:Math.floor(i[1])}}};t.MouseService=a=s([r(0,n.IRenderService),r(1,n.ICharSizeService)],a)},3230:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.RenderService=void 0;const n=i(3656),o=i(6193),a=i(5596),h=i(4725),c=i(8460),l=i(844),d=i(7226),_=i(2585);let u=t.RenderService=class extends l.Disposable{get dimensions(){return this._renderer.value.dimensions}constructor(e,t,i,s,r,h,_,u){if(super(),this._rowCount=e,this._charSizeService=s,this._renderer=this.register(new l.MutableDisposable),this._pausedResizeTask=new d.DebouncedIdleTask,this._isPaused=!1,this._needsFullRefresh=!1,this._isNextRenderRedrawOnly=!0,this._needsSelectionRefresh=!1,this._canvasWidth=0,this._canvasHeight=0,this._selectionState={start:void 0,end:void 0,columnSelectMode:!1},this._onDimensionsChange=this.register(new c.EventEmitter),this.onDimensionsChange=this._onDimensionsChange.event,this._onRenderedViewportChange=this.register(new c.EventEmitter),this.onRenderedViewportChange=this._onRenderedViewportChange.event,this._onRender=this.register(new c.EventEmitter),this.onRender=this._onRender.event,this._onRefreshRequest=this.register(new c.EventEmitter),this.onRefreshRequest=this._onRefreshRequest.event,this._renderDebouncer=new o.RenderDebouncer(_.window,((e,t)=>this._renderRows(e,t))),this.register(this._renderDebouncer),this._screenDprMonitor=new a.ScreenDprMonitor(_.window),this._screenDprMonitor.setListener((()=>this.handleDevicePixelRatioChange())),this.register(this._screenDprMonitor),this.register(h.onResize((()=>this._fullRefresh()))),this.register(h.buffers.onBufferActivate((()=>{var e;return null===(e=this._renderer.value)||void 0===e?void 0:e.clear()}))),this.register(i.onOptionChange((()=>this._handleOptionsChanged()))),this.register(this._charSizeService.onCharSizeChange((()=>this.handleCharSizeChanged()))),this.register(r.onDecorationRegistered((()=>this._fullRefresh()))),this.register(r.onDecorationRemoved((()=>this._fullRefresh()))),this.register(i.onMultipleOptionChange(["customGlyphs","drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio"],(()=>{this.clear(),this.handleResize(h.cols,h.rows),this._fullRefresh()}))),this.register(i.onMultipleOptionChange(["cursorBlink","cursorStyle"],(()=>this.refreshRows(h.buffer.y,h.buffer.y,!0)))),this.register((0,n.addDisposableDomListener)(_.window,"resize",(()=>this.handleDevicePixelRatioChange()))),this.register(u.onChangeColors((()=>this._fullRefresh()))),"IntersectionObserver"in _.window){const e=new _.window.IntersectionObserver((e=>this._handleIntersectionChange(e[e.length-1])),{threshold:0});e.observe(t),this.register({dispose:()=>e.disconnect()})}}_handleIntersectionChange(e){this._isPaused=void 0===e.isIntersecting?0===e.intersectionRatio:!e.isIntersecting,this._isPaused||this._charSizeService.hasValidSize||this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,i=!1){this._isPaused?this._needsFullRefresh=!0:(i||(this._isNextRenderRedrawOnly=!1),this._renderDebouncer.refresh(e,t,this._rowCount))}_renderRows(e,t){this._renderer.value&&(e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0)}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value.onRequestRedraw((e=>this.refreshRows(e.start,e.end,!0))),this._needsSelectionRefresh=!0,this._fullRefresh()}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){var e,t;this._renderer.value&&(null===(t=(e=this._renderer.value).clearTextureAtlas)||void 0===t||t.call(e),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set((()=>this._renderer.value.handleResize(e,t))):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){var e;null===(e=this._renderer.value)||void 0===e||e.handleCharSizeChanged()}handleBlur(){var e;null===(e=this._renderer.value)||void 0===e||e.handleBlur()}handleFocus(){var e;null===(e=this._renderer.value)||void 0===e||e.handleFocus()}handleSelectionChanged(e,t,i){var s;this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=i,null===(s=this._renderer.value)||void 0===s||s.handleSelectionChanged(e,t,i)}handleCursorMove(){var e;null===(e=this._renderer.value)||void 0===e||e.handleCursorMove()}clear(){var e;null===(e=this._renderer.value)||void 0===e||e.clear()}};t.RenderService=u=s([r(2,_.IOptionsService),r(3,h.ICharSizeService),r(4,_.IDecorationService),r(5,_.IBufferService),r(6,h.ICoreBrowserService),r(7,h.IThemeService)],u)},9312:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionService=void 0;const n=i(9806),o=i(9504),a=i(456),h=i(4725),c=i(8460),l=i(844),d=i(6114),_=i(4841),u=i(511),f=i(2585),v=String.fromCharCode(160),p=new RegExp(v,"g");let g=t.SelectionService=class extends l.Disposable{constructor(e,t,i,s,r,n,o,h,d){super(),this._element=e,this._screenElement=t,this._linkifier=i,this._bufferService=s,this._coreService=r,this._mouseService=n,this._optionsService=o,this._renderService=h,this._coreBrowserService=d,this._dragScrollAmount=0,this._enabled=!0,this._workCell=new u.CellData,this._mouseDownTimeStamp=0,this._oldHasSelection=!1,this._oldSelectionStart=void 0,this._oldSelectionEnd=void 0,this._onLinuxMouseSelection=this.register(new c.EventEmitter),this.onLinuxMouseSelection=this._onLinuxMouseSelection.event,this._onRedrawRequest=this.register(new c.EventEmitter),this.onRequestRedraw=this._onRedrawRequest.event,this._onSelectionChange=this.register(new c.EventEmitter),this.onSelectionChange=this._onSelectionChange.event,this._onRequestScrollLines=this.register(new c.EventEmitter),this.onRequestScrollLines=this._onRequestScrollLines.event,this._mouseMoveListener=e=>this._handleMouseMove(e),this._mouseUpListener=e=>this._handleMouseUp(e),this._coreService.onUserInput((()=>{this.hasSelection&&this.clearSelection()})),this._trimListener=this._bufferService.buffer.lines.onTrim((e=>this._handleTrim(e))),this.register(this._bufferService.buffers.onBufferActivate((e=>this._handleBufferActivate(e)))),this.enable(),this._model=new a.SelectionModel(this._bufferService),this._activeSelectionMode=0,this.register((0,l.toDisposable)((()=>{this._removeMouseDownListeners()})))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!(!e||!t||e[0]===t[0]&&e[1]===t[1])}get selectionText(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";const i=this._bufferService.buffer,s=[];if(3===this._activeSelectionMode){if(e[0]===t[0])return"";const r=e[0]e.replace(p," "))).join(d.isWindows?"\r\n":"\n")}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>this._refresh()))),d.isLinux&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:3===this._activeSelectionMode})}_isClickInSelection(e){const t=this._getMouseBufferCoords(e),i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!!(i&&s&&t)&&this._areCoordsInSelection(t,i,s)}isCellInSelection(e,t){const i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!(!i||!s)&&this._areCoordsInSelection([e,t],i,s)}_areCoordsInSelection(e,t,i){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){var i,s;const r=null===(s=null===(i=this._linkifier.currentLink)||void 0===i?void 0:i.link)||void 0===s?void 0:s.range;if(r)return this._model.selectionStart=[r.start.x-1,r.start.y-1],this._model.selectionStartLength=(0,_.getRangeLength)(r,this._bufferService.cols),this._model.selectionEnd=void 0,!0;const n=this._getMouseBufferCoords(e);return!!n&&(this._selectWordAt(n,t),this._model.selectionEnd=void 0,!0)}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){const t=this._mouseService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t}_getMouseEventScrollAmount(e){let t=(0,n.getCoordsRelativeToElement)(this._coreBrowserService.window,e,this._screenElement)[1];const i=this._renderService.dimensions.css.canvas.height;return t>=0&&t<=i?0:(t>i&&(t-=i),t=Math.min(Math.max(t,-50),50),t/=50,t/Math.abs(t)+Math.round(14*t))}shouldForceSelection(e){return d.isMac?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}handleMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,(2!==e.button||!this.hasSelection)&&0===e.button){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._handleIncrementalClick(e):1===e.detail?this._handleSingleClick(e):2===e.detail?this._handleDoubleClick(e):3===e.detail&&this._handleTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval((()=>this._dragScroll()),50)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_handleSingleClick(e){if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),!this._model.selectionStart)return;this._model.selectionEnd=void 0;const t=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);t&&t.length!==this._model.selectionStart[0]&&0===t.hasWidth(this._model.selectionStart[0])&&this._model.selectionStart[0]++}_handleDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(e){const t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))}shouldColumnSelect(e){return e.altKey&&!(d.isMac&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;const t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),!this._model.selectionEnd)return void this.refresh(!0);2===this._activeSelectionMode?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));const i=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows,e.lines.length-1)):(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}}_handleMouseUp(e){const t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&t<500&&e.altKey&&this._optionsService.rawOptions.altClickMovesCursor){if(this._bufferService.buffer.ybase===this._bufferService.buffer.ydisp){const t=this._mouseService.getCoords(e,this._element,this._bufferService.cols,this._bufferService.rows,!1);if(t&&void 0!==t[0]&&void 0!==t[1]){const e=(0,o.moveToCellSequence)(t[0]-1,t[1]-1,this._bufferService,this._coreService.decPrivateModes.applicationCursorKeys);this._coreService.triggerDataEvent(e,!0)}}}else this._fireEventIfSelectionChanged()}_fireEventIfSelectionChanged(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd,i=!(!e||!t||e[0]===t[0]&&e[1]===t[1]);i?e&&t&&(this._oldSelectionStart&&this._oldSelectionEnd&&e[0]===this._oldSelectionStart[0]&&e[1]===this._oldSelectionStart[1]&&t[0]===this._oldSelectionEnd[0]&&t[1]===this._oldSelectionEnd[1]||this._fireOnSelectionChange(e,t,i)):this._oldHasSelection&&this._fireOnSelectionChange(e,t,i)}_fireOnSelectionChange(e,t,i){this._oldSelectionStart=e,this._oldSelectionEnd=t,this._oldHasSelection=i,this._onSelectionChange.fire()}_handleBufferActivate(e){this.clearSelection(),this._trimListener.dispose(),this._trimListener=e.activeBuffer.lines.onTrim((e=>this._handleTrim(e)))}_convertViewportColToCharacterIndex(e,t){let i=t;for(let s=0;t>=s;s++){const r=e.loadCell(s,this._workCell).getChars().length;0===this._workCell.getWidth()?i--:r>1&&t!==s&&(i+=r-1)}return i}setSelection(e,t,i){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=i,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,t,i=!0,s=!0){if(e[0]>=this._bufferService.cols)return;const r=this._bufferService.buffer,n=r.lines.get(e[1]);if(!n)return;const o=r.translateBufferLineToString(e[1],!1);let a=this._convertViewportColToCharacterIndex(n,e[0]),h=a;const c=e[0]-a;let l=0,d=0,_=0,u=0;if(" "===o.charAt(a)){for(;a>0&&" "===o.charAt(a-1);)a--;for(;h1&&(u+=s-1,h+=s-1);t>0&&a>0&&!this._isCharWordSeparator(n.loadCell(t-1,this._workCell));){n.loadCell(t-1,this._workCell);const e=this._workCell.getChars().length;0===this._workCell.getWidth()?(l++,t--):e>1&&(_+=e-1,a-=e-1),a--,t--}for(;i1&&(u+=e-1,h+=e-1),h++,i++}}h++;let f=a+c-l+_,v=Math.min(this._bufferService.cols,h-a+l+d-_-u);if(t||""!==o.slice(a,h).trim()){if(i&&0===f&&32!==n.getCodePoint(0)){const t=r.lines.get(e[1]-1);if(t&&n.isWrapped&&32!==t.getCodePoint(this._bufferService.cols-1)){const t=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(t){const e=this._bufferService.cols-t.start;f-=e,v+=e}}}if(s&&f+v===this._bufferService.cols&&32!==n.getCodePoint(this._bufferService.cols-1)){const t=r.lines.get(e[1]+1);if((null==t?void 0:t.isWrapped)&&32!==t.getCodePoint(0)){const t=this._getWordAt([0,e[1]+1],!1,!1,!0);t&&(v+=t.length)}}return{start:f,length:v}}}_selectWordAt(e,t){const i=this._getWordAt(e,t);if(i){for(;i.start<0;)i.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[i.start,e[1]],this._model.selectionStartLength=i.length}}_selectToWordAt(e){const t=this._getWordAt(e,!0);if(t){let i=e[1];for(;t.start<0;)t.start+=this._bufferService.cols,i--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,i++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,i]}}_isCharWordSeparator(e){return 0!==e.getWidth()&&this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){const t=this._bufferService.buffer.getWrappedRangeForLine(e),i={start:{x:0,y:t.first},end:{x:this._bufferService.cols-1,y:t.last}};this._model.selectionStart=[0,t.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=(0,_.getRangeLength)(i,this._bufferService.cols)}};t.SelectionService=g=s([r(3,f.IBufferService),r(4,f.ICoreService),r(5,h.IMouseService),r(6,f.IOptionsService),r(7,h.IRenderService),r(8,h.ICoreBrowserService)],g)},4725:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.IThemeService=t.ICharacterJoinerService=t.ISelectionService=t.IRenderService=t.IMouseService=t.ICoreBrowserService=t.ICharSizeService=void 0;const s=i(8343);t.ICharSizeService=(0,s.createDecorator)("CharSizeService"),t.ICoreBrowserService=(0,s.createDecorator)("CoreBrowserService"),t.IMouseService=(0,s.createDecorator)("MouseService"),t.IRenderService=(0,s.createDecorator)("RenderService"),t.ISelectionService=(0,s.createDecorator)("SelectionService"),t.ICharacterJoinerService=(0,s.createDecorator)("CharacterJoinerService"),t.IThemeService=(0,s.createDecorator)("ThemeService")},6731:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.ThemeService=t.DEFAULT_ANSI_COLORS=void 0;const n=i(7239),o=i(8055),a=i(8460),h=i(844),c=i(2585),l=o.css.toColor("#ffffff"),d=o.css.toColor("#000000"),_=o.css.toColor("#ffffff"),u=o.css.toColor("#000000"),f={css:"rgba(255, 255, 255, 0.3)",rgba:4294967117};t.DEFAULT_ANSI_COLORS=Object.freeze((()=>{const e=[o.css.toColor("#2e3436"),o.css.toColor("#cc0000"),o.css.toColor("#4e9a06"),o.css.toColor("#c4a000"),o.css.toColor("#3465a4"),o.css.toColor("#75507b"),o.css.toColor("#06989a"),o.css.toColor("#d3d7cf"),o.css.toColor("#555753"),o.css.toColor("#ef2929"),o.css.toColor("#8ae234"),o.css.toColor("#fce94f"),o.css.toColor("#729fcf"),o.css.toColor("#ad7fa8"),o.css.toColor("#34e2e2"),o.css.toColor("#eeeeec")],t=[0,95,135,175,215,255];for(let i=0;i<216;i++){const s=t[i/36%6|0],r=t[i/6%6|0],n=t[i%6];e.push({css:o.channels.toCss(s,r,n),rgba:o.channels.toRgba(s,r,n)})}for(let t=0;t<24;t++){const i=8+10*t;e.push({css:o.channels.toCss(i,i,i),rgba:o.channels.toRgba(i,i,i)})}return e})());let v=t.ThemeService=class extends h.Disposable{get colors(){return this._colors}constructor(e){super(),this._optionsService=e,this._contrastCache=new n.ColorContrastCache,this._halfContrastCache=new n.ColorContrastCache,this._onChangeColors=this.register(new a.EventEmitter),this.onChangeColors=this._onChangeColors.event,this._colors={foreground:l,background:d,cursor:_,cursorAccent:u,selectionForeground:void 0,selectionBackgroundTransparent:f,selectionBackgroundOpaque:o.color.blend(d,f),selectionInactiveBackgroundTransparent:f,selectionInactiveBackgroundOpaque:o.color.blend(d,f),ansi:t.DEFAULT_ANSI_COLORS.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this.register(this._optionsService.onSpecificOptionChange("minimumContrastRatio",(()=>this._contrastCache.clear()))),this.register(this._optionsService.onSpecificOptionChange("theme",(()=>this._setTheme(this._optionsService.rawOptions.theme))))}_setTheme(e={}){const i=this._colors;if(i.foreground=p(e.foreground,l),i.background=p(e.background,d),i.cursor=p(e.cursor,_),i.cursorAccent=p(e.cursorAccent,u),i.selectionBackgroundTransparent=p(e.selectionBackground,f),i.selectionBackgroundOpaque=o.color.blend(i.background,i.selectionBackgroundTransparent),i.selectionInactiveBackgroundTransparent=p(e.selectionInactiveBackground,i.selectionBackgroundTransparent),i.selectionInactiveBackgroundOpaque=o.color.blend(i.background,i.selectionInactiveBackgroundTransparent),i.selectionForeground=e.selectionForeground?p(e.selectionForeground,o.NULL_COLOR):void 0,i.selectionForeground===o.NULL_COLOR&&(i.selectionForeground=void 0),o.color.isOpaque(i.selectionBackgroundTransparent)){const e=.3;i.selectionBackgroundTransparent=o.color.opacity(i.selectionBackgroundTransparent,e)}if(o.color.isOpaque(i.selectionInactiveBackgroundTransparent)){const e=.3;i.selectionInactiveBackgroundTransparent=o.color.opacity(i.selectionInactiveBackgroundTransparent,e)}if(i.ansi=t.DEFAULT_ANSI_COLORS.slice(),i.ansi[0]=p(e.black,t.DEFAULT_ANSI_COLORS[0]),i.ansi[1]=p(e.red,t.DEFAULT_ANSI_COLORS[1]),i.ansi[2]=p(e.green,t.DEFAULT_ANSI_COLORS[2]),i.ansi[3]=p(e.yellow,t.DEFAULT_ANSI_COLORS[3]),i.ansi[4]=p(e.blue,t.DEFAULT_ANSI_COLORS[4]),i.ansi[5]=p(e.magenta,t.DEFAULT_ANSI_COLORS[5]),i.ansi[6]=p(e.cyan,t.DEFAULT_ANSI_COLORS[6]),i.ansi[7]=p(e.white,t.DEFAULT_ANSI_COLORS[7]),i.ansi[8]=p(e.brightBlack,t.DEFAULT_ANSI_COLORS[8]),i.ansi[9]=p(e.brightRed,t.DEFAULT_ANSI_COLORS[9]),i.ansi[10]=p(e.brightGreen,t.DEFAULT_ANSI_COLORS[10]),i.ansi[11]=p(e.brightYellow,t.DEFAULT_ANSI_COLORS[11]),i.ansi[12]=p(e.brightBlue,t.DEFAULT_ANSI_COLORS[12]),i.ansi[13]=p(e.brightMagenta,t.DEFAULT_ANSI_COLORS[13]),i.ansi[14]=p(e.brightCyan,t.DEFAULT_ANSI_COLORS[14]),i.ansi[15]=p(e.brightWhite,t.DEFAULT_ANSI_COLORS[15]),e.extendedAnsi){const s=Math.min(i.ansi.length-16,e.extendedAnsi.length);for(let r=0;r{Object.defineProperty(t,"__esModule",{value:!0}),t.CircularList=void 0;const s=i(8460),r=i(844);class n extends r.Disposable{constructor(e){super(),this._maxLength=e,this.onDeleteEmitter=this.register(new s.EventEmitter),this.onDelete=this.onDeleteEmitter.event,this.onInsertEmitter=this.register(new s.EventEmitter),this.onInsert=this.onInsertEmitter.event,this.onTrimEmitter=this.register(new s.EventEmitter),this.onTrim=this.onTrimEmitter.event,this._array=new Array(this._maxLength),this._startIndex=0,this._length=0}get maxLength(){return this._maxLength}set maxLength(e){if(this._maxLength===e)return;const t=new Array(e);for(let i=0;ithis._length)for(let t=this._length;t=e;t--)this._array[this._getCyclicIndex(t+i.length)]=this._array[this._getCyclicIndex(t)];for(let t=0;tthis._maxLength){const e=this._length+i.length-this._maxLength;this._startIndex+=e,this._length=this._maxLength,this.onTrimEmitter.fire(e)}else this._length+=i.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,t,i){if(!(t<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+i<0)throw new Error("Cannot shift elements in list beyond index 0");if(i>0){for(let s=t-1;s>=0;s--)this.set(e+s+i,this.get(e+s));const s=e+t+i-this._length;if(s>0)for(this._length+=s;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let s=0;s{Object.defineProperty(t,"__esModule",{value:!0}),t.clone=void 0,t.clone=function e(t,i=5){if("object"!=typeof t)return t;const s=Array.isArray(t)?[]:{};for(const r in t)s[r]=i<=1?t[r]:t[r]&&e(t[r],i-1);return s}},8055:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.contrastRatio=t.toPaddedHex=t.rgba=t.rgb=t.css=t.color=t.channels=t.NULL_COLOR=void 0;const s=i(6114);let r=0,n=0,o=0,a=0;var h,c,l,d,_;function u(e){const t=e.toString(16);return t.length<2?"0"+t:t}function f(e,t){return e>>0}}(h||(t.channels=h={})),function(e){function t(e,t){return a=Math.round(255*t),[r,n,o]=_.toChannels(e.rgba),{css:h.toCss(r,n,o,a),rgba:h.toRgba(r,n,o,a)}}e.blend=function(e,t){if(a=(255&t.rgba)/255,1===a)return{css:t.css,rgba:t.rgba};const i=t.rgba>>24&255,s=t.rgba>>16&255,c=t.rgba>>8&255,l=e.rgba>>24&255,d=e.rgba>>16&255,_=e.rgba>>8&255;return r=l+Math.round((i-l)*a),n=d+Math.round((s-d)*a),o=_+Math.round((c-_)*a),{css:h.toCss(r,n,o),rgba:h.toRgba(r,n,o)}},e.isOpaque=function(e){return 255==(255&e.rgba)},e.ensureContrastRatio=function(e,t,i){const s=_.ensureContrastRatio(e.rgba,t.rgba,i);if(s)return _.toColor(s>>24&255,s>>16&255,s>>8&255)},e.opaque=function(e){const t=(255|e.rgba)>>>0;return[r,n,o]=_.toChannels(t),{css:h.toCss(r,n,o),rgba:t}},e.opacity=t,e.multiplyOpacity=function(e,i){return a=255&e.rgba,t(e,a*i/255)},e.toColorRGB=function(e){return[e.rgba>>24&255,e.rgba>>16&255,e.rgba>>8&255]}}(c||(t.color=c={})),function(e){let t,i;if(!s.isNode){const e=document.createElement("canvas");e.width=1,e.height=1;const s=e.getContext("2d",{willReadFrequently:!0});s&&(t=s,t.globalCompositeOperation="copy",i=t.createLinearGradient(0,0,1,1))}e.toColor=function(e){if(e.match(/#[\da-f]{3,8}/i))switch(e.length){case 4:return r=parseInt(e.slice(1,2).repeat(2),16),n=parseInt(e.slice(2,3).repeat(2),16),o=parseInt(e.slice(3,4).repeat(2),16),_.toColor(r,n,o);case 5:return r=parseInt(e.slice(1,2).repeat(2),16),n=parseInt(e.slice(2,3).repeat(2),16),o=parseInt(e.slice(3,4).repeat(2),16),a=parseInt(e.slice(4,5).repeat(2),16),_.toColor(r,n,o,a);case 7:return{css:e,rgba:(parseInt(e.slice(1),16)<<8|255)>>>0};case 9:return{css:e,rgba:parseInt(e.slice(1),16)>>>0}}const s=e.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(s)return r=parseInt(s[1]),n=parseInt(s[2]),o=parseInt(s[3]),a=Math.round(255*(void 0===s[5]?1:parseFloat(s[5]))),_.toColor(r,n,o,a);if(!t||!i)throw new Error("css.toColor: Unsupported css format");if(t.fillStyle=i,t.fillStyle=e,"string"!=typeof t.fillStyle)throw new Error("css.toColor: Unsupported css format");if(t.fillRect(0,0,1,1),[r,n,o,a]=t.getImageData(0,0,1,1).data,255!==a)throw new Error("css.toColor: Unsupported css format");return{rgba:h.toRgba(r,n,o,a),css:e}}}(l||(t.css=l={})),function(e){function t(e,t,i){const s=e/255,r=t/255,n=i/255;return.2126*(s<=.03928?s/12.92:Math.pow((s+.055)/1.055,2.4))+.7152*(r<=.03928?r/12.92:Math.pow((r+.055)/1.055,2.4))+.0722*(n<=.03928?n/12.92:Math.pow((n+.055)/1.055,2.4))}e.relativeLuminance=function(e){return t(e>>16&255,e>>8&255,255&e)},e.relativeLuminance2=t}(d||(t.rgb=d={})),function(e){function t(e,t,i){const s=e>>24&255,r=e>>16&255,n=e>>8&255;let o=t>>24&255,a=t>>16&255,h=t>>8&255,c=f(d.relativeLuminance2(o,a,h),d.relativeLuminance2(s,r,n));for(;c0||a>0||h>0);)o-=Math.max(0,Math.ceil(.1*o)),a-=Math.max(0,Math.ceil(.1*a)),h-=Math.max(0,Math.ceil(.1*h)),c=f(d.relativeLuminance2(o,a,h),d.relativeLuminance2(s,r,n));return(o<<24|a<<16|h<<8|255)>>>0}function i(e,t,i){const s=e>>24&255,r=e>>16&255,n=e>>8&255;let o=t>>24&255,a=t>>16&255,h=t>>8&255,c=f(d.relativeLuminance2(o,a,h),d.relativeLuminance2(s,r,n));for(;c>>0}e.ensureContrastRatio=function(e,s,r){const n=d.relativeLuminance(e>>8),o=d.relativeLuminance(s>>8);if(f(n,o)>8));if(af(n,d.relativeLuminance(t>>8))?o:t}return o}const a=i(e,s,r),h=f(n,d.relativeLuminance(a>>8));if(hf(n,d.relativeLuminance(i>>8))?a:i}return a}},e.reduceLuminance=t,e.increaseLuminance=i,e.toChannels=function(e){return[e>>24&255,e>>16&255,e>>8&255,255&e]},e.toColor=function(e,t,i,s){return{css:h.toCss(e,t,i,s),rgba:h.toRgba(e,t,i,s)}}}(_||(t.rgba=_={})),t.toPaddedHex=u,t.contrastRatio=f},8969:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CoreTerminal=void 0;const s=i(844),r=i(2585),n=i(4348),o=i(7866),a=i(744),h=i(7302),c=i(6975),l=i(8460),d=i(1753),_=i(1480),u=i(7994),f=i(9282),v=i(5435),p=i(5981),g=i(2660);let m=!1;class S extends s.Disposable{get onScroll(){return this._onScrollApi||(this._onScrollApi=this.register(new l.EventEmitter),this._onScroll.event((e=>{var t;null===(t=this._onScrollApi)||void 0===t||t.fire(e.position)}))),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(e){for(const t in e)this.optionsService.options[t]=e[t]}constructor(e){super(),this._windowsWrappingHeuristics=this.register(new s.MutableDisposable),this._onBinary=this.register(new l.EventEmitter),this.onBinary=this._onBinary.event,this._onData=this.register(new l.EventEmitter),this.onData=this._onData.event,this._onLineFeed=this.register(new l.EventEmitter),this.onLineFeed=this._onLineFeed.event,this._onResize=this.register(new l.EventEmitter),this.onResize=this._onResize.event,this._onWriteParsed=this.register(new l.EventEmitter),this.onWriteParsed=this._onWriteParsed.event,this._onScroll=this.register(new l.EventEmitter),this._instantiationService=new n.InstantiationService,this.optionsService=this.register(new h.OptionsService(e)),this._instantiationService.setService(r.IOptionsService,this.optionsService),this._bufferService=this.register(this._instantiationService.createInstance(a.BufferService)),this._instantiationService.setService(r.IBufferService,this._bufferService),this._logService=this.register(this._instantiationService.createInstance(o.LogService)),this._instantiationService.setService(r.ILogService,this._logService),this.coreService=this.register(this._instantiationService.createInstance(c.CoreService)),this._instantiationService.setService(r.ICoreService,this.coreService),this.coreMouseService=this.register(this._instantiationService.createInstance(d.CoreMouseService)),this._instantiationService.setService(r.ICoreMouseService,this.coreMouseService),this.unicodeService=this.register(this._instantiationService.createInstance(_.UnicodeService)),this._instantiationService.setService(r.IUnicodeService,this.unicodeService),this._charsetService=this._instantiationService.createInstance(u.CharsetService),this._instantiationService.setService(r.ICharsetService,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(g.OscLinkService),this._instantiationService.setService(r.IOscLinkService,this._oscLinkService),this._inputHandler=this.register(new v.InputHandler(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.coreMouseService,this.unicodeService)),this.register((0,l.forwardEvent)(this._inputHandler.onLineFeed,this._onLineFeed)),this.register(this._inputHandler),this.register((0,l.forwardEvent)(this._bufferService.onResize,this._onResize)),this.register((0,l.forwardEvent)(this.coreService.onData,this._onData)),this.register((0,l.forwardEvent)(this.coreService.onBinary,this._onBinary)),this.register(this.coreService.onRequestScrollToBottom((()=>this.scrollToBottom()))),this.register(this.coreService.onUserInput((()=>this._writeBuffer.handleUserInput()))),this.register(this.optionsService.onMultipleOptionChange(["windowsMode","windowsPty"],(()=>this._handleWindowsPtyOptionChange()))),this.register(this._bufferService.onScroll((e=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp,source:0}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)}))),this.register(this._inputHandler.onScroll((e=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp,source:0}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)}))),this._writeBuffer=this.register(new p.WriteBuffer(((e,t)=>this._inputHandler.parse(e,t)))),this.register((0,l.forwardEvent)(this._writeBuffer.onWriteParsed,this._onWriteParsed))}write(e,t){this._writeBuffer.write(e,t)}writeSync(e,t){this._logService.logLevel<=r.LogLevelEnum.WARN&&!m&&(this._logService.warn("writeSync is unreliable and will be removed soon."),m=!0),this._writeBuffer.writeSync(e,t)}resize(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,a.MINIMUM_COLS),t=Math.max(t,a.MINIMUM_ROWS),this._bufferService.resize(e,t))}scroll(e,t=!1){this._bufferService.scroll(e,t)}scrollLines(e,t,i){this._bufferService.scrollLines(e,t,i)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){const t=e-this._bufferService.buffer.ydisp;0!==t&&this.scrollLines(t)}registerEscHandler(e,t){return this._inputHandler.registerEscHandler(e,t)}registerDcsHandler(e,t){return this._inputHandler.registerDcsHandler(e,t)}registerCsiHandler(e,t){return this._inputHandler.registerCsiHandler(e,t)}registerOscHandler(e,t){return this._inputHandler.registerOscHandler(e,t)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.coreMouseService.reset()}_handleWindowsPtyOptionChange(){let e=!1;const t=this.optionsService.rawOptions.windowsPty;t&&void 0!==t.buildNumber&&void 0!==t.buildNumber?e=!!("conpty"===t.backend&&t.buildNumber<21376):this.optionsService.rawOptions.windowsMode&&(e=!0),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){const e=[];e.push(this.onLineFeed(f.updateWindowsModeWrappedState.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:"H"},(()=>((0,f.updateWindowsModeWrappedState)(this._bufferService),!1)))),this._windowsWrappingHeuristics.value=(0,s.toDisposable)((()=>{for(const t of e)t.dispose()}))}}}t.CoreTerminal=S},8460:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.forwardEvent=t.EventEmitter=void 0,t.EventEmitter=class{constructor(){this._listeners=[],this._disposed=!1}get event(){return this._event||(this._event=e=>(this._listeners.push(e),{dispose:()=>{if(!this._disposed)for(let t=0;tt.fire(e)))}},5435:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.InputHandler=t.WindowsOptionsReportType=void 0;const n=i(2584),o=i(7116),a=i(2015),h=i(844),c=i(482),l=i(8437),d=i(8460),_=i(643),u=i(511),f=i(3734),v=i(2585),p=i(6242),g=i(6351),m=i(5941),S={"(":0,")":1,"*":2,"+":3,"-":1,".":2},C=131072;function b(e,t){if(e>24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}var y;!function(e){e[e.GET_WIN_SIZE_PIXELS=0]="GET_WIN_SIZE_PIXELS",e[e.GET_CELL_SIZE_PIXELS=1]="GET_CELL_SIZE_PIXELS"}(y||(t.WindowsOptionsReportType=y={}));let w=0;class E extends h.Disposable{getAttrData(){return this._curAttrData}constructor(e,t,i,s,r,h,_,f,v=new a.EscapeSequenceParser){super(),this._bufferService=e,this._charsetService=t,this._coreService=i,this._logService=s,this._optionsService=r,this._oscLinkService=h,this._coreMouseService=_,this._unicodeService=f,this._parser=v,this._parseBuffer=new Uint32Array(4096),this._stringDecoder=new c.StringToUtf32,this._utf8Decoder=new c.Utf8ToUtf32,this._workCell=new u.CellData,this._windowTitle="",this._iconName="",this._windowTitleStack=[],this._iconNameStack=[],this._curAttrData=l.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=l.DEFAULT_ATTR_DATA.clone(),this._onRequestBell=this.register(new d.EventEmitter),this.onRequestBell=this._onRequestBell.event,this._onRequestRefreshRows=this.register(new d.EventEmitter),this.onRequestRefreshRows=this._onRequestRefreshRows.event,this._onRequestReset=this.register(new d.EventEmitter),this.onRequestReset=this._onRequestReset.event,this._onRequestSendFocus=this.register(new d.EventEmitter),this.onRequestSendFocus=this._onRequestSendFocus.event,this._onRequestSyncScrollBar=this.register(new d.EventEmitter),this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event,this._onRequestWindowsOptionsReport=this.register(new d.EventEmitter),this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event,this._onA11yChar=this.register(new d.EventEmitter),this.onA11yChar=this._onA11yChar.event,this._onA11yTab=this.register(new d.EventEmitter),this.onA11yTab=this._onA11yTab.event,this._onCursorMove=this.register(new d.EventEmitter),this.onCursorMove=this._onCursorMove.event,this._onLineFeed=this.register(new d.EventEmitter),this.onLineFeed=this._onLineFeed.event,this._onScroll=this.register(new d.EventEmitter),this.onScroll=this._onScroll.event,this._onTitleChange=this.register(new d.EventEmitter),this.onTitleChange=this._onTitleChange.event,this._onColor=this.register(new d.EventEmitter),this.onColor=this._onColor.event,this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0},this._specialColors=[256,257,258],this.register(this._parser),this._dirtyRowTracker=new k(this._bufferService),this._activeBuffer=this._bufferService.buffer,this.register(this._bufferService.buffers.onBufferActivate((e=>this._activeBuffer=e.activeBuffer))),this._parser.setCsiHandlerFallback(((e,t)=>{this._logService.debug("Unknown CSI code: ",{identifier:this._parser.identToString(e),params:t.toArray()})})),this._parser.setEscHandlerFallback((e=>{this._logService.debug("Unknown ESC code: ",{identifier:this._parser.identToString(e)})})),this._parser.setExecuteHandlerFallback((e=>{this._logService.debug("Unknown EXECUTE code: ",{code:e})})),this._parser.setOscHandlerFallback(((e,t,i)=>{this._logService.debug("Unknown OSC code: ",{identifier:e,action:t,data:i})})),this._parser.setDcsHandlerFallback(((e,t,i)=>{"HOOK"===t&&(i=i.toArray()),this._logService.debug("Unknown DCS code: ",{identifier:this._parser.identToString(e),action:t,payload:i})})),this._parser.setPrintHandler(((e,t,i)=>this.print(e,t,i))),this._parser.registerCsiHandler({final:"@"},(e=>this.insertChars(e))),this._parser.registerCsiHandler({intermediates:" ",final:"@"},(e=>this.scrollLeft(e))),this._parser.registerCsiHandler({final:"A"},(e=>this.cursorUp(e))),this._parser.registerCsiHandler({intermediates:" ",final:"A"},(e=>this.scrollRight(e))),this._parser.registerCsiHandler({final:"B"},(e=>this.cursorDown(e))),this._parser.registerCsiHandler({final:"C"},(e=>this.cursorForward(e))),this._parser.registerCsiHandler({final:"D"},(e=>this.cursorBackward(e))),this._parser.registerCsiHandler({final:"E"},(e=>this.cursorNextLine(e))),this._parser.registerCsiHandler({final:"F"},(e=>this.cursorPrecedingLine(e))),this._parser.registerCsiHandler({final:"G"},(e=>this.cursorCharAbsolute(e))),this._parser.registerCsiHandler({final:"H"},(e=>this.cursorPosition(e))),this._parser.registerCsiHandler({final:"I"},(e=>this.cursorForwardTab(e))),this._parser.registerCsiHandler({final:"J"},(e=>this.eraseInDisplay(e,!1))),this._parser.registerCsiHandler({prefix:"?",final:"J"},(e=>this.eraseInDisplay(e,!0))),this._parser.registerCsiHandler({final:"K"},(e=>this.eraseInLine(e,!1))),this._parser.registerCsiHandler({prefix:"?",final:"K"},(e=>this.eraseInLine(e,!0))),this._parser.registerCsiHandler({final:"L"},(e=>this.insertLines(e))),this._parser.registerCsiHandler({final:"M"},(e=>this.deleteLines(e))),this._parser.registerCsiHandler({final:"P"},(e=>this.deleteChars(e))),this._parser.registerCsiHandler({final:"S"},(e=>this.scrollUp(e))),this._parser.registerCsiHandler({final:"T"},(e=>this.scrollDown(e))),this._parser.registerCsiHandler({final:"X"},(e=>this.eraseChars(e))),this._parser.registerCsiHandler({final:"Z"},(e=>this.cursorBackwardTab(e))),this._parser.registerCsiHandler({final:"`"},(e=>this.charPosAbsolute(e))),this._parser.registerCsiHandler({final:"a"},(e=>this.hPositionRelative(e))),this._parser.registerCsiHandler({final:"b"},(e=>this.repeatPrecedingCharacter(e))),this._parser.registerCsiHandler({final:"c"},(e=>this.sendDeviceAttributesPrimary(e))),this._parser.registerCsiHandler({prefix:">",final:"c"},(e=>this.sendDeviceAttributesSecondary(e))),this._parser.registerCsiHandler({final:"d"},(e=>this.linePosAbsolute(e))),this._parser.registerCsiHandler({final:"e"},(e=>this.vPositionRelative(e))),this._parser.registerCsiHandler({final:"f"},(e=>this.hVPosition(e))),this._parser.registerCsiHandler({final:"g"},(e=>this.tabClear(e))),this._parser.registerCsiHandler({final:"h"},(e=>this.setMode(e))),this._parser.registerCsiHandler({prefix:"?",final:"h"},(e=>this.setModePrivate(e))),this._parser.registerCsiHandler({final:"l"},(e=>this.resetMode(e))),this._parser.registerCsiHandler({prefix:"?",final:"l"},(e=>this.resetModePrivate(e))),this._parser.registerCsiHandler({final:"m"},(e=>this.charAttributes(e))),this._parser.registerCsiHandler({final:"n"},(e=>this.deviceStatus(e))),this._parser.registerCsiHandler({prefix:"?",final:"n"},(e=>this.deviceStatusPrivate(e))),this._parser.registerCsiHandler({intermediates:"!",final:"p"},(e=>this.softReset(e))),this._parser.registerCsiHandler({intermediates:" ",final:"q"},(e=>this.setCursorStyle(e))),this._parser.registerCsiHandler({final:"r"},(e=>this.setScrollRegion(e))),this._parser.registerCsiHandler({final:"s"},(e=>this.saveCursor(e))),this._parser.registerCsiHandler({final:"t"},(e=>this.windowOptions(e))),this._parser.registerCsiHandler({final:"u"},(e=>this.restoreCursor(e))),this._parser.registerCsiHandler({intermediates:"'",final:"}"},(e=>this.insertColumns(e))),this._parser.registerCsiHandler({intermediates:"'",final:"~"},(e=>this.deleteColumns(e))),this._parser.registerCsiHandler({intermediates:'"',final:"q"},(e=>this.selectProtected(e))),this._parser.registerCsiHandler({intermediates:"$",final:"p"},(e=>this.requestMode(e,!0))),this._parser.registerCsiHandler({prefix:"?",intermediates:"$",final:"p"},(e=>this.requestMode(e,!1))),this._parser.setExecuteHandler(n.C0.BEL,(()=>this.bell())),this._parser.setExecuteHandler(n.C0.LF,(()=>this.lineFeed())),this._parser.setExecuteHandler(n.C0.VT,(()=>this.lineFeed())),this._parser.setExecuteHandler(n.C0.FF,(()=>this.lineFeed())),this._parser.setExecuteHandler(n.C0.CR,(()=>this.carriageReturn())),this._parser.setExecuteHandler(n.C0.BS,(()=>this.backspace())),this._parser.setExecuteHandler(n.C0.HT,(()=>this.tab())),this._parser.setExecuteHandler(n.C0.SO,(()=>this.shiftOut())),this._parser.setExecuteHandler(n.C0.SI,(()=>this.shiftIn())),this._parser.setExecuteHandler(n.C1.IND,(()=>this.index())),this._parser.setExecuteHandler(n.C1.NEL,(()=>this.nextLine())),this._parser.setExecuteHandler(n.C1.HTS,(()=>this.tabSet())),this._parser.registerOscHandler(0,new p.OscHandler((e=>(this.setTitle(e),this.setIconName(e),!0)))),this._parser.registerOscHandler(1,new p.OscHandler((e=>this.setIconName(e)))),this._parser.registerOscHandler(2,new p.OscHandler((e=>this.setTitle(e)))),this._parser.registerOscHandler(4,new p.OscHandler((e=>this.setOrReportIndexedColor(e)))),this._parser.registerOscHandler(8,new p.OscHandler((e=>this.setHyperlink(e)))),this._parser.registerOscHandler(10,new p.OscHandler((e=>this.setOrReportFgColor(e)))),this._parser.registerOscHandler(11,new p.OscHandler((e=>this.setOrReportBgColor(e)))),this._parser.registerOscHandler(12,new p.OscHandler((e=>this.setOrReportCursorColor(e)))),this._parser.registerOscHandler(104,new p.OscHandler((e=>this.restoreIndexedColor(e)))),this._parser.registerOscHandler(110,new p.OscHandler((e=>this.restoreFgColor(e)))),this._parser.registerOscHandler(111,new p.OscHandler((e=>this.restoreBgColor(e)))),this._parser.registerOscHandler(112,new p.OscHandler((e=>this.restoreCursorColor(e)))),this._parser.registerEscHandler({final:"7"},(()=>this.saveCursor())),this._parser.registerEscHandler({final:"8"},(()=>this.restoreCursor())),this._parser.registerEscHandler({final:"D"},(()=>this.index())),this._parser.registerEscHandler({final:"E"},(()=>this.nextLine())),this._parser.registerEscHandler({final:"H"},(()=>this.tabSet())),this._parser.registerEscHandler({final:"M"},(()=>this.reverseIndex())),this._parser.registerEscHandler({final:"="},(()=>this.keypadApplicationMode())),this._parser.registerEscHandler({final:">"},(()=>this.keypadNumericMode())),this._parser.registerEscHandler({final:"c"},(()=>this.fullReset())),this._parser.registerEscHandler({final:"n"},(()=>this.setgLevel(2))),this._parser.registerEscHandler({final:"o"},(()=>this.setgLevel(3))),this._parser.registerEscHandler({final:"|"},(()=>this.setgLevel(3))),this._parser.registerEscHandler({final:"}"},(()=>this.setgLevel(2))),this._parser.registerEscHandler({final:"~"},(()=>this.setgLevel(1))),this._parser.registerEscHandler({intermediates:"%",final:"@"},(()=>this.selectDefaultCharset())),this._parser.registerEscHandler({intermediates:"%",final:"G"},(()=>this.selectDefaultCharset()));for(const e in o.CHARSETS)this._parser.registerEscHandler({intermediates:"(",final:e},(()=>this.selectCharset("("+e))),this._parser.registerEscHandler({intermediates:")",final:e},(()=>this.selectCharset(")"+e))),this._parser.registerEscHandler({intermediates:"*",final:e},(()=>this.selectCharset("*"+e))),this._parser.registerEscHandler({intermediates:"+",final:e},(()=>this.selectCharset("+"+e))),this._parser.registerEscHandler({intermediates:"-",final:e},(()=>this.selectCharset("-"+e))),this._parser.registerEscHandler({intermediates:".",final:e},(()=>this.selectCharset("."+e))),this._parser.registerEscHandler({intermediates:"/",final:e},(()=>this.selectCharset("/"+e)));this._parser.registerEscHandler({intermediates:"#",final:"8"},(()=>this.screenAlignmentPattern())),this._parser.setErrorHandler((e=>(this._logService.error("Parsing error: ",e),e))),this._parser.registerDcsHandler({intermediates:"$",final:"q"},new g.DcsHandler(((e,t)=>this.requestStatusString(e,t))))}_preserveStack(e,t,i,s){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=t,this._parseStack.decodedLength=i,this._parseStack.position=s}_logSlowResolvingAsync(e){this._logService.logLevel<=v.LogLevelEnum.WARN&&Promise.race([e,new Promise(((e,t)=>setTimeout((()=>t("#SLOW_TIMEOUT")),5e3)))]).catch((e=>{if("#SLOW_TIMEOUT"!==e)throw e;console.warn("async parser handler taking longer than 5000 ms")}))}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,t){let i,s=this._activeBuffer.x,r=this._activeBuffer.y,n=0;const o=this._parseStack.paused;if(o){if(i=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,t))return this._logSlowResolvingAsync(i),i;s=this._parseStack.cursorStartX,r=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>C&&(n=this._parseStack.position+C)}if(this._logService.logLevel<=v.LogLevelEnum.DEBUG&&this._logService.debug("parsing data"+("string"==typeof e?` "${e}"`:` "${Array.prototype.map.call(e,(e=>String.fromCharCode(e))).join("")}"`),"string"==typeof e?e.split("").map((e=>e.charCodeAt(0))):e),this._parseBuffer.lengthC)for(let t=n;t0&&2===u.getWidth(this._activeBuffer.x-1)&&u.setCellFromCodePoint(this._activeBuffer.x-1,0,1,d.fg,d.bg,d.extended);for(let f=t;f=a)if(h){for(;this._activeBuffer.x=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),u=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y)}else if(this._activeBuffer.x=a-1,2===r)continue;if(l&&(u.insertCells(this._activeBuffer.x,r,this._activeBuffer.getNullCell(d),d),2===u.getWidth(a-1)&&u.setCellFromCodePoint(a-1,_.NULL_CELL_CODE,_.NULL_CELL_WIDTH,d.fg,d.bg,d.extended)),u.setCellFromCodePoint(this._activeBuffer.x++,s,r,d.fg,d.bg,d.extended),r>0)for(;--r;)u.setCellFromCodePoint(this._activeBuffer.x++,0,0,d.fg,d.bg,d.extended)}else u.getWidth(this._activeBuffer.x-1)?u.addCodepointToCell(this._activeBuffer.x-1,s):u.addCodepointToCell(this._activeBuffer.x-2,s)}i-t>0&&(u.loadCell(this._activeBuffer.x-1,this._workCell),2===this._workCell.getWidth()||this._workCell.getCode()>65535?this._parser.precedingCodepoint=0:this._workCell.isCombined()?this._parser.precedingCodepoint=this._workCell.getChars().charCodeAt(0):this._parser.precedingCodepoint=this._workCell.content),this._activeBuffer.x0&&0===u.getWidth(this._activeBuffer.x)&&!u.hasContent(this._activeBuffer.x)&&u.setCellFromCodePoint(this._activeBuffer.x,0,1,d.fg,d.bg,d.extended),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,t){return"t"!==e.final||e.prefix||e.intermediates?this._parser.registerCsiHandler(e,t):this._parser.registerCsiHandler(e,(e=>!b(e.params[0],this._optionsService.rawOptions.windowOptions)||t(e)))}registerDcsHandler(e,t){return this._parser.registerDcsHandler(e,new g.DcsHandler(t))}registerEscHandler(e,t){return this._parser.registerEscHandler(e,t)}registerOscHandler(e,t){return this._parser.registerOscHandler(e,new p.OscHandler(t))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){var e;if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(0===this._activeBuffer.x&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&(null===(e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y))||void 0===e?void 0:e.isWrapped)){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;const e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);e.hasWidth(this._activeBuffer.x)&&!e.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;const e=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-e),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(e=this._bufferService.cols-1){this._activeBuffer.x=Math.min(e,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(e,t){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+t):(this._activeBuffer.x=e,this._activeBuffer.y=t),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,t){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+t)}cursorUp(e){const t=this._activeBuffer.y-this._activeBuffer.scrollTop;return t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){const t=this._activeBuffer.scrollBottom-this._activeBuffer.y;return t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1),!0}cursorForward(e){return this._moveCursor(e.params[0]||1,0),!0}cursorBackward(e){return this._moveCursor(-(e.params[0]||1),0),!0}cursorNextLine(e){return this.cursorDown(e),this._activeBuffer.x=0,!0}cursorPrecedingLine(e){return this.cursorUp(e),this._activeBuffer.x=0,!0}cursorCharAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(e){return this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1),!0}charPosAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(e){return this._moveCursor(e.params[0]||1,0),!0}linePosAbsolute(e){return this._setCursor(this._activeBuffer.x,(e.params[0]||1)-1),!0}vPositionRelative(e){return this._moveCursor(0,e.params[0]||1),!0}hVPosition(e){return this.cursorPosition(e),!0}tabClear(e){const t=e.params[0];return 0===t?delete this._activeBuffer.tabs[this._activeBuffer.x]:3===t&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){const t=e.params[0];return 1===t&&(this._curAttrData.bg|=536870912),2!==t&&0!==t||(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,t,i,s=!1,r=!1){const n=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);n.replaceCells(t,i,this._activeBuffer.getNullCell(this._eraseAttrData()),this._eraseAttrData(),r),s&&(n.isWrapped=!1)}_resetBufferLine(e,t=!1){const i=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);i&&(i.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),t),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),i.isWrapped=!1)}eraseInDisplay(e,t=!1){let i;switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:for(i=this._activeBuffer.y,this._dirtyRowTracker.markDirty(i),this._eraseInBufferLine(i++,this._activeBuffer.x,this._bufferService.cols,0===this._activeBuffer.x,t);i=this._bufferService.cols&&(this._activeBuffer.lines.get(i+1).isWrapped=!1);i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0);break;case 2:for(i=this._bufferService.rows,this._dirtyRowTracker.markDirty(i-1);i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0);break;case 3:const e=this._activeBuffer.lines.length-this._bufferService.rows;e>0&&(this._activeBuffer.lines.trimStart(e),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-e,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-e,0),this._onScroll.fire(0))}return!0}eraseInLine(e,t=!1){switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,0===this._activeBuffer.x,t);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,t);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,t)}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let t=e.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent(n.C0.ESC+"[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent(n.C0.ESC+"[?6c")),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent(n.C0.ESC+"[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent(n.C0.ESC+"[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent(n.C0.ESC+"[>83;40003;0c")),!0}_is(e){return 0===(this._optionsService.rawOptions.termName+"").indexOf(e)}setMode(e){for(let t=0;te?1:2,u=e.params[0];return f=u,v=t?2===u?4:4===u?_(o.modes.insertMode):12===u?3:20===u?_(d.convertEol):0:1===u?_(i.applicationCursorKeys):3===u?d.windowOptions.setWinLines?80===h?2:132===h?1:0:0:6===u?_(i.origin):7===u?_(i.wraparound):8===u?3:9===u?_("X10"===s):12===u?_(d.cursorBlink):25===u?_(!o.isCursorHidden):45===u?_(i.reverseWraparound):66===u?_(i.applicationKeypad):67===u?4:1e3===u?_("VT200"===s):1002===u?_("DRAG"===s):1003===u?_("ANY"===s):1004===u?_(i.sendFocus):1005===u?4:1006===u?_("SGR"===r):1015===u?4:1016===u?_("SGR_PIXELS"===r):1048===u?1:47===u||1047===u||1049===u?_(c===l):2004===u?_(i.bracketedPasteMode):0,o.triggerDataEvent(`${n.C0.ESC}[${t?"":"?"}${f};${v}$y`),!0;var f,v}_updateAttrColor(e,t,i,s,r){return 2===t?(e|=50331648,e&=-16777216,e|=f.AttributeData.fromColorRGB([i,s,r])):5===t&&(e&=-50331904,e|=33554432|255&i),e}_extractColor(e,t,i){const s=[0,0,-1,0,0,0];let r=0,n=0;do{if(s[n+r]=e.params[t+n],e.hasSubParams(t+n)){const i=e.getSubParams(t+n);let o=0;do{5===s[1]&&(r=1),s[n+o+1+r]=i[o]}while(++o=2||2===s[1]&&n+r>=5)break;s[1]&&(r=1)}while(++n+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,0===e&&(t.fg&=-268435457),t.updateExtended()}_processSGR0(e){e.fg=l.DEFAULT_ATTR_DATA.fg,e.bg=l.DEFAULT_ATTR_DATA.bg,e.extended=e.extended.clone(),e.extended.underlineStyle=0,e.extended.underlineColor&=-67108864,e.updateExtended()}charAttributes(e){if(1===e.length&&0===e.params[0])return this._processSGR0(this._curAttrData),!0;const t=e.length;let i;const s=this._curAttrData;for(let r=0;r=30&&i<=37?(s.fg&=-50331904,s.fg|=16777216|i-30):i>=40&&i<=47?(s.bg&=-50331904,s.bg|=16777216|i-40):i>=90&&i<=97?(s.fg&=-50331904,s.fg|=16777224|i-90):i>=100&&i<=107?(s.bg&=-50331904,s.bg|=16777224|i-100):0===i?this._processSGR0(s):1===i?s.fg|=134217728:3===i?s.bg|=67108864:4===i?(s.fg|=268435456,this._processUnderline(e.hasSubParams(r)?e.getSubParams(r)[0]:1,s)):5===i?s.fg|=536870912:7===i?s.fg|=67108864:8===i?s.fg|=1073741824:9===i?s.fg|=2147483648:2===i?s.bg|=134217728:21===i?this._processUnderline(2,s):22===i?(s.fg&=-134217729,s.bg&=-134217729):23===i?s.bg&=-67108865:24===i?(s.fg&=-268435457,this._processUnderline(0,s)):25===i?s.fg&=-536870913:27===i?s.fg&=-67108865:28===i?s.fg&=-1073741825:29===i?s.fg&=2147483647:39===i?(s.fg&=-67108864,s.fg|=16777215&l.DEFAULT_ATTR_DATA.fg):49===i?(s.bg&=-67108864,s.bg|=16777215&l.DEFAULT_ATTR_DATA.bg):38===i||48===i||58===i?r+=this._extractColor(e,r,s):53===i?s.bg|=1073741824:55===i?s.bg&=-1073741825:59===i?(s.extended=s.extended.clone(),s.extended.underlineColor=-1,s.updateExtended()):100===i?(s.fg&=-67108864,s.fg|=16777215&l.DEFAULT_ATTR_DATA.fg,s.bg&=-67108864,s.bg|=16777215&l.DEFAULT_ATTR_DATA.bg):this._logService.debug("Unknown SGR attribute: %d.",i);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent(`${n.C0.ESC}[0n`);break;case 6:const e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`${n.C0.ESC}[${e};${t}R`)}return!0}deviceStatusPrivate(e){if(6===e.params[0]){const e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`${n.C0.ESC}[?${e};${t}R`)}return!0}softReset(e){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=l.DEFAULT_ATTR_DATA.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(e){const t=e.params[0]||1;switch(t){case 1:case 2:this._optionsService.options.cursorStyle="block";break;case 3:case 4:this._optionsService.options.cursorStyle="underline";break;case 5:case 6:this._optionsService.options.cursorStyle="bar"}const i=t%2==1;return this._optionsService.options.cursorBlink=i,!0}setScrollRegion(e){const t=e.params[0]||1;let i;return(e.length<2||(i=e.params[1])>this._bufferService.rows||0===i)&&(i=this._bufferService.rows),i>t&&(this._activeBuffer.scrollTop=t-1,this._activeBuffer.scrollBottom=i-1,this._setCursor(0,0)),!0}windowOptions(e){if(!b(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;const t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:2!==t&&this._onRequestWindowsOptionsReport.fire(y.GET_WIN_SIZE_PIXELS);break;case 16:this._onRequestWindowsOptionsReport.fire(y.GET_CELL_SIZE_PIXELS);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`${n.C0.ESC}[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:0!==t&&2!==t||(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>10&&this._windowTitleStack.shift()),0!==t&&1!==t||(this._iconNameStack.push(this._iconName),this._iconNameStack.length>10&&this._iconNameStack.shift());break;case 23:0!==t&&2!==t||this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),0!==t&&1!==t||this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop())}return!0}saveCursor(e){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,!0}restoreCursor(e){return this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg,this._charsetService.charset=this._savedCharset,this._activeBuffer.savedCharset&&(this._charsetService.charset=this._activeBuffer.savedCharset),this._restrictCursor(),!0}setTitle(e){return this._windowTitle=e,this._onTitleChange.fire(e),!0}setIconName(e){return this._iconName=e,!0}setOrReportIndexedColor(e){const t=[],i=e.split(";");for(;i.length>1;){const e=i.shift(),s=i.shift();if(/^\d+$/.exec(e)){const i=parseInt(e);if(L(i))if("?"===s)t.push({type:0,index:i});else{const e=(0,m.parseColor)(s);e&&t.push({type:1,index:i,color:e})}}}return t.length&&this._onColor.fire(t),!0}setHyperlink(e){const t=e.split(";");return!(t.length<2)&&(t[1]?this._createHyperlink(t[0],t[1]):!t[0]&&this._finishHyperlink())}_createHyperlink(e,t){this._getCurrentLinkId()&&this._finishHyperlink();const i=e.split(":");let s;const r=i.findIndex((e=>e.startsWith("id=")));return-1!==r&&(s=i[r].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:s,uri:t}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,t){const i=e.split(";");for(let e=0;e=this._specialColors.length);++e,++t)if("?"===i[e])this._onColor.fire([{type:0,index:this._specialColors[t]}]);else{const s=(0,m.parseColor)(i[e]);s&&this._onColor.fire([{type:1,index:this._specialColors[t],color:s}])}return!0}setOrReportFgColor(e){return this._setOrReportSpecialColor(e,0)}setOrReportBgColor(e){return this._setOrReportSpecialColor(e,1)}setOrReportCursorColor(e){return this._setOrReportSpecialColor(e,2)}restoreIndexedColor(e){if(!e)return this._onColor.fire([{type:2}]),!0;const t=[],i=e.split(";");for(let e=0;e=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){const e=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,e,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=l.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=l.DEFAULT_ATTR_DATA.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=67108863&this._curAttrData.bg,this._eraseAttrDataInternal}setgLevel(e){return this._charsetService.setgLevel(e),!0}screenAlignmentPattern(){const e=new u.CellData;e.content=1<<22|"E".charCodeAt(0),e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let t=0;t(this._coreService.triggerDataEvent(`${n.C0.ESC}${e}${n.C0.ESC}\\`),!0))('"q'===e?`P1$r${this._curAttrData.isProtected()?1:0}"q`:'"p'===e?'P1$r61;1"p':"r"===e?`P1$r${i.scrollTop+1};${i.scrollBottom+1}r`:"m"===e?"P1$r0m":" q"===e?`P1$r${{block:2,underline:4,bar:6}[s.cursorStyle]-(s.cursorBlink?1:0)} q`:"P0$r")}markRangeDirty(e,t){this._dirtyRowTracker.markRangeDirty(e,t)}}t.InputHandler=E;let k=class{constructor(e){this._bufferService=e,this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(e){ethis.end&&(this.end=e)}markRangeDirty(e,t){e>t&&(w=e,e=t,t=w),ethis.end&&(this.end=t)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};function L(e){return 0<=e&&e<256}k=s([r(0,v.IBufferService)],k)},844:(e,t)=>{function i(e){for(const t of e)t.dispose();e.length=0}Object.defineProperty(t,"__esModule",{value:!0}),t.getDisposeArrayDisposable=t.disposeArray=t.toDisposable=t.MutableDisposable=t.Disposable=void 0,t.Disposable=class{constructor(){this._disposables=[],this._isDisposed=!1}dispose(){this._isDisposed=!0;for(const e of this._disposables)e.dispose();this._disposables.length=0}register(e){return this._disposables.push(e),e}unregister(e){const t=this._disposables.indexOf(e);-1!==t&&this._disposables.splice(t,1)}},t.MutableDisposable=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(e){var t;this._isDisposed||e===this._value||(null===(t=this._value)||void 0===t||t.dispose(),this._value=e)}clear(){this.value=void 0}dispose(){var e;this._isDisposed=!0,null===(e=this._value)||void 0===e||e.dispose(),this._value=void 0}},t.toDisposable=function(e){return{dispose:e}},t.disposeArray=i,t.getDisposeArrayDisposable=function(e){return{dispose:()=>i(e)}}},1505:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.FourKeyMap=t.TwoKeyMap=void 0;class i{constructor(){this._data={}}set(e,t,i){this._data[e]||(this._data[e]={}),this._data[e][t]=i}get(e,t){return this._data[e]?this._data[e][t]:void 0}clear(){this._data={}}}t.TwoKeyMap=i,t.FourKeyMap=class{constructor(){this._data=new i}set(e,t,s,r,n){this._data.get(e,t)||this._data.set(e,t,new i),this._data.get(e,t).set(s,r,n)}get(e,t,i,s){var r;return null===(r=this._data.get(e,t))||void 0===r?void 0:r.get(i,s)}clear(){this._data.clear()}}},6114:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.isChromeOS=t.isLinux=t.isWindows=t.isIphone=t.isIpad=t.isMac=t.getSafariVersion=t.isSafari=t.isLegacyEdge=t.isFirefox=t.isNode=void 0,t.isNode="undefined"==typeof navigator;const i=t.isNode?"node":navigator.userAgent,s=t.isNode?"node":navigator.platform;t.isFirefox=i.includes("Firefox"),t.isLegacyEdge=i.includes("Edge"),t.isSafari=/^((?!chrome|android).)*safari/i.test(i),t.getSafariVersion=function(){if(!t.isSafari)return 0;const e=i.match(/Version\/(\d+)/);return null===e||e.length<2?0:parseInt(e[1])},t.isMac=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(s),t.isIpad="iPad"===s,t.isIphone="iPhone"===s,t.isWindows=["Windows","Win16","Win32","WinCE"].includes(s),t.isLinux=s.indexOf("Linux")>=0,t.isChromeOS=/\bCrOS\b/.test(i)},6106:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.SortedList=void 0;let i=0;t.SortedList=class{constructor(e){this._getKey=e,this._array=[]}clear(){this._array.length=0}insert(e){0!==this._array.length?(i=this._search(this._getKey(e)),this._array.splice(i,0,e)):this._array.push(e)}delete(e){if(0===this._array.length)return!1;const t=this._getKey(e);if(void 0===t)return!1;if(i=this._search(t),-1===i)return!1;if(this._getKey(this._array[i])!==t)return!1;do{if(this._array[i]===e)return this._array.splice(i,1),!0}while(++i=this._array.length)&&this._getKey(this._array[i])===e))do{yield this._array[i]}while(++i=this._array.length)&&this._getKey(this._array[i])===e))do{t(this._array[i])}while(++i=t;){let s=t+i>>1;const r=this._getKey(this._array[s]);if(r>e)i=s-1;else{if(!(r0&&this._getKey(this._array[s-1])===e;)s--;return s}t=s+1}}return t}}},7226:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DebouncedIdleTask=t.IdleTaskQueue=t.PriorityTaskQueue=void 0;const s=i(6114);class r{constructor(){this._tasks=[],this._i=0}enqueue(e){this._tasks.push(e),this._start()}flush(){for(;this._ir)return s-t<-20&&console.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(s-t))}ms`),void this._start();s=r}this.clear()}}class n extends r{_requestCallback(e){return setTimeout((()=>e(this._createDeadline(16))))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){const t=Date.now()+e;return{timeRemaining:()=>Math.max(0,t-Date.now())}}}t.PriorityTaskQueue=n,t.IdleTaskQueue=!s.isNode&&"requestIdleCallback"in window?class extends r{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}}:n,t.DebouncedIdleTask=class{constructor(){this._queue=new t.IdleTaskQueue}set(e){this._queue.clear(),this._queue.enqueue(e)}flush(){this._queue.flush()}}},9282:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.updateWindowsModeWrappedState=void 0;const s=i(643);t.updateWindowsModeWrappedState=function(e){const t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1),i=null==t?void 0:t.get(e.cols-1),r=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);r&&i&&(r.isWrapped=i[s.CHAR_DATA_CODE_INDEX]!==s.NULL_CELL_CODE&&i[s.CHAR_DATA_CODE_INDEX]!==s.WHITESPACE_CELL_CODE)}},3734:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ExtendedAttrs=t.AttributeData=void 0;class i{constructor(){this.fg=0,this.bg=0,this.extended=new s}static toColorRGB(e){return[e>>>16&255,e>>>8&255,255&e]}static fromColorRGB(e){return(255&e[0])<<16|(255&e[1])<<8|255&e[2]}clone(){const e=new i;return e.fg=this.fg,e.bg=this.bg,e.extended=this.extended.clone(),e}isInverse(){return 67108864&this.fg}isBold(){return 134217728&this.fg}isUnderline(){return this.hasExtendedAttrs()&&0!==this.extended.underlineStyle?1:268435456&this.fg}isBlink(){return 536870912&this.fg}isInvisible(){return 1073741824&this.fg}isItalic(){return 67108864&this.bg}isDim(){return 134217728&this.bg}isStrikethrough(){return 2147483648&this.fg}isProtected(){return 536870912&this.bg}isOverline(){return 1073741824&this.bg}getFgColorMode(){return 50331648&this.fg}getBgColorMode(){return 50331648&this.bg}isFgRGB(){return 50331648==(50331648&this.fg)}isBgRGB(){return 50331648==(50331648&this.bg)}isFgPalette(){return 16777216==(50331648&this.fg)||33554432==(50331648&this.fg)}isBgPalette(){return 16777216==(50331648&this.bg)||33554432==(50331648&this.bg)}isFgDefault(){return 0==(50331648&this.fg)}isBgDefault(){return 0==(50331648&this.bg)}isAttributeDefault(){return 0===this.fg&&0===this.bg}getFgColor(){switch(50331648&this.fg){case 16777216:case 33554432:return 255&this.fg;case 50331648:return 16777215&this.fg;default:return-1}}getBgColor(){switch(50331648&this.bg){case 16777216:case 33554432:return 255&this.bg;case 50331648:return 16777215&this.bg;default:return-1}}hasExtendedAttrs(){return 268435456&this.bg}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(268435456&this.bg&&~this.extended.underlineColor)switch(50331648&this.extended.underlineColor){case 16777216:case 33554432:return 255&this.extended.underlineColor;case 50331648:return 16777215&this.extended.underlineColor;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return 268435456&this.bg&&~this.extended.underlineColor?50331648&this.extended.underlineColor:this.getFgColorMode()}isUnderlineColorRGB(){return 268435456&this.bg&&~this.extended.underlineColor?50331648==(50331648&this.extended.underlineColor):this.isFgRGB()}isUnderlineColorPalette(){return 268435456&this.bg&&~this.extended.underlineColor?16777216==(50331648&this.extended.underlineColor)||33554432==(50331648&this.extended.underlineColor):this.isFgPalette()}isUnderlineColorDefault(){return 268435456&this.bg&&~this.extended.underlineColor?0==(50331648&this.extended.underlineColor):this.isFgDefault()}getUnderlineStyle(){return 268435456&this.fg?268435456&this.bg?this.extended.underlineStyle:1:0}}t.AttributeData=i;class s{get ext(){return this._urlId?-469762049&this._ext|this.underlineStyle<<26:this._ext}set ext(e){this._ext=e}get underlineStyle(){return this._urlId?5:(469762048&this._ext)>>26}set underlineStyle(e){this._ext&=-469762049,this._ext|=e<<26&469762048}get underlineColor(){return 67108863&this._ext}set underlineColor(e){this._ext&=-67108864,this._ext|=67108863&e}get urlId(){return this._urlId}set urlId(e){this._urlId=e}constructor(e=0,t=0){this._ext=0,this._urlId=0,this._ext=e,this._urlId=t}clone(){return new s(this._ext,this._urlId)}isEmpty(){return 0===this.underlineStyle&&0===this._urlId}}t.ExtendedAttrs=s},9092:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Buffer=t.MAX_BUFFER_SIZE=void 0;const s=i(6349),r=i(7226),n=i(3734),o=i(8437),a=i(4634),h=i(511),c=i(643),l=i(4863),d=i(7116);t.MAX_BUFFER_SIZE=4294967295,t.Buffer=class{constructor(e,t,i){this._hasScrollback=e,this._optionsService=t,this._bufferService=i,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.tabs={},this.savedY=0,this.savedX=0,this.savedCurAttrData=o.DEFAULT_ATTR_DATA.clone(),this.savedCharset=d.DEFAULT_CHARSET,this.markers=[],this._nullCell=h.CellData.fromCharData([0,c.NULL_CELL_CHAR,c.NULL_CELL_WIDTH,c.NULL_CELL_CODE]),this._whitespaceCell=h.CellData.fromCharData([0,c.WHITESPACE_CELL_CHAR,c.WHITESPACE_CELL_WIDTH,c.WHITESPACE_CELL_CODE]),this._isClearing=!1,this._memoryCleanupQueue=new r.IdleTaskQueue,this._memoryCleanupPosition=0,this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new s.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}getNullCell(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new n.ExtendedAttrs),this._nullCell}getWhitespaceCell(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new n.ExtendedAttrs),this._whitespaceCell}getBlankLine(e,t){return new o.BufferLine(this._bufferService.cols,this.getNullCell(e),t)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){const e=this.ybase+this.y-this.ydisp;return e>=0&&et.MAX_BUFFER_SIZE?t.MAX_BUFFER_SIZE:i}fillViewportRows(e){if(0===this.lines.length){void 0===e&&(e=o.DEFAULT_ATTR_DATA);let t=this._rows;for(;t--;)this.lines.push(this.getBlankLine(e))}}clear(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new s.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(e,t){const i=this.getNullCell(o.DEFAULT_ATTR_DATA);let s=0;const r=this._getCorrectBufferLength(t);if(r>this.lines.maxLength&&(this.lines.maxLength=r),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+n+1?(this.ybase--,n++,this.ydisp>0&&this.ydisp--):this.lines.push(new o.BufferLine(e,i)));else for(let e=this._rows;e>t;e--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(r0&&(this.lines.trimStart(e),this.ybase=Math.max(this.ybase-e,0),this.ydisp=Math.max(this.ydisp-e,0),this.savedY=Math.max(this.savedY-e,0)),this.lines.maxLength=r}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),n&&(this.y+=n),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(let t=0;t.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue((()=>this._batchedMemoryCleanup())))}_batchedMemoryCleanup(){let e=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,e=!1);let t=0;for(;this._memoryCleanupPosition100)return!0;return e}get _isReflowEnabled(){const e=this._optionsService.rawOptions.windowsPty;return e&&e.buildNumber?this._hasScrollback&&"conpty"===e.backend&&e.buildNumber>=21376:this._hasScrollback&&!this._optionsService.rawOptions.windowsMode}_reflow(e,t){this._cols!==e&&(e>this._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))}_reflowLarger(e,t){const i=(0,a.reflowLargerGetLinesToRemove)(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(o.DEFAULT_ATTR_DATA));if(i.length>0){const s=(0,a.reflowLargerCreateNewLayout)(this.lines,i);(0,a.reflowLargerApplyNewLayout)(this.lines,s.layout),this._reflowLargerAdjustViewport(e,t,s.countRemoved)}}_reflowLargerAdjustViewport(e,t,i){const s=this.getNullCell(o.DEFAULT_ATTR_DATA);let r=i;for(;r-- >0;)0===this.ybase?(this.y>0&&this.y--,this.lines.length=0;n--){let h=this.lines.get(n);if(!h||!h.isWrapped&&h.getTrimmedLength()<=e)continue;const c=[h];for(;h.isWrapped&&n>0;)h=this.lines.get(--n),c.unshift(h);const l=this.ybase+this.y;if(l>=n&&l0&&(s.push({start:n+c.length+r,newLines:v}),r+=v.length),c.push(...v);let p=_.length-1,g=_[p];0===g&&(p--,g=_[p]);let m=c.length-u-1,S=d;for(;m>=0;){const e=Math.min(S,g);if(void 0===c[p])break;if(c[p].copyCellsFrom(c[m],S-e,g-e,e,!0),g-=e,0===g&&(p--,g=_[p]),S-=e,0===S){m--;const e=Math.max(m,0);S=(0,a.getWrappedLineTrimmedLength)(c,e,this._cols)}}for(let t=0;t0;)0===this.ybase?this.y0){const e=[],t=[];for(let e=0;e=0;c--)if(a&&a.start>n+h){for(let e=a.newLines.length-1;e>=0;e--)this.lines.set(c--,a.newLines[e]);c++,e.push({index:n+1,amount:a.newLines.length}),h+=a.newLines.length,a=s[++o]}else this.lines.set(c,t[n--]);let c=0;for(let t=e.length-1;t>=0;t--)e[t].index+=c,this.lines.onInsertEmitter.fire(e[t]),c+=e[t].amount;const l=Math.max(0,i+r-this.lines.maxLength);l>0&&this.lines.onTrimEmitter.fire(l)}}translateBufferLineToString(e,t,i=0,s){const r=this.lines.get(e);return r?r.translateToString(t,i,s):""}getWrappedRangeForLine(e){let t=e,i=e;for(;t>0&&this.lines.get(t).isWrapped;)t--;for(;i+10;);return e>=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(null==e&&(e=this.x);!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e}clearMarkers(e){this._isClearing=!0;for(let t=0;t{t.line-=e,t.line<0&&t.dispose()}))),t.register(this.lines.onInsert((e=>{t.line>=e.index&&(t.line+=e.amount)}))),t.register(this.lines.onDelete((e=>{t.line>=e.index&&t.linee.index&&(t.line-=e.amount)}))),t.register(t.onDispose((()=>this._removeMarker(t)))),t}_removeMarker(e){this._isClearing||this.markers.splice(this.markers.indexOf(e),1)}}},8437:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BufferLine=t.DEFAULT_ATTR_DATA=void 0;const s=i(3734),r=i(511),n=i(643),o=i(482);t.DEFAULT_ATTR_DATA=Object.freeze(new s.AttributeData);let a=0;class h{constructor(e,t,i=!1){this.isWrapped=i,this._combined={},this._extendedAttrs={},this._data=new Uint32Array(3*e);const s=t||r.CellData.fromCharData([0,n.NULL_CELL_CHAR,n.NULL_CELL_WIDTH,n.NULL_CELL_CODE]);for(let t=0;t>22,2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):i]}set(e,t){this._data[3*e+1]=t[n.CHAR_DATA_ATTR_INDEX],t[n.CHAR_DATA_CHAR_INDEX].length>1?(this._combined[e]=t[1],this._data[3*e+0]=2097152|e|t[n.CHAR_DATA_WIDTH_INDEX]<<22):this._data[3*e+0]=t[n.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|t[n.CHAR_DATA_WIDTH_INDEX]<<22}getWidth(e){return this._data[3*e+0]>>22}hasWidth(e){return 12582912&this._data[3*e+0]}getFg(e){return this._data[3*e+1]}getBg(e){return this._data[3*e+2]}hasContent(e){return 4194303&this._data[3*e+0]}getCodePoint(e){const t=this._data[3*e+0];return 2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):2097151&t}isCombined(e){return 2097152&this._data[3*e+0]}getString(e){const t=this._data[3*e+0];return 2097152&t?this._combined[e]:2097151&t?(0,o.stringFromCodePoint)(2097151&t):""}isProtected(e){return 536870912&this._data[3*e+2]}loadCell(e,t){return a=3*e,t.content=this._data[a+0],t.fg=this._data[a+1],t.bg=this._data[a+2],2097152&t.content&&(t.combinedData=this._combined[e]),268435456&t.bg&&(t.extended=this._extendedAttrs[e]),t}setCell(e,t){2097152&t.content&&(this._combined[e]=t.combinedData),268435456&t.bg&&(this._extendedAttrs[e]=t.extended),this._data[3*e+0]=t.content,this._data[3*e+1]=t.fg,this._data[3*e+2]=t.bg}setCellFromCodePoint(e,t,i,s,r,n){268435456&r&&(this._extendedAttrs[e]=n),this._data[3*e+0]=t|i<<22,this._data[3*e+1]=s,this._data[3*e+2]=r}addCodepointToCell(e,t){let i=this._data[3*e+0];2097152&i?this._combined[e]+=(0,o.stringFromCodePoint)(t):(2097151&i?(this._combined[e]=(0,o.stringFromCodePoint)(2097151&i)+(0,o.stringFromCodePoint)(t),i&=-2097152,i|=2097152):i=t|1<<22,this._data[3*e+0]=i)}insertCells(e,t,i,n){if((e%=this.length)&&2===this.getWidth(e-1)&&this.setCellFromCodePoint(e-1,0,1,(null==n?void 0:n.fg)||0,(null==n?void 0:n.bg)||0,(null==n?void 0:n.extended)||new s.ExtendedAttrs),t=0;--i)this.setCell(e+t+i,this.loadCell(e+i,s));for(let s=0;sthis.length){if(this._data.buffer.byteLength>=4*i)this._data=new Uint32Array(this._data.buffer,0,i);else{const e=new Uint32Array(i);e.set(this._data),this._data=e}for(let i=this.length;i=e&&delete this._combined[s]}const s=Object.keys(this._extendedAttrs);for(let t=0;t=e&&delete this._extendedAttrs[i]}}return this.length=e,4*i*2=0;--e)if(4194303&this._data[3*e+0])return e+(this._data[3*e+0]>>22);return 0}getNoBgTrimmedLength(){for(let e=this.length-1;e>=0;--e)if(4194303&this._data[3*e+0]||50331648&this._data[3*e+2])return e+(this._data[3*e+0]>>22);return 0}copyCellsFrom(e,t,i,s,r){const n=e._data;if(r)for(let r=s-1;r>=0;r--){for(let e=0;e<3;e++)this._data[3*(i+r)+e]=n[3*(t+r)+e];268435456&n[3*(t+r)+2]&&(this._extendedAttrs[i+r]=e._extendedAttrs[t+r])}else for(let r=0;r=t&&(this._combined[r-t+i]=e._combined[r])}}translateToString(e=!1,t=0,i=this.length){e&&(i=Math.min(i,this.getTrimmedLength()));let s="";for(;t>22||1}return s}}t.BufferLine=h},4841:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.getRangeLength=void 0,t.getRangeLength=function(e,t){if(e.start.y>e.end.y)throw new Error(`Buffer range end (${e.end.x}, ${e.end.y}) cannot be before start (${e.start.x}, ${e.start.y})`);return t*(e.end.y-e.start.y)+(e.end.x-e.start.x+1)}},4634:(e,t)=>{function i(e,t,i){if(t===e.length-1)return e[t].getTrimmedLength();const s=!e[t].hasContent(i-1)&&1===e[t].getWidth(i-1),r=2===e[t+1].getWidth(0);return s&&r?i-1:i}Object.defineProperty(t,"__esModule",{value:!0}),t.getWrappedLineTrimmedLength=t.reflowSmallerGetNewLineLengths=t.reflowLargerApplyNewLayout=t.reflowLargerCreateNewLayout=t.reflowLargerGetLinesToRemove=void 0,t.reflowLargerGetLinesToRemove=function(e,t,s,r,n){const o=[];for(let a=0;a=a&&r0&&(e>d||0===l[e].getTrimmedLength());e--)v++;v>0&&(o.push(a+l.length-v),o.push(v)),a+=l.length-1}return o},t.reflowLargerCreateNewLayout=function(e,t){const i=[];let s=0,r=t[s],n=0;for(let o=0;oi(e,r,t))).reduce(((e,t)=>e+t));let o=0,a=0,h=0;for(;hc&&(o-=c,a++);const l=2===e[a].getWidth(o-1);l&&o--;const d=l?s-1:s;r.push(d),h+=d}return r},t.getWrappedLineTrimmedLength=i},5295:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BufferSet=void 0;const s=i(8460),r=i(844),n=i(9092);class o extends r.Disposable{constructor(e,t){super(),this._optionsService=e,this._bufferService=t,this._onBufferActivate=this.register(new s.EventEmitter),this.onBufferActivate=this._onBufferActivate.event,this.reset(),this.register(this._optionsService.onSpecificOptionChange("scrollback",(()=>this.resize(this._bufferService.cols,this._bufferService.rows)))),this.register(this._optionsService.onSpecificOptionChange("tabStopWidth",(()=>this.setupTabStops())))}reset(){this._normal=new n.Buffer(!0,this._optionsService,this._bufferService),this._normal.fillViewportRows(),this._alt=new n.Buffer(!1,this._optionsService,this._bufferService),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}}t.BufferSet=o},511:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CellData=void 0;const s=i(482),r=i(643),n=i(3734);class o extends n.AttributeData{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new n.ExtendedAttrs,this.combinedData=""}static fromCharData(e){const t=new o;return t.setFromCharData(e),t}isCombined(){return 2097152&this.content}getWidth(){return this.content>>22}getChars(){return 2097152&this.content?this.combinedData:2097151&this.content?(0,s.stringFromCodePoint)(2097151&this.content):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):2097151&this.content}setFromCharData(e){this.fg=e[r.CHAR_DATA_ATTR_INDEX],this.bg=0;let t=!1;if(e[r.CHAR_DATA_CHAR_INDEX].length>2)t=!0;else if(2===e[r.CHAR_DATA_CHAR_INDEX].length){const i=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0);if(55296<=i&&i<=56319){const s=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(1);56320<=s&&s<=57343?this.content=1024*(i-55296)+s-56320+65536|e[r.CHAR_DATA_WIDTH_INDEX]<<22:t=!0}else t=!0}else this.content=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|e[r.CHAR_DATA_WIDTH_INDEX]<<22;t&&(this.combinedData=e[r.CHAR_DATA_CHAR_INDEX],this.content=2097152|e[r.CHAR_DATA_WIDTH_INDEX]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}}t.CellData=o},643:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.WHITESPACE_CELL_CODE=t.WHITESPACE_CELL_WIDTH=t.WHITESPACE_CELL_CHAR=t.NULL_CELL_CODE=t.NULL_CELL_WIDTH=t.NULL_CELL_CHAR=t.CHAR_DATA_CODE_INDEX=t.CHAR_DATA_WIDTH_INDEX=t.CHAR_DATA_CHAR_INDEX=t.CHAR_DATA_ATTR_INDEX=t.DEFAULT_EXT=t.DEFAULT_ATTR=t.DEFAULT_COLOR=void 0,t.DEFAULT_COLOR=0,t.DEFAULT_ATTR=256|t.DEFAULT_COLOR<<9,t.DEFAULT_EXT=0,t.CHAR_DATA_ATTR_INDEX=0,t.CHAR_DATA_CHAR_INDEX=1,t.CHAR_DATA_WIDTH_INDEX=2,t.CHAR_DATA_CODE_INDEX=3,t.NULL_CELL_CHAR="",t.NULL_CELL_WIDTH=1,t.NULL_CELL_CODE=0,t.WHITESPACE_CELL_CHAR=" ",t.WHITESPACE_CELL_WIDTH=1,t.WHITESPACE_CELL_CODE=32},4863:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Marker=void 0;const s=i(8460),r=i(844);class n{get id(){return this._id}constructor(e){this.line=e,this.isDisposed=!1,this._disposables=[],this._id=n._nextId++,this._onDispose=this.register(new s.EventEmitter),this.onDispose=this._onDispose.event}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),(0,r.disposeArray)(this._disposables),this._disposables.length=0)}register(e){return this._disposables.push(e),e}}t.Marker=n,n._nextId=1},7116:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DEFAULT_CHARSET=t.CHARSETS=void 0,t.CHARSETS={},t.DEFAULT_CHARSET=t.CHARSETS.B,t.CHARSETS[0]={"`":"◆",a:"▒",b:"␉",c:"␌",d:"␍",e:"␊",f:"°",g:"±",h:"␤",i:"␋",j:"┘",k:"┐",l:"┌",m:"└",n:"┼",o:"⎺",p:"⎻",q:"─",r:"⎼",s:"⎽",t:"├",u:"┤",v:"┴",w:"┬",x:"│",y:"≤",z:"≥","{":"π","|":"≠","}":"£","~":"·"},t.CHARSETS.A={"#":"£"},t.CHARSETS.B=void 0,t.CHARSETS[4]={"#":"£","@":"¾","[":"ij","\\":"½","]":"|","{":"¨","|":"f","}":"¼","~":"´"},t.CHARSETS.C=t.CHARSETS[5]={"[":"Ä","\\":"Ö","]":"Å","^":"Ü","`":"é","{":"ä","|":"ö","}":"å","~":"ü"},t.CHARSETS.R={"#":"£","@":"à","[":"°","\\":"ç","]":"§","{":"é","|":"ù","}":"è","~":"¨"},t.CHARSETS.Q={"@":"à","[":"â","\\":"ç","]":"ê","^":"î","`":"ô","{":"é","|":"ù","}":"è","~":"û"},t.CHARSETS.K={"@":"§","[":"Ä","\\":"Ö","]":"Ü","{":"ä","|":"ö","}":"ü","~":"ß"},t.CHARSETS.Y={"#":"£","@":"§","[":"°","\\":"ç","]":"é","`":"ù","{":"à","|":"ò","}":"è","~":"ì"},t.CHARSETS.E=t.CHARSETS[6]={"@":"Ä","[":"Æ","\\":"Ø","]":"Å","^":"Ü","`":"ä","{":"æ","|":"ø","}":"å","~":"ü"},t.CHARSETS.Z={"#":"£","@":"§","[":"¡","\\":"Ñ","]":"¿","{":"°","|":"ñ","}":"ç"},t.CHARSETS.H=t.CHARSETS[7]={"@":"É","[":"Ä","\\":"Ö","]":"Å","^":"Ü","`":"é","{":"ä","|":"ö","}":"å","~":"ü"},t.CHARSETS["="]={"#":"ù","@":"à","[":"é","\\":"ç","]":"ê","^":"î",_:"è","`":"ô","{":"ä","|":"ö","}":"ü","~":"û"}},2584:(e,t)=>{var i,s,r;Object.defineProperty(t,"__esModule",{value:!0}),t.C1_ESCAPED=t.C1=t.C0=void 0,function(e){e.NUL="\0",e.SOH="",e.STX="",e.ETX="",e.EOT="",e.ENQ="",e.ACK="",e.BEL="",e.BS="\b",e.HT="\t",e.LF="\n",e.VT="\v",e.FF="\f",e.CR="\r",e.SO="",e.SI="",e.DLE="",e.DC1="",e.DC2="",e.DC3="",e.DC4="",e.NAK="",e.SYN="",e.ETB="",e.CAN="",e.EM="",e.SUB="",e.ESC="",e.FS="",e.GS="",e.RS="",e.US="",e.SP=" ",e.DEL=""}(i||(t.C0=i={})),function(e){e.PAD="€",e.HOP="",e.BPH="‚",e.NBH="ƒ",e.IND="„",e.NEL="…",e.SSA="†",e.ESA="‡",e.HTS="ˆ",e.HTJ="‰",e.VTS="Š",e.PLD="‹",e.PLU="Œ",e.RI="",e.SS2="Ž",e.SS3="",e.DCS="",e.PU1="‘",e.PU2="’",e.STS="“",e.CCH="”",e.MW="•",e.SPA="–",e.EPA="—",e.SOS="˜",e.SGCI="™",e.SCI="š",e.CSI="›",e.ST="œ",e.OSC="",e.PM="ž",e.APC="Ÿ"}(s||(t.C1=s={})),function(e){e.ST=`${i.ESC}\\`}(r||(t.C1_ESCAPED=r={}))},7399:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.evaluateKeyboardEvent=void 0;const s=i(2584),r={48:["0",")"],49:["1","!"],50:["2","@"],51:["3","#"],52:["4","$"],53:["5","%"],54:["6","^"],55:["7","&"],56:["8","*"],57:["9","("],186:[";",":"],187:["=","+"],188:[",","<"],189:["-","_"],190:[".",">"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']};t.evaluateKeyboardEvent=function(e,t,i,n){const o={type:0,cancel:!1,key:void 0},a=(e.shiftKey?1:0)|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:"UIKeyInputUpArrow"===e.key?o.key=t?s.C0.ESC+"OA":s.C0.ESC+"[A":"UIKeyInputLeftArrow"===e.key?o.key=t?s.C0.ESC+"OD":s.C0.ESC+"[D":"UIKeyInputRightArrow"===e.key?o.key=t?s.C0.ESC+"OC":s.C0.ESC+"[C":"UIKeyInputDownArrow"===e.key&&(o.key=t?s.C0.ESC+"OB":s.C0.ESC+"[B");break;case 8:if(e.altKey){o.key=s.C0.ESC+s.C0.DEL;break}o.key=s.C0.DEL;break;case 9:if(e.shiftKey){o.key=s.C0.ESC+"[Z";break}o.key=s.C0.HT,o.cancel=!0;break;case 13:o.key=e.altKey?s.C0.ESC+s.C0.CR:s.C0.CR,o.cancel=!0;break;case 27:o.key=s.C0.ESC,e.altKey&&(o.key=s.C0.ESC+s.C0.ESC),o.cancel=!0;break;case 37:if(e.metaKey)break;a?(o.key=s.C0.ESC+"[1;"+(a+1)+"D",o.key===s.C0.ESC+"[1;3D"&&(o.key=s.C0.ESC+(i?"b":"[1;5D"))):o.key=t?s.C0.ESC+"OD":s.C0.ESC+"[D";break;case 39:if(e.metaKey)break;a?(o.key=s.C0.ESC+"[1;"+(a+1)+"C",o.key===s.C0.ESC+"[1;3C"&&(o.key=s.C0.ESC+(i?"f":"[1;5C"))):o.key=t?s.C0.ESC+"OC":s.C0.ESC+"[C";break;case 38:if(e.metaKey)break;a?(o.key=s.C0.ESC+"[1;"+(a+1)+"A",i||o.key!==s.C0.ESC+"[1;3A"||(o.key=s.C0.ESC+"[1;5A")):o.key=t?s.C0.ESC+"OA":s.C0.ESC+"[A";break;case 40:if(e.metaKey)break;a?(o.key=s.C0.ESC+"[1;"+(a+1)+"B",i||o.key!==s.C0.ESC+"[1;3B"||(o.key=s.C0.ESC+"[1;5B")):o.key=t?s.C0.ESC+"OB":s.C0.ESC+"[B";break;case 45:e.shiftKey||e.ctrlKey||(o.key=s.C0.ESC+"[2~");break;case 46:o.key=a?s.C0.ESC+"[3;"+(a+1)+"~":s.C0.ESC+"[3~";break;case 36:o.key=a?s.C0.ESC+"[1;"+(a+1)+"H":t?s.C0.ESC+"OH":s.C0.ESC+"[H";break;case 35:o.key=a?s.C0.ESC+"[1;"+(a+1)+"F":t?s.C0.ESC+"OF":s.C0.ESC+"[F";break;case 33:e.shiftKey?o.type=2:e.ctrlKey?o.key=s.C0.ESC+"[5;"+(a+1)+"~":o.key=s.C0.ESC+"[5~";break;case 34:e.shiftKey?o.type=3:e.ctrlKey?o.key=s.C0.ESC+"[6;"+(a+1)+"~":o.key=s.C0.ESC+"[6~";break;case 112:o.key=a?s.C0.ESC+"[1;"+(a+1)+"P":s.C0.ESC+"OP";break;case 113:o.key=a?s.C0.ESC+"[1;"+(a+1)+"Q":s.C0.ESC+"OQ";break;case 114:o.key=a?s.C0.ESC+"[1;"+(a+1)+"R":s.C0.ESC+"OR";break;case 115:o.key=a?s.C0.ESC+"[1;"+(a+1)+"S":s.C0.ESC+"OS";break;case 116:o.key=a?s.C0.ESC+"[15;"+(a+1)+"~":s.C0.ESC+"[15~";break;case 117:o.key=a?s.C0.ESC+"[17;"+(a+1)+"~":s.C0.ESC+"[17~";break;case 118:o.key=a?s.C0.ESC+"[18;"+(a+1)+"~":s.C0.ESC+"[18~";break;case 119:o.key=a?s.C0.ESC+"[19;"+(a+1)+"~":s.C0.ESC+"[19~";break;case 120:o.key=a?s.C0.ESC+"[20;"+(a+1)+"~":s.C0.ESC+"[20~";break;case 121:o.key=a?s.C0.ESC+"[21;"+(a+1)+"~":s.C0.ESC+"[21~";break;case 122:o.key=a?s.C0.ESC+"[23;"+(a+1)+"~":s.C0.ESC+"[23~";break;case 123:o.key=a?s.C0.ESC+"[24;"+(a+1)+"~":s.C0.ESC+"[24~";break;default:if(!e.ctrlKey||e.shiftKey||e.altKey||e.metaKey)if(i&&!n||!e.altKey||e.metaKey)!i||e.altKey||e.ctrlKey||e.shiftKey||!e.metaKey?e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&1===e.key.length?o.key=e.key:e.key&&e.ctrlKey&&("_"===e.key&&(o.key=s.C0.US),"@"===e.key&&(o.key=s.C0.NUL)):65===e.keyCode&&(o.type=1);else{const t=r[e.keyCode],i=null==t?void 0:t[e.shiftKey?1:0];if(i)o.key=s.C0.ESC+i;else if(e.keyCode>=65&&e.keyCode<=90){const t=e.ctrlKey?e.keyCode-64:e.keyCode+32;let i=String.fromCharCode(t);e.shiftKey&&(i=i.toUpperCase()),o.key=s.C0.ESC+i}else if(32===e.keyCode)o.key=s.C0.ESC+(e.ctrlKey?s.C0.NUL:" ");else if("Dead"===e.key&&e.code.startsWith("Key")){let t=e.code.slice(3,4);e.shiftKey||(t=t.toLowerCase()),o.key=s.C0.ESC+t,o.cancel=!0}}else e.keyCode>=65&&e.keyCode<=90?o.key=String.fromCharCode(e.keyCode-64):32===e.keyCode?o.key=s.C0.NUL:e.keyCode>=51&&e.keyCode<=55?o.key=String.fromCharCode(e.keyCode-51+27):56===e.keyCode?o.key=s.C0.DEL:219===e.keyCode?o.key=s.C0.ESC:220===e.keyCode?o.key=s.C0.FS:221===e.keyCode&&(o.key=s.C0.GS)}return o}},482:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Utf8ToUtf32=t.StringToUtf32=t.utf32ToString=t.stringFromCodePoint=void 0,t.stringFromCodePoint=function(e){return e>65535?(e-=65536,String.fromCharCode(55296+(e>>10))+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)},t.utf32ToString=function(e,t=0,i=e.length){let s="";for(let r=t;r65535?(t-=65536,s+=String.fromCharCode(55296+(t>>10))+String.fromCharCode(t%1024+56320)):s+=String.fromCharCode(t)}return s},t.StringToUtf32=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){const i=e.length;if(!i)return 0;let s=0,r=0;if(this._interim){const i=e.charCodeAt(r++);56320<=i&&i<=57343?t[s++]=1024*(this._interim-55296)+i-56320+65536:(t[s++]=this._interim,t[s++]=i),this._interim=0}for(let n=r;n=i)return this._interim=r,s;const o=e.charCodeAt(n);56320<=o&&o<=57343?t[s++]=1024*(r-55296)+o-56320+65536:(t[s++]=r,t[s++]=o)}else 65279!==r&&(t[s++]=r)}return s}},t.Utf8ToUtf32=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){const i=e.length;if(!i)return 0;let s,r,n,o,a=0,h=0,c=0;if(this.interim[0]){let s=!1,r=this.interim[0];r&=192==(224&r)?31:224==(240&r)?15:7;let n,o=0;for(;(n=63&this.interim[++o])&&o<4;)r<<=6,r|=n;const h=192==(224&this.interim[0])?2:224==(240&this.interim[0])?3:4,l=h-o;for(;c=i)return 0;if(n=e[c++],128!=(192&n)){c--,s=!0;break}this.interim[o++]=n,r<<=6,r|=63&n}s||(2===h?r<128?c--:t[a++]=r:3===h?r<2048||r>=55296&&r<=57343||65279===r||(t[a++]=r):r<65536||r>1114111||(t[a++]=r)),this.interim.fill(0)}const l=i-4;let d=c;for(;d=i)return this.interim[0]=s,a;if(r=e[d++],128!=(192&r)){d--;continue}if(h=(31&s)<<6|63&r,h<128){d--;continue}t[a++]=h}else if(224==(240&s)){if(d>=i)return this.interim[0]=s,a;if(r=e[d++],128!=(192&r)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,a;if(n=e[d++],128!=(192&n)){d--;continue}if(h=(15&s)<<12|(63&r)<<6|63&n,h<2048||h>=55296&&h<=57343||65279===h)continue;t[a++]=h}else if(240==(248&s)){if(d>=i)return this.interim[0]=s,a;if(r=e[d++],128!=(192&r)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,a;if(n=e[d++],128!=(192&n)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,this.interim[2]=n,a;if(o=e[d++],128!=(192&o)){d--;continue}if(h=(7&s)<<18|(63&r)<<12|(63&n)<<6|63&o,h<65536||h>1114111)continue;t[a++]=h}}return a}}},225:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV6=void 0;const i=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],s=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]];let r;t.UnicodeV6=class{constructor(){if(this.version="6",!r){r=new Uint8Array(65536),r.fill(1),r[0]=0,r.fill(0,1,32),r.fill(0,127,160),r.fill(2,4352,4448),r[9001]=2,r[9002]=2,r.fill(2,11904,42192),r[12351]=1,r.fill(2,44032,55204),r.fill(2,63744,64256),r.fill(2,65040,65050),r.fill(2,65072,65136),r.fill(2,65280,65377),r.fill(2,65504,65511);for(let e=0;et[r][1])return!1;for(;r>=s;)if(i=s+r>>1,e>t[i][1])s=i+1;else{if(!(e=131072&&e<=196605||e>=196608&&e<=262141?2:1}}},5981:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.WriteBuffer=void 0;const s=i(8460),r=i(844);class n extends r.Disposable{constructor(e){super(),this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0,this._isSyncWriting=!1,this._syncCalls=0,this._didUserInput=!1,this._onWriteParsed=this.register(new s.EventEmitter),this.onWriteParsed=this._onWriteParsed.event}handleUserInput(){this._didUserInput=!0}writeSync(e,t){if(void 0!==t&&this._syncCalls>t)return void(this._syncCalls=0);if(this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;let i;for(this._isSyncWriting=!0;i=this._writeBuffer.shift();){this._action(i);const e=this._callbacks.shift();e&&e()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,t){if(this._pendingData>5e7)throw new Error("write data discarded, use flow control to avoid losing data");if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput)return this._didUserInput=!1,this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t),void this._innerWrite();setTimeout((()=>this._innerWrite()))}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)}_innerWrite(e=0,t=!0){const i=e||Date.now();for(;this._writeBuffer.length>this._bufferOffset;){const e=this._writeBuffer[this._bufferOffset],s=this._action(e,t);if(s){const e=e=>Date.now()-i>=12?setTimeout((()=>this._innerWrite(0,e))):this._innerWrite(i,e);return void s.catch((e=>(queueMicrotask((()=>{throw e})),Promise.resolve(!1)))).then(e)}const r=this._callbacks[this._bufferOffset];if(r&&r(),this._bufferOffset++,this._pendingData-=e.length,Date.now()-i>=12)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>50&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),setTimeout((()=>this._innerWrite()))):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}}t.WriteBuffer=n},5941:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.toRgbString=t.parseColor=void 0;const i=/^([\da-f])\/([\da-f])\/([\da-f])$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/,s=/^[\da-f]+$/;function r(e,t){const i=e.toString(16),s=i.length<2?"0"+i:i;switch(t){case 4:return i[0];case 8:return s;case 12:return(s+s).slice(0,3);default:return s+s}}t.parseColor=function(e){if(!e)return;let t=e.toLowerCase();if(0===t.indexOf("rgb:")){t=t.slice(4);const e=i.exec(t);if(e){const t=e[1]?15:e[4]?255:e[7]?4095:65535;return[Math.round(parseInt(e[1]||e[4]||e[7]||e[10],16)/t*255),Math.round(parseInt(e[2]||e[5]||e[8]||e[11],16)/t*255),Math.round(parseInt(e[3]||e[6]||e[9]||e[12],16)/t*255)]}}else if(0===t.indexOf("#")&&(t=t.slice(1),s.exec(t)&&[3,6,9,12].includes(t.length))){const e=t.length/3,i=[0,0,0];for(let s=0;s<3;++s){const r=parseInt(t.slice(e*s,e*s+e),16);i[s]=1===e?r<<4:2===e?r:3===e?r>>4:r>>8}return i}},t.toRgbString=function(e,t=16){const[i,s,n]=e;return`rgb:${r(i,t)}/${r(s,t)}/${r(n,t)}`}},5770:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.PAYLOAD_LIMIT=void 0,t.PAYLOAD_LIMIT=1e7},6351:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DcsHandler=t.DcsParser=void 0;const s=i(482),r=i(8742),n=i(5770),o=[];t.DcsParser=class{constructor(){this._handlers=Object.create(null),this._active=o,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=o}registerHandler(e,t){void 0===this._handlers[e]&&(this._handlers[e]=[]);const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].unhook(!1);this._stack.paused=!1,this._active=o,this._ident=0}hook(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||o,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].hook(t);else this._handlerFb(this._ident,"HOOK",t)}put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._ident,"PUT",(0,s.utf32ToString)(e,t,i))}unhook(e,t=!0){if(this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].unhook(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].unhook(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._ident,"UNHOOK",e);this._active=o,this._ident=0}};const a=new r.Params;a.addParam(0),t.DcsHandler=class{constructor(e){this._handler=e,this._data="",this._params=a,this._hitLimit=!1}hook(e){this._params=e.length>1||e.params[0]?e.clone():a,this._data="",this._hitLimit=!1}put(e,t,i){this._hitLimit||(this._data+=(0,s.utf32ToString)(e,t,i),this._data.length>n.PAYLOAD_LIMIT&&(this._data="",this._hitLimit=!0))}unhook(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data,this._params),t instanceof Promise))return t.then((e=>(this._params=a,this._data="",this._hitLimit=!1,e)));return this._params=a,this._data="",this._hitLimit=!1,t}}},2015:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.EscapeSequenceParser=t.VT500_TRANSITION_TABLE=t.TransitionTable=void 0;const s=i(844),r=i(8742),n=i(6242),o=i(6351);class a{constructor(e){this.table=new Uint8Array(e)}setDefault(e,t){this.table.fill(e<<4|t)}add(e,t,i,s){this.table[t<<8|e]=i<<4|s}addMany(e,t,i,s){for(let r=0;rt)),i=(e,i)=>t.slice(e,i),s=i(32,127),r=i(0,24);r.push(25),r.push.apply(r,i(28,32));const n=i(0,14);let o;for(o in e.setDefault(1,0),e.addMany(s,0,2,0),n)e.addMany([24,26,153,154],o,3,0),e.addMany(i(128,144),o,3,0),e.addMany(i(144,152),o,3,0),e.add(156,o,0,0),e.add(27,o,11,1),e.add(157,o,4,8),e.addMany([152,158,159],o,0,7),e.add(155,o,11,3),e.add(144,o,11,9);return e.addMany(r,0,3,0),e.addMany(r,1,3,1),e.add(127,1,0,1),e.addMany(r,8,0,8),e.addMany(r,3,3,3),e.add(127,3,0,3),e.addMany(r,4,3,4),e.add(127,4,0,4),e.addMany(r,6,3,6),e.addMany(r,5,3,5),e.add(127,5,0,5),e.addMany(r,2,3,2),e.add(127,2,0,2),e.add(93,1,4,8),e.addMany(s,8,5,8),e.add(127,8,5,8),e.addMany([156,27,24,26,7],8,6,0),e.addMany(i(28,32),8,0,8),e.addMany([88,94,95],1,0,7),e.addMany(s,7,0,7),e.addMany(r,7,0,7),e.add(156,7,0,0),e.add(127,7,0,7),e.add(91,1,11,3),e.addMany(i(64,127),3,7,0),e.addMany(i(48,60),3,8,4),e.addMany([60,61,62,63],3,9,4),e.addMany(i(48,60),4,8,4),e.addMany(i(64,127),4,7,0),e.addMany([60,61,62,63],4,0,6),e.addMany(i(32,64),6,0,6),e.add(127,6,0,6),e.addMany(i(64,127),6,0,0),e.addMany(i(32,48),3,9,5),e.addMany(i(32,48),5,9,5),e.addMany(i(48,64),5,0,6),e.addMany(i(64,127),5,7,0),e.addMany(i(32,48),4,9,5),e.addMany(i(32,48),1,9,2),e.addMany(i(32,48),2,9,2),e.addMany(i(48,127),2,10,0),e.addMany(i(48,80),1,10,0),e.addMany(i(81,88),1,10,0),e.addMany([89,90,92],1,10,0),e.addMany(i(96,127),1,10,0),e.add(80,1,11,9),e.addMany(r,9,0,9),e.add(127,9,0,9),e.addMany(i(28,32),9,0,9),e.addMany(i(32,48),9,9,12),e.addMany(i(48,60),9,8,10),e.addMany([60,61,62,63],9,9,10),e.addMany(r,11,0,11),e.addMany(i(32,128),11,0,11),e.addMany(i(28,32),11,0,11),e.addMany(r,10,0,10),e.add(127,10,0,10),e.addMany(i(28,32),10,0,10),e.addMany(i(48,60),10,8,10),e.addMany([60,61,62,63],10,0,11),e.addMany(i(32,48),10,9,12),e.addMany(r,12,0,12),e.add(127,12,0,12),e.addMany(i(28,32),12,0,12),e.addMany(i(32,48),12,9,12),e.addMany(i(48,64),12,0,11),e.addMany(i(64,127),12,12,13),e.addMany(i(64,127),10,12,13),e.addMany(i(64,127),9,12,13),e.addMany(r,13,13,13),e.addMany(s,13,13,13),e.add(127,13,0,13),e.addMany([27,156,24,26],13,14,0),e.add(h,0,2,0),e.add(h,8,5,8),e.add(h,6,0,6),e.add(h,11,0,11),e.add(h,13,13,13),e}();class c extends s.Disposable{constructor(e=t.VT500_TRANSITION_TABLE){super(),this._transitions=e,this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0},this.initialState=0,this.currentState=this.initialState,this._params=new r.Params,this._params.addParam(0),this._collect=0,this.precedingCodepoint=0,this._printHandlerFb=(e,t,i)=>{},this._executeHandlerFb=e=>{},this._csiHandlerFb=(e,t)=>{},this._escHandlerFb=e=>{},this._errorHandlerFb=e=>e,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this.register((0,s.toDisposable)((()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._escHandlers=Object.create(null)}))),this._oscParser=this.register(new n.OscParser),this._dcsParser=this.register(new o.DcsParser),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:"\\"},(()=>!0))}_identifier(e,t=[64,126]){let i=0;if(e.prefix){if(e.prefix.length>1)throw new Error("only one byte as prefix supported");if(i=e.prefix.charCodeAt(0),i&&60>i||i>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(e.intermediates){if(e.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(let t=0;ts||s>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");i<<=8,i|=s}}if(1!==e.final.length)throw new Error("final must be a single byte");const s=e.final.charCodeAt(0);if(t[0]>s||s>t[1])throw new Error(`final must be in range ${t[0]} .. ${t[1]}`);return i<<=8,i|=s,i}identToString(e){const t=[];for(;e;)t.push(String.fromCharCode(255&e)),e>>=8;return t.reverse().join("")}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,t){const i=this._identifier(e,[48,126]);void 0===this._escHandlers[i]&&(this._escHandlers[i]=[]);const s=this._escHandlers[i];return s.push(t),{dispose:()=>{const e=s.indexOf(t);-1!==e&&s.splice(e,1)}}}clearEscHandler(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]}setEscHandlerFallback(e){this._escHandlerFb=e}setExecuteHandler(e,t){this._executeHandlers[e.charCodeAt(0)]=t}clearExecuteHandler(e){this._executeHandlers[e.charCodeAt(0)]&&delete this._executeHandlers[e.charCodeAt(0)]}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,t){const i=this._identifier(e);void 0===this._csiHandlers[i]&&(this._csiHandlers[i]=[]);const s=this._csiHandlers[i];return s.push(t),{dispose:()=>{const e=s.indexOf(t);-1!==e&&s.splice(e,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,t){return this._dcsParser.registerHandler(this._identifier(e),t)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,t){return this._oscParser.registerHandler(e,t)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._params.reset(),this._params.addParam(0),this._collect=0,this.precedingCodepoint=0,0!==this._parseStack.state&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(e,t,i,s,r){this._parseStack.state=e,this._parseStack.handlers=t,this._parseStack.handlerPos=i,this._parseStack.transition=s,this._parseStack.chunkPos=r}parse(e,t,i){let s,r=0,n=0,o=0;if(this._parseStack.state)if(2===this._parseStack.state)this._parseStack.state=0,o=this._parseStack.chunkPos+1;else{if(void 0===i||1===this._parseStack.state)throw this._parseStack.state=1,new Error("improper continuation due to previous async handler, giving up parsing");const t=this._parseStack.handlers;let n=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(!1===i&&n>-1)for(;n>=0&&(s=t[n](this._params),!0!==s);n--)if(s instanceof Promise)return this._parseStack.handlerPos=n,s;this._parseStack.handlers=[];break;case 4:if(!1===i&&n>-1)for(;n>=0&&(s=t[n](),!0!==s);n--)if(s instanceof Promise)return this._parseStack.handlerPos=n,s;this._parseStack.handlers=[];break;case 6:if(r=e[this._parseStack.chunkPos],s=this._dcsParser.unhook(24!==r&&26!==r,i),s)return s;27===r&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0;break;case 5:if(r=e[this._parseStack.chunkPos],s=this._oscParser.end(24!==r&&26!==r,i),s)return s;27===r&&(this._parseStack.transition|=1),this._params.reset(),this._params.addParam(0),this._collect=0}this._parseStack.state=0,o=this._parseStack.chunkPos+1,this.precedingCodepoint=0,this.currentState=15&this._parseStack.transition}for(let i=o;i>4){case 2:for(let s=i+1;;++s){if(s>=t||(r=e[s])<32||r>126&&r=t||(r=e[s])<32||r>126&&r=t||(r=e[s])<32||r>126&&r=t||(r=e[s])<32||r>126&&r=0&&(s=o[a](this._params),!0!==s);a--)if(s instanceof Promise)return this._preserveStack(3,o,a,n,i),s;a<0&&this._csiHandlerFb(this._collect<<8|r,this._params),this.precedingCodepoint=0;break;case 8:do{switch(r){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(r-48)}}while(++i47&&r<60);i--;break;case 9:this._collect<<=8,this._collect|=r;break;case 10:const c=this._escHandlers[this._collect<<8|r];let l=c?c.length-1:-1;for(;l>=0&&(s=c[l](),!0!==s);l--)if(s instanceof Promise)return this._preserveStack(4,c,l,n,i),s;l<0&&this._escHandlerFb(this._collect<<8|r),this.precedingCodepoint=0;break;case 11:this._params.reset(),this._params.addParam(0),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|r,this._params);break;case 13:for(let s=i+1;;++s)if(s>=t||24===(r=e[s])||26===r||27===r||r>127&&r=t||(r=e[s])<32||r>127&&r{Object.defineProperty(t,"__esModule",{value:!0}),t.OscHandler=t.OscParser=void 0;const s=i(5770),r=i(482),n=[];t.OscParser=class{constructor(){this._state=0,this._active=n,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){void 0===this._handlers[e]&&(this._handlers[e]=[]);const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=n}reset(){if(2===this._state)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=n,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||n,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].start();else this._handlerFb(this._id,"START")}_put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._id,"PUT",(0,r.utf32ToString)(e,t,i))}start(){this.reset(),this._state=1}put(e,t,i){if(3!==this._state){if(1===this._state)for(;t0&&this._put(e,t,i)}}end(e,t=!0){if(0!==this._state){if(3!==this._state)if(1===this._state&&this._start(),this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].end(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._id,"END",e);this._active=n,this._id=-1,this._state=0}}},t.OscHandler=class{constructor(e){this._handler=e,this._data="",this._hitLimit=!1}start(){this._data="",this._hitLimit=!1}put(e,t,i){this._hitLimit||(this._data+=(0,r.utf32ToString)(e,t,i),this._data.length>s.PAYLOAD_LIMIT&&(this._data="",this._hitLimit=!0))}end(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data),t instanceof Promise))return t.then((e=>(this._data="",this._hitLimit=!1,e)));return this._data="",this._hitLimit=!1,t}}},8742:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.Params=void 0;const i=2147483647;class s{static fromArray(e){const t=new s;if(!e.length)return t;for(let i=Array.isArray(e[0])?1:0;i256)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(e),this.length=0,this._subParams=new Int32Array(t),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(e),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}clone(){const e=new s(this.maxLength,this.maxSubParamsLength);return e.params.set(this.params),e.length=this.length,e._subParams.set(this._subParams),e._subParamsLength=this._subParamsLength,e._subParamsIdx.set(this._subParamsIdx),e._rejectDigits=this._rejectDigits,e._rejectSubDigits=this._rejectSubDigits,e._digitIsSub=this._digitIsSub,e}toArray(){const e=[];for(let t=0;t>8,s=255&this._subParamsIdx[t];s-i>0&&e.push(Array.prototype.slice.call(this._subParams,i,s))}return e}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}addParam(e){if(this._digitIsSub=!1,this.length>=this.maxLength)this._rejectDigits=!0;else{if(e<-1)throw new Error("values lesser than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=e>i?i:e}}addSubParam(e){if(this._digitIsSub=!0,this.length)if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength)this._rejectSubDigits=!0;else{if(e<-1)throw new Error("values lesser than -1 are not allowed");this._subParams[this._subParamsLength++]=e>i?i:e,this._subParamsIdx[this.length-1]++}}hasSubParams(e){return(255&this._subParamsIdx[e])-(this._subParamsIdx[e]>>8)>0}getSubParams(e){const t=this._subParamsIdx[e]>>8,i=255&this._subParamsIdx[e];return i-t>0?this._subParams.subarray(t,i):null}getSubParamsAll(){const e={};for(let t=0;t>8,s=255&this._subParamsIdx[t];s-i>0&&(e[t]=this._subParams.slice(i,s))}return e}addDigit(e){let t;if(this._rejectDigits||!(t=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;const s=this._digitIsSub?this._subParams:this.params,r=s[t-1];s[t-1]=~r?Math.min(10*r+e,i):e}}t.Params=s},5741:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.AddonManager=void 0,t.AddonManager=class{constructor(){this._addons=[]}dispose(){for(let e=this._addons.length-1;e>=0;e--)this._addons[e].instance.dispose()}loadAddon(e,t){const i={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(i),t.dispose=()=>this._wrappedAddonDispose(i),t.activate(e)}_wrappedAddonDispose(e){if(e.isDisposed)return;let t=-1;for(let i=0;i{Object.defineProperty(t,"__esModule",{value:!0}),t.BufferApiView=void 0;const s=i(3785),r=i(511);t.BufferApiView=class{constructor(e,t){this._buffer=e,this.type=t}init(e){return this._buffer=e,this}get cursorY(){return this._buffer.y}get cursorX(){return this._buffer.x}get viewportY(){return this._buffer.ydisp}get baseY(){return this._buffer.ybase}get length(){return this._buffer.lines.length}getLine(e){const t=this._buffer.lines.get(e);if(t)return new s.BufferLineApiView(t)}getNullCell(){return new r.CellData}}},3785:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BufferLineApiView=void 0;const s=i(511);t.BufferLineApiView=class{constructor(e){this._line=e}get isWrapped(){return this._line.isWrapped}get length(){return this._line.length}getCell(e,t){if(!(e<0||e>=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new s.CellData)}translateToString(e,t,i){return this._line.translateToString(e,t,i)}}},8285:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.BufferNamespaceApi=void 0;const s=i(8771),r=i(8460),n=i(844);class o extends n.Disposable{constructor(e){super(),this._core=e,this._onBufferChange=this.register(new r.EventEmitter),this.onBufferChange=this._onBufferChange.event,this._normal=new s.BufferApiView(this._core.buffers.normal,"normal"),this._alternate=new s.BufferApiView(this._core.buffers.alt,"alternate"),this._core.buffers.onBufferActivate((()=>this._onBufferChange.fire(this.active)))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw new Error("Active buffer is neither normal nor alternate")}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}}t.BufferNamespaceApi=o},7975:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.ParserApi=void 0,t.ParserApi=class{constructor(e){this._core=e}registerCsiHandler(e,t){return this._core.registerCsiHandler(e,(e=>t(e.toArray())))}addCsiHandler(e,t){return this.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._core.registerDcsHandler(e,((e,i)=>t(e,i.toArray())))}addDcsHandler(e,t){return this.registerDcsHandler(e,t)}registerEscHandler(e,t){return this._core.registerEscHandler(e,t)}addEscHandler(e,t){return this.registerEscHandler(e,t)}registerOscHandler(e,t){return this._core.registerOscHandler(e,t)}addOscHandler(e,t){return this.registerOscHandler(e,t)}}},7090:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeApi=void 0,t.UnicodeApi=class{constructor(e){this._core=e}register(e){this._core.unicodeService.register(e)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(e){this._core.unicodeService.activeVersion=e}}},744:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.BufferService=t.MINIMUM_ROWS=t.MINIMUM_COLS=void 0;const n=i(8460),o=i(844),a=i(5295),h=i(2585);t.MINIMUM_COLS=2,t.MINIMUM_ROWS=1;let c=t.BufferService=class extends o.Disposable{get buffer(){return this.buffers.active}constructor(e){super(),this.isUserScrolling=!1,this._onResize=this.register(new n.EventEmitter),this.onResize=this._onResize.event,this._onScroll=this.register(new n.EventEmitter),this.onScroll=this._onScroll.event,this.cols=Math.max(e.rawOptions.cols||0,t.MINIMUM_COLS),this.rows=Math.max(e.rawOptions.rows||0,t.MINIMUM_ROWS),this.buffers=this.register(new a.BufferSet(e,this))}resize(e,t){this.cols=e,this.rows=t,this.buffers.resize(e,t),this._onResize.fire({cols:e,rows:t})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,t=!1){const i=this.buffer;let s;s=this._cachedBlankLine,s&&s.length===this.cols&&s.getFg(0)===e.fg&&s.getBg(0)===e.bg||(s=i.getBlankLine(e,t),this._cachedBlankLine=s),s.isWrapped=t;const r=i.ybase+i.scrollTop,n=i.ybase+i.scrollBottom;if(0===i.scrollTop){const e=i.lines.isFull;n===i.lines.length-1?e?i.lines.recycle().copyFrom(s):i.lines.push(s.clone()):i.lines.splice(n+1,0,s.clone()),e?this.isUserScrolling&&(i.ydisp=Math.max(i.ydisp-1,0)):(i.ybase++,this.isUserScrolling||i.ydisp++)}else{const e=n-r+1;i.lines.shiftElements(r+1,e-1,-1),i.lines.set(n,s.clone())}this.isUserScrolling||(i.ydisp=i.ybase),this._onScroll.fire(i.ydisp)}scrollLines(e,t,i){const s=this.buffer;if(e<0){if(0===s.ydisp)return;this.isUserScrolling=!0}else e+s.ydisp>=s.ybase&&(this.isUserScrolling=!1);const r=s.ydisp;s.ydisp=Math.max(Math.min(s.ydisp+e,s.ybase),0),r!==s.ydisp&&(t||this._onScroll.fire(s.ydisp))}};t.BufferService=c=s([r(0,h.IOptionsService)],c)},7994:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CharsetService=void 0,t.CharsetService=class{constructor(){this.glevel=0,this._charsets=[]}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(e){this.glevel=e,this.charset=this._charsets[e]}setgCharset(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)}}},1753:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CoreMouseService=void 0;const n=i(2585),o=i(8460),a=i(844),h={NONE:{events:0,restrict:()=>!1},X10:{events:1,restrict:e=>4!==e.button&&1===e.action&&(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)},VT200:{events:19,restrict:e=>32!==e.action},DRAG:{events:23,restrict:e=>32!==e.action||3!==e.button},ANY:{events:31,restrict:e=>!0}};function c(e,t){let i=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return 4===e.button?(i|=64,i|=e.action):(i|=3&e.button,4&e.button&&(i|=64),8&e.button&&(i|=128),32===e.action?i|=32:0!==e.action||t||(i|=3)),i}const l=String.fromCharCode,d={DEFAULT:e=>{const t=[c(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?"":`${l(t[0])}${l(t[1])}${l(t[2])}`},SGR:e=>{const t=0===e.action&&4!==e.button?"m":"M";return`[<${c(e,!0)};${e.col};${e.row}${t}`},SGR_PIXELS:e=>{const t=0===e.action&&4!==e.button?"m":"M";return`[<${c(e,!0)};${e.x};${e.y}${t}`}};let _=t.CoreMouseService=class extends a.Disposable{constructor(e,t){super(),this._bufferService=e,this._coreService=t,this._protocols={},this._encodings={},this._activeProtocol="",this._activeEncoding="",this._lastEvent=null,this._onProtocolChange=this.register(new o.EventEmitter),this.onProtocolChange=this._onProtocolChange.event;for(const e of Object.keys(h))this.addProtocol(e,h[e]);for(const e of Object.keys(d))this.addEncoding(e,d[e]);this.reset()}addProtocol(e,t){this._protocols[e]=t}addEncoding(e,t){this._encodings[e]=t}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return 0!==this._protocols[this._activeProtocol].events}set activeProtocol(e){if(!this._protocols[e])throw new Error(`unknown protocol "${e}"`);this._activeProtocol=e,this._onProtocolChange.fire(this._protocols[e].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(e){if(!this._encodings[e])throw new Error(`unknown encoding "${e}"`);this._activeEncoding=e}reset(){this.activeProtocol="NONE",this.activeEncoding="DEFAULT",this._lastEvent=null}triggerMouseEvent(e){if(e.col<0||e.col>=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows)return!1;if(4===e.button&&32===e.action)return!1;if(3===e.button&&32!==e.action)return!1;if(4!==e.button&&(2===e.action||3===e.action))return!1;if(e.col++,e.row++,32===e.action&&this._lastEvent&&this._equalEvents(this._lastEvent,e,"SGR_PIXELS"===this._activeEncoding))return!1;if(!this._protocols[this._activeProtocol].restrict(e))return!1;const t=this._encodings[this._activeEncoding](e);return t&&("DEFAULT"===this._activeEncoding?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0}explainEvents(e){return{down:!!(1&e),up:!!(2&e),drag:!!(4&e),move:!!(8&e),wheel:!!(16&e)}}_equalEvents(e,t,i){if(i){if(e.x!==t.x)return!1;if(e.y!==t.y)return!1}else{if(e.col!==t.col)return!1;if(e.row!==t.row)return!1}return e.button===t.button&&e.action===t.action&&e.ctrl===t.ctrl&&e.alt===t.alt&&e.shift===t.shift}};t.CoreMouseService=_=s([r(0,n.IBufferService),r(1,n.ICoreService)],_)},6975:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CoreService=void 0;const n=i(1439),o=i(8460),a=i(844),h=i(2585),c=Object.freeze({insertMode:!1}),l=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,origin:!1,reverseWraparound:!1,sendFocus:!1,wraparound:!0});let d=t.CoreService=class extends a.Disposable{constructor(e,t,i){super(),this._bufferService=e,this._logService=t,this._optionsService=i,this.isCursorInitialized=!1,this.isCursorHidden=!1,this._onData=this.register(new o.EventEmitter),this.onData=this._onData.event,this._onUserInput=this.register(new o.EventEmitter),this.onUserInput=this._onUserInput.event,this._onBinary=this.register(new o.EventEmitter),this.onBinary=this._onBinary.event,this._onRequestScrollToBottom=this.register(new o.EventEmitter),this.onRequestScrollToBottom=this._onRequestScrollToBottom.event,this.modes=(0,n.clone)(c),this.decPrivateModes=(0,n.clone)(l)}reset(){this.modes=(0,n.clone)(c),this.decPrivateModes=(0,n.clone)(l)}triggerDataEvent(e,t=!1){if(this._optionsService.rawOptions.disableStdin)return;const i=this._bufferService.buffer;t&&this._optionsService.rawOptions.scrollOnUserInput&&i.ybase!==i.ydisp&&this._onRequestScrollToBottom.fire(),t&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`,(()=>e.split("").map((e=>e.charCodeAt(0))))),this._onData.fire(e)}triggerBinaryEvent(e){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${e}"`,(()=>e.split("").map((e=>e.charCodeAt(0))))),this._onBinary.fire(e))}};t.CoreService=d=s([r(0,h.IBufferService),r(1,h.ILogService),r(2,h.IOptionsService)],d)},9074:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DecorationService=void 0;const s=i(8055),r=i(8460),n=i(844),o=i(6106);let a=0,h=0;class c extends n.Disposable{get decorations(){return this._decorations.values()}constructor(){super(),this._decorations=new o.SortedList((e=>null==e?void 0:e.marker.line)),this._onDecorationRegistered=this.register(new r.EventEmitter),this.onDecorationRegistered=this._onDecorationRegistered.event,this._onDecorationRemoved=this.register(new r.EventEmitter),this.onDecorationRemoved=this._onDecorationRemoved.event,this.register((0,n.toDisposable)((()=>this.reset())))}registerDecoration(e){if(e.marker.isDisposed)return;const t=new l(e);if(t){const e=t.marker.onDispose((()=>t.dispose()));t.onDispose((()=>{t&&(this._decorations.delete(t)&&this._onDecorationRemoved.fire(t),e.dispose())})),this._decorations.insert(t),this._onDecorationRegistered.fire(t)}return t}reset(){for(const e of this._decorations.values())e.dispose();this._decorations.clear()}*getDecorationsAtCell(e,t,i){var s,r,n;let o=0,a=0;for(const h of this._decorations.getKeyIterator(t))o=null!==(s=h.options.x)&&void 0!==s?s:0,a=o+(null!==(r=h.options.width)&&void 0!==r?r:1),e>=o&&e{var r,n,o;a=null!==(r=t.options.x)&&void 0!==r?r:0,h=a+(null!==(n=t.options.width)&&void 0!==n?n:1),e>=a&&e{Object.defineProperty(t,"__esModule",{value:!0}),t.InstantiationService=t.ServiceCollection=void 0;const s=i(2585),r=i(8343);class n{constructor(...e){this._entries=new Map;for(const[t,i]of e)this.set(t,i)}set(e,t){const i=this._entries.get(e);return this._entries.set(e,t),i}forEach(e){for(const[t,i]of this._entries.entries())e(t,i)}has(e){return this._entries.has(e)}get(e){return this._entries.get(e)}}t.ServiceCollection=n,t.InstantiationService=class{constructor(){this._services=new n,this._services.set(s.IInstantiationService,this)}setService(e,t){this._services.set(e,t)}getService(e){return this._services.get(e)}createInstance(e,...t){const i=(0,r.getServiceDependencies)(e).sort(((e,t)=>e.index-t.index)),s=[];for(const t of i){const i=this._services.get(t.id);if(!i)throw new Error(`[createInstance] ${e.name} depends on UNKNOWN service ${t.id}.`);s.push(i)}const n=i.length>0?i[0].index:t.length;if(t.length!==n)throw new Error(`[createInstance] First service dependency of ${e.name} at position ${n+1} conflicts with ${t.length} static arguments`);return new e(...[...t,...s])}}},7866:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.traceCall=t.setTraceLogger=t.LogService=void 0;const n=i(844),o=i(2585),a={trace:o.LogLevelEnum.TRACE,debug:o.LogLevelEnum.DEBUG,info:o.LogLevelEnum.INFO,warn:o.LogLevelEnum.WARN,error:o.LogLevelEnum.ERROR,off:o.LogLevelEnum.OFF};let h,c=t.LogService=class extends n.Disposable{get logLevel(){return this._logLevel}constructor(e){super(),this._optionsService=e,this._logLevel=o.LogLevelEnum.OFF,this._updateLogLevel(),this.register(this._optionsService.onSpecificOptionChange("logLevel",(()=>this._updateLogLevel()))),h=this}_updateLogLevel(){this._logLevel=a[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let t=0;tJSON.stringify(e))).join(", ")})`);const t=s.apply(this,e);return h.trace(`GlyphRenderer#${s.name} return`,t),t}}},7302:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.OptionsService=t.DEFAULT_OPTIONS=void 0;const s=i(8460),r=i(844),n=i(6114);t.DEFAULT_OPTIONS={cols:80,rows:24,cursorBlink:!1,cursorStyle:"block",cursorWidth:1,cursorInactiveStyle:"outline",customGlyphs:!0,drawBoldTextInBrightColors:!0,fastScrollModifier:"alt",fastScrollSensitivity:5,fontFamily:"courier-new, courier, monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:"info",logger:null,scrollback:1e3,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},rightClickSelectsWord:n.isMac,windowOptions:{},windowsMode:!1,windowsPty:{},wordSeparator:" ()[]{}',\"`",altClickMovesCursor:!0,convertEol:!1,termName:"xterm",cancelEvents:!1,overviewRulerWidth:0};const o=["normal","bold","100","200","300","400","500","600","700","800","900"];class a extends r.Disposable{constructor(e){super(),this._onOptionChange=this.register(new s.EventEmitter),this.onOptionChange=this._onOptionChange.event;const i=Object.assign({},t.DEFAULT_OPTIONS);for(const t in e)if(t in i)try{const s=e[t];i[t]=this._sanitizeAndValidateOption(t,s)}catch(e){console.error(e)}this.rawOptions=i,this.options=Object.assign({},i),this._setupOptions()}onSpecificOptionChange(e,t){return this.onOptionChange((i=>{i===e&&t(this.rawOptions[e])}))}onMultipleOptionChange(e,t){return this.onOptionChange((i=>{-1!==e.indexOf(i)&&t()}))}_setupOptions(){const e=e=>{if(!(e in t.DEFAULT_OPTIONS))throw new Error(`No option with key "${e}"`);return this.rawOptions[e]},i=(e,i)=>{if(!(e in t.DEFAULT_OPTIONS))throw new Error(`No option with key "${e}"`);i=this._sanitizeAndValidateOption(e,i),this.rawOptions[e]!==i&&(this.rawOptions[e]=i,this._onOptionChange.fire(e))};for(const t in this.rawOptions){const s={get:e.bind(this,t),set:i.bind(this,t)};Object.defineProperty(this.options,t,s)}}_sanitizeAndValidateOption(e,i){switch(e){case"cursorStyle":if(i||(i=t.DEFAULT_OPTIONS[e]),!function(e){return"block"===e||"underline"===e||"bar"===e}(i))throw new Error(`"${i}" is not a valid value for ${e}`);break;case"wordSeparator":i||(i=t.DEFAULT_OPTIONS[e]);break;case"fontWeight":case"fontWeightBold":if("number"==typeof i&&1<=i&&i<=1e3)break;i=o.includes(i)?i:t.DEFAULT_OPTIONS[e];break;case"cursorWidth":i=Math.floor(i);case"lineHeight":case"tabStopWidth":if(i<1)throw new Error(`${e} cannot be less than 1, value: ${i}`);break;case"minimumContrastRatio":i=Math.max(1,Math.min(21,Math.round(10*i)/10));break;case"scrollback":if((i=Math.min(i,4294967295))<0)throw new Error(`${e} cannot be less than 0, value: ${i}`);break;case"fastScrollSensitivity":case"scrollSensitivity":if(i<=0)throw new Error(`${e} cannot be less than or equal to 0, value: ${i}`);break;case"rows":case"cols":if(!i&&0!==i)throw new Error(`${e} must be numeric, value: ${i}`);break;case"windowsPty":i=null!=i?i:{}}return i}}t.OptionsService=a},2660:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,n=arguments.length,o=n<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)o=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(o=(n<3?r(o):n>3?r(t,i,o):r(t,i))||o);return n>3&&o&&Object.defineProperty(t,i,o),o},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OscLinkService=void 0;const n=i(2585);let o=t.OscLinkService=class{constructor(e){this._bufferService=e,this._nextId=1,this._entriesWithId=new Map,this._dataByLinkId=new Map}registerLink(e){const t=this._bufferService.buffer;if(void 0===e.id){const i=t.addMarker(t.ybase+t.y),s={data:e,id:this._nextId++,lines:[i]};return i.onDispose((()=>this._removeMarkerFromLink(s,i))),this._dataByLinkId.set(s.id,s),s.id}const i=e,s=this._getEntryIdKey(i),r=this._entriesWithId.get(s);if(r)return this.addLineToLink(r.id,t.ybase+t.y),r.id;const n=t.addMarker(t.ybase+t.y),o={id:this._nextId++,key:this._getEntryIdKey(i),data:i,lines:[n]};return n.onDispose((()=>this._removeMarkerFromLink(o,n))),this._entriesWithId.set(o.key,o),this._dataByLinkId.set(o.id,o),o.id}addLineToLink(e,t){const i=this._dataByLinkId.get(e);if(i&&i.lines.every((e=>e.line!==t))){const e=this._bufferService.buffer.addMarker(t);i.lines.push(e),e.onDispose((()=>this._removeMarkerFromLink(i,e)))}}getLinkData(e){var t;return null===(t=this._dataByLinkId.get(e))||void 0===t?void 0:t.data}_getEntryIdKey(e){return`${e.id};;${e.uri}`}_removeMarkerFromLink(e,t){const i=e.lines.indexOf(t);-1!==i&&(e.lines.splice(i,1),0===e.lines.length&&(void 0!==e.data.id&&this._entriesWithId.delete(e.key),this._dataByLinkId.delete(e.id)))}};t.OscLinkService=o=s([r(0,n.IBufferService)],o)},8343:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.createDecorator=t.getServiceDependencies=t.serviceRegistry=void 0;const i="di$target",s="di$dependencies";t.serviceRegistry=new Map,t.getServiceDependencies=function(e){return e[s]||[]},t.createDecorator=function(e){if(t.serviceRegistry.has(e))return t.serviceRegistry.get(e);const r=function(e,t,n){if(3!==arguments.length)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");!function(e,t,r){t[i]===t?t[s].push({id:e,index:r}):(t[s]=[{id:e,index:r}],t[i]=t)}(r,e,n)};return r.toString=()=>e,t.serviceRegistry.set(e,r),r}},2585:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.IDecorationService=t.IUnicodeService=t.IOscLinkService=t.IOptionsService=t.ILogService=t.LogLevelEnum=t.IInstantiationService=t.ICharsetService=t.ICoreService=t.ICoreMouseService=t.IBufferService=void 0;const s=i(8343);var r;t.IBufferService=(0,s.createDecorator)("BufferService"),t.ICoreMouseService=(0,s.createDecorator)("CoreMouseService"),t.ICoreService=(0,s.createDecorator)("CoreService"),t.ICharsetService=(0,s.createDecorator)("CharsetService"),t.IInstantiationService=(0,s.createDecorator)("InstantiationService"),function(e){e[e.TRACE=0]="TRACE",e[e.DEBUG=1]="DEBUG",e[e.INFO=2]="INFO",e[e.WARN=3]="WARN",e[e.ERROR=4]="ERROR",e[e.OFF=5]="OFF"}(r||(t.LogLevelEnum=r={})),t.ILogService=(0,s.createDecorator)("LogService"),t.IOptionsService=(0,s.createDecorator)("OptionsService"),t.IOscLinkService=(0,s.createDecorator)("OscLinkService"),t.IUnicodeService=(0,s.createDecorator)("UnicodeService"),t.IDecorationService=(0,s.createDecorator)("DecorationService")},1480:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeService=void 0;const s=i(8460),r=i(225);t.UnicodeService=class{constructor(){this._providers=Object.create(null),this._active="",this._onChange=new s.EventEmitter,this.onChange=this._onChange.event;const e=new r.UnicodeV6;this.register(e),this._active=e.version,this._activeProvider=e}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(e){if(!this._providers[e])throw new Error(`unknown Unicode version "${e}"`);this._active=e,this._activeProvider=this._providers[e],this._onChange.fire(e)}register(e){this._providers[e.version]=e}wcwidth(e){return this._activeProvider.wcwidth(e)}getStringCellWidth(e){let t=0;const i=e.length;for(let s=0;s=i)return t+this.wcwidth(r);const n=e.charCodeAt(s);56320<=n&&n<=57343?r=1024*(r-55296)+n-56320+65536:t+=this.wcwidth(n)}t+=this.wcwidth(r)}return t}}}},t={};function i(s){var r=t[s];if(void 0!==r)return r.exports;var n=t[s]={exports:{}};return e[s].call(n.exports,n,n.exports,i),n.exports}var s={};return(()=>{var e=s;Object.defineProperty(e,"__esModule",{value:!0}),e.Terminal=void 0;const t=i(9042),r=i(3236),n=i(844),o=i(5741),a=i(8285),h=i(7975),c=i(7090),l=["cols","rows"];class d extends n.Disposable{constructor(e){super(),this._core=this.register(new r.Terminal(e)),this._addonManager=this.register(new o.AddonManager),this._publicOptions=Object.assign({},this._core.options);const t=e=>this._core.options[e],i=(e,t)=>{this._checkReadonlyOptions(e),this._core.options[e]=t};for(const e in this._core.options){const s={get:t.bind(this,e),set:i.bind(this,e)};Object.defineProperty(this._publicOptions,e,s)}}_checkReadonlyOptions(e){if(l.includes(e))throw new Error(`Option "${e}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get element(){return this._core.element}get parser(){return this._parser||(this._parser=new h.ParserApi(this._core)),this._parser}get unicode(){return this._checkProposedApi(),new c.UnicodeApi(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer||(this._buffer=this.register(new a.BufferNamespaceApi(this._core))),this._buffer}get markers(){return this._checkProposedApi(),this._core.markers}get modes(){const e=this._core.coreService.decPrivateModes;let t="none";switch(this._core.coreMouseService.activeProtocol){case"X10":t="x10";break;case"VT200":t="vt200";break;case"DRAG":t="drag";break;case"ANY":t="any"}return{applicationCursorKeysMode:e.applicationCursorKeys,applicationKeypadMode:e.applicationKeypad,bracketedPasteMode:e.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:t,originMode:e.origin,reverseWraparoundMode:e.reverseWraparound,sendFocusMode:e.sendFocus,wraparoundMode:e.wraparound}}get options(){return this._publicOptions}set options(e){for(const t in e)this._publicOptions[t]=e[t]}blur(){this._core.blur()}focus(){this._core.focus()}resize(e,t){this._verifyIntegers(e,t),this._core.resize(e,t)}open(e){this._core.open(e)}attachCustomKeyEventHandler(e){this._core.attachCustomKeyEventHandler(e)}registerLinkProvider(e){return this._core.registerLinkProvider(e)}registerCharacterJoiner(e){return this._checkProposedApi(),this._core.registerCharacterJoiner(e)}deregisterCharacterJoiner(e){this._checkProposedApi(),this._core.deregisterCharacterJoiner(e)}registerMarker(e=0){return this._verifyIntegers(e),this._core.registerMarker(e)}registerDecoration(e){var t,i,s;return this._checkProposedApi(),this._verifyPositiveIntegers(null!==(t=e.x)&&void 0!==t?t:0,null!==(i=e.width)&&void 0!==i?i:0,null!==(s=e.height)&&void 0!==s?s:0),this._core.registerDecoration(e)}hasSelection(){return this._core.hasSelection()}select(e,t,i){this._verifyIntegers(e,t,i),this._core.select(e,t,i)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(e,t){this._verifyIntegers(e,t),this._core.selectLines(e,t)}dispose(){super.dispose()}scrollLines(e){this._verifyIntegers(e),this._core.scrollLines(e)}scrollPages(e){this._verifyIntegers(e),this._core.scrollPages(e)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(e){this._verifyIntegers(e),this._core.scrollToLine(e)}clear(){this._core.clear()}write(e,t){this._core.write(e,t)}writeln(e,t){this._core.write(e),this._core.write("\r\n",t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){this._addonManager.loadAddon(this,e)}static get strings(){return t}_verifyIntegers(...e){for(const t of e)if(t===1/0||isNaN(t)||t%1!=0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...e){for(const t of e)if(t&&(t===1/0||isNaN(t)||t%1!=0||t<0))throw new Error("This API only accepts positive integers")}}e.Terminal=d})(),s})())); +//# sourceMappingURL=xterm.js.map \ No newline at end of file diff --git a/internal/server/web/templates/dashboard.html b/internal/server/web/templates/dashboard.html new file mode 100644 index 0000000..cb2d9a9 --- /dev/null +++ b/internal/server/web/templates/dashboard.html @@ -0,0 +1,62 @@ + + + + + +Машины — LPRA + + + +
+
LPRA · Удалённый доступ
+ +
+
+

Машины-клиенты

+
+ {{if .Agents}} + + + + {{range .Agents}} + + + + + + + + + {{end}} + +
СтатусИмяHostnameОС / АрхАктивность
{{if .Online}}online{{else}}offline{{end}}{{.Name}}{{.Hostname}}{{.OS}}/{{.Arch}}{{.Seen}} + +
+ {{else}} +

Пока нет подключённых машин. Добавьте первую в разделе + Настройки — там одна команда для установки.

+ {{end}} +
+
+ + diff --git a/internal/server/web/templates/edit.html b/internal/server/web/templates/edit.html new file mode 100644 index 0000000..bddc391 --- /dev/null +++ b/internal/server/web/templates/edit.html @@ -0,0 +1,62 @@ + + + + + +Изменить машину — LPRA + + + +
+
LPRA · Изменить машину
+ +
+
+

{{.Agent.Name}}

+ +
+

Имя

+
+ + + +
+

+ Hostname: {{.Agent.Hostname}} · ОС: {{.Agent.OS}}/{{.Agent.Arch}} · ID: {{.Agent.ID}} +

+
+ +
+

Версия клиента

+ {{if .Updated}}
Команда обновления отправлена. Клиент скачает свежий бинарник и перезапустится.
{{end}} +

+ Клиент: {{if .Agent.Version}}{{.Agent.Version}}{{else}}неизвестно{{end}} + · Сервер: {{.ServerVersion}} + {{if .Outdated}} · требуется обновление{{else}} · актуальна{{end}} +

+ {{if .Outdated}} +
+ + +
+ {{else}} +

Обновление доступно, когда версия клиента отличается от версии сервера.

+ {{end}} +
+ +
+

Удаление

+

Удаляет машину из панели. Сервис на самой машине продолжит работать, + но перестанет быть виден; чтобы подключиться снова — переустановите агента.

+
+ + +
+
+
+ + diff --git a/internal/server/web/templates/files.html b/internal/server/web/templates/files.html new file mode 100644 index 0000000..29ef3fc --- /dev/null +++ b/internal/server/web/templates/files.html @@ -0,0 +1,287 @@ + + + + + +Файлы · {{.Agent.Name}} — LPRA + + + + +
+
LPRA · Файлы — {{.Agent.Name}} ({{.Agent.Hostname}})
+ +
+
+
+ + + + +
+
+ + + + + + + +
+ +
+ + + + + + + +
ИмяРазмерИзменён
+
+

Перетащите файлы сюда для загрузки. Клик по файлу — просмотр (правка, если < 2 КБ).

+
+ + +
+ + + +
+
+
+ +
+
+ +
+ + + + diff --git a/internal/server/web/templates/forwards.html b/internal/server/web/templates/forwards.html new file mode 100644 index 0000000..782539d --- /dev/null +++ b/internal/server/web/templates/forwards.html @@ -0,0 +1,116 @@ + + + + + +Порты · {{.Agent.Name}} — LPRA + + + + +
+
LPRA · Порты — {{.Agent.Name}} ({{.Agent.Hostname}})
+ +
+
+

Проброс портов

+

Порт сервиса на машине-клиенте становится доступен на порту хоста. + Клиент за NAT — трафик идёт исходящим подключением агента.

+ {{if .Error}}
{{.Error}}
{{end}} + +
+

Правила

+ {{if .Rules}} + + + + {{range .Rules}} + + + + + + + + {{end}} + +
ТипХост (порт)→ Клиент (порт)Слушать на
{{.Proto}}:{{.HostPort}}127.0.0.1:{{.ClientPort}}{{.ListenAddr}} +
+ + +
+
+ {{else}} +

Правил пока нет.

+ {{end}} +
+ +
+

Новое правило

+
+ +
+
+ + +
+
+ + +
+
+ + +
+
+ + +
+ +
+
+

+
+ +
+

Режимы

+
    +
  • http — туннель поверх HTTP long-poll (через nginx :8445). Работает везде, где доступна панель, но задержка выше.
  • +
  • tcp — прямой TCP-мультиплексор через порт 8446 хоста. Минимальная задержка, keep-alive, TCP_NODELAY. Нужно открыть 8446/tcp на хосте.
  • +
  • udp — прямой UDP через порт 8446 хоста. Нужно открыть 8446/udp.
  • +
+

Для tcp/udp агент держит исходящее соединение к host:8446; несколько клиентов и портов мультиплексируются на нём.

+
+
+ + + diff --git a/internal/server/web/templates/login.html b/internal/server/web/templates/login.html new file mode 100644 index 0000000..176c1e5 --- /dev/null +++ b/internal/server/web/templates/login.html @@ -0,0 +1,24 @@ + + + + + +Вход — LPRA + + + +
+
+

Вход в панель

+ {{if .Error}}
{{.Error}}
{{end}} +
+ + + + +
+
+
+
+ + diff --git a/internal/server/web/templates/settings.html b/internal/server/web/templates/settings.html new file mode 100644 index 0000000..ed76191 --- /dev/null +++ b/internal/server/web/templates/settings.html @@ -0,0 +1,143 @@ + + + + + +Настройки — LPRA + + + +
+
LPRA · Удалённый доступ
+ +
+
+

Настройки

+ +
+

Установка на новую машину

+

Выполните на Linux-клиенте под root. Скрипт сам скачает бинарник, + поставит сервис и зарегистрирует машину.

+ + +
{{.InstallShort}}
+ +
+ Полные варианты (секрет в команде) + +
{{.InstallCurl}}
+ +
{{.InstallWget}}
+

Без переменных окружения скрипт спросит секрет интерактивно: + curl -fsSL {{.Host}}/install.sh | sudo sh

+
+
+ +
+

Адрес хоста

+

URL, на который звонят агенты. Подставляется в скрипт установки.

+
+ + + +
+
+ +
+

Enroll-секрет

+

Общий секрет для регистрации новых агентов.

+
+ {{.Secret}} +
+ + +
+
+
+ +
+

Администраторы

+ + + {{range .Admins}} + + + + + {{end}} + +
{{.Username}} +
+ + + +
+
+
+ +
+ + + +
+
+
+ +
+

Firewall (ufw)

+ {{if not .UfwInstalled}} +

ufw не установлен на хосте — интеграция недоступна.

+ {{else if not .UfwRunnable}} +

ufw установлен, но сервер не может им управлять (нужны права root). + Запустите сервер под root или настройте sudo для ufw.

+ {{else}} +

+ Статус ufw: {{if .UfwActive}}активен{{else}}выключен{{end}} + · Интеграция: {{if .UfwEnabled}}включена{{else}}выключена{{end}} +

+

При создании правила проброса порт открывается в ufw автоматически (для tcp/udp — также порт 8446).

+
+ + + +
+ + {{if .UfwRules}} +

Правила, созданные панелью

+ + + {{range .UfwRules}} + + + + + {{end}} + +
{{.Port}}/{{.Proto}} +
+ + + +
+
+ {{else}} +

Панель пока не открывала портов в ufw.

+ {{end}} + {{end}} +
+ +

LPRA сервер · версия {{.ServerVersion}}

+
+ + diff --git a/internal/server/web/templates/setup.html b/internal/server/web/templates/setup.html new file mode 100644 index 0000000..87b6e1b --- /dev/null +++ b/internal/server/web/templates/setup.html @@ -0,0 +1,27 @@ + + + + + +Настройка — LPRA + + + +
+
+

Первый запуск

+

Создайте учётную запись администратора панели.

+ {{if .Error}}
{{.Error}}
{{end}} +
+ + + + + + +
+
+
+
+ + diff --git a/internal/server/web/templates/terminal.html b/internal/server/web/templates/terminal.html new file mode 100644 index 0000000..ec2d3ec --- /dev/null +++ b/internal/server/web/templates/terminal.html @@ -0,0 +1,149 @@ + + + + + +Терминал · {{.Agent.Name}} — LPRA + + + + + +
+
LPRA · Терминал — {{.Agent.Name}} ({{.Agent.Hostname}})
+ +
+
+
+
+ + + + + + diff --git a/internal/tunnel/frame.go b/internal/tunnel/frame.go new file mode 100644 index 0000000..556c026 --- /dev/null +++ b/internal/tunnel/frame.go @@ -0,0 +1,100 @@ +// Package tunnel defines the framed multiplexing protocol used for DIRECT port +// forwarding over the dedicated host port (default 8446). Unlike the HTTP +// long-poll forward, this is a raw TCP stream the agent dials outbound; it +// carries many concurrent forwarded connections (streams) for one agent and +// has far lower latency than long-polling. +// +// Frame layout (big-endian): +// +// type(1) | stream(4) | length(4) | payload(length) +package tunnel + +import ( + "crypto/hmac" + "crypto/sha256" + "encoding/binary" + "encoding/hex" + "io" +) + +// DefaultPort is the host port for the direct tunnel (TCP and UDP). +const DefaultPort = 8446 + +// Frame types. +const ( + TypeOpen byte = 1 // host->agent: open a TCP stream to 127.0.0.1:port (payload = port uint16) + TypeData byte = 2 // both: stream bytes + TypeClose byte = 3 // both: close the stream + TypePing byte = 4 // keepalive +) + +// MaxPayload caps a single frame payload (1 MiB). +const MaxPayload = 1 << 20 + +// Frame is one multiplexed message. +type Frame struct { + Type byte + Stream uint32 + Payload []byte +} + +// WriteFrame writes a frame. The caller must serialize writes to one conn. +func WriteFrame(w io.Writer, f Frame) error { + var hdr [9]byte + hdr[0] = f.Type + binary.BigEndian.PutUint32(hdr[1:5], f.Stream) + binary.BigEndian.PutUint32(hdr[5:9], uint32(len(f.Payload))) + if _, err := w.Write(hdr[:]); err != nil { + return err + } + if len(f.Payload) > 0 { + _, err := w.Write(f.Payload) + return err + } + return nil +} + +// ReadFrame reads one frame. +func ReadFrame(r io.Reader) (Frame, error) { + var hdr [9]byte + if _, err := io.ReadFull(r, hdr[:]); err != nil { + return Frame{}, err + } + n := binary.BigEndian.Uint32(hdr[5:9]) + if n > MaxPayload { + return Frame{}, io.ErrUnexpectedEOF + } + f := Frame{Type: hdr[0], Stream: binary.BigEndian.Uint32(hdr[1:5])} + if n > 0 { + f.Payload = make([]byte, n) + if _, err := io.ReadFull(r, f.Payload); err != nil { + return Frame{}, err + } + } + return f, nil +} + +// AuthKey derives the shared HMAC key from an agent token: sha256(token). +// The server holds the same value as the stored token hash, so neither side +// ever puts the plaintext token on the wire. +func AuthKey(token string) []byte { + sum := sha256.Sum256([]byte(token)) + return sum[:] +} + +// KeyFromHash decodes a stored hex token hash into the HMAC key bytes. +func KeyFromHash(tokenHashHex string) ([]byte, error) { + return hex.DecodeString(tokenHashHex) +} + +// Sign returns hex(HMAC-SHA256(key, nonce)). +func Sign(key, nonce []byte) string { + m := hmac.New(sha256.New, key) + m.Write(nonce) + return hex.EncodeToString(m.Sum(nil)) +} + +// EqualSig compares two hex signatures in constant time. +func EqualSig(a, b string) bool { + return hmac.Equal([]byte(a), []byte(b)) +} diff --git a/lpra.db b/lpra.db new file mode 100644 index 0000000..9a47220 Binary files /dev/null and b/lpra.db differ diff --git a/lpra.db-shm b/lpra.db-shm new file mode 100644 index 0000000..ae6bf9f Binary files /dev/null and b/lpra.db-shm differ diff --git a/lpra.db-wal b/lpra.db-wal new file mode 100644 index 0000000..0a1e18d Binary files /dev/null and b/lpra.db-wal differ