diff --git a/Makefile b/Makefile
index d1b853a..16e770b 100644
--- a/Makefile
+++ b/Makefile
@@ -21,6 +21,14 @@ WASM_DIR := web/chess-client-wasm
WASM_BINARY := $(WASM_DIR)/chess-client.wasm
WASM_EXEC_JS := $(WASM_DIR)/wasm_exec.js
WASM_EXEC_SRC := $(GOROOT)/lib/wasm/wasm_exec.js
+WASM_LIB_DIR := $(WASM_DIR)/lib
+
+# xterm.js versions (5.5.0 compatible)
+XTERM_VERSION := 5.5.0
+XTERM_FIT_VERSION := 0.10.0
+XTERM_WEBGL_VERSION := 0.18.0
+XTERM_LINKS_VERSION := 0.11.0
+XTERM_UNICODE_VERSION := 0.8.0
# Default target
.PHONY: all
@@ -61,15 +69,20 @@ wasm: $(WASM_DIR)
@echo "Built WASM client: $(WASM_BINARY)"
@echo "Size: $$(du -h $(WASM_BINARY) | cut -f1)"
-# Download xterm.js dependencies
+# Download xterm.js and all addons
.PHONY: wasm-deps
wasm-deps: $(WASM_DIR)
- @echo "Downloading xterm.js 5.5.0..."
- @mkdir -p $(WASM_DIR)/lib
- @cd $(WASM_DIR)/lib && \
- curl -sO https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/lib/xterm.min.js && \
- curl -sO https://cdn.jsdelivr.net/npm/@xterm/xterm@5.5.0/css/xterm.css
- @echo "xterm.js 5.5.0 downloaded to $(WASM_DIR)/lib/"
+ @echo "Downloading xterm.js $(XTERM_VERSION) and addons..."
+ @mkdir -p $(WASM_LIB_DIR)
+ @cd $(WASM_LIB_DIR) && \
+ curl -sO https://cdn.jsdelivr.net/npm/@xterm/xterm@$(XTERM_VERSION)/lib/xterm.min.js && \
+ curl -sO https://cdn.jsdelivr.net/npm/@xterm/xterm@$(XTERM_VERSION)/css/xterm.min.css && \
+ curl -sO https://cdn.jsdelivr.net/npm/@xterm/addon-fit@$(XTERM_FIT_VERSION)/lib/addon-fit.min.js && \
+ curl -sO https://cdn.jsdelivr.net/npm/@xterm/addon-webgl@$(XTERM_WEBGL_VERSION)/lib/addon-webgl.min.js && \
+ curl -sO https://cdn.jsdelivr.net/npm/@xterm/addon-web-links@$(XTERM_LINKS_VERSION)/lib/addon-web-links.min.js && \
+ curl -sO https://cdn.jsdelivr.net/npm/@xterm/addon-unicode11@$(XTERM_UNICODE_VERSION)/lib/addon-unicode11.min.js
+ @echo "Downloaded to $(WASM_LIB_DIR)/"
+ @ls -la $(WASM_LIB_DIR)/
# Build WASM with dependencies
.PHONY: wasm-full
diff --git a/cmd/chess-client-cli/exit_native.go b/cmd/chess-client-cli/exit_native.go
index bccfcee..e06a7d4 100644
--- a/cmd/chess-client-cli/exit_native.go
+++ b/cmd/chess-client-cli/exit_native.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/cmd/chess-client-cli/exit_native.go
//go:build !js && !wasm
package main
diff --git a/cmd/chess-client-cli/exit_wasm.go b/cmd/chess-client-cli/exit_wasm.go
index 85fd957..cf6f6d5 100644
--- a/cmd/chess-client-cli/exit_wasm.go
+++ b/cmd/chess-client-cli/exit_wasm.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/cmd/chess-client-cli/exit_wasm.go
//go:build js && wasm
package main
diff --git a/cmd/chess-client-cli/main.go b/cmd/chess-client-cli/main.go
index c6f867a..96a61af 100644
--- a/cmd/chess-client-cli/main.go
+++ b/cmd/chess-client-cli/main.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/cmd/chess-client-cli/main.go
// Package main implements an interactive cli debugging client for the chess server API.
package main
diff --git a/cmd/chess-server/cli/cli.go b/cmd/chess-server/cli/cli.go
index 7ebf887..1f4464c 100644
--- a/cmd/chess-server/cli/cli.go
+++ b/cmd/chess-server/cli/cli.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/cmd/chess-server/cli/cli.go
package cli
import (
@@ -168,9 +167,10 @@ func runUserAdd(args []string) error {
path := fs.String("path", "", "Database file path (required)")
username := fs.String("username", "", "Username (required)")
email := fs.String("email", "", "Email address (optional)")
- password := fs.String("password", "", "Password (use this or -hash, not both)")
- hash := fs.String("hash", "", "Password hash (use this or -password, not both)")
+ password := fs.String("password", "", "Password (optional, will prompt if not provided)")
+ hash := fs.String("hash", "", "Pre-computed password hash (optional)")
interactive := fs.Bool("interactive", false, "Interactive password prompt")
+ temp := fs.Bool("temp", false, "Create as temporary user (24h TTL, default: permanent)")
if err := fs.Parse(args); err != nil {
return err
@@ -244,12 +244,23 @@ func runUserAdd(args []string) error {
}
}
+ // Determine account type (CLI default = permanent)
+ accountType := "permanent"
+ var expiresAt *time.Time
+ if *temp {
+ accountType = "temp"
+ expiry := time.Now().UTC().Add(24 * time.Hour)
+ expiresAt = &expiry
+ }
+
record := storage.UserRecord{
UserID: userID,
Username: strings.ToLower(*username),
Email: strings.ToLower(*email),
PasswordHash: passwordHash,
+ AccountType: accountType,
CreatedAt: time.Now().UTC(),
+ ExpiresAt: expiresAt,
}
if err := store.CreateUser(record); err != nil {
@@ -534,23 +545,29 @@ func runUserList(args []string) error {
// Print results in tabular format
w := tabwriter.NewWriter(os.Stdout, 0, 0, 2, ' ', 0)
- fmt.Fprintln(w, "User ID\tUsername\tEmail\tCreated\tLast Login")
- fmt.Fprintln(w, strings.Repeat("-", 100))
+ fmt.Fprintln(w, "User ID\tUsername\tType\tEmail\tCreated\tExpires\tLast Login")
+ fmt.Fprintln(w, strings.Repeat("-", 120))
for _, u := range users {
lastLogin := "never"
if u.LastLoginAt != nil {
- lastLogin = u.LastLoginAt.Format("2006-01-02 15:04:05")
+ lastLogin = u.LastLoginAt.Format("2006-01-02 15:04")
}
email := u.Email
if email == "" {
email = "(none)"
}
- fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\n",
+ expires := "never"
+ if u.ExpiresAt != nil {
+ expires = u.ExpiresAt.Format("2006-01-02 15:04")
+ }
+ fmt.Fprintf(w, "%s\t%s\t%s\t%s\t%s\t%s\t%s\n",
u.UserID[:8]+"...",
u.Username,
+ u.AccountType,
email,
- u.CreatedAt.Format("2006-01-02 15:04:05"),
+ u.CreatedAt.Format("2006-01-02 15:04"),
+ expires,
lastLogin,
)
}
diff --git a/cmd/chess-server/main.go b/cmd/chess-server/main.go
index 1293cd4..6cb72ac 100644
--- a/cmd/chess-server/main.go
+++ b/cmd/chess-server/main.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/cmd/chess-server/main.go
// Package main implements the chess server application with RESTful API,
// user authentication, and optional web UI serving capabilities.
package main
@@ -103,6 +102,10 @@ func main() {
// 2. Initialize the Service with optional storage and auth
svc := service.New(store, jwtSecret)
+ // Start cleanup job for expired users/sessions
+ cleanupCtx, cleanupCancel := context.WithCancel(context.Background())
+ go svc.RunCleanupJob(cleanupCtx, service.CleanupJobInterval)
+
// 3. Initialize the Processor (Orchestrator), injecting the service
proc, err := processor.New(svc)
if err != nil {
@@ -178,6 +181,8 @@ func main() {
log.Printf("Processor close error: %v", err)
}
+ cleanupCancel() // Stop cleanup job
+
// Shutdown service first (includes wait registry cleanup)
if err = svc.Shutdown(gracefulShutdownTimeout); err != nil {
log.Printf("Service shutdown error: %v", err)
diff --git a/cmd/chess-server/pid.go b/cmd/chess-server/pid.go
index fa15533..c5e8fbf 100644
--- a/cmd/chess-server/pid.go
+++ b/cmd/chess-server/pid.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/cmd/chess-server/pid.go
package main
import (
diff --git a/go.mod b/go.mod
index 2ae28ee..4a6f019 100644
--- a/go.mod
+++ b/go.mod
@@ -3,32 +3,32 @@ module chess
go 1.25.4
require (
- github.com/go-playground/validator/v10 v10.28.0
- github.com/gofiber/fiber/v2 v2.52.9
+ github.com/go-playground/validator/v10 v10.30.1
+ github.com/gofiber/fiber/v2 v2.52.11
github.com/google/uuid v1.6.0
github.com/lixenwraith/auth v0.0.0-20251104131016-e5a810f4e226
- github.com/mattn/go-sqlite3 v1.14.32
- golang.org/x/term v0.37.0
+ github.com/mattn/go-sqlite3 v1.14.33
+ golang.org/x/term v0.39.0
)
require (
github.com/andybalholm/brotli v1.2.0 // indirect
github.com/clipperhouse/stringish v0.1.1 // indirect
- github.com/clipperhouse/uax29/v2 v2.3.0 // indirect
- github.com/gabriel-vasile/mimetype v1.4.11 // indirect
+ github.com/clipperhouse/uax29/v2 v2.5.0 // indirect
+ github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
- github.com/golang-jwt/jwt/v5 v5.3.0 // indirect
- github.com/klauspost/compress v1.18.1 // indirect
+ github.com/golang-jwt/jwt/v5 v5.3.1 // indirect
+ github.com/klauspost/compress v1.18.3 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-colorable v0.1.14 // indirect
github.com/mattn/go-isatty v0.0.20 // indirect
github.com/mattn/go-runewidth v0.0.19 // indirect
github.com/philhofer/fwd v1.2.0 // indirect
- github.com/tinylib/msgp v1.5.0 // indirect
+ github.com/tinylib/msgp v1.6.3 // indirect
github.com/valyala/bytebufferpool v1.0.0 // indirect
- github.com/valyala/fasthttp v1.68.0 // indirect
- golang.org/x/crypto v0.44.0 // indirect
- golang.org/x/sys v0.38.0 // indirect
- golang.org/x/text v0.31.0 // indirect
+ github.com/valyala/fasthttp v1.69.0 // indirect
+ golang.org/x/crypto v0.47.0 // indirect
+ golang.org/x/sys v0.40.0 // indirect
+ golang.org/x/text v0.33.0 // indirect
)
diff --git a/go.sum b/go.sum
index 26504ee..0600983 100644
--- a/go.sum
+++ b/go.sum
@@ -4,10 +4,14 @@ github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfa
github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA=
github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4=
github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
+github.com/clipperhouse/uax29/v2 v2.5.0 h1:x7T0T4eTHDONxFJsL94uKNKPHrclyFI0lm7+w94cO8U=
+github.com/clipperhouse/uax29/v2 v2.5.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/gabriel-vasile/mimetype v1.4.11 h1:AQvxbp830wPhHTqc1u7nzoLT+ZFxGY7emj5DR5DYFik=
github.com/gabriel-vasile/mimetype v1.4.11/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
+github.com/gabriel-vasile/mimetype v1.4.13 h1:46nXokslUBsAJE/wMsp5gtO500a4F3Nkz9Ufpk2AcUM=
+github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
@@ -16,14 +20,22 @@ github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJn
github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY=
github.com/go-playground/validator/v10 v10.28.0 h1:Q7ibns33JjyW48gHkuFT91qX48KG0ktULL6FgHdG688=
github.com/go-playground/validator/v10 v10.28.0/go.mod h1:GoI6I1SjPBh9p7ykNE/yj3fFYbyDOpwMn5KXd+m2hUU=
+github.com/go-playground/validator/v10 v10.30.1 h1:f3zDSN/zOma+w6+1Wswgd9fLkdwy06ntQJp0BBvFG0w=
+github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM=
github.com/gofiber/fiber/v2 v2.52.9 h1:YjKl5DOiyP3j0mO61u3NTmK7or8GzzWzCFzkboyP5cw=
github.com/gofiber/fiber/v2 v2.52.9/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
+github.com/gofiber/fiber/v2 v2.52.11 h1:5f4yzKLcBcF8ha1GQTWB+mpblWz3Vz6nSAbTL31HkWs=
+github.com/gofiber/fiber/v2 v2.52.11/go.mod h1:YEcBbO/FB+5M1IZNBP9FO3J9281zgPAreiI1oqg8nDw=
github.com/golang-jwt/jwt/v5 v5.3.0 h1:pv4AsKCKKZuqlgs5sUmn4x8UlGa0kEVt/puTpKx9vvo=
github.com/golang-jwt/jwt/v5 v5.3.0/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
+github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY=
+github.com/golang-jwt/jwt/v5 v5.3.1/go.mod h1:fxCRLWMO43lRc8nhHWY6LGqRcf+1gQWArsqaEUEa5bE=
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/klauspost/compress v1.18.1 h1:bcSGx7UbpBqMChDtsF28Lw6v/G94LPrrbMbdC3JH2co=
github.com/klauspost/compress v1.18.1/go.mod h1:ZQFFVG+MdnR0P+l6wpXgIL4NTtwiKIdBnrBd8Nrxr+0=
+github.com/klauspost/compress v1.18.3 h1:9PJRvfbmTabkOX8moIpXPbMMbYN60bWImDDU7L+/6zw=
+github.com/klauspost/compress v1.18.3/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4=
github.com/leodido/go-urn v1.4.0 h1:WT9HwE9SGECu3lg4d/dIA+jxlljEa1/ffXKmRjqdmIQ=
github.com/leodido/go-urn v1.4.0/go.mod h1:bvxc+MVxLKB4z00jd1z+Dvzr47oO32F/QSNjSBOlFxI=
github.com/lixenwraith/auth v0.0.0-20251104131016-e5a810f4e226 h1:c7wfyZGdy6RkM/b6mIazoYrAS+3qDL7d9M1CFm2e1VA=
@@ -36,6 +48,8 @@ github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byF
github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs=
github.com/mattn/go-sqlite3 v1.14.32 h1:JD12Ag3oLy1zQA+BNn74xRgaBbdhbNIDYvQUEuuErjs=
github.com/mattn/go-sqlite3 v1.14.32/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
+github.com/mattn/go-sqlite3 v1.14.33 h1:A5blZ5ulQo2AtayQ9/limgHEkFreKj1Dv226a1K73s0=
+github.com/mattn/go-sqlite3 v1.14.33/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
github.com/philhofer/fwd v1.2.0 h1:e6DnBTl7vGY+Gz322/ASL4Gyp1FspeMvx1RNDoToZuM=
github.com/philhofer/fwd v1.2.0/go.mod h1:RqIHx9QI14HlwKwm98g9Re5prTQ6LdeRQn+gXJFxsJM=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
@@ -44,20 +58,32 @@ github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/tinylib/msgp v1.5.0 h1:GWnqAE54wmnlFazjq2+vgr736Akg58iiHImh+kPY2pc=
github.com/tinylib/msgp v1.5.0/go.mod h1:cvjFkb4RiC8qSBOPMGPSzSAx47nAsfhLVTCZZNuHv5o=
+github.com/tinylib/msgp v1.6.3 h1:bCSxiTz386UTgyT1i0MSCvdbWjVW+8sG3PjkGsZQt4s=
+github.com/tinylib/msgp v1.6.3/go.mod h1:RSp0LW9oSxFut3KzESt5Voq4GVWyS+PSulT77roAqEA=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasthttp v1.68.0 h1:v12Nx16iepr8r9ySOwqI+5RBJ/DqTxhOy1HrHoDFnok=
github.com/valyala/fasthttp v1.68.0/go.mod h1:5EXiRfYQAoiO/khu4oU9VISC/eVY6JqmSpPJoHCKsz4=
+github.com/valyala/fasthttp v1.69.0 h1:fNLLESD2SooWeh2cidsuFtOcrEi4uB4m1mPrkJMZyVI=
+github.com/valyala/fasthttp v1.69.0/go.mod h1:4wA4PfAraPlAsJ5jMSqCE2ug5tqUPwKXxVj8oNECGcw=
github.com/xyproto/randomstring v1.0.5 h1:YtlWPoRdgMu3NZtP45drfy1GKoojuR7hmRcnhZqKjWU=
github.com/xyproto/randomstring v1.0.5/go.mod h1:rgmS5DeNXLivK7YprL0pY+lTuhNQW3iGxZ18UQApw/E=
golang.org/x/crypto v0.44.0 h1:A97SsFvM3AIwEEmTBiaxPPTYpDC47w720rdiiUvgoAU=
golang.org/x/crypto v0.44.0/go.mod h1:013i+Nw79BMiQiMsOPcVCB5ZIJbYkerPrGnOa00tvmc=
+golang.org/x/crypto v0.47.0 h1:V6e3FRj+n4dbpw86FJ8Fv7XVOql7TEwpHapKoMJ/GO8=
+golang.org/x/crypto v0.47.0/go.mod h1:ff3Y9VzzKbwSSEzWqJsJVBnWmRwRSHt/6Op5n9bQc4A=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.38.0 h1:3yZWxaJjBmCWXqhN1qh02AkOnCQ1poK6oF+a7xWL6Gc=
golang.org/x/sys v0.38.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
+golang.org/x/sys v0.40.0 h1:DBZZqJ2Rkml6QMQsZywtnjnnGvHza6BTfYFWY9kjEWQ=
+golang.org/x/sys v0.40.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
golang.org/x/term v0.37.0 h1:8EGAD0qCmHYZg6J17DvsMy9/wJ7/D/4pV/wfnld5lTU=
golang.org/x/term v0.37.0/go.mod h1:5pB4lxRNYYVZuTLmy8oR2BH8dflOR+IbTYFD8fi3254=
+golang.org/x/term v0.39.0 h1:RclSuaJf32jOqZz74CkPA9qFuVTX7vhLlpfj/IGWlqY=
+golang.org/x/term v0.39.0/go.mod h1:yxzUCTP/U+FzoxfdKmLaA0RV1WgE0VY7hXBwKtY/4ww=
golang.org/x/text v0.31.0 h1:aC8ghyu4JhP8VojJ2lEHBnochRno1sgL6nEi9WGFGMM=
golang.org/x/text v0.31.0/go.mod h1:tKRAlv61yKIjGGHX/4tP1LTbc13YSec1pxVEWXzfoeM=
+golang.org/x/text v0.33.0 h1:B3njUFyqtHDUI5jMn1YIr5B0IE2U0qck04r6d4KPAxE=
+golang.org/x/text v0.33.0/go.mod h1:LuMebE6+rBincTi9+xWTY8TztLzKHc/9C1uBCG27+q8=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
diff --git a/internal/client/api/client.go b/internal/client/api/client.go
index 79b297b..5578423 100644
--- a/internal/client/api/client.go
+++ b/internal/client/api/client.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/api/client.go
package api
import (
@@ -226,6 +225,10 @@ func (c *Client) Login(identifier, password string) (*AuthResponse, error) {
return &resp, err
}
+func (c *Client) Logout() error {
+ return c.doRequest("POST", "/api/v1/auth/logout", nil, nil)
+}
+
func (c *Client) GetCurrentUser() (*UserResponse, error) {
var resp UserResponse
err := c.doRequest("GET", "/api/v1/auth/me", nil, &resp)
diff --git a/internal/client/api/types.go b/internal/client/api/types.go
index 1d230db..77bf183 100644
--- a/internal/client/api/types.go
+++ b/internal/client/api/types.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/client/api/types.go
package api
import "time"
@@ -71,9 +70,11 @@ type BoardResponse struct {
}
type AuthResponse struct {
- Token string `json:"token"`
- UserID string `json:"userId"`
- Username string `json:"username"`
+ Token string `json:"token"`
+ UserID string `json:"userId"`
+ Username string `json:"username"`
+ Email string `json:"email,omitempty"`
+ ExpiresAt time.Time `json:"expiresAt,omitempty"`
}
type UserResponse struct {
diff --git a/internal/client/command/auth.go b/internal/client/command/auth.go
index bd67adf..a6e97c0 100644
--- a/internal/client/command/auth.go
+++ b/internal/client/command/auth.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/client/command/auth.go
package command
import (
@@ -119,10 +118,20 @@ func loginHandler(s *session.Session, args []string) error {
}
func logoutHandler(s *session.Session, args []string) error {
+ c := s.GetClient().(*api.Client)
+
+ // Call server to invalidate session if authenticated
+ if s.GetAuthToken() != "" {
+ if err := c.Logout(); err != nil {
+ // Log but don't fail - clear local state anyway
+ display.Println(display.Yellow, "Server logout failed: %s", err.Error())
+ }
+ }
+
+ // Clear local state
s.SetAuthToken("")
s.SetCurrentUser("")
s.SetUsername("")
- c := s.GetClient().(*api.Client)
c.SetToken("")
display.Println(display.Green, "Logged out")
diff --git a/internal/client/command/debug.go b/internal/client/command/debug.go
index 3fd25a8..6fb695d 100644
--- a/internal/client/command/debug.go
+++ b/internal/client/command/debug.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/client/command/debug.go
package command
import (
diff --git a/internal/client/command/game.go b/internal/client/command/game.go
index 79d8f52..a6ea31a 100644
--- a/internal/client/command/game.go
+++ b/internal/client/command/game.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/client/command/game.go
package command
import (
diff --git a/internal/client/command/pass_native.go b/internal/client/command/pass_native.go
index 295d6ca..4b27c05 100644
--- a/internal/client/command/pass_native.go
+++ b/internal/client/command/pass_native.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/client/command/pass_native.go
//go:build !js && !wasm
package command
diff --git a/internal/client/command/pass_wasm.go b/internal/client/command/pass_wasm.go
index 8492935..56ec284 100644
--- a/internal/client/command/pass_wasm.go
+++ b/internal/client/command/pass_wasm.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/client/command/pass_wasm.go
//go:build js && wasm
package command
diff --git a/internal/client/command/registry.go b/internal/client/command/registry.go
index 14f85d6..83ab85c 100644
--- a/internal/client/command/registry.go
+++ b/internal/client/command/registry.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/client/command/registry.go
package command
import (
diff --git a/internal/client/display/board.go b/internal/client/display/board.go
index 8595ed1..58484c8 100644
--- a/internal/client/display/board.go
+++ b/internal/client/display/board.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/client/display/board.go
package display
import (
diff --git a/internal/client/display/colors.go b/internal/client/display/colors.go
index 29c7b9f..07cf4ab 100644
--- a/internal/client/display/colors.go
+++ b/internal/client/display/colors.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/client/display/colors.go
package display
import (
diff --git a/internal/client/display/format.go b/internal/client/display/format.go
index e90fb51..9644ddf 100644
--- a/internal/client/display/format.go
+++ b/internal/client/display/format.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/client/display/format.go
package display
import (
diff --git a/internal/client/session/session.go b/internal/client/session/session.go
index 4a110b8..9a8a222 100644
--- a/internal/client/session/session.go
+++ b/internal/client/session/session.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/client/session/session.go
package session
import (
diff --git a/internal/server/board/board.go b/internal/server/board/board.go
index b09a423..5eb8b28 100644
--- a/internal/server/board/board.go
+++ b/internal/server/board/board.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/board/board.go
package board
import (
diff --git a/internal/server/core/api.go b/internal/server/core/api.go
index bac9c97..3b59c94 100644
--- a/internal/server/core/api.go
+++ b/internal/server/core/api.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/core/api.go
package core
// Request types
diff --git a/internal/server/core/error.go b/internal/server/core/error.go
index 1fa4ebb..47043e9 100644
--- a/internal/server/core/error.go
+++ b/internal/server/core/error.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/core/error.go
package core
// Error codes
@@ -12,4 +11,6 @@ const (
ErrInvalidRequest = "INVALID_REQUEST"
ErrInvalidFEN = "INVALID_FEN"
ErrInternalError = "INTERNAL_ERROR"
+ ErrResourceLimit = "RESOURCE_LIMIT"
+ ErrUnauthorized = "UNAUTHORIZED"
)
\ No newline at end of file
diff --git a/internal/server/core/player.go b/internal/server/core/player.go
index 9c3aa22..075c5cc 100644
--- a/internal/server/core/player.go
+++ b/internal/server/core/player.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/core/player.go
package core
import (
@@ -19,6 +18,7 @@ type Player struct {
Type PlayerType `json:"type"`
Level int `json:"level,omitempty"` // Only for computer
SearchTime int `json:"searchTime,omitempty"` // Only for computer
+ ClaimedBy string `json:"claimedBy,omitempty"` // UserID that claimed this slot
}
// PlayerConfig for API requests and configuration
@@ -28,7 +28,7 @@ type PlayerConfig struct {
SearchTime int `json:"searchTime,omitempty" validate:"omitempty,min=100,max=10000"` // Processor sets the min value
}
-// PlayersResponse for API responses - now contains full Player structs
+// PlayersResponse for API responses
type PlayersResponse struct {
White *Player `json:"white"`
Black *Player `json:"black"`
@@ -50,10 +50,20 @@ func NewPlayer(config PlayerConfig, color Color) *Player {
return player
}
+// IsClaimed returns true if this slot has been claimed by a user
+func (p *Player) IsClaimed() bool {
+ return p.ClaimedBy != ""
+}
+
+// CanBeClaimed returns true if this slot can be claimed
+func (p *Player) CanBeClaimed() bool {
+ return p.Type == PlayerHuman && !p.IsClaimed()
+}
+
type Color byte
const (
- ColorWhite = iota + 1
+ ColorWhite Color = iota + 1
ColorBlack
)
diff --git a/internal/server/core/state.go b/internal/server/core/state.go
index b7d2bac..314fd83 100644
--- a/internal/server/core/state.go
+++ b/internal/server/core/state.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/core/state.go
package core
type State int
diff --git a/internal/server/engine/engine.go b/internal/server/engine/engine.go
index a3099d9..a739e12 100644
--- a/internal/server/engine/engine.go
+++ b/internal/server/engine/engine.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/engine/engine.go
package engine
import (
diff --git a/internal/server/game/game.go b/internal/server/game/game.go
index 0cbc09e..d892c4a 100644
--- a/internal/server/game/game.go
+++ b/internal/server/game/game.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/game/game.go
package game
import (
@@ -149,4 +148,47 @@ func (g *Game) InitialFEN() string {
return g.snapshots[0].FEN
}
return board.StartingFEN
+}
+
+// ClaimSlot claims a player slot for a user
+// Caller must hold the lock
+func (g *Game) ClaimSlot(color core.Color, userID string) error {
+ player := g.players[color]
+ if player == nil {
+ return fmt.Errorf("invalid color")
+ }
+
+ if player.Type != core.PlayerHuman {
+ return fmt.Errorf("cannot claim computer slot")
+ }
+
+ if player.ClaimedBy != "" && player.ClaimedBy != userID {
+ return fmt.Errorf("slot already claimed by another user")
+ }
+
+ player.ClaimedBy = userID
+ return nil
+}
+
+// GetSlotOwner returns the userID that claimed the slot, empty if unclaimed
+// Caller must hold the lock
+func (g *Game) GetSlotOwner(color core.Color) string {
+ player := g.players[color]
+ if player == nil {
+ return ""
+ }
+ return player.ClaimedBy
+}
+
+// IsSlotClaimedBy checks if a specific user owns the slot
+func (g *Game) IsSlotClaimedBy(color core.Color, userID string) bool {
+ return g.GetSlotOwner(color) == userID
+}
+
+// HasComputerPlayer returns true if at least one player is computer
+func (g *Game) HasComputerPlayer() bool {
+ white := g.players[core.ColorWhite]
+ black := g.players[core.ColorBlack]
+ return (white != nil && white.Type == core.PlayerComputer) ||
+ (black != nil && black.Type == core.PlayerComputer)
}
\ No newline at end of file
diff --git a/internal/server/http/auth.go b/internal/server/http/auth.go
index 34c3a3f..1232415 100644
--- a/internal/server/http/auth.go
+++ b/internal/server/http/auth.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/http/auth.go
package http
import (
@@ -9,6 +8,7 @@ import (
"unicode"
"chess/internal/server/core"
+ "chess/internal/server/service"
"github.com/gofiber/fiber/v2"
)
@@ -90,8 +90,8 @@ func (h *HTTPHandler) RegisterHandler(c *fiber.Ctx) error {
req.Email = strings.ToLower(req.Email)
}
- // Create user
- user, err := h.svc.CreateUser(req.Username, req.Email, req.Password)
+ // Create user (temp by default via API)
+ user, err := h.svc.CreateUser(req.Username, req.Email, req.Password, false)
if err != nil {
if strings.Contains(err.Error(), "already exists") {
return c.Status(fiber.StatusConflict).JSON(core.ErrorResponse{
@@ -100,14 +100,30 @@ func (h *HTTPHandler) RegisterHandler(c *fiber.Ctx) error {
Details: "username or email already taken",
})
}
+ if strings.Contains(err.Error(), "limit") || strings.Contains(err.Error(), "capacity") {
+ return c.Status(fiber.StatusServiceUnavailable).JSON(core.ErrorResponse{
+ Error: "registration temporarily unavailable",
+ Code: core.ErrResourceLimit,
+ Details: err.Error(),
+ })
+ }
return c.Status(fiber.StatusInternalServerError).JSON(core.ErrorResponse{
Error: "failed to create user",
Code: core.ErrInternalError,
})
}
+ // Create session for new user
+ sessionID, err := h.svc.CreateUserSession(user.UserID)
+ if err != nil {
+ return c.Status(fiber.StatusInternalServerError).JSON(core.ErrorResponse{
+ Error: "failed to create session",
+ Code: core.ErrInternalError,
+ })
+ }
+
// Generate JWT token
- token, err := h.svc.GenerateUserToken(user.UserID)
+ token, err := h.svc.GenerateUserToken(user.UserID, sessionID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(core.ErrorResponse{
Error: "failed to generate token",
@@ -120,7 +136,7 @@ func (h *HTTPHandler) RegisterHandler(c *fiber.Ctx) error {
UserID: user.UserID,
Username: user.Username,
Email: user.Email,
- ExpiresAt: time.Now().Add(7 * 24 * time.Hour),
+ ExpiresAt: time.Now().Add(service.SessionTTL),
})
}
@@ -173,18 +189,17 @@ func (h *HTTPHandler) LoginHandler(c *fiber.Ctx) error {
// Normalize identifier for case-insensitive lookup
req.Identifier = strings.ToLower(req.Identifier)
- // Authenticate user
- user, err := h.svc.AuthenticateUser(req.Identifier, req.Password)
+ // Authenticate user and create session (invalidates previous session)
+ user, sessionID, err := h.svc.AuthenticateUser(req.Identifier, req.Password)
if err != nil {
- // Always return same error to prevent user enumeration
return c.Status(fiber.StatusUnauthorized).JSON(core.ErrorResponse{
Error: "invalid credentials",
Code: core.ErrInvalidRequest,
})
}
- // Generate JWT token
- token, err := h.svc.GenerateUserToken(user.UserID)
+ // Generate JWT token with session ID
+ token, err := h.svc.GenerateUserToken(user.UserID, sessionID)
if err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(core.ErrorResponse{
Error: "failed to generate token",
@@ -192,10 +207,6 @@ func (h *HTTPHandler) LoginHandler(c *fiber.Ctx) error {
})
}
- // Update last login
- // TODO: for now, non-blocking if login time update fails, log/block in the future
- _ = h.svc.UpdateLastLogin(user.UserID)
-
return c.JSON(AuthResponse{
Token: token,
UserID: user.UserID,
@@ -229,4 +240,25 @@ func (h *HTTPHandler) GetCurrentUserHandler(c *fiber.Ctx) error {
Email: user.Email,
CreatedAt: user.CreatedAt,
})
+}
+
+// LogoutHandler invalidates the current session
+func (h *HTTPHandler) LogoutHandler(c *fiber.Ctx) error {
+ // Extract session ID from token claims
+ sessionID, ok := c.Locals("sessionID").(string)
+ if !ok || sessionID == "" {
+ return c.Status(fiber.StatusBadRequest).JSON(core.ErrorResponse{
+ Error: "no active session",
+ Code: core.ErrInvalidRequest,
+ })
+ }
+
+ if err := h.svc.InvalidateSession(sessionID); err != nil {
+ return c.Status(fiber.StatusInternalServerError).JSON(core.ErrorResponse{
+ Error: "failed to logout",
+ Code: core.ErrInternalError,
+ })
+ }
+
+ return c.JSON(fiber.Map{"message": "logged out"})
}
\ No newline at end of file
diff --git a/internal/server/http/handler.go b/internal/server/http/handler.go
index b39c5de..1a71391 100644
--- a/internal/server/http/handler.go
+++ b/internal/server/http/handler.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/http/handler.go
package http
import (
@@ -100,6 +99,9 @@ func NewFiberApp(proc *processor.Processor, svc *service.Service, devMode bool)
// Current user (requires auth)
auth.Get("/me", AuthRequired(validateToken), h.GetCurrentUserHandler)
+ // Logout
+ auth.Post("/logout", AuthRequired(validateToken), h.LogoutHandler)
+
// Game routes with standard rate limiting
maxReq := rateLimitRate
if devMode {
@@ -137,7 +139,7 @@ func NewFiberApp(proc *processor.Processor, svc *service.Service, devMode bool)
api.Put("/games/:gameId/players", h.ConfigurePlayers)
api.Get("/games/:gameId", h.GetGame)
api.Delete("/games/:gameId", h.DeleteGame)
- api.Post("/games/:gameId/moves", h.MakeMove)
+ api.Post("/games/:gameId/moves", OptionalAuth(validateToken), h.MakeMove)
api.Post("/games/:gameId/undo", h.UndoMove)
api.Get("/games/:gameId/board", h.GetBoard)
@@ -370,7 +372,6 @@ func (h *HTTPHandler) GetGame(c *fiber.Ctx) error {
func (h *HTTPHandler) MakeMove(c *fiber.Ctx) error {
gameID := c.Params("gameId")
- // Validate UUID format
if !isValidUUID(gameID) {
return c.Status(fiber.StatusBadRequest).JSON(core.ErrorResponse{
Error: "invalid game ID format",
@@ -379,7 +380,6 @@ func (h *HTTPHandler) MakeMove(c *fiber.Ctx) error {
})
}
- // Ensure middleware validation ran
validated, ok := c.Locals("validated").(bool)
if !ok || !validated {
return c.Status(fiber.StatusInternalServerError).JSON(core.ErrorResponse{
@@ -388,7 +388,6 @@ func (h *HTTPHandler) MakeMove(c *fiber.Ctx) error {
})
}
- // Retrieve validated parsed body
validatedBody := c.Locals("validatedBody")
if validatedBody == nil {
return c.Status(fiber.StatusInternalServerError).JSON(core.ErrorResponse{
@@ -399,15 +398,21 @@ func (h *HTTPHandler) MakeMove(c *fiber.Ctx) error {
var req core.MoveRequest
req = *(validatedBody.(*core.MoveRequest))
- // Create command and execute
+ // Get authenticated user ID if present
+ userID, _ := c.Locals("userID").(string)
+
cmd := processor.NewMakeMoveCommand(gameID, req)
+ cmd.UserID = userID // Pass user context for authorization
+
resp := h.proc.Execute(cmd)
- // Return appropriate HTTP response with correct status code
if !resp.Success {
statusCode := fiber.StatusBadRequest
- if resp.Error.Code == core.ErrGameNotFound {
+ switch resp.Error.Code {
+ case core.ErrGameNotFound:
statusCode = fiber.StatusNotFound
+ case core.ErrUnauthorized:
+ statusCode = fiber.StatusForbidden
}
return c.Status(statusCode).JSON(resp.Error)
}
diff --git a/internal/server/http/middleware.go b/internal/server/http/middleware.go
index af863ff..5bd6b3a 100644
--- a/internal/server/http/middleware.go
+++ b/internal/server/http/middleware.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/http/middleware.go
package http
import (
@@ -23,7 +22,7 @@ func AuthRequired(validateToken TokenValidator) fiber.Handler {
})
}
- userID, _, err := validateToken(token)
+ userID, claims, err := validateToken(token)
if err != nil {
return c.Status(fiber.StatusUnauthorized).JSON(core.ErrorResponse{
Error: "invalid or expired token",
@@ -32,6 +31,9 @@ func AuthRequired(validateToken TokenValidator) fiber.Handler {
}
c.Locals("userID", userID)
+ if sessionID, ok := claims["session_id"].(string); ok {
+ c.Locals("sessionID", sessionID)
+ }
return c.Next()
}
}
@@ -44,11 +46,13 @@ func OptionalAuth(validateToken TokenValidator) fiber.Handler {
return c.Next()
}
- userID, _, err := validateToken(token)
+ userID, claims, err := validateToken(token)
if err == nil {
c.Locals("userID", userID)
+ if sessionID, ok := claims["session_id"].(string); ok {
+ c.Locals("sessionID", sessionID)
+ }
}
- // Continue regardless of token validity
return c.Next()
}
}
diff --git a/internal/server/http/validator.go b/internal/server/http/validator.go
index f537b9b..54fc87f 100644
--- a/internal/server/http/validator.go
+++ b/internal/server/http/validator.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/http/handler.go
package http
import (
diff --git a/internal/server/processor/command.go b/internal/server/processor/command.go
index ea16fee..176f5c1 100644
--- a/internal/server/processor/command.go
+++ b/internal/server/processor/command.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/processor/command.go
package processor
import (
diff --git a/internal/server/processor/processor.go b/internal/server/processor/processor.go
index 2a1618c..3f31ff1 100644
--- a/internal/server/processor/processor.go
+++ b/internal/server/processor/processor.go
@@ -1,5 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/processor/processor.go
-
package processor
import (
@@ -131,6 +129,15 @@ func (p *Processor) handleCreateGame(cmd Command) ProcessorResponse {
args.Black.SearchTime = minSearchTime
}
+ // Check computer game limit
+ hasComputer := args.White.Type == core.PlayerComputer || args.Black.Type == core.PlayerComputer
+ if hasComputer && !p.svc.CanCreateComputerGame() {
+ return p.errorResponse(
+ fmt.Sprintf("computer game limit reached (%d/%d)", p.svc.GetComputerGameCount(), service.MaxComputerGames),
+ core.ErrResourceLimit,
+ )
+ }
+
// Generate game ID
gameID := p.svc.GenerateGameID()
@@ -163,12 +170,17 @@ func (p *Processor) handleCreateGame(cmd Command) ProcessorResponse {
whitePlayer := core.NewPlayer(args.White, core.ColorWhite)
blackPlayer := core.NewPlayer(args.Black, core.ColorBlack)
- // Override player IDs for authenticated human players
- if args.White.Type == core.PlayerHuman && cmd.UserID != "" {
- whitePlayer.ID = cmd.UserID
- }
- if args.Black.Type == core.PlayerHuman && cmd.UserID != "" {
- blackPlayer.ID = cmd.UserID
+ // FIX: Only assign authenticated user to ONE human slot
+ // If both are human, authenticated user gets white; black remains unclaimed
+ if cmd.UserID != "" {
+ if args.White.Type == core.PlayerHuman {
+ whitePlayer.ID = cmd.UserID
+ whitePlayer.ClaimedBy = cmd.UserID
+ } else if args.Black.Type == core.PlayerHuman {
+ // Only claim black if white is not human (i.e., H vs C scenario)
+ blackPlayer.ID = cmd.UserID
+ blackPlayer.ClaimedBy = cmd.UserID
+ }
}
// Create game in service with fully-formed players
@@ -252,7 +264,7 @@ func (p *Processor) handleGetGame(cmd Command) ProcessorResponse {
}
}
-// handleMakeMove processes human moves
+// handleMakeMove processes human moves with authorization
func (p *Processor) handleMakeMove(cmd Command) ProcessorResponse {
args, ok := cmd.Args.(core.MoveRequest)
if !ok {
@@ -278,21 +290,22 @@ func (p *Processor) handleMakeMove(cmd Command) ProcessorResponse {
return p.errorResponse("game is in invalid state", core.ErrInvalidRequest)
}
- // Handle empty move string - trigger computer move
+ currentColor := g.NextTurnColor()
+ currentPlayer := g.NextPlayer()
+
+ // Handle computer move trigger
if strings.TrimSpace(args.Move) == "cccc" {
- if g.NextPlayer().Type != core.PlayerComputer {
+ if currentPlayer.Type != core.PlayerComputer {
return p.errorResponse("not computer player's turn", core.ErrNotHumanTurn)
}
- // Set state to pending and trigger computer move
p.svc.UpdateGameState(cmd.GameID, core.StatePending)
p.triggerComputerMove(cmd.GameID, g)
- // Re-fetch for updated state
g, _ = p.svc.GetGame(cmd.GameID)
response := p.buildGameResponse(cmd.GameID, g)
response.LastMove = &core.MoveInfo{
- PlayerColor: g.NextTurnColor().String(),
+ PlayerColor: currentColor.String(),
}
return ProcessorResponse{
@@ -302,11 +315,32 @@ func (p *Processor) handleMakeMove(cmd Command) ProcessorResponse {
}
}
- // Handle human move
- if g.NextPlayer().Type != core.PlayerHuman {
+ // Human move - validate authorization
+ if currentPlayer.Type != core.PlayerHuman {
return p.errorResponse("not human player's turn", core.ErrNotHumanTurn)
}
+ // Authorization: first-move-claims-slot model
+ slotOwner := g.GetSlotOwner(currentColor)
+
+ if slotOwner == "" {
+ // Slot unclaimed - claim it with this move
+ if cmd.UserID != "" {
+ if err := p.svc.ClaimGameSlot(cmd.GameID, currentColor, cmd.UserID); err != nil {
+ return p.errorResponse(fmt.Sprintf("failed to claim slot: %v", err), core.ErrInternalError)
+ }
+ }
+ // Anonymous users can also claim by making a move (slot remains "unclaimed" but move proceeds)
+ } else if cmd.UserID != "" && slotOwner != cmd.UserID {
+ // Slot claimed by different user
+ return p.errorResponse("not your turn - slot claimed by another player", core.ErrUnauthorized)
+ }
+ // If slotOwner == cmd.UserID, authorized to proceed
+ // If slotOwner != "" && cmd.UserID == "", anonymous trying to move claimed slot - block
+ if slotOwner != "" && cmd.UserID == "" {
+ return p.errorResponse("slot claimed - authentication required", core.ErrUnauthorized)
+ }
+
// Normalize and validate move format
move := strings.ToLower(strings.TrimSpace(args.Move))
if !p.isMoveSafe(move) {
@@ -314,7 +348,6 @@ func (p *Processor) handleMakeMove(cmd Command) ProcessorResponse {
}
currentFEN := g.CurrentFEN()
- currentColor := g.NextTurnColor()
// Validate move with engine
p.mu.Lock()
diff --git a/internal/server/processor/queue.go b/internal/server/processor/queue.go
index 4534a19..13be5f3 100644
--- a/internal/server/processor/queue.go
+++ b/internal/server/processor/queue.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/processor/queue.go
package processor
import (
diff --git a/internal/server/service/game.go b/internal/server/service/game.go
index 1302bdf..badc1b9 100644
--- a/internal/server/service/game.go
+++ b/internal/server/service/game.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/service/game.go
package service
import (
@@ -21,6 +20,15 @@ func (s *Service) CreateGame(id string, whitePlayer, blackPlayer *core.Player, i
return fmt.Errorf("game %s already exists", id)
}
+ // Check computer game limit
+ hasComputer := whitePlayer.Type == core.PlayerComputer || blackPlayer.Type == core.PlayerComputer
+ if hasComputer {
+ if s.computerGames.Load() >= MaxComputerGames {
+ return fmt.Errorf("computer game limit reached (%d/%d)", s.computerGames.Load(), MaxComputerGames)
+ }
+ s.computerGames.Add(1)
+ }
+
// Store game with provided players
s.games[id] = game.New(initialFEN, whitePlayer, blackPlayer, startingTurn)
@@ -186,16 +194,22 @@ func (s *Service) UndoMoves(gameID string, count int) error {
return nil
}
-// DeleteGame removes a game from memory
+// DeleteGame removes a game from the service
func (s *Service) DeleteGame(gameID string) error {
s.mu.Lock()
defer s.mu.Unlock()
- if _, ok := s.games[gameID]; !ok {
+ g, ok := s.games[gameID]
+ if !ok {
return fmt.Errorf("game not found: %s", gameID)
}
- // Notify and remove all waiters before deletion
+ // Decrement computer game count if applicable
+ if g.HasComputerPlayer() {
+ s.computerGames.Add(-1)
+ }
+
+ // Remove from wait registry
s.waiter.RemoveGame(gameID)
delete(s.games, gameID)
diff --git a/internal/server/service/service.go b/internal/server/service/service.go
index 0f0c091..70cba18 100644
--- a/internal/server/service/service.go
+++ b/internal/server/service/service.go
@@ -1,24 +1,35 @@
-// FILE: lixenwraith/chess/internal/server/service/service.go
package service
import (
+ "chess/internal/server/core"
"context"
"errors"
"fmt"
"sync"
+ "sync/atomic"
"time"
"chess/internal/server/game"
"chess/internal/server/storage"
)
-// Service is a pure state manager for chess games with optional persistence
+const (
+ MaxComputerGames = 10
+ MaxUsers = 100
+ PermanentSlots = 10
+ TempUserTTL = 24 * time.Hour
+ SessionTTL = 7 * 24 * time.Hour
+ CleanupJobInterval = 1 * time.Hour
+)
+
+// Service coordinates game state, user management, and storage
type Service struct {
- games map[string]*game.Game
- mu sync.RWMutex
- store *storage.Store // nil if persistence disabled
- jwtSecret []byte
- waiter *WaitRegistry // Long-polling notification registry
+ games map[string]*game.Game
+ mu sync.RWMutex
+ store *storage.Store
+ jwtSecret []byte
+ waiter *WaitRegistry
+ computerGames atomic.Int32 // Active games with computer players
}
// New creates a new service instance with optional storage
@@ -47,12 +58,56 @@ func (s *Service) RegisterWait(gameID string, moveCount int, ctx context.Context
return s.waiter.RegisterWait(gameID, moveCount, ctx)
}
+// CanCreateComputerGame checks if a new computer game can be created
+func (s *Service) CanCreateComputerGame() bool {
+ return s.computerGames.Load() < MaxComputerGames
+}
+
+// IncrementComputerGames increments the computer game counter
+func (s *Service) IncrementComputerGames() {
+ s.computerGames.Add(1)
+}
+
+// DecrementComputerGames decrements the computer game counter
+func (s *Service) DecrementComputerGames() {
+ s.computerGames.Add(-1)
+}
+
+// GetComputerGameCount returns current computer game count
+func (s *Service) GetComputerGameCount() int32 {
+ return s.computerGames.Load()
+}
+
+// ClaimGameSlot claims a player slot for a user
+func (s *Service) ClaimGameSlot(gameID string, color core.Color, userID string) error {
+ s.mu.Lock()
+ defer s.mu.Unlock()
+
+ g, ok := s.games[gameID]
+ if !ok {
+ return fmt.Errorf("game not found: %s", gameID)
+ }
+
+ return g.ClaimSlot(color, userID)
+}
+
+// GetSlotOwner returns the user who claimed a slot
+func (s *Service) GetSlotOwner(gameID string, color core.Color) (string, error) {
+ s.mu.RLock()
+ defer s.mu.RUnlock()
+
+ g, ok := s.games[gameID]
+ if !ok {
+ return "", fmt.Errorf("game not found: %s", gameID)
+ }
+
+ return g.GetSlotOwner(color), nil
+}
+
// Shutdown gracefully shuts down the service
func (s *Service) Shutdown(timeout time.Duration) error {
- // Collect all errors
var errs []error
- // Shutdown wait registry
if err := s.waiter.Shutdown(timeout); err != nil {
errs = append(errs, fmt.Errorf("wait registry: %w", err))
}
@@ -60,10 +115,8 @@ func (s *Service) Shutdown(timeout time.Duration) error {
s.mu.Lock()
defer s.mu.Unlock()
- // Clear all games
s.games = make(map[string]*game.Game)
- // Close storage if enabled
if s.store != nil {
if err := s.store.Close(); err != nil {
errs = append(errs, fmt.Errorf("storage: %w", err))
@@ -71,4 +124,40 @@ func (s *Service) Shutdown(timeout time.Duration) error {
}
return errors.Join(errs...)
+}
+
+// RunCleanupJob runs periodic cleanup of expired users and sessions
+func (s *Service) RunCleanupJob(ctx context.Context, interval time.Duration) {
+ ticker := time.NewTicker(interval)
+ defer ticker.Stop()
+
+ for {
+ select {
+ case <-ctx.Done():
+ return
+ case <-ticker.C:
+ s.cleanupExpired()
+ }
+ }
+}
+
+func (s *Service) cleanupExpired() {
+ if s.store == nil {
+ return
+ }
+
+ // Cleanup expired temp users
+ if deleted, err := s.store.DeleteExpiredTempUsers(); err != nil {
+ // Log but don't fail
+ fmt.Printf("cleanup: failed to delete expired users: %v\n", err)
+ } else if deleted > 0 {
+ fmt.Printf("cleanup: deleted %d expired temp users\n", deleted)
+ }
+
+ // Cleanup expired sessions
+ if deleted, err := s.store.DeleteExpiredSessions(); err != nil {
+ fmt.Printf("cleanup: failed to delete expired sessions: %v\n", err)
+ } else if deleted > 0 {
+ fmt.Printf("cleanup: deleted %d expired sessions\n", deleted)
+ }
}
\ No newline at end of file
diff --git a/internal/server/service/user.go b/internal/server/service/user.go
index ca258df..627aab5 100644
--- a/internal/server/service/user.go
+++ b/internal/server/service/user.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/service/user.go
package service
import (
@@ -14,25 +13,54 @@ import (
// User represents a registered user account
type User struct {
- UserID string
- Username string
- Email string
- CreatedAt time.Time
+ UserID string
+ Username string
+ Email string
+ AccountType string
+ CreatedAt time.Time
+ ExpiresAt *time.Time
}
-// CreateUser creates new user with transactional consistency
-func (s *Service) CreateUser(username, email, password string) (*User, error) {
+// CreateUser creates new user with registration limits enforcement
+func (s *Service) CreateUser(username, email, password string, permanent bool) (*User, error) {
if s.store == nil {
return nil, fmt.Errorf("storage disabled")
}
+ // Check registration limits
+ total, permCount, _, err := s.store.GetUserCounts()
+ if err != nil {
+ return nil, fmt.Errorf("failed to check user limits: %w", err)
+ }
+
+ // Determine account type
+ accountType := "temp"
+ var expiresAt *time.Time
+
+ if permanent {
+ if permCount >= PermanentSlots {
+ return nil, fmt.Errorf("permanent user slots full (%d/%d)", permCount, PermanentSlots)
+ }
+ accountType = "permanent"
+ } else {
+ expiry := time.Now().UTC().Add(TempUserTTL)
+ expiresAt = &expiry
+ }
+
+ // Handle capacity - remove oldest temp user if at max
+ if total >= MaxUsers {
+ if err := s.removeOldestTempUser(); err != nil {
+ return nil, fmt.Errorf("at capacity and cannot make room: %w", err)
+ }
+ }
+
// Hash password
passwordHash, err := auth.HashPassword(password)
if err != nil {
return nil, fmt.Errorf("failed to hash password: %w", err)
}
- // Generate guaranteed unique user ID with proper collision handling
+ // Generate unique user ID
userID, err := s.generateUniqueUserID()
if err != nil {
return nil, fmt.Errorf("failed to generate unique ID: %w", err)
@@ -40,19 +68,22 @@ func (s *Service) CreateUser(username, email, password string) (*User, error) {
// Create user record
user := &User{
- UserID: userID,
- Username: username,
- Email: email,
- CreatedAt: time.Now().UTC(),
+ UserID: userID,
+ Username: username,
+ Email: email,
+ AccountType: accountType,
+ CreatedAt: time.Now().UTC(),
+ ExpiresAt: expiresAt,
}
- // Use transactional storage method
record := storage.UserRecord{
UserID: userID,
- Username: username,
- Email: email,
+ Username: strings.ToLower(username),
+ Email: strings.ToLower(email),
PasswordHash: passwordHash,
+ AccountType: accountType,
CreatedAt: user.CreatedAt,
+ ExpiresAt: expiresAt,
}
if err = s.store.CreateUser(record); err != nil {
@@ -62,11 +93,28 @@ func (s *Service) CreateUser(username, email, password string) (*User, error) {
return user, nil
}
-// AuthenticateUser verifies user credentials and returns user information
-// AuthenticateUser verifies user credentials and returns user information
-func (s *Service) AuthenticateUser(identifier, password string) (*User, error) {
+// removeOldestTempUser removes the oldest temporary user to make room
+func (s *Service) removeOldestTempUser() error {
+ oldest, err := s.store.GetOldestTempUser()
+ if err != nil {
+ return fmt.Errorf("no temp users to remove: %w", err)
+ }
+
+ // Delete their session first
+ _ = s.store.DeleteSessionByUserID(oldest.UserID)
+
+ // Delete the user
+ if err := s.store.DeleteUserByID(oldest.UserID); err != nil {
+ return fmt.Errorf("failed to remove oldest user: %w", err)
+ }
+
+ return nil
+}
+
+// AuthenticateUser verifies credentials and creates a new session
+func (s *Service) AuthenticateUser(identifier, password string) (*User, string, error) {
if s.store == nil {
- return nil, fmt.Errorf("storage disabled")
+ return nil, "", fmt.Errorf("storage disabled")
}
var userRecord *storage.UserRecord
@@ -80,36 +128,62 @@ func (s *Service) AuthenticateUser(identifier, password string) (*User, error) {
}
if err != nil {
- // Always hash to prevent timing attacks
- auth.HashPassword(password)
- return nil, fmt.Errorf("invalid credentials")
+ auth.HashPassword(password) // Timing attack prevention
+ return nil, "", fmt.Errorf("invalid credentials")
}
// Verify password
if err := auth.VerifyPassword(password, userRecord.PasswordHash); err != nil {
- return nil, fmt.Errorf("invalid credentials")
+ return nil, "", fmt.Errorf("invalid credentials")
}
- return &User{
+ // Check if temp user expired
+ if userRecord.AccountType == "temp" && userRecord.ExpiresAt != nil {
+ if time.Now().UTC().After(*userRecord.ExpiresAt) {
+ return nil, "", fmt.Errorf("account expired")
+ }
+ }
+
+ // Create new session (invalidates any existing session)
+ sessionID := uuid.New().String()
+ sessionRecord := storage.SessionRecord{
+ SessionID: sessionID,
UserID: userRecord.UserID,
- Username: userRecord.Username,
- Email: userRecord.Email,
- CreatedAt: userRecord.CreatedAt,
- }, nil
+ CreatedAt: time.Now().UTC(),
+ ExpiresAt: time.Now().UTC().Add(SessionTTL),
+ }
+
+ if err := s.store.CreateSession(sessionRecord); err != nil {
+ return nil, "", fmt.Errorf("failed to create session: %w", err)
+ }
+
+ // Update last login
+ _ = s.store.UpdateUserLastLoginSync(userRecord.UserID, time.Now().UTC())
+
+ return &User{
+ UserID: userRecord.UserID,
+ Username: userRecord.Username,
+ Email: userRecord.Email,
+ AccountType: userRecord.AccountType,
+ CreatedAt: userRecord.CreatedAt,
+ ExpiresAt: userRecord.ExpiresAt,
+ }, sessionID, nil
}
-// UpdateLastLogin updates the last login timestamp for a user
-func (s *Service) UpdateLastLogin(userID string) error {
+// ValidateSession checks if a session is valid
+func (s *Service) ValidateSession(sessionID string) (bool, error) {
+ if s.store == nil {
+ return false, fmt.Errorf("storage disabled")
+ }
+ return s.store.IsSessionValid(sessionID)
+}
+
+// InvalidateSession removes a session (logout)
+func (s *Service) InvalidateSession(sessionID string) error {
if s.store == nil {
return fmt.Errorf("storage disabled")
}
-
- err := s.store.UpdateUserLastLoginSync(userID, time.Now().UTC())
- if err != nil {
- return fmt.Errorf("failed to update last login time for user %s: %w\n", userID, err)
- }
-
- return nil
+ return s.store.DeleteSession(sessionID)
}
// GetUserByID retrieves user information by user ID
@@ -124,31 +198,47 @@ func (s *Service) GetUserByID(userID string) (*User, error) {
}
return &User{
- UserID: userRecord.UserID,
- Username: userRecord.Username,
- Email: userRecord.Email,
- CreatedAt: userRecord.CreatedAt,
+ UserID: userRecord.UserID,
+ Username: userRecord.Username,
+ Email: userRecord.Email,
+ AccountType: userRecord.AccountType,
+ CreatedAt: userRecord.CreatedAt,
+ ExpiresAt: userRecord.ExpiresAt,
}, nil
}
-// GenerateUserToken creates a JWT token for the specified user
-func (s *Service) GenerateUserToken(userID string) (string, error) {
+// GenerateUserToken creates a JWT token for the specified user with session ID
+func (s *Service) GenerateUserToken(userID, sessionID string) (string, error) {
user, err := s.GetUserByID(userID)
if err != nil {
return "", err
}
claims := map[string]any{
- "username": user.Username,
- "email": user.Email,
+ "username": user.Username,
+ "email": user.Email,
+ "session_id": sessionID,
}
- return auth.GenerateHS256Token(s.jwtSecret, userID, claims, 7*24*time.Hour)
+ return auth.GenerateHS256Token(s.jwtSecret, userID, claims, SessionTTL)
}
-// ValidateToken verifies JWT token and returns user ID with claims
+// ValidateToken verifies JWT token and session validity
func (s *Service) ValidateToken(token string) (string, map[string]any, error) {
- return auth.ValidateHS256Token(s.jwtSecret, token)
+ userID, claims, err := auth.ValidateHS256Token(s.jwtSecret, token)
+ if err != nil {
+ return "", nil, err
+ }
+
+ // Validate session is still active
+ if sessionID, ok := claims["session_id"].(string); ok && s.store != nil {
+ valid, err := s.store.IsSessionValid(sessionID)
+ if err != nil || !valid {
+ return "", nil, fmt.Errorf("session invalidated")
+ }
+ }
+
+ return userID, claims, nil
}
// generateUniqueUserID creates a unique user ID with collision detection
@@ -157,19 +247,32 @@ func (s *Service) generateUniqueUserID() (string, error) {
for i := 0; i < maxAttempts; i++ {
id := uuid.New().String()
-
- // Check for collision
if _, err := s.store.GetUserByID(id); err != nil {
- // Error means not found, ID is unique
return id, nil
}
-
- // Collision detected, try again
- if i == maxAttempts-1 {
- // After max attempts, fail and don't risk collision
- return "", fmt.Errorf("failed to generate unique ID after %d attempts", maxAttempts)
- }
}
return "", fmt.Errorf("failed to generate unique user ID")
+}
+
+// CreateUserSession creates a session for a user without re-authenticating
+// Used after registration to avoid redundant password hashing
+func (s *Service) CreateUserSession(userID string) (string, error) {
+ if s.store == nil {
+ return "", fmt.Errorf("storage disabled")
+ }
+
+ sessionID := uuid.New().String()
+ sessionRecord := storage.SessionRecord{
+ SessionID: sessionID,
+ UserID: userID,
+ CreatedAt: time.Now().UTC(),
+ ExpiresAt: time.Now().UTC().Add(SessionTTL),
+ }
+
+ if err := s.store.CreateSession(sessionRecord); err != nil {
+ return "", fmt.Errorf("failed to create session: %w", err)
+ }
+
+ return sessionID, nil
}
\ No newline at end of file
diff --git a/internal/server/service/waiter.go b/internal/server/service/waiter.go
index 0e40bcb..432b274 100644
--- a/internal/server/service/waiter.go
+++ b/internal/server/service/waiter.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/service/waiter.go
package service
import (
diff --git a/internal/server/storage/game.go b/internal/server/storage/game.go
index ae152ff..65b9bca 100644
--- a/internal/server/storage/game.go
+++ b/internal/server/storage/game.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/storage/game.go
package storage
import (
diff --git a/internal/server/storage/schema.go b/internal/server/storage/schema.go
index 7a7e235..91ac47e 100644
--- a/internal/server/storage/schema.go
+++ b/internal/server/storage/schema.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/storage/schema.go
package storage
import "time"
@@ -9,10 +8,20 @@ type UserRecord struct {
Username string `db:"username"`
Email string `db:"email"`
PasswordHash string `db:"password_hash"`
+ AccountType string `db:"account_type"` // "permanent" or "temp"
CreatedAt time.Time `db:"created_at"`
+ ExpiresAt *time.Time `db:"expires_at"` // nil for permanent
LastLoginAt *time.Time `db:"last_login_at"`
}
+// SessionRecord represents an active user session
+type SessionRecord struct {
+ SessionID string `db:"session_id"`
+ UserID string `db:"user_id"`
+ CreatedAt time.Time `db:"created_at"`
+ ExpiresAt time.Time `db:"expires_at"`
+}
+
// GameRecord represents a row in the games table
type GameRecord struct {
GameID string `db:"game_id"`
@@ -35,7 +44,7 @@ type MoveRecord struct {
MoveNumber int `db:"move_number"`
MoveUCI string `db:"move_uci"`
FENAfterMove string `db:"fen_after_move"`
- PlayerColor string `db:"player_color"` // "w" or "b"
+ PlayerColor string `db:"player_color"`
MoveTimeUTC time.Time `db:"move_time_utc"`
}
@@ -46,14 +55,29 @@ CREATE TABLE IF NOT EXISTS users (
username TEXT UNIQUE NOT NULL COLLATE NOCASE,
email TEXT COLLATE NOCASE,
password_hash TEXT NOT NULL,
+ account_type TEXT NOT NULL DEFAULT 'temp' CHECK(account_type IN ('permanent', 'temp')),
created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ expires_at DATETIME,
last_login_at DATETIME
);
CREATE INDEX IF NOT EXISTS idx_users_username ON users(username);
CREATE INDEX IF NOT EXISTS idx_users_email ON users(email);
+CREATE INDEX IF NOT EXISTS idx_users_account_type ON users(account_type);
+CREATE INDEX IF NOT EXISTS idx_users_expires_at ON users(expires_at);
CREATE UNIQUE INDEX IF NOT EXISTS idx_users_email_unique ON users(email) WHERE email IS NOT NULL AND email != '';
+CREATE TABLE IF NOT EXISTS sessions (
+ session_id TEXT PRIMARY KEY,
+ user_id TEXT NOT NULL UNIQUE,
+ created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
+ expires_at DATETIME NOT NULL,
+ FOREIGN KEY (user_id) REFERENCES users(user_id) ON DELETE CASCADE
+);
+
+CREATE INDEX IF NOT EXISTS idx_sessions_user_id ON sessions(user_id);
+CREATE INDEX IF NOT EXISTS idx_sessions_expires_at ON sessions(expires_at);
+
CREATE TABLE IF NOT EXISTS games (
game_id TEXT PRIMARY KEY,
initial_fen TEXT NOT NULL,
diff --git a/internal/server/storage/session.go b/internal/server/storage/session.go
new file mode 100644
index 0000000..a61fa28
--- /dev/null
+++ b/internal/server/storage/session.go
@@ -0,0 +1,92 @@
+package storage
+
+import (
+ "fmt"
+ "time"
+)
+
+// CreateSession creates or replaces the session for a user (single session per user)
+func (s *Store) CreateSession(record SessionRecord) error {
+ tx, err := s.db.Begin()
+ if err != nil {
+ return fmt.Errorf("failed to begin transaction: %w", err)
+ }
+ defer tx.Rollback()
+
+ // Delete any existing session for this user
+ deleteQuery := `DELETE FROM sessions WHERE user_id = ?`
+ if _, err := tx.Exec(deleteQuery, record.UserID); err != nil {
+ return fmt.Errorf("failed to delete existing session: %w", err)
+ }
+
+ // Insert new session
+ insertQuery := `INSERT INTO sessions (session_id, user_id, created_at, expires_at) VALUES (?, ?, ?, ?)`
+ if _, err := tx.Exec(insertQuery, record.SessionID, record.UserID, record.CreatedAt, record.ExpiresAt); err != nil {
+ return fmt.Errorf("failed to create session: %w", err)
+ }
+
+ return tx.Commit()
+}
+
+// GetSession retrieves a session by ID
+func (s *Store) GetSession(sessionID string) (*SessionRecord, error) {
+ var session SessionRecord
+ query := `SELECT session_id, user_id, created_at, expires_at FROM sessions WHERE session_id = ?`
+
+ err := s.db.QueryRow(query, sessionID).Scan(
+ &session.SessionID, &session.UserID, &session.CreatedAt, &session.ExpiresAt,
+ )
+ if err != nil {
+ return nil, err
+ }
+ return &session, nil
+}
+
+// GetSessionByUserID retrieves the active session for a user
+func (s *Store) GetSessionByUserID(userID string) (*SessionRecord, error) {
+ var session SessionRecord
+ query := `SELECT session_id, user_id, created_at, expires_at FROM sessions WHERE user_id = ?`
+
+ err := s.db.QueryRow(query, userID).Scan(
+ &session.SessionID, &session.UserID, &session.CreatedAt, &session.ExpiresAt,
+ )
+ if err != nil {
+ return nil, err
+ }
+ return &session, nil
+}
+
+// DeleteSession removes a session
+func (s *Store) DeleteSession(sessionID string) error {
+ query := `DELETE FROM sessions WHERE session_id = ?`
+ _, err := s.db.Exec(query, sessionID)
+ return err
+}
+
+// DeleteSessionByUserID removes all sessions for a user
+func (s *Store) DeleteSessionByUserID(userID string) error {
+ query := `DELETE FROM sessions WHERE user_id = ?`
+ _, err := s.db.Exec(query, userID)
+ return err
+}
+
+// DeleteExpiredSessions removes expired sessions
+func (s *Store) DeleteExpiredSessions() (int64, error) {
+ query := `DELETE FROM sessions WHERE expires_at < ?`
+ result, err := s.db.Exec(query, time.Now().UTC())
+ if err != nil {
+ return 0, err
+ }
+ return result.RowsAffected()
+}
+
+// IsSessionValid checks if a session exists and is not expired
+func (s *Store) IsSessionValid(sessionID string) (bool, error) {
+ var count int
+ query := `SELECT COUNT(*) FROM sessions WHERE session_id = ? AND expires_at > ?`
+ err := s.db.QueryRow(query, sessionID, time.Now().UTC()).Scan(&count)
+ if err != nil {
+ return false, err
+ }
+ return count > 0, nil
+}
\ No newline at end of file
diff --git a/internal/server/storage/storage.go b/internal/server/storage/storage.go
index cfd7f78..765dc0b 100644
--- a/internal/server/storage/storage.go
+++ b/internal/server/storage/storage.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/storage/storage.go
package storage
import (
diff --git a/internal/server/storage/user.go b/internal/server/storage/user.go
index 91784cf..a9ac4fb 100644
--- a/internal/server/storage/user.go
+++ b/internal/server/storage/user.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/storage/user.go
package storage
import (
@@ -8,6 +7,64 @@ import (
"time"
)
+// UserLimits defines registration constraints
+type UserLimits struct {
+ MaxUsers int
+ PermanentSlots int
+ TempTTL time.Duration
+}
+
+// DefaultUserLimits returns default POC limits
+func DefaultUserLimits() UserLimits {
+ return UserLimits{
+ MaxUsers: 100,
+ PermanentSlots: 10,
+ TempTTL: 24 * time.Hour,
+ }
+}
+
+// GetUserCounts returns current user counts by type
+func (s *Store) GetUserCounts() (total, permanent, temp int, err error) {
+ query := `SELECT
+ COUNT(*) as total,
+ SUM(CASE WHEN account_type = 'permanent' THEN 1 ELSE 0 END) as permanent,
+ SUM(CASE WHEN account_type = 'temp' THEN 1 ELSE 0 END) as temp
+ FROM users`
+
+ err = s.db.QueryRow(query).Scan(&total, &permanent, &temp)
+ return
+}
+
+// GetOldestTempUser returns the oldest temporary user for replacement
+func (s *Store) GetOldestTempUser() (*UserRecord, error) {
+ var user UserRecord
+ query := `SELECT user_id, username, email, password_hash, account_type, created_at, expires_at, last_login_at
+ FROM users
+ WHERE account_type = 'temp'
+ ORDER BY created_at ASC
+ LIMIT 1`
+
+ err := s.db.QueryRow(query).Scan(
+ &user.UserID, &user.Username, &user.Email,
+ &user.PasswordHash, &user.AccountType, &user.CreatedAt,
+ &user.ExpiresAt, &user.LastLoginAt,
+ )
+ if err != nil {
+ return nil, err
+ }
+ return &user, nil
+}
+
+// DeleteExpiredTempUsers removes temporary users past their expiry
+func (s *Store) DeleteExpiredTempUsers() (int64, error) {
+ query := `DELETE FROM users WHERE account_type = 'temp' AND expires_at < ?`
+ result, err := s.db.Exec(query, time.Now().UTC())
+ if err != nil {
+ return 0, err
+ }
+ return result.RowsAffected()
+}
+
// CreateUser creates user with transaction isolation to prevent race conditions
func (s *Store) CreateUser(record UserRecord) error {
tx, err := s.db.Begin()
@@ -27,12 +84,12 @@ func (s *Store) CreateUser(record UserRecord) error {
// Insert user
query := `INSERT INTO users (
- user_id, username, email, password_hash, created_at
- ) VALUES (?, ?, ?, ?, ?)`
+ user_id, username, email, password_hash, account_type, created_at, expires_at
+ ) VALUES (?, ?, ?, ?, ?, ?, ?)`
_, err = tx.Exec(query,
record.UserID, record.Username, record.Email,
- record.PasswordHash, record.CreatedAt,
+ record.PasswordHash, record.AccountType, record.CreatedAt, record.ExpiresAt,
)
if err != nil {
return err
@@ -41,6 +98,20 @@ func (s *Store) CreateUser(record UserRecord) error {
return tx.Commit()
}
+// DeleteUserByID removes a user by ID (synchronous, for replacement logic)
+func (s *Store) DeleteUserByID(userID string) error {
+ query := `DELETE FROM users WHERE user_id = ?`
+ _, err := s.db.Exec(query, userID)
+ return err
+}
+
+// PromoteToPermament upgrades a temp user to permanent
+func (s *Store) PromoteToPermanent(userID string) error {
+ query := `UPDATE users SET account_type = 'permanent', expires_at = NULL WHERE user_id = ?`
+ _, err := s.db.Exec(query, userID)
+ return err
+}
+
// userExists verifies username/email uniqueness within a transaction
func (s *Store) userExists(tx *sql.Tx, username, email string) (bool, error) {
var count int
@@ -82,7 +153,7 @@ func (s *Store) UpdateUserUsername(userID string, username string) error {
// GetAllUsers retrieves all users
func (s *Store) GetAllUsers() ([]UserRecord, error) {
- query := `SELECT user_id, username, email, password_hash, created_at, last_login_at
+ query := `SELECT user_id, username, email, password_hash, account_type, created_at, expires_at, last_login_at
FROM users ORDER BY created_at DESC`
rows, err := s.db.Query(query)
@@ -96,7 +167,8 @@ func (s *Store) GetAllUsers() ([]UserRecord, error) {
var user UserRecord
err := rows.Scan(
&user.UserID, &user.Username, &user.Email,
- &user.PasswordHash, &user.CreatedAt, &user.LastLoginAt,
+ &user.PasswordHash, &user.AccountType, &user.CreatedAt,
+ &user.ExpiresAt, &user.LastLoginAt,
)
if err != nil {
return nil, err
@@ -110,24 +182,23 @@ func (s *Store) GetAllUsers() ([]UserRecord, error) {
// UpdateUserLastLoginSync updates user last login time
func (s *Store) UpdateUserLastLoginSync(userID string, loginTime time.Time) error {
query := `UPDATE users SET last_login_at = ? WHERE user_id = ?`
-
_, err := s.db.Exec(query, loginTime, userID)
if err != nil {
return fmt.Errorf("failed to update last login for user %s: %w", userID, err)
}
-
return nil
}
// GetUserByUsername retrieves user by username with case-insensitive matching
func (s *Store) GetUserByUsername(username string) (*UserRecord, error) {
var user UserRecord
- query := `SELECT user_id, username, email, password_hash, created_at, last_login_at
+ query := `SELECT user_id, username, email, password_hash, account_type, created_at, expires_at, last_login_at
FROM users WHERE username = ? COLLATE NOCASE`
err := s.db.QueryRow(query, username).Scan(
&user.UserID, &user.Username, &user.Email,
- &user.PasswordHash, &user.CreatedAt, &user.LastLoginAt,
+ &user.PasswordHash, &user.AccountType, &user.CreatedAt,
+ &user.ExpiresAt, &user.LastLoginAt,
)
if err != nil {
return nil, err
@@ -138,12 +209,13 @@ func (s *Store) GetUserByUsername(username string) (*UserRecord, error) {
// GetUserByEmail retrieves user by email with case-insensitive matching
func (s *Store) GetUserByEmail(email string) (*UserRecord, error) {
var user UserRecord
- query := `SELECT user_id, username, email, password_hash, created_at, last_login_at
+ query := `SELECT user_id, username, email, password_hash, account_type, created_at, expires_at, last_login_at
FROM users WHERE email = ? COLLATE NOCASE`
err := s.db.QueryRow(query, email).Scan(
&user.UserID, &user.Username, &user.Email,
- &user.PasswordHash, &user.CreatedAt, &user.LastLoginAt,
+ &user.PasswordHash, &user.AccountType, &user.CreatedAt,
+ &user.ExpiresAt, &user.LastLoginAt,
)
if err != nil {
return nil, err
@@ -154,12 +226,13 @@ func (s *Store) GetUserByEmail(email string) (*UserRecord, error) {
// GetUserByID retrieves user by unique user ID
func (s *Store) GetUserByID(userID string) (*UserRecord, error) {
var user UserRecord
- query := `SELECT user_id, username, email, password_hash, created_at, last_login_at
+ query := `SELECT user_id, username, email, password_hash, account_type, created_at, expires_at, last_login_at
FROM users WHERE user_id = ?`
err := s.db.QueryRow(query, userID).Scan(
&user.UserID, &user.Username, &user.Email,
- &user.PasswordHash, &user.CreatedAt, &user.LastLoginAt,
+ &user.PasswordHash, &user.AccountType, &user.CreatedAt,
+ &user.ExpiresAt, &user.LastLoginAt,
)
if err != nil {
return nil, err
@@ -167,7 +240,7 @@ func (s *Store) GetUserByID(userID string) (*UserRecord, error) {
return &user, nil
}
-// DeleteUser removes a user from the database
+// DeleteUser removes a user from the database (async)
func (s *Store) DeleteUser(userID string) error {
if !s.healthStatus.Load() {
return nil
diff --git a/internal/server/webserver/chess-client-web/app.js b/internal/server/webserver/chess-client-web/app.js
index d4d527f..46264ff 100644
--- a/internal/server/webserver/chess-client-web/app.js
+++ b/internal/server/webserver/chess-client-web/app.js
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/webserver/web/app.js
// Game state management
let gameState = {
gameId: null,
@@ -681,6 +680,14 @@ function handleApiError(action, error, response = null) {
statusMessage = 'Invalid Request';
}
break;
+ case 403:
+ serverStatus = 'healthy';
+ if (action === 'move' || action === 'trigger computer move') {
+ statusMessage = 'Slot Claimed';
+ } else {
+ statusMessage = 'Not Authorized';
+ }
+ break;
case 404:
serverStatus = 'healthy'; // Server is fine, game doesn't exist
statusMessage = 'Game Not Found';
diff --git a/internal/server/webserver/chess-client-web/index.html b/internal/server/webserver/chess-client-web/index.html
index b1deb45..4d78ed9 100644
--- a/internal/server/webserver/chess-client-web/index.html
+++ b/internal/server/webserver/chess-client-web/index.html
@@ -1,4 +1,3 @@
-
diff --git a/internal/server/webserver/chess-client-web/style.css b/internal/server/webserver/chess-client-web/style.css
index dbf4286..671d1d7 100644
--- a/internal/server/webserver/chess-client-web/style.css
+++ b/internal/server/webserver/chess-client-web/style.css
@@ -1,4 +1,3 @@
-/* FILE: lixenwraith/chess/internal/server/webserver/web/style.css */
* {
margin: 0;
padding: 0;
diff --git a/internal/server/webserver/server.go b/internal/server/webserver/server.go
index 39731d9..074804e 100644
--- a/internal/server/webserver/server.go
+++ b/internal/server/webserver/server.go
@@ -1,4 +1,3 @@
-// FILE: lixenwraith/chess/internal/server/webserver/server.go
package webserver
import (
diff --git a/test/README.md b/test/README.md
index cf38f05..eb31d8e 100755
--- a/test/README.md
+++ b/test/README.md
@@ -11,8 +11,9 @@ This directory contains comprehensive test suites for the Chess API server, cove
- Compiled `chessd` binary in accessible path
## Running the test server
+From repo root
```bash
-./run-test-server.sh
+test/run-test-server.sh
```
Pass binary path as first argument of the script if it's not placed in current directory `./chessd`.
@@ -143,4 +144,4 @@ test/test-longpoll.sh
2. **Multiple Waiters**: 3 clients wait, all receive notification
3. **Timeout**: Verify 25-second timeout with valid response
4. **Skip Wait**: Immediate return when moveCount outdated
-5. **Disconnection**: Proper cleanup on client disconnect
\ No newline at end of file
+5. **Disconnection**: Proper cleanup on client disconnect
diff --git a/test/run-test-server.sh b/test/run-test-server.sh
index 7de5dcc..19df2b1 100755
--- a/test/run-test-server.sh
+++ b/test/run-test-server.sh
@@ -1,5 +1,4 @@
#!/usr/bin/env bash
-# FILE: lixenwraith/chess/test/run-test-server.sh
set -e
diff --git a/test/test-api.sh b/test/test-api.sh
index f144cee..09f0170 100755
--- a/test/test-api.sh
+++ b/test/test-api.sh
@@ -1,5 +1,4 @@
#!/usr/bin/env bash
-# FILE: lixenwraith/chess/test/test-api.sh
# Chess API Robustness Test Suite
# Tests the refactored chess API with security hardening
diff --git a/test/test-db.sh b/test/test-db.sh
index a870ee6..e9b605c 100755
--- a/test/test-db.sh
+++ b/test/test-db.sh
@@ -1,5 +1,4 @@
#!/usr/bin/env bash
-# FILE: lixenwraith/chess/test/test-db.sh
# Database & Authentication API Integration Test Suite
# Tests user operations, authentication, and persistence via HTTP API
diff --git a/test/test-longpoll.sh b/test/test-longpoll.sh
index f2f81e0..2abd0dd 100755
--- a/test/test-longpoll.sh
+++ b/test/test-longpoll.sh
@@ -1,5 +1,4 @@
#!/bin/bash
-# FILE: lixenwraith/chess/test/test-longpoll.sh
set -e
diff --git a/web/chess-client-wasm/index.html b/web/chess-client-wasm/index.html
index 2ebf21f..f5d6cf5 100644
--- a/web/chess-client-wasm/index.html
+++ b/web/chess-client-wasm/index.html
@@ -3,14 +3,18 @@
Chess Client Terminal
-
-
+
+
+
+
+
+
-
\ No newline at end of file
+>
\ No newline at end of file
diff --git a/web/chess-client-wasm/lib/addon-fit.min.js b/web/chess-client-wasm/lib/addon-fit.min.js
new file mode 100644
index 0000000..c3f65e2
--- /dev/null
+++ b/web/chess-client-wasm/lib/addon-fit.min.js
@@ -0,0 +1,8 @@
+/**
+ * Skipped minification because the original files appears to be already minified.
+ * Original file: /npm/@xterm/addon-fit@0.10.0/lib/addon-fit.js
+ *
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
+ */
+!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=addon-fit.js.map
\ No newline at end of file
diff --git a/web/chess-client-wasm/lib/addon-unicode11.min.js b/web/chess-client-wasm/lib/addon-unicode11.min.js
new file mode 100644
index 0000000..22377cf
--- /dev/null
+++ b/web/chess-client-wasm/lib/addon-unicode11.min.js
@@ -0,0 +1,8 @@
+/**
+ * Skipped minification because the original files appears to be already minified.
+ * Original file: /npm/@xterm/addon-unicode11@0.8.0/lib/addon-unicode11.js
+ *
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
+ */
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.Unicode11Addon=t():e.Unicode11Addon=t()}(this,(()=>(()=>{"use strict";var e={433:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV11=void 0;const r=i(938),s=[[768,879],[1155,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1541],[1552,1562],[1564,1564],[1611,1631],[1648,1648],[1750,1757],[1759,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2045,2045],[2070,2073],[2075,2083],[2085,2087],[2089,2093],[2137,2139],[2259,2306],[2362,2362],[2364,2364],[2369,2376],[2381,2381],[2385,2391],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2558,2558],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2641,2641],[2672,2673],[2677,2677],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2810,2815],[2817,2817],[2876,2876],[2879,2879],[2881,2884],[2893,2893],[2902,2902],[2914,2915],[2946,2946],[3008,3008],[3021,3021],[3072,3072],[3076,3076],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3170,3171],[3201,3201],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3328,3329],[3387,3388],[3393,3396],[3405,3405],[3426,3427],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3981,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4151],[4153,4154],[4157,4158],[4184,4185],[4190,4192],[4209,4212],[4226,4226],[4229,4230],[4237,4237],[4253,4253],[4448,4607],[4957,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6158],[6277,6278],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6683,6683],[6742,6742],[6744,6750],[6752,6752],[6754,6754],[6757,6764],[6771,6780],[6783,6783],[6832,6846],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7040,7041],[7074,7077],[7080,7081],[7083,7085],[7142,7142],[7144,7145],[7149,7149],[7151,7153],[7212,7219],[7222,7223],[7376,7378],[7380,7392],[7394,7400],[7405,7405],[7412,7412],[7416,7417],[7616,7673],[7675,7679],[8203,8207],[8234,8238],[8288,8292],[8294,8303],[8400,8432],[11503,11505],[11647,11647],[11744,11775],[12330,12333],[12441,12442],[42607,42610],[42612,42621],[42654,42655],[42736,42737],[43010,43010],[43014,43014],[43019,43019],[43045,43046],[43204,43205],[43232,43249],[43263,43263],[43302,43309],[43335,43345],[43392,43394],[43443,43443],[43446,43449],[43452,43453],[43493,43493],[43561,43566],[43569,43570],[43573,43574],[43587,43587],[43596,43596],[43644,43644],[43696,43696],[43698,43700],[43703,43704],[43710,43711],[43713,43713],[43756,43757],[43766,43766],[44005,44005],[44008,44008],[44013,44013],[64286,64286],[65024,65039],[65056,65071],[65279,65279],[65529,65531]],n=[[66045,66045],[66272,66272],[66422,66426],[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[68325,68326],[68900,68903],[69446,69456],[69633,69633],[69688,69702],[69759,69761],[69811,69814],[69817,69818],[69821,69821],[69837,69837],[69888,69890],[69927,69931],[69933,69940],[70003,70003],[70016,70017],[70070,70078],[70089,70092],[70191,70193],[70196,70196],[70198,70199],[70206,70206],[70367,70367],[70371,70378],[70400,70401],[70459,70460],[70464,70464],[70502,70508],[70512,70516],[70712,70719],[70722,70724],[70726,70726],[70750,70750],[70835,70840],[70842,70842],[70847,70848],[70850,70851],[71090,71093],[71100,71101],[71103,71104],[71132,71133],[71219,71226],[71229,71229],[71231,71232],[71339,71339],[71341,71341],[71344,71349],[71351,71351],[71453,71455],[71458,71461],[71463,71467],[71727,71735],[71737,71738],[72148,72151],[72154,72155],[72160,72160],[72193,72202],[72243,72248],[72251,72254],[72263,72263],[72273,72278],[72281,72283],[72330,72342],[72344,72345],[72752,72758],[72760,72765],[72767,72767],[72850,72871],[72874,72880],[72882,72883],[72885,72886],[73009,73014],[73018,73018],[73020,73021],[73023,73029],[73031,73031],[73104,73105],[73109,73109],[73111,73111],[73459,73460],[78896,78904],[92912,92916],[92976,92982],[94031,94031],[94095,94098],[113821,113822],[113824,113827],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[121344,121398],[121403,121452],[121461,121461],[121476,121476],[121499,121503],[121505,121519],[122880,122886],[122888,122904],[122907,122913],[122915,122916],[122918,122922],[123184,123190],[123628,123631],[125136,125142],[125252,125258],[917505,917505],[917536,917631],[917760,917999]],o=[[4352,4447],[8986,8987],[9001,9002],[9193,9196],[9200,9200],[9203,9203],[9725,9726],[9748,9749],[9800,9811],[9855,9855],[9875,9875],[9889,9889],[9898,9899],[9917,9918],[9924,9925],[9934,9934],[9940,9940],[9962,9962],[9970,9971],[9973,9973],[9978,9978],[9981,9981],[9989,9989],[9994,9995],[10024,10024],[10060,10060],[10062,10062],[10067,10069],[10071,10071],[10133,10135],[10160,10160],[10175,10175],[11035,11036],[11088,11088],[11093,11093],[11904,11929],[11931,12019],[12032,12245],[12272,12283],[12288,12329],[12334,12350],[12353,12438],[12443,12543],[12549,12591],[12593,12686],[12688,12730],[12736,12771],[12784,12830],[12832,12871],[12880,19903],[19968,42124],[42128,42182],[43360,43388],[44032,55203],[63744,64255],[65040,65049],[65072,65106],[65108,65126],[65128,65131],[65281,65376],[65504,65510]],c=[[94176,94179],[94208,100343],[100352,101106],[110592,110878],[110928,110930],[110948,110951],[110960,111355],[126980,126980],[127183,127183],[127374,127374],[127377,127386],[127488,127490],[127504,127547],[127552,127560],[127568,127569],[127584,127589],[127744,127776],[127789,127797],[127799,127868],[127870,127891],[127904,127946],[127951,127955],[127968,127984],[127988,127988],[127992,128062],[128064,128064],[128066,128252],[128255,128317],[128331,128334],[128336,128359],[128378,128378],[128405,128406],[128420,128420],[128507,128591],[128640,128709],[128716,128716],[128720,128722],[128725,128725],[128747,128748],[128756,128762],[128992,129003],[129293,129393],[129395,129398],[129402,129442],[129445,129450],[129454,129482],[129485,129535],[129648,129651],[129656,129658],[129664,129666],[129680,129685],[131072,196605],[196608,262141]];let l;function d(e,t){let i,r=0,s=t.length-1;if(et[s][1])return!1;for(;s>=r;)if(i=r+s>>1,e>t[i][1])r=i+1;else{if(!(ei&&(i=e)}return r.UnicodeService.createPropertyValue(0,i,s)}}},345:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.runAndSubscribe=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)))},t.runAndSubscribe=function(e,t){return t(void 0),e((e=>t(e)))}},490:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV6=void 0;const r=i(938),s=[[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]],n=[[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 o;t.UnicodeV6=class{constructor(){if(this.version="6",!o){o=new Uint8Array(65536),o.fill(1),o[0]=0,o.fill(0,1,32),o.fill(0,127,160),o.fill(2,4352,4448),o[9001]=2,o[9002]=2,o.fill(2,11904,42192),o[12351]=1,o.fill(2,44032,55204),o.fill(2,63744,64256),o.fill(2,65040,65050),o.fill(2,65072,65136),o.fill(2,65280,65377),o.fill(2,65504,65511);for(let e=0;et[s][1])return!1;for(;s>=r;)if(i=r+s>>1,e>t[i][1])r=i+1;else{if(!(e=131072&&e<=196605||e>=196608&&e<=262141?2:1}charProperties(e,t){let i=this.wcwidth(e),s=0===i&&0!==t;if(s){const e=r.UnicodeService.extractWidth(t);0===e?s=!1:e>i&&(i=e)}return r.UnicodeService.createPropertyValue(0,i,s)}}},938:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeService=void 0;const r=i(345),s=i(490);class n{static extractShouldJoin(e){return 0!=(1&e)}static extractWidth(e){return e>>1&3}static extractCharKind(e){return e>>3}static createPropertyValue(e,t,i=!1){return(16777215&e)<<3|(3&t)<<1|(i?1:0)}constructor(){this._providers=Object.create(null),this._active="",this._onChange=new r.EventEmitter,this.onChange=this._onChange.event;const e=new s.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,i=0;const r=e.length;for(let s=0;s=r)return t+this.wcwidth(o);const i=e.charCodeAt(s);56320<=i&&i<=57343?o=1024*(o-55296)+i-56320+65536:t+=this.wcwidth(i)}const c=this.charProperties(o,i);let l=n.extractWidth(c);n.extractShouldJoin(c)&&(l-=n.extractWidth(i)),t+=l,i=c}return t}charProperties(e,t){return this._activeProvider.charProperties(e,t)}}t.UnicodeService=n}},t={};function i(r){var s=t[r];if(void 0!==s)return s.exports;var n=t[r]={exports:{}};return e[r](n,n.exports,i),n.exports}var r={};return(()=>{var e=r;Object.defineProperty(e,"__esModule",{value:!0}),e.Unicode11Addon=void 0;const t=i(433);e.Unicode11Addon=class{activate(e){e.unicode.register(new t.UnicodeV11)}dispose(){}}})(),r})()));
+//# sourceMappingURL=addon-unicode11.js.map
\ No newline at end of file
diff --git a/web/chess-client-wasm/lib/addon-web-links.min.js b/web/chess-client-wasm/lib/addon-web-links.min.js
new file mode 100644
index 0000000..0e99772
--- /dev/null
+++ b/web/chess-client-wasm/lib/addon-web-links.min.js
@@ -0,0 +1,8 @@
+/**
+ * Skipped minification because the original files appears to be already minified.
+ * Original file: /npm/@xterm/addon-web-links@0.11.0/lib/addon-web-links.js
+ *
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
+ */
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.WebLinksAddon=t():e.WebLinksAddon=t()}(self,(()=>(()=>{"use strict";var e={6:(e,t)=>{function n(e){try{const t=new URL(e),n=t.password&&t.username?`${t.protocol}//${t.username}:${t.password}@${t.host}`:t.username?`${t.protocol}//${t.username}@${t.host}`:`${t.protocol}//${t.host}`;return e.toLocaleLowerCase().startsWith(n.toLocaleLowerCase())}catch(e){return!1}}Object.defineProperty(t,"__esModule",{value:!0}),t.LinkComputer=t.WebLinkProvider=void 0,t.WebLinkProvider=class{constructor(e,t,n,o={}){this._terminal=e,this._regex=t,this._handler=n,this._options=o}provideLinks(e,t){const n=o.computeLink(e,this._regex,this._terminal,this._handler);t(this._addCallbacks(n))}_addCallbacks(e){return e.map((e=>(e.leave=this._options.leave,e.hover=(t,n)=>{if(this._options.hover){const{range:o}=e;this._options.hover(t,n,o)}},e)))}};class o{static computeLink(e,t,r,i){const s=new RegExp(t.source,(t.flags||"")+"g"),[a,c]=o._getWindowedLineStrings(e-1,r),l=a.join("");let d;const p=[];for(;d=s.exec(l);){const e=d[0];if(!n(e))continue;const[t,s]=o._mapStrIdx(r,c,0,d.index),[a,l]=o._mapStrIdx(r,t,s,e.length);if(-1===t||-1===s||-1===a||-1===l)continue;const h={start:{x:s+1,y:t+1},end:{x:l,y:a+1}};p.push({range:h,text:e,activate:i})}return p}static _getWindowedLineStrings(e,t){let n,o=e,r=e,i=0,s="";const a=[];if(n=t.buffer.active.getLine(e)){const e=n.translateToString(!0);if(n.isWrapped&&" "!==e[0]){for(i=0;(n=t.buffer.active.getLine(--o))&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),n.isWrapped&&-1===s.indexOf(" ")););a.reverse()}for(a.push(e),i=0;(n=t.buffer.active.getLine(++r))&&n.isWrapped&&i<2048&&(s=n.translateToString(!0),i+=s.length,a.push(s),-1===s.indexOf(" ")););}return[a,o]}static _mapStrIdx(e,t,n,o){const r=e.buffer.active,i=r.getNullCell();let s=n;for(;o;){const e=r.getLine(t);if(!e)return[-1,-1];for(let n=s;n{var e=o;Object.defineProperty(e,"__esModule",{value:!0}),e.WebLinksAddon=void 0;const t=n(6),r=/(https?|HTTPS?):[/]{2}[^\s"'!*(){}|\\\^<>`]*[^\s"':,.!?{}|\\\^~\[\]`()<>]/;function i(e,t){const n=window.open();if(n){try{n.opener=null}catch{}n.location.href=t}else console.warn("Opening link blocked as opener could not be cleared")}e.WebLinksAddon=class{constructor(e=i,t={}){this._handler=e,this._options=t}activate(e){this._terminal=e;const n=this._options,o=n.urlRegex||r;this._linkProvider=this._terminal.registerLinkProvider(new t.WebLinkProvider(this._terminal,o,this._handler,n))}dispose(){this._linkProvider?.dispose()}}})(),o})()));
+//# sourceMappingURL=addon-web-links.js.map
\ No newline at end of file
diff --git a/web/chess-client-wasm/lib/addon-webgl.min.js b/web/chess-client-wasm/lib/addon-webgl.min.js
new file mode 100644
index 0000000..813293e
--- /dev/null
+++ b/web/chess-client-wasm/lib/addon-webgl.min.js
@@ -0,0 +1,8 @@
+/**
+ * Skipped minification because the original files appears to be already minified.
+ * Original file: /npm/@xterm/addon-webgl@0.18.0/lib/addon-webgl.js
+ *
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
+ */
+!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.WebglAddon=t():e.WebglAddon=t()}(self,(()=>(()=>{"use strict";var e={965:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.GlyphRenderer=void 0;const s=i(374),r=i(509),o=i(855),n=i(859),a=i(381),h=11,l=h*Float32Array.BYTES_PER_ELEMENT;let c,d=0,_=0,u=0;class g extends n.Disposable{constructor(e,t,i,o){super(),this._terminal=e,this._gl=t,this._dimensions=i,this._optionsService=o,this._activeBuffer=0,this._vertices={count:0,attributes:new Float32Array(0),attributesBuffers:[new Float32Array(0),new Float32Array(0)]};const h=this._gl;void 0===r.TextureAtlas.maxAtlasPages&&(r.TextureAtlas.maxAtlasPages=Math.min(32,(0,s.throwIfFalsy)(h.getParameter(h.MAX_TEXTURE_IMAGE_UNITS))),r.TextureAtlas.maxTextureSize=(0,s.throwIfFalsy)(h.getParameter(h.MAX_TEXTURE_SIZE))),this._program=(0,s.throwIfFalsy)((0,a.createProgram)(h,"#version 300 es\nlayout (location = 0) in vec2 a_unitquad;\nlayout (location = 1) in vec2 a_cellpos;\nlayout (location = 2) in vec2 a_offset;\nlayout (location = 3) in vec2 a_size;\nlayout (location = 4) in float a_texpage;\nlayout (location = 5) in vec2 a_texcoord;\nlayout (location = 6) in vec2 a_texsize;\n\nuniform mat4 u_projection;\nuniform vec2 u_resolution;\n\nout vec2 v_texcoord;\nflat out int v_texpage;\n\nvoid main() {\n vec2 zeroToOne = (a_offset / u_resolution) + a_cellpos + (a_unitquad * a_size);\n gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0);\n v_texpage = int(a_texpage);\n v_texcoord = a_texcoord + a_unitquad * a_texsize;\n}",function(e){let t="";for(let i=1;ih.deleteProgram(this._program)))),this._projectionLocation=(0,s.throwIfFalsy)(h.getUniformLocation(this._program,"u_projection")),this._resolutionLocation=(0,s.throwIfFalsy)(h.getUniformLocation(this._program,"u_resolution")),this._textureLocation=(0,s.throwIfFalsy)(h.getUniformLocation(this._program,"u_texture")),this._vertexArrayObject=h.createVertexArray(),h.bindVertexArray(this._vertexArrayObject);const c=new Float32Array([0,0,1,0,0,1,1,1]),d=h.createBuffer();this.register((0,n.toDisposable)((()=>h.deleteBuffer(d)))),h.bindBuffer(h.ARRAY_BUFFER,d),h.bufferData(h.ARRAY_BUFFER,c,h.STATIC_DRAW),h.enableVertexAttribArray(0),h.vertexAttribPointer(0,2,this._gl.FLOAT,!1,0,0);const _=new Uint8Array([0,1,2,3]),u=h.createBuffer();this.register((0,n.toDisposable)((()=>h.deleteBuffer(u)))),h.bindBuffer(h.ELEMENT_ARRAY_BUFFER,u),h.bufferData(h.ELEMENT_ARRAY_BUFFER,_,h.STATIC_DRAW),this._attributesBuffer=(0,s.throwIfFalsy)(h.createBuffer()),this.register((0,n.toDisposable)((()=>h.deleteBuffer(this._attributesBuffer)))),h.bindBuffer(h.ARRAY_BUFFER,this._attributesBuffer),h.enableVertexAttribArray(2),h.vertexAttribPointer(2,2,h.FLOAT,!1,l,0),h.vertexAttribDivisor(2,1),h.enableVertexAttribArray(3),h.vertexAttribPointer(3,2,h.FLOAT,!1,l,2*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(3,1),h.enableVertexAttribArray(4),h.vertexAttribPointer(4,1,h.FLOAT,!1,l,4*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(4,1),h.enableVertexAttribArray(5),h.vertexAttribPointer(5,2,h.FLOAT,!1,l,5*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(5,1),h.enableVertexAttribArray(6),h.vertexAttribPointer(6,2,h.FLOAT,!1,l,7*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(6,1),h.enableVertexAttribArray(1),h.vertexAttribPointer(1,2,h.FLOAT,!1,l,9*Float32Array.BYTES_PER_ELEMENT),h.vertexAttribDivisor(1,1),h.useProgram(this._program);const g=new Int32Array(r.TextureAtlas.maxAtlasPages);for(let e=0;eh.deleteTexture(t.texture)))),h.activeTexture(h.TEXTURE0+e),h.bindTexture(h.TEXTURE_2D,t.texture),h.texParameteri(h.TEXTURE_2D,h.TEXTURE_WRAP_S,h.CLAMP_TO_EDGE),h.texParameteri(h.TEXTURE_2D,h.TEXTURE_WRAP_T,h.CLAMP_TO_EDGE),h.texImage2D(h.TEXTURE_2D,0,h.RGBA,1,1,0,h.RGBA,h.UNSIGNED_BYTE,new Uint8Array([255,0,0,255])),this._atlasTextures[e]=t}h.enable(h.BLEND),h.blendFunc(h.SRC_ALPHA,h.ONE_MINUS_SRC_ALPHA),this.handleResize()}beginFrame(){return!this._atlas||this._atlas.beginFrame()}updateCell(e,t,i,s,r,o,n,a,h){this._updateCell(this._vertices.attributes,e,t,i,s,r,o,n,a,h)}_updateCell(e,t,i,r,n,a,l,g,v,f){d=(i*this._terminal.cols+t)*h,r!==o.NULL_CELL_CODE&&void 0!==r?this._atlas&&(c=g&&g.length>1?this._atlas.getRasterizedGlyphCombinedChar(g,n,a,l,!1):this._atlas.getRasterizedGlyph(r,n,a,l,!1),_=Math.floor((this._dimensions.device.cell.width-this._dimensions.device.char.width)/2),n!==f&&c.offset.x>_?(u=c.offset.x-_,e[d]=-(c.offset.x-u)+this._dimensions.device.char.left,e[d+1]=-c.offset.y+this._dimensions.device.char.top,e[d+2]=(c.size.x-u)/this._dimensions.device.canvas.width,e[d+3]=c.size.y/this._dimensions.device.canvas.height,e[d+4]=c.texturePage,e[d+5]=c.texturePositionClipSpace.x+u/this._atlas.pages[c.texturePage].canvas.width,e[d+6]=c.texturePositionClipSpace.y,e[d+7]=c.sizeClipSpace.x-u/this._atlas.pages[c.texturePage].canvas.width,e[d+8]=c.sizeClipSpace.y):(e[d]=-c.offset.x+this._dimensions.device.char.left,e[d+1]=-c.offset.y+this._dimensions.device.char.top,e[d+2]=c.size.x/this._dimensions.device.canvas.width,e[d+3]=c.size.y/this._dimensions.device.canvas.height,e[d+4]=c.texturePage,e[d+5]=c.texturePositionClipSpace.x,e[d+6]=c.texturePositionClipSpace.y,e[d+7]=c.sizeClipSpace.x,e[d+8]=c.sizeClipSpace.y),this._optionsService.rawOptions.rescaleOverlappingGlyphs&&(0,s.allowRescaling)(r,v,c.size.x,this._dimensions.device.cell.width)&&(e[d+2]=(this._dimensions.device.cell.width-1)/this._dimensions.device.canvas.width)):e.fill(0,d,d+h-1-2)}clear(){const e=this._terminal,t=e.cols*e.rows*h;this._vertices.count!==t?this._vertices.attributes=new Float32Array(t):this._vertices.attributes.fill(0);let i=0;for(;i{Object.defineProperty(t,"__esModule",{value:!0}),t.RectangleRenderer=void 0;const s=i(374),r=i(859),o=i(310),n=i(381),a=8*Float32Array.BYTES_PER_ELEMENT;class h{constructor(){this.attributes=new Float32Array(160),this.count=0}}let l=0,c=0,d=0,_=0,u=0,g=0,v=0;class f extends r.Disposable{constructor(e,t,i,o){super(),this._terminal=e,this._gl=t,this._dimensions=i,this._themeService=o,this._vertices=new h,this._verticesCursor=new h;const l=this._gl;this._program=(0,s.throwIfFalsy)((0,n.createProgram)(l,"#version 300 es\nlayout (location = 0) in vec2 a_position;\nlayout (location = 1) in vec2 a_size;\nlayout (location = 2) in vec4 a_color;\nlayout (location = 3) in vec2 a_unitquad;\n\nuniform mat4 u_projection;\n\nout vec4 v_color;\n\nvoid main() {\n vec2 zeroToOne = a_position + (a_unitquad * a_size);\n gl_Position = u_projection * vec4(zeroToOne, 0.0, 1.0);\n v_color = a_color;\n}","#version 300 es\nprecision lowp float;\n\nin vec4 v_color;\n\nout vec4 outColor;\n\nvoid main() {\n outColor = v_color;\n}")),this.register((0,r.toDisposable)((()=>l.deleteProgram(this._program)))),this._projectionLocation=(0,s.throwIfFalsy)(l.getUniformLocation(this._program,"u_projection")),this._vertexArrayObject=l.createVertexArray(),l.bindVertexArray(this._vertexArrayObject);const c=new Float32Array([0,0,1,0,0,1,1,1]),d=l.createBuffer();this.register((0,r.toDisposable)((()=>l.deleteBuffer(d)))),l.bindBuffer(l.ARRAY_BUFFER,d),l.bufferData(l.ARRAY_BUFFER,c,l.STATIC_DRAW),l.enableVertexAttribArray(3),l.vertexAttribPointer(3,2,this._gl.FLOAT,!1,0,0);const _=new Uint8Array([0,1,2,3]),u=l.createBuffer();this.register((0,r.toDisposable)((()=>l.deleteBuffer(u)))),l.bindBuffer(l.ELEMENT_ARRAY_BUFFER,u),l.bufferData(l.ELEMENT_ARRAY_BUFFER,_,l.STATIC_DRAW),this._attributesBuffer=(0,s.throwIfFalsy)(l.createBuffer()),this.register((0,r.toDisposable)((()=>l.deleteBuffer(this._attributesBuffer)))),l.bindBuffer(l.ARRAY_BUFFER,this._attributesBuffer),l.enableVertexAttribArray(0),l.vertexAttribPointer(0,2,l.FLOAT,!1,a,0),l.vertexAttribDivisor(0,1),l.enableVertexAttribArray(1),l.vertexAttribPointer(1,2,l.FLOAT,!1,a,2*Float32Array.BYTES_PER_ELEMENT),l.vertexAttribDivisor(1,1),l.enableVertexAttribArray(2),l.vertexAttribPointer(2,4,l.FLOAT,!1,a,4*Float32Array.BYTES_PER_ELEMENT),l.vertexAttribDivisor(2,1),this._updateCachedColors(o.colors),this.register(this._themeService.onChangeColors((e=>{this._updateCachedColors(e),this._updateViewportRectangle()})))}renderBackgrounds(){this._renderVertices(this._vertices)}renderCursor(){this._renderVertices(this._verticesCursor)}_renderVertices(e){const t=this._gl;t.useProgram(this._program),t.bindVertexArray(this._vertexArrayObject),t.uniformMatrix4fv(this._projectionLocation,!1,n.PROJECTION_MATRIX),t.bindBuffer(t.ARRAY_BUFFER,this._attributesBuffer),t.bufferData(t.ARRAY_BUFFER,e.attributes,t.DYNAMIC_DRAW),t.drawElementsInstanced(this._gl.TRIANGLE_STRIP,4,t.UNSIGNED_BYTE,0,e.count)}handleResize(){this._updateViewportRectangle()}setDimensions(e){this._dimensions=e}_updateCachedColors(e){this._bgFloat=this._colorToFloat32Array(e.background),this._cursorFloat=this._colorToFloat32Array(e.cursor)}_updateViewportRectangle(){this._addRectangleFloat(this._vertices.attributes,0,0,0,this._terminal.cols*this._dimensions.device.cell.width,this._terminal.rows*this._dimensions.device.cell.height,this._bgFloat)}updateBackgrounds(e){const t=this._terminal,i=this._vertices;let s,r,n,a,h,l,c,d,_,u,g,v=1;for(s=0;s>24&255)/255,u=(l>>16&255)/255,g=(l>>8&255)/255,v=1,this._addRectangle(e.attributes,t,c,d,(o-r)*this._dimensions.device.cell.width,this._dimensions.device.cell.height,_,u,g,v)}_addRectangle(e,t,i,s,r,o,n,a,h,l){e[t]=i/this._dimensions.device.canvas.width,e[t+1]=s/this._dimensions.device.canvas.height,e[t+2]=r/this._dimensions.device.canvas.width,e[t+3]=o/this._dimensions.device.canvas.height,e[t+4]=n,e[t+5]=a,e[t+6]=h,e[t+7]=l}_addRectangleFloat(e,t,i,s,r,o,n){e[t]=i/this._dimensions.device.canvas.width,e[t+1]=s/this._dimensions.device.canvas.height,e[t+2]=r/this._dimensions.device.canvas.width,e[t+3]=o/this._dimensions.device.canvas.height,e[t+4]=n[0],e[t+5]=n[1],e[t+6]=n[2],e[t+7]=n[3]}_colorToFloat32Array(e){return new Float32Array([(e.rgba>>24&255)/255,(e.rgba>>16&255)/255,(e.rgba>>8&255)/255,(255&e.rgba)/255])}}t.RectangleRenderer=f},310:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.RenderModel=t.COMBINED_CHAR_BIT_MASK=t.RENDER_MODEL_EXT_OFFSET=t.RENDER_MODEL_FG_OFFSET=t.RENDER_MODEL_BG_OFFSET=t.RENDER_MODEL_INDICIES_PER_CELL=void 0;const s=i(296);t.RENDER_MODEL_INDICIES_PER_CELL=4,t.RENDER_MODEL_BG_OFFSET=1,t.RENDER_MODEL_FG_OFFSET=2,t.RENDER_MODEL_EXT_OFFSET=3,t.COMBINED_CHAR_BIT_MASK=2147483648,t.RenderModel=class{constructor(){this.cells=new Uint32Array(0),this.lineLengths=new Uint32Array(0),this.selection=(0,s.createSelectionRenderModel)()}resize(e,i){const s=e*i*t.RENDER_MODEL_INDICIES_PER_CELL;s!==this.cells.length&&(this.cells=new Uint32Array(s),this.lineLengths=new Uint32Array(i))}clear(){this.cells.fill(0,0),this.lineLengths.fill(0,0)}}},666:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.JoinedCellData=t.WebglRenderer=void 0;const s=i(820),r=i(274),o=i(627),n=i(457),a=i(56),h=i(374),l=i(345),c=i(859),d=i(147),_=i(782),u=i(855),g=i(965),v=i(742),f=i(310),p=i(733);class C extends c.Disposable{constructor(e,t,i,n,d,u,g,v,C){super(),this._terminal=e,this._characterJoinerService=t,this._charSizeService=i,this._coreBrowserService=n,this._coreService=d,this._decorationService=u,this._optionsService=g,this._themeService=v,this._cursorBlinkStateManager=new c.MutableDisposable,this._charAtlasDisposable=this.register(new c.MutableDisposable),this._observerDisposable=this.register(new c.MutableDisposable),this._model=new f.RenderModel,this._workCell=new _.CellData,this._workCell2=new _.CellData,this._rectangleRenderer=this.register(new c.MutableDisposable),this._glyphRenderer=this.register(new c.MutableDisposable),this._onChangeTextureAtlas=this.register(new l.EventEmitter),this.onChangeTextureAtlas=this._onChangeTextureAtlas.event,this._onAddTextureAtlasCanvas=this.register(new l.EventEmitter),this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event,this._onRemoveTextureAtlasCanvas=this.register(new l.EventEmitter),this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event,this._onRequestRedraw=this.register(new l.EventEmitter),this.onRequestRedraw=this._onRequestRedraw.event,this._onContextLoss=this.register(new l.EventEmitter),this.onContextLoss=this._onContextLoss.event,this.register(this._themeService.onChangeColors((()=>this._handleColorChange()))),this._cellColorResolver=new r.CellColorResolver(this._terminal,this._optionsService,this._model.selection,this._decorationService,this._coreBrowserService,this._themeService),this._core=this._terminal._core,this._renderLayers=[new p.LinkRenderLayer(this._core.screenElement,2,this._terminal,this._core.linkifier,this._coreBrowserService,g,this._themeService)],this.dimensions=(0,h.createRenderDimensions)(),this._devicePixelRatio=this._coreBrowserService.dpr,this._updateDimensions(),this._updateCursorBlink(),this.register(g.onOptionChange((()=>this._handleOptionsChanged()))),this._canvas=this._coreBrowserService.mainDocument.createElement("canvas");const m={antialias:!1,depth:!1,preserveDrawingBuffer:C};if(this._gl=this._canvas.getContext("webgl2",m),!this._gl)throw new Error("WebGL2 not supported "+this._gl);this.register((0,s.addDisposableDomListener)(this._canvas,"webglcontextlost",(e=>{console.log("webglcontextlost event received"),e.preventDefault(),this._contextRestorationTimeout=setTimeout((()=>{this._contextRestorationTimeout=void 0,console.warn("webgl context not restored; firing onContextLoss"),this._onContextLoss.fire(e)}),3e3)}))),this.register((0,s.addDisposableDomListener)(this._canvas,"webglcontextrestored",(e=>{console.warn("webglcontextrestored event received"),clearTimeout(this._contextRestorationTimeout),this._contextRestorationTimeout=void 0,(0,o.removeTerminalFromCache)(this._terminal),this._initializeWebGLState(),this._requestRedrawViewport()}))),this._observerDisposable.value=(0,a.observeDevicePixelDimensions)(this._canvas,this._coreBrowserService.window,((e,t)=>this._setCanvasDevicePixelDimensions(e,t))),this.register(this._coreBrowserService.onWindowChange((e=>{this._observerDisposable.value=(0,a.observeDevicePixelDimensions)(this._canvas,e,((e,t)=>this._setCanvasDevicePixelDimensions(e,t)))}))),this._core.screenElement.appendChild(this._canvas),[this._rectangleRenderer.value,this._glyphRenderer.value]=this._initializeWebGLState(),this._isAttached=this._coreBrowserService.window.document.body.contains(this._core.screenElement),this.register((0,c.toDisposable)((()=>{for(const e of this._renderLayers)e.dispose();this._canvas.parentElement?.removeChild(this._canvas),(0,o.removeTerminalFromCache)(this._terminal)})))}get textureAtlas(){return this._charAtlas?.pages[0].canvas}_handleColorChange(){this._refreshCharAtlas(),this._clearModel(!0)}handleDevicePixelRatioChange(){this._devicePixelRatio!==this._coreBrowserService.dpr&&(this._devicePixelRatio=this._coreBrowserService.dpr,this.handleResize(this._terminal.cols,this._terminal.rows))}handleResize(e,t){this._updateDimensions(),this._model.resize(this._terminal.cols,this._terminal.rows);for(const e of this._renderLayers)e.resize(this._terminal,this.dimensions);this._canvas.width=this.dimensions.device.canvas.width,this._canvas.height=this.dimensions.device.canvas.height,this._canvas.style.width=`${this.dimensions.css.canvas.width}px`,this._canvas.style.height=`${this.dimensions.css.canvas.height}px`,this._core.screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._core.screenElement.style.height=`${this.dimensions.css.canvas.height}px`,this._rectangleRenderer.value?.setDimensions(this.dimensions),this._rectangleRenderer.value?.handleResize(),this._glyphRenderer.value?.setDimensions(this.dimensions),this._glyphRenderer.value?.handleResize(),this._refreshCharAtlas(),this._clearModel(!1)}handleCharSizeChanged(){this.handleResize(this._terminal.cols,this._terminal.rows)}handleBlur(){for(const e of this._renderLayers)e.handleBlur(this._terminal);this._cursorBlinkStateManager.value?.pause(),this._requestRedrawViewport()}handleFocus(){for(const e of this._renderLayers)e.handleFocus(this._terminal);this._cursorBlinkStateManager.value?.resume(),this._requestRedrawViewport()}handleSelectionChanged(e,t,i){for(const s of this._renderLayers)s.handleSelectionChanged(this._terminal,e,t,i);this._model.selection.update(this._core,e,t,i),this._requestRedrawViewport()}handleCursorMove(){for(const e of this._renderLayers)e.handleCursorMove(this._terminal);this._cursorBlinkStateManager.value?.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._refreshCharAtlas(),this._updateCursorBlink()}_initializeWebGLState(){return this._rectangleRenderer.value=new v.RectangleRenderer(this._terminal,this._gl,this.dimensions,this._themeService),this._glyphRenderer.value=new g.GlyphRenderer(this._terminal,this._gl,this.dimensions,this._optionsService),this.handleCharSizeChanged(),[this._rectangleRenderer.value,this._glyphRenderer.value]}_refreshCharAtlas(){if(this.dimensions.device.char.width<=0&&this.dimensions.device.char.height<=0)return void(this._isAttached=!1);const e=(0,o.acquireTextureAtlas)(this._terminal,this._optionsService.rawOptions,this._themeService.colors,this.dimensions.device.cell.width,this.dimensions.device.cell.height,this.dimensions.device.char.width,this.dimensions.device.char.height,this._coreBrowserService.dpr);this._charAtlas!==e&&(this._onChangeTextureAtlas.fire(e.pages[0].canvas),this._charAtlasDisposable.value=(0,c.getDisposeArrayDisposable)([(0,l.forwardEvent)(e.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas),(0,l.forwardEvent)(e.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas)])),this._charAtlas=e,this._charAtlas.warmUp(),this._glyphRenderer.value?.setAtlas(this._charAtlas)}_clearModel(e){this._model.clear(),e&&this._glyphRenderer.value?.clear()}clearTextureAtlas(){this._charAtlas?.clearTexture(),this._clearModel(!0),this._requestRedrawViewport()}clear(){this._clearModel(!0);for(const e of this._renderLayers)e.reset(this._terminal);this._cursorBlinkStateManager.value?.restartBlinkAnimation(),this._updateCursorBlink()}registerCharacterJoiner(e){return-1}deregisterCharacterJoiner(e){return!1}renderRows(e,t){if(!this._isAttached){if(!(this._coreBrowserService.window.document.body.contains(this._core.screenElement)&&this._charSizeService.width&&this._charSizeService.height))return;this._updateDimensions(),this._refreshCharAtlas(),this._isAttached=!0}for(const i of this._renderLayers)i.handleGridChanged(this._terminal,e,t);this._glyphRenderer.value&&this._rectangleRenderer.value&&(this._glyphRenderer.value.beginFrame()?(this._clearModel(!0),this._updateModel(0,this._terminal.rows-1)):this._updateModel(e,t),this._rectangleRenderer.value.renderBackgrounds(),this._glyphRenderer.value.render(this._model),this._cursorBlinkStateManager.value&&!this._cursorBlinkStateManager.value.isCursorVisible||this._rectangleRenderer.value.renderCursor())}_updateCursorBlink(){this._terminal.options.cursorBlink?this._cursorBlinkStateManager.value=new n.CursorBlinkStateManager((()=>{this._requestRedrawCursor()}),this._coreBrowserService):this._cursorBlinkStateManager.clear(),this._requestRedrawCursor()}_updateModel(e,t){const i=this._core;let s,r,o,n,a,h,l,c,d,_,g,v,p,C,x=this._workCell;e=L(e,i.rows-1,0),t=L(t,i.rows-1,0);const w=this._terminal.buffer.active.baseY+this._terminal.buffer.active.cursorY,b=w-i.buffer.ydisp,M=Math.min(this._terminal.buffer.active.cursorX,i.cols-1);let R=-1;const y=this._coreService.isCursorInitialized&&!this._coreService.isCursorHidden&&(!this._cursorBlinkStateManager.value||this._cursorBlinkStateManager.value.isCursorVisible);this._model.cursor=void 0;let A=!1;for(r=e;r<=t;r++)for(o=r+i.buffer.ydisp,n=i.buffer.lines.get(o),this._model.lineLengths[r]=0,a=this._characterJoinerService.getJoinedCharacters(o),p=0;p0&&p===a[0][0]&&(h=!0,c=a.shift(),x=new m(x,n.translateToString(!0,c[0],c[1]),c[1]-c[0]),l=c[1]-1),d=x.getChars(),_=x.getCode(),v=(r*i.cols+p)*f.RENDER_MODEL_INDICIES_PER_CELL,this._cellColorResolver.resolve(x,p,o,this.dimensions.device.cell.width),y&&o===w&&(p===M&&(this._model.cursor={x:M,y:b,width:x.getWidth(),style:this._coreBrowserService.isFocused?i.options.cursorStyle||"block":i.options.cursorInactiveStyle,cursorWidth:i.options.cursorWidth,dpr:this._devicePixelRatio},R=M+x.getWidth()-1),p>=M&&p<=R&&(this._coreBrowserService.isFocused&&"block"===(i.options.cursorStyle||"block")||!1===this._coreBrowserService.isFocused&&"block"===i.options.cursorInactiveStyle)&&(this._cellColorResolver.result.fg=50331648|this._themeService.colors.cursorAccent.rgba>>8&16777215,this._cellColorResolver.result.bg=50331648|this._themeService.colors.cursor.rgba>>8&16777215)),_!==u.NULL_CELL_CODE&&(this._model.lineLengths[r]=p+1),(this._model.cells[v]!==_||this._model.cells[v+f.RENDER_MODEL_BG_OFFSET]!==this._cellColorResolver.result.bg||this._model.cells[v+f.RENDER_MODEL_FG_OFFSET]!==this._cellColorResolver.result.fg||this._model.cells[v+f.RENDER_MODEL_EXT_OFFSET]!==this._cellColorResolver.result.ext)&&(A=!0,d.length>1&&(_|=f.COMBINED_CHAR_BIT_MASK),this._model.cells[v]=_,this._model.cells[v+f.RENDER_MODEL_BG_OFFSET]=this._cellColorResolver.result.bg,this._model.cells[v+f.RENDER_MODEL_FG_OFFSET]=this._cellColorResolver.result.fg,this._model.cells[v+f.RENDER_MODEL_EXT_OFFSET]=this._cellColorResolver.result.ext,g=x.getWidth(),this._glyphRenderer.value.updateCell(p,r,_,this._cellColorResolver.result.bg,this._cellColorResolver.result.fg,this._cellColorResolver.result.ext,d,g,s),h))for(x=this._workCell,p++;p{Object.defineProperty(t,"__esModule",{value:!0}),t.GLTexture=t.expandFloat32Array=t.createShader=t.createProgram=t.PROJECTION_MATRIX=void 0;const s=i(374);function r(e,t,i){const r=(0,s.throwIfFalsy)(e.createShader(t));if(e.shaderSource(r,i),e.compileShader(r),e.getShaderParameter(r,e.COMPILE_STATUS))return r;console.error(e.getShaderInfoLog(r)),e.deleteShader(r)}t.PROJECTION_MATRIX=new Float32Array([2,0,0,0,0,-2,0,0,0,0,1,0,-1,1,0,1]),t.createProgram=function(e,t,i){const o=(0,s.throwIfFalsy)(e.createProgram());if(e.attachShader(o,(0,s.throwIfFalsy)(r(e,e.VERTEX_SHADER,t))),e.attachShader(o,(0,s.throwIfFalsy)(r(e,e.FRAGMENT_SHADER,i))),e.linkProgram(o),e.getProgramParameter(o,e.LINK_STATUS))return o;console.error(e.getProgramInfoLog(o)),e.deleteProgram(o)},t.createShader=r,t.expandFloat32Array=function(e,t){const i=Math.min(2*e.length,t),s=new Float32Array(i);for(let t=0;t{Object.defineProperty(t,"__esModule",{value:!0}),t.BaseRenderLayer=void 0;const s=i(627),r=i(237),o=i(374),n=i(859);class a extends n.Disposable{constructor(e,t,i,s,r,o,a,h){super(),this._container=t,this._alpha=r,this._coreBrowserService=o,this._optionsService=a,this._themeService=h,this._deviceCharWidth=0,this._deviceCharHeight=0,this._deviceCellWidth=0,this._deviceCellHeight=0,this._deviceCharLeft=0,this._deviceCharTop=0,this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add(`xterm-${i}-layer`),this._canvas.style.zIndex=s.toString(),this._initCanvas(),this._container.appendChild(this._canvas),this.register(this._themeService.onChangeColors((t=>{this._refreshCharAtlas(e,t),this.reset(e)}))),this.register((0,n.toDisposable)((()=>{this._canvas.remove()})))}_initCanvas(){this._ctx=(0,o.throwIfFalsy)(this._canvas.getContext("2d",{alpha:this._alpha})),this._alpha||this._clearAll()}handleBlur(e){}handleFocus(e){}handleCursorMove(e){}handleGridChanged(e,t,i){}handleSelectionChanged(e,t,i,s=!1){}_setTransparency(e,t){if(t===this._alpha)return;const i=this._canvas;this._alpha=t,this._canvas=this._canvas.cloneNode(),this._initCanvas(),this._container.replaceChild(this._canvas,i),this._refreshCharAtlas(e,this._themeService.colors),this.handleGridChanged(e,0,e.rows-1)}_refreshCharAtlas(e,t){this._deviceCharWidth<=0&&this._deviceCharHeight<=0||(this._charAtlas=(0,s.acquireTextureAtlas)(e,this._optionsService.rawOptions,t,this._deviceCellWidth,this._deviceCellHeight,this._deviceCharWidth,this._deviceCharHeight,this._coreBrowserService.dpr),this._charAtlas.warmUp())}resize(e,t){this._deviceCellWidth=t.device.cell.width,this._deviceCellHeight=t.device.cell.height,this._deviceCharWidth=t.device.char.width,this._deviceCharHeight=t.device.char.height,this._deviceCharLeft=t.device.char.left,this._deviceCharTop=t.device.char.top,this._canvas.width=t.device.canvas.width,this._canvas.height=t.device.canvas.height,this._canvas.style.width=`${t.css.canvas.width}px`,this._canvas.style.height=`${t.css.canvas.height}px`,this._alpha||this._clearAll(),this._refreshCharAtlas(e,this._themeService.colors)}_fillBottomLineAtCells(e,t,i=1){this._ctx.fillRect(e*this._deviceCellWidth,(t+1)*this._deviceCellHeight-this._coreBrowserService.dpr-1,i*this._deviceCellWidth,this._coreBrowserService.dpr)}_clearAll(){this._alpha?this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(0,0,this._canvas.width,this._canvas.height))}_clearCells(e,t,i,s){this._alpha?this._ctx.clearRect(e*this._deviceCellWidth,t*this._deviceCellHeight,i*this._deviceCellWidth,s*this._deviceCellHeight):(this._ctx.fillStyle=this._themeService.colors.background.css,this._ctx.fillRect(e*this._deviceCellWidth,t*this._deviceCellHeight,i*this._deviceCellWidth,s*this._deviceCellHeight))}_fillCharTrueColor(e,t,i,s){this._ctx.font=this._getFont(e,!1,!1),this._ctx.textBaseline=r.TEXT_BASELINE,this._clipCell(i,s,t.getWidth()),this._ctx.fillText(t.getChars(),i*this._deviceCellWidth+this._deviceCharLeft,s*this._deviceCellHeight+this._deviceCharTop+this._deviceCharHeight)}_clipCell(e,t,i){this._ctx.beginPath(),this._ctx.rect(e*this._deviceCellWidth,t*this._deviceCellHeight,i*this._deviceCellWidth,this._deviceCellHeight),this._ctx.clip()}_getFont(e,t,i){return`${i?"italic":""} ${t?e.options.fontWeightBold:e.options.fontWeight} ${e.options.fontSize*this._coreBrowserService.dpr}px ${e.options.fontFamily}`}}t.BaseRenderLayer=a},733:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.LinkRenderLayer=void 0;const s=i(197),r=i(237),o=i(592);class n extends o.BaseRenderLayer{constructor(e,t,i,s,r,o,n){super(i,e,"link",t,!0,r,o,n),this.register(s.onShowLinkUnderline((e=>this._handleShowLinkUnderline(e)))),this.register(s.onHideLinkUnderline((e=>this._handleHideLinkUnderline(e))))}resize(e,t){super.resize(e,t),this._state=void 0}reset(e){this._clearCurrentLink()}_clearCurrentLink(){if(this._state){this._clearCells(this._state.x1,this._state.y1,this._state.cols-this._state.x1,1);const e=this._state.y2-this._state.y1-1;e>0&&this._clearCells(0,this._state.y1+1,this._state.cols,e),this._clearCells(0,this._state.y2,this._state.x2,1),this._state=void 0}}_handleShowLinkUnderline(e){if(e.fg===r.INVERTED_DEFAULT_COLOR?this._ctx.fillStyle=this._themeService.colors.background.css:void 0!==e.fg&&(0,s.is256Color)(e.fg)?this._ctx.fillStyle=this._themeService.colors.ansi[e.fg].css:this._ctx.fillStyle=this._themeService.colors.foreground.css,e.y1===e.y2)this._fillBottomLineAtCells(e.x1,e.y1,e.x2-e.x1);else{this._fillBottomLineAtCells(e.x1,e.y1,e.cols-e.x1);for(let t=e.y1+1;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))}}}},274:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CellColorResolver=void 0;const s=i(855),r=i(160),o=i(374);let n,a=0,h=0,l=!1,c=!1,d=!1,_=0;t.CellColorResolver=class{constructor(e,t,i,s,r,o){this._terminal=e,this._optionService=t,this._selectionRenderModel=i,this._decorationService=s,this._coreBrowserService=r,this._themeService=o,this.result={fg:0,bg:0,ext:0}}resolve(e,t,i,u){if(this.result.bg=e.bg,this.result.fg=e.fg,this.result.ext=268435456&e.bg?e.extended.ext:0,h=0,a=0,c=!1,l=!1,d=!1,n=this._themeService.colors,_=0,e.getCode()!==s.NULL_CELL_CODE&&4===e.extended.underlineStyle){const e=Math.max(1,Math.floor(this._optionService.rawOptions.fontSize*this._coreBrowserService.dpr/15));_=t*u%(2*Math.round(e))}if(this._decorationService.forEachDecorationAtCell(t,i,"bottom",(e=>{e.backgroundColorRGB&&(h=e.backgroundColorRGB.rgba>>8&16777215,c=!0),e.foregroundColorRGB&&(a=e.foregroundColorRGB.rgba>>8&16777215,l=!0)})),d=this._selectionRenderModel.isCellSelected(this._terminal,t,i),d){if(67108864&this.result.fg||0!=(50331648&this.result.bg)){if(67108864&this.result.fg)switch(50331648&this.result.fg){case 16777216:case 33554432:h=this._themeService.colors.ansi[255&this.result.fg].rgba;break;case 50331648:h=(16777215&this.result.fg)<<8|255;break;default:h=this._themeService.colors.foreground.rgba}else switch(50331648&this.result.bg){case 16777216:case 33554432:h=this._themeService.colors.ansi[255&this.result.bg].rgba;break;case 50331648:h=(16777215&this.result.bg)<<8|255}h=r.rgba.blend(h,4294967040&(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba|128)>>8&16777215}else h=(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba>>8&16777215;if(c=!0,n.selectionForeground&&(a=n.selectionForeground.rgba>>8&16777215,l=!0),(0,o.treatGlyphAsBackgroundColor)(e.getCode())){if(67108864&this.result.fg&&0==(50331648&this.result.bg))a=(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba>>8&16777215;else{if(67108864&this.result.fg)switch(50331648&this.result.bg){case 16777216:case 33554432:a=this._themeService.colors.ansi[255&this.result.bg].rgba;break;case 50331648:a=(16777215&this.result.bg)<<8|255}else switch(50331648&this.result.fg){case 16777216:case 33554432:a=this._themeService.colors.ansi[255&this.result.fg].rgba;break;case 50331648:a=(16777215&this.result.fg)<<8|255;break;default:a=this._themeService.colors.foreground.rgba}a=r.rgba.blend(a,4294967040&(this._coreBrowserService.isFocused?n.selectionBackgroundOpaque:n.selectionInactiveBackgroundOpaque).rgba|128)>>8&16777215}l=!0}}this._decorationService.forEachDecorationAtCell(t,i,"top",(e=>{e.backgroundColorRGB&&(h=e.backgroundColorRGB.rgba>>8&16777215,c=!0),e.foregroundColorRGB&&(a=e.foregroundColorRGB.rgba>>8&16777215,l=!0)})),c&&(h=d?-16777216&e.bg&-134217729|h|50331648:-16777216&e.bg|h|50331648),l&&(a=-16777216&e.fg&-67108865|a|50331648),67108864&this.result.fg&&(c&&!l&&(a=0==(50331648&this.result.bg)?-134217728&this.result.fg|16777215&n.background.rgba>>8|50331648:-134217728&this.result.fg|67108863&this.result.bg,l=!0),!c&&l&&(h=0==(50331648&this.result.fg)?-67108864&this.result.bg|16777215&n.foreground.rgba>>8|50331648:-67108864&this.result.bg|67108863&this.result.fg,c=!0)),n=void 0,this.result.bg=c?h:this.result.bg,this.result.fg=l?a:this.result.fg,this.result.ext&=536870911,this.result.ext|=_<<29&3758096384}}},627:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.removeTerminalFromCache=t.acquireTextureAtlas=void 0;const s=i(509),r=i(197),o=[];t.acquireTextureAtlas=function(e,t,i,n,a,h,l,c){const d=(0,r.generateConfig)(n,a,h,l,t,i,c);for(let t=0;t=0){if((0,r.configEquals)(i.config,d))return i.atlas;1===i.ownedBy.length?(i.atlas.dispose(),o.splice(t,1)):i.ownedBy.splice(s,1);break}}for(let t=0;t{Object.defineProperty(t,"__esModule",{value:!0}),t.is256Color=t.configEquals=t.generateConfig=void 0;const s=i(160);t.generateConfig=function(e,t,i,r,o,n,a){const h={foreground:n.foreground,background:n.background,cursor:s.NULL_COLOR,cursorAccent:s.NULL_COLOR,selectionForeground:s.NULL_COLOR,selectionBackgroundTransparent:s.NULL_COLOR,selectionBackgroundOpaque:s.NULL_COLOR,selectionInactiveBackgroundTransparent:s.NULL_COLOR,selectionInactiveBackgroundOpaque:s.NULL_COLOR,ansi:n.ansi.slice(),contrastCache:n.contrastCache,halfContrastCache:n.halfContrastCache};return{customGlyphs:o.customGlyphs,devicePixelRatio:a,letterSpacing:o.letterSpacing,lineHeight:o.lineHeight,deviceCellWidth:e,deviceCellHeight:t,deviceCharWidth:i,deviceCharHeight:r,fontFamily:o.fontFamily,fontSize:o.fontSize,fontWeight:o.fontWeight,fontWeightBold:o.fontWeightBold,allowTransparency:o.allowTransparency,drawBoldTextInBrightColors:o.drawBoldTextInBrightColors,minimumContrastRatio:o.minimumContrastRatio,colors:h}},t.configEquals=function(e,t){for(let i=0;i{Object.defineProperty(t,"__esModule",{value:!0}),t.TEXT_BASELINE=t.DIM_OPACITY=t.INVERTED_DEFAULT_COLOR=void 0;const s=i(399);t.INVERTED_DEFAULT_COLOR=257,t.DIM_OPACITY=.5,t.TEXT_BASELINE=s.isFirefox||s.isLegacyEdge?"bottom":"ideographic"},457:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CursorBlinkStateManager=void 0;t.CursorBlinkStateManager=class{constructor(e,t){this._renderCallback=e,this._coreBrowserService=t,this.isCursorVisible=!0,this._coreBrowserService.isFocused&&this._restartInterval()}get isPaused(){return!(this._blinkStartTimeout||this._blinkInterval)}dispose(){this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}restartBlinkAnimation(){this.isPaused||(this._animationTimeRestarted=Date.now(),this.isCursorVisible=!0,this._animationFrame||(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>{this._renderCallback(),this._animationFrame=void 0}))))}_restartInterval(e=600){this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout=this._coreBrowserService.window.setTimeout((()=>{if(this._animationTimeRestarted){const e=600-(Date.now()-this._animationTimeRestarted);if(this._animationTimeRestarted=void 0,e>0)return void this._restartInterval(e)}this.isCursorVisible=!1,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>{this._renderCallback(),this._animationFrame=void 0})),this._blinkInterval=this._coreBrowserService.window.setInterval((()=>{if(this._animationTimeRestarted){const e=600-(Date.now()-this._animationTimeRestarted);return this._animationTimeRestarted=void 0,void this._restartInterval(e)}this.isCursorVisible=!this.isCursorVisible,this._animationFrame=this._coreBrowserService.window.requestAnimationFrame((()=>{this._renderCallback(),this._animationFrame=void 0}))}),600)}),e)}pause(){this.isCursorVisible=!0,this._blinkInterval&&(this._coreBrowserService.window.clearInterval(this._blinkInterval),this._blinkInterval=void 0),this._blinkStartTimeout&&(this._coreBrowserService.window.clearTimeout(this._blinkStartTimeout),this._blinkStartTimeout=void 0),this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)}resume(){this.pause(),this._animationTimeRestarted=void 0,this._restartInterval(),this.restartBlinkAnimation()}}},860:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.tryDrawCustomChar=t.powerlineDefinitions=t.boxDrawingDefinitions=t.blockElementDefinitions=void 0;const s=i(374);t.blockElementDefinitions={"▀":[{x:0,y:0,w:8,h:4}],"▁":[{x:0,y:7,w:8,h:1}],"▂":[{x:0,y:6,w:8,h:2}],"▃":[{x:0,y:5,w:8,h:3}],"▄":[{x:0,y:4,w:8,h:4}],"▅":[{x:0,y:3,w:8,h:5}],"▆":[{x:0,y:2,w:8,h:6}],"▇":[{x:0,y:1,w:8,h:7}],"█":[{x:0,y:0,w:8,h:8}],"▉":[{x:0,y:0,w:7,h:8}],"▊":[{x:0,y:0,w:6,h:8}],"▋":[{x:0,y:0,w:5,h:8}],"▌":[{x:0,y:0,w:4,h:8}],"▍":[{x:0,y:0,w:3,h:8}],"▎":[{x:0,y:0,w:2,h:8}],"▏":[{x:0,y:0,w:1,h:8}],"▐":[{x:4,y:0,w:4,h:8}],"▔":[{x:0,y:0,w:8,h:1}],"▕":[{x:7,y:0,w:1,h:8}],"▖":[{x:0,y:4,w:4,h:4}],"▗":[{x:4,y:4,w:4,h:4}],"▘":[{x:0,y:0,w:4,h:4}],"▙":[{x:0,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}],"▚":[{x:0,y:0,w:4,h:4},{x:4,y:4,w:4,h:4}],"▛":[{x:0,y:0,w:4,h:8},{x:4,y:0,w:4,h:4}],"▜":[{x:0,y:0,w:8,h:4},{x:4,y:0,w:4,h:8}],"▝":[{x:4,y:0,w:4,h:4}],"▞":[{x:4,y:0,w:4,h:4},{x:0,y:4,w:4,h:4}],"▟":[{x:4,y:0,w:4,h:8},{x:0,y:4,w:8,h:4}],"🭰":[{x:1,y:0,w:1,h:8}],"🭱":[{x:2,y:0,w:1,h:8}],"🭲":[{x:3,y:0,w:1,h:8}],"🭳":[{x:4,y:0,w:1,h:8}],"🭴":[{x:5,y:0,w:1,h:8}],"🭵":[{x:6,y:0,w:1,h:8}],"🭶":[{x:0,y:1,w:8,h:1}],"🭷":[{x:0,y:2,w:8,h:1}],"🭸":[{x:0,y:3,w:8,h:1}],"🭹":[{x:0,y:4,w:8,h:1}],"🭺":[{x:0,y:5,w:8,h:1}],"🭻":[{x:0,y:6,w:8,h:1}],"🭼":[{x:0,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}],"🭽":[{x:0,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}],"🭾":[{x:7,y:0,w:1,h:8},{x:0,y:0,w:8,h:1}],"🭿":[{x:7,y:0,w:1,h:8},{x:0,y:7,w:8,h:1}],"🮀":[{x:0,y:0,w:8,h:1},{x:0,y:7,w:8,h:1}],"🮁":[{x:0,y:0,w:8,h:1},{x:0,y:2,w:8,h:1},{x:0,y:4,w:8,h:1},{x:0,y:7,w:8,h:1}],"🮂":[{x:0,y:0,w:8,h:2}],"🮃":[{x:0,y:0,w:8,h:3}],"🮄":[{x:0,y:0,w:8,h:5}],"🮅":[{x:0,y:0,w:8,h:6}],"🮆":[{x:0,y:0,w:8,h:7}],"🮇":[{x:6,y:0,w:2,h:8}],"🮈":[{x:5,y:0,w:3,h:8}],"🮉":[{x:3,y:0,w:5,h:8}],"🮊":[{x:2,y:0,w:6,h:8}],"🮋":[{x:1,y:0,w:7,h:8}],"🮕":[{x:0,y:0,w:2,h:2},{x:4,y:0,w:2,h:2},{x:2,y:2,w:2,h:2},{x:6,y:2,w:2,h:2},{x:0,y:4,w:2,h:2},{x:4,y:4,w:2,h:2},{x:2,y:6,w:2,h:2},{x:6,y:6,w:2,h:2}],"🮖":[{x:2,y:0,w:2,h:2},{x:6,y:0,w:2,h:2},{x:0,y:2,w:2,h:2},{x:4,y:2,w:2,h:2},{x:2,y:4,w:2,h:2},{x:6,y:4,w:2,h:2},{x:0,y:6,w:2,h:2},{x:4,y:6,w:2,h:2}],"🮗":[{x:0,y:2,w:8,h:2},{x:0,y:6,w:8,h:2}]};const r={"░":[[1,0,0,0],[0,0,0,0],[0,0,1,0],[0,0,0,0]],"▒":[[1,0],[0,0],[0,1],[0,0]],"▓":[[0,1],[1,1],[1,0],[1,1]]};t.boxDrawingDefinitions={"─":{1:"M0,.5 L1,.5"},"━":{3:"M0,.5 L1,.5"},"│":{1:"M.5,0 L.5,1"},"┃":{3:"M.5,0 L.5,1"},"┌":{1:"M0.5,1 L.5,.5 L1,.5"},"┏":{3:"M0.5,1 L.5,.5 L1,.5"},"┐":{1:"M0,.5 L.5,.5 L.5,1"},"┓":{3:"M0,.5 L.5,.5 L.5,1"},"└":{1:"M.5,0 L.5,.5 L1,.5"},"┗":{3:"M.5,0 L.5,.5 L1,.5"},"┘":{1:"M.5,0 L.5,.5 L0,.5"},"┛":{3:"M.5,0 L.5,.5 L0,.5"},"├":{1:"M.5,0 L.5,1 M.5,.5 L1,.5"},"┣":{3:"M.5,0 L.5,1 M.5,.5 L1,.5"},"┤":{1:"M.5,0 L.5,1 M.5,.5 L0,.5"},"┫":{3:"M.5,0 L.5,1 M.5,.5 L0,.5"},"┬":{1:"M0,.5 L1,.5 M.5,.5 L.5,1"},"┳":{3:"M0,.5 L1,.5 M.5,.5 L.5,1"},"┴":{1:"M0,.5 L1,.5 M.5,.5 L.5,0"},"┻":{3:"M0,.5 L1,.5 M.5,.5 L.5,0"},"┼":{1:"M0,.5 L1,.5 M.5,0 L.5,1"},"╋":{3:"M0,.5 L1,.5 M.5,0 L.5,1"},"╴":{1:"M.5,.5 L0,.5"},"╸":{3:"M.5,.5 L0,.5"},"╵":{1:"M.5,.5 L.5,0"},"╹":{3:"M.5,.5 L.5,0"},"╶":{1:"M.5,.5 L1,.5"},"╺":{3:"M.5,.5 L1,.5"},"╷":{1:"M.5,.5 L.5,1"},"╻":{3:"M.5,.5 L.5,1"},"═":{1:(e,t)=>`M0,${.5-t} L1,${.5-t} M0,${.5+t} L1,${.5+t}`},"║":{1:(e,t)=>`M${.5-e},0 L${.5-e},1 M${.5+e},0 L${.5+e},1`},"╒":{1:(e,t)=>`M.5,1 L.5,${.5-t} L1,${.5-t} M.5,${.5+t} L1,${.5+t}`},"╓":{1:(e,t)=>`M${.5-e},1 L${.5-e},.5 L1,.5 M${.5+e},.5 L${.5+e},1`},"╔":{1:(e,t)=>`M1,${.5-t} L${.5-e},${.5-t} L${.5-e},1 M1,${.5+t} L${.5+e},${.5+t} L${.5+e},1`},"╕":{1:(e,t)=>`M0,${.5-t} L.5,${.5-t} L.5,1 M0,${.5+t} L.5,${.5+t}`},"╖":{1:(e,t)=>`M${.5+e},1 L${.5+e},.5 L0,.5 M${.5-e},.5 L${.5-e},1`},"╗":{1:(e,t)=>`M0,${.5+t} L${.5-e},${.5+t} L${.5-e},1 M0,${.5-t} L${.5+e},${.5-t} L${.5+e},1`},"╘":{1:(e,t)=>`M.5,0 L.5,${.5+t} L1,${.5+t} M.5,${.5-t} L1,${.5-t}`},"╙":{1:(e,t)=>`M1,.5 L${.5-e},.5 L${.5-e},0 M${.5+e},.5 L${.5+e},0`},"╚":{1:(e,t)=>`M1,${.5-t} L${.5+e},${.5-t} L${.5+e},0 M1,${.5+t} L${.5-e},${.5+t} L${.5-e},0`},"╛":{1:(e,t)=>`M0,${.5+t} L.5,${.5+t} L.5,0 M0,${.5-t} L.5,${.5-t}`},"╜":{1:(e,t)=>`M0,.5 L${.5+e},.5 L${.5+e},0 M${.5-e},.5 L${.5-e},0`},"╝":{1:(e,t)=>`M0,${.5-t} L${.5-e},${.5-t} L${.5-e},0 M0,${.5+t} L${.5+e},${.5+t} L${.5+e},0`},"╞":{1:(e,t)=>`M.5,0 L.5,1 M.5,${.5-t} L1,${.5-t} M.5,${.5+t} L1,${.5+t}`},"╟":{1:(e,t)=>`M${.5-e},0 L${.5-e},1 M${.5+e},0 L${.5+e},1 M${.5+e},.5 L1,.5`},"╠":{1:(e,t)=>`M${.5-e},0 L${.5-e},1 M1,${.5+t} L${.5+e},${.5+t} L${.5+e},1 M1,${.5-t} L${.5+e},${.5-t} L${.5+e},0`},"╡":{1:(e,t)=>`M.5,0 L.5,1 M0,${.5-t} L.5,${.5-t} M0,${.5+t} L.5,${.5+t}`},"╢":{1:(e,t)=>`M0,.5 L${.5-e},.5 M${.5-e},0 L${.5-e},1 M${.5+e},0 L${.5+e},1`},"╣":{1:(e,t)=>`M${.5+e},0 L${.5+e},1 M0,${.5+t} L${.5-e},${.5+t} L${.5-e},1 M0,${.5-t} L${.5-e},${.5-t} L${.5-e},0`},"╤":{1:(e,t)=>`M0,${.5-t} L1,${.5-t} M0,${.5+t} L1,${.5+t} M.5,${.5+t} L.5,1`},"╥":{1:(e,t)=>`M0,.5 L1,.5 M${.5-e},.5 L${.5-e},1 M${.5+e},.5 L${.5+e},1`},"╦":{1:(e,t)=>`M0,${.5-t} L1,${.5-t} M0,${.5+t} L${.5-e},${.5+t} L${.5-e},1 M1,${.5+t} L${.5+e},${.5+t} L${.5+e},1`},"╧":{1:(e,t)=>`M.5,0 L.5,${.5-t} M0,${.5-t} L1,${.5-t} M0,${.5+t} L1,${.5+t}`},"╨":{1:(e,t)=>`M0,.5 L1,.5 M${.5-e},.5 L${.5-e},0 M${.5+e},.5 L${.5+e},0`},"╩":{1:(e,t)=>`M0,${.5+t} L1,${.5+t} M0,${.5-t} L${.5-e},${.5-t} L${.5-e},0 M1,${.5-t} L${.5+e},${.5-t} L${.5+e},0`},"╪":{1:(e,t)=>`M.5,0 L.5,1 M0,${.5-t} L1,${.5-t} M0,${.5+t} L1,${.5+t}`},"╫":{1:(e,t)=>`M0,.5 L1,.5 M${.5-e},0 L${.5-e},1 M${.5+e},0 L${.5+e},1`},"╬":{1:(e,t)=>`M0,${.5+t} L${.5-e},${.5+t} L${.5-e},1 M1,${.5+t} L${.5+e},${.5+t} L${.5+e},1 M0,${.5-t} L${.5-e},${.5-t} L${.5-e},0 M1,${.5-t} L${.5+e},${.5-t} L${.5+e},0`},"╱":{1:"M1,0 L0,1"},"╲":{1:"M0,0 L1,1"},"╳":{1:"M1,0 L0,1 M0,0 L1,1"},"╼":{1:"M.5,.5 L0,.5",3:"M.5,.5 L1,.5"},"╽":{1:"M.5,.5 L.5,0",3:"M.5,.5 L.5,1"},"╾":{1:"M.5,.5 L1,.5",3:"M.5,.5 L0,.5"},"╿":{1:"M.5,.5 L.5,1",3:"M.5,.5 L.5,0"},"┍":{1:"M.5,.5 L.5,1",3:"M.5,.5 L1,.5"},"┎":{1:"M.5,.5 L1,.5",3:"M.5,.5 L.5,1"},"┑":{1:"M.5,.5 L.5,1",3:"M.5,.5 L0,.5"},"┒":{1:"M.5,.5 L0,.5",3:"M.5,.5 L.5,1"},"┕":{1:"M.5,.5 L.5,0",3:"M.5,.5 L1,.5"},"┖":{1:"M.5,.5 L1,.5",3:"M.5,.5 L.5,0"},"┙":{1:"M.5,.5 L.5,0",3:"M.5,.5 L0,.5"},"┚":{1:"M.5,.5 L0,.5",3:"M.5,.5 L.5,0"},"┝":{1:"M.5,0 L.5,1",3:"M.5,.5 L1,.5"},"┞":{1:"M0.5,1 L.5,.5 L1,.5",3:"M.5,.5 L.5,0"},"┟":{1:"M.5,0 L.5,.5 L1,.5",3:"M.5,.5 L.5,1"},"┠":{1:"M.5,.5 L1,.5",3:"M.5,0 L.5,1"},"┡":{1:"M.5,.5 L.5,1",3:"M.5,0 L.5,.5 L1,.5"},"┢":{1:"M.5,.5 L.5,0",3:"M0.5,1 L.5,.5 L1,.5"},"┥":{1:"M.5,0 L.5,1",3:"M.5,.5 L0,.5"},"┦":{1:"M0,.5 L.5,.5 L.5,1",3:"M.5,.5 L.5,0"},"┧":{1:"M.5,0 L.5,.5 L0,.5",3:"M.5,.5 L.5,1"},"┨":{1:"M.5,.5 L0,.5",3:"M.5,0 L.5,1"},"┩":{1:"M.5,.5 L.5,1",3:"M.5,0 L.5,.5 L0,.5"},"┪":{1:"M.5,.5 L.5,0",3:"M0,.5 L.5,.5 L.5,1"},"┭":{1:"M0.5,1 L.5,.5 L1,.5",3:"M.5,.5 L0,.5"},"┮":{1:"M0,.5 L.5,.5 L.5,1",3:"M.5,.5 L1,.5"},"┯":{1:"M.5,.5 L.5,1",3:"M0,.5 L1,.5"},"┰":{1:"M0,.5 L1,.5",3:"M.5,.5 L.5,1"},"┱":{1:"M.5,.5 L1,.5",3:"M0,.5 L.5,.5 L.5,1"},"┲":{1:"M.5,.5 L0,.5",3:"M0.5,1 L.5,.5 L1,.5"},"┵":{1:"M.5,0 L.5,.5 L1,.5",3:"M.5,.5 L0,.5"},"┶":{1:"M.5,0 L.5,.5 L0,.5",3:"M.5,.5 L1,.5"},"┷":{1:"M.5,.5 L.5,0",3:"M0,.5 L1,.5"},"┸":{1:"M0,.5 L1,.5",3:"M.5,.5 L.5,0"},"┹":{1:"M.5,.5 L1,.5",3:"M.5,0 L.5,.5 L0,.5"},"┺":{1:"M.5,.5 L0,.5",3:"M.5,0 L.5,.5 L1,.5"},"┽":{1:"M.5,0 L.5,1 M.5,.5 L1,.5",3:"M.5,.5 L0,.5"},"┾":{1:"M.5,0 L.5,1 M.5,.5 L0,.5",3:"M.5,.5 L1,.5"},"┿":{1:"M.5,0 L.5,1",3:"M0,.5 L1,.5"},"╀":{1:"M0,.5 L1,.5 M.5,.5 L.5,1",3:"M.5,.5 L.5,0"},"╁":{1:"M.5,.5 L.5,0 M0,.5 L1,.5",3:"M.5,.5 L.5,1"},"╂":{1:"M0,.5 L1,.5",3:"M.5,0 L.5,1"},"╃":{1:"M0.5,1 L.5,.5 L1,.5",3:"M.5,0 L.5,.5 L0,.5"},"╄":{1:"M0,.5 L.5,.5 L.5,1",3:"M.5,0 L.5,.5 L1,.5"},"╅":{1:"M.5,0 L.5,.5 L1,.5",3:"M0,.5 L.5,.5 L.5,1"},"╆":{1:"M.5,0 L.5,.5 L0,.5",3:"M0.5,1 L.5,.5 L1,.5"},"╇":{1:"M.5,.5 L.5,1",3:"M.5,.5 L.5,0 M0,.5 L1,.5"},"╈":{1:"M.5,.5 L.5,0",3:"M0,.5 L1,.5 M.5,.5 L.5,1"},"╉":{1:"M.5,.5 L1,.5",3:"M.5,0 L.5,1 M.5,.5 L0,.5"},"╊":{1:"M.5,.5 L0,.5",3:"M.5,0 L.5,1 M.5,.5 L1,.5"},"╌":{1:"M.1,.5 L.4,.5 M.6,.5 L.9,.5"},"╍":{3:"M.1,.5 L.4,.5 M.6,.5 L.9,.5"},"┄":{1:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5"},"┅":{3:"M.0667,.5 L.2667,.5 M.4,.5 L.6,.5 M.7333,.5 L.9333,.5"},"┈":{1:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5"},"┉":{3:"M.05,.5 L.2,.5 M.3,.5 L.45,.5 M.55,.5 L.7,.5 M.8,.5 L.95,.5"},"╎":{1:"M.5,.1 L.5,.4 M.5,.6 L.5,.9"},"╏":{3:"M.5,.1 L.5,.4 M.5,.6 L.5,.9"},"┆":{1:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333"},"┇":{3:"M.5,.0667 L.5,.2667 M.5,.4 L.5,.6 M.5,.7333 L.5,.9333"},"┊":{1:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95"},"┋":{3:"M.5,.05 L.5,.2 M.5,.3 L.5,.45 L.5,.55 M.5,.7 L.5,.95"},"╭":{1:(e,t)=>`M.5,1 L.5,${.5+t/.15*.5} C.5,${.5+t/.15*.5},.5,.5,1,.5`},"╮":{1:(e,t)=>`M.5,1 L.5,${.5+t/.15*.5} C.5,${.5+t/.15*.5},.5,.5,0,.5`},"╯":{1:(e,t)=>`M.5,0 L.5,${.5-t/.15*.5} C.5,${.5-t/.15*.5},.5,.5,0,.5`},"╰":{1:(e,t)=>`M.5,0 L.5,${.5-t/.15*.5} C.5,${.5-t/.15*.5},.5,.5,1,.5`}},t.powerlineDefinitions={"":{d:"M0,0 L1,.5 L0,1",type:0,rightPadding:2},"":{d:"M-1,-.5 L1,.5 L-1,1.5",type:1,leftPadding:1,rightPadding:1},"":{d:"M1,0 L0,.5 L1,1",type:0,leftPadding:2},"":{d:"M2,-.5 L0,.5 L2,1.5",type:1,leftPadding:1,rightPadding:1},"":{d:"M0,0 L0,1 C0.552,1,1,0.776,1,.5 C1,0.224,0.552,0,0,0",type:0,rightPadding:1},"":{d:"M.2,1 C.422,1,.8,.826,.78,.5 C.8,.174,0.422,0,.2,0",type:1,rightPadding:1},"":{d:"M1,0 L1,1 C0.448,1,0,0.776,0,.5 C0,0.224,0.448,0,1,0",type:0,leftPadding:1},"":{d:"M.8,1 C0.578,1,0.2,.826,.22,.5 C0.2,0.174,0.578,0,0.8,0",type:1,leftPadding:1},"":{d:"M-.5,-.5 L1.5,1.5 L-.5,1.5",type:0},"":{d:"M-.5,-.5 L1.5,1.5",type:1,leftPadding:1,rightPadding:1},"":{d:"M1.5,-.5 L-.5,1.5 L1.5,1.5",type:0},"":{d:"M1.5,-.5 L-.5,1.5 L-.5,-.5",type:0},"":{d:"M1.5,-.5 L-.5,1.5",type:1,leftPadding:1,rightPadding:1},"":{d:"M-.5,-.5 L1.5,1.5 L1.5,-.5",type:0}},t.powerlineDefinitions[""]=t.powerlineDefinitions[""],t.powerlineDefinitions[""]=t.powerlineDefinitions[""],t.tryDrawCustomChar=function(e,i,n,l,c,d,_,u){const g=t.blockElementDefinitions[i];if(g)return function(e,t,i,s,r,o){for(let n=0;n7&&parseInt(l.slice(7,9),16)||1;else{if(!l.startsWith("rgba"))throw new Error(`Unexpected fillStyle color format "${l}" when drawing pattern glyph`);[d,_,u,g]=l.substring(5,l.length-1).split(",").map((e=>parseFloat(e)))}for(let e=0;ee.bezierCurveTo(t[0],t[1],t[2],t[3],t[4],t[5]),L:(e,t)=>e.lineTo(t[0],t[1]),M:(e,t)=>e.moveTo(t[0],t[1])};function h(e,t,i,s,r,o,a,h=0,l=0){const c=e.map((e=>parseFloat(e)||parseInt(e)));if(c.length<2)throw new Error("Too few arguments for instruction");for(let e=0;e{Object.defineProperty(t,"__esModule",{value:!0}),t.observeDevicePixelDimensions=void 0;const s=i(859);t.observeDevicePixelDimensions=function(e,t,i){let r=new t.ResizeObserver((t=>{const s=t.find((t=>t.target===e));if(!s)return;if(!("devicePixelContentBoxSize"in s))return r?.disconnect(),void(r=void 0);const o=s.devicePixelContentBoxSize[0].inlineSize,n=s.devicePixelContentBoxSize[0].blockSize;o>0&&n>0&&i(o,n)}));try{r.observe(e,{box:["device-pixel-content-box"]})}catch{r.disconnect(),r=void 0}return(0,s.toDisposable)((()=>r?.disconnect()))}},374:(e,t)=>{function i(e){return 57508<=e&&e<=57558}function s(e){return e>=128512&&e<=128591||e>=127744&&e<=128511||e>=128640&&e<=128767||e>=9728&&e<=9983||e>=9984&&e<=10175||e>=65024&&e<=65039||e>=129280&&e<=129535||e>=127462&&e<=127487}Object.defineProperty(t,"__esModule",{value:!0}),t.computeNextVariantOffset=t.createRenderDimensions=t.treatGlyphAsBackgroundColor=t.allowRescaling=t.isEmoji=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.isEmoji=s,t.allowRescaling=function(e,t,r,o){return 1===t&&r>Math.ceil(1.5*o)&&void 0!==e&&e>255&&!s(e)&&!i(e)&&!function(e){return 57344<=e&&e<=63743}(e)},t.treatGlyphAsBackgroundColor=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}}}},t.computeNextVariantOffset=function(e,t,i=0){return(e-(2*Math.round(t)-i))%(2*Math.round(t))}},296:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.createSelectionRenderModel=void 0;class i{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(e,t,i,s=!1){if(this.selectionStart=t,this.selectionEnd=i,!t||!i||t[0]===i[0]&&t[1]===i[1])return void this.clear();const r=e.buffers.active.ydisp,o=t[1]-r,n=i[1]-r,a=Math.max(o,0),h=Math.min(n,e.rows-1);a>=e.rows||h<0?this.clear():(this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=o,this.viewportEndRow=n,this.viewportCappedStartRow=a,this.viewportCappedEndRow=h,this.startCol=t[0],this.endCol=i[0])}isCellSelected(e,t,i){return!!this.hasSelection&&(i-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&i>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&i<=this.viewportCappedEndRow:i>this.viewportStartRow&&i=this.startCol&&t=this.startCol)}}t.createSelectionRenderModel=function(){return new i}},509:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.TextureAtlas=void 0;const s=i(237),r=i(860),o=i(374),n=i(160),a=i(345),h=i(485),l=i(385),c=i(147),d=i(855),_={texturePage:0,texturePosition:{x:0,y:0},texturePositionClipSpace:{x:0,y:0},offset:{x:0,y:0},size:{x:0,y:0},sizeClipSpace:{x:0,y:0}};let u;class g{get pages(){return this._pages}constructor(e,t,i){this._document=e,this._config=t,this._unicodeService=i,this._didWarmUp=!1,this._cacheMap=new h.FourKeyMap,this._cacheMapCombined=new h.FourKeyMap,this._pages=[],this._activePages=[],this._workBoundingBox={top:0,left:0,bottom:0,right:0},this._workAttributeData=new c.AttributeData,this._textureSize=512,this._onAddTextureAtlasCanvas=new a.EventEmitter,this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event,this._onRemoveTextureAtlasCanvas=new a.EventEmitter,this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event,this._requestClearModel=!1,this._createNewPage(),this._tmpCanvas=p(e,4*this._config.deviceCellWidth+4,this._config.deviceCellHeight+4),this._tmpCtx=(0,o.throwIfFalsy)(this._tmpCanvas.getContext("2d",{alpha:this._config.allowTransparency,willReadFrequently:!0}))}dispose(){for(const e of this.pages)e.canvas.remove();this._onAddTextureAtlasCanvas.dispose()}warmUp(){this._didWarmUp||(this._doWarmUp(),this._didWarmUp=!0)}_doWarmUp(){const e=new l.IdleTaskQueue;for(let t=33;t<126;t++)e.enqueue((()=>{if(!this._cacheMap.get(t,d.DEFAULT_COLOR,d.DEFAULT_COLOR,d.DEFAULT_EXT)){const e=this._drawToCache(t,d.DEFAULT_COLOR,d.DEFAULT_COLOR,d.DEFAULT_EXT);this._cacheMap.set(t,d.DEFAULT_COLOR,d.DEFAULT_COLOR,d.DEFAULT_EXT,e)}}))}beginFrame(){return this._requestClearModel}clearTexture(){if(0!==this._pages[0].currentRow.x||0!==this._pages[0].currentRow.y){for(const e of this._pages)e.clear();this._cacheMap.clear(),this._cacheMapCombined.clear(),this._didWarmUp=!1}}_createNewPage(){if(g.maxAtlasPages&&this._pages.length>=Math.max(4,g.maxAtlasPages)){const e=this._pages.filter((e=>2*e.canvas.width<=(g.maxTextureSize||4096))).sort(((e,t)=>t.canvas.width!==e.canvas.width?t.canvas.width-e.canvas.width:t.percentageUsed-e.percentageUsed));let t=-1,i=0;for(let s=0;se.glyphs[0].texturePage)).sort(((e,t)=>e>t?1:-1)),o=this.pages.length-s.length,n=this._mergePages(s,o);n.version++;for(let e=r.length-1;e>=0;e--)this._deletePage(r[e]);this.pages.push(n),this._requestClearModel=!0,this._onAddTextureAtlasCanvas.fire(n.canvas)}const e=new v(this._document,this._textureSize);return this._pages.push(e),this._activePages.push(e),this._onAddTextureAtlasCanvas.fire(e.canvas),e}_mergePages(e,t){const i=2*e[0].canvas.width,s=new v(this._document,i,e);for(const[r,o]of e.entries()){const e=r*o.canvas.width%i,n=Math.floor(r/2)*o.canvas.height;s.ctx.drawImage(o.canvas,e,n);for(const s of o.glyphs)s.texturePage=t,s.sizeClipSpace.x=s.size.x/i,s.sizeClipSpace.y=s.size.y/i,s.texturePosition.x+=e,s.texturePosition.y+=n,s.texturePositionClipSpace.x=s.texturePosition.x/i,s.texturePositionClipSpace.y=s.texturePosition.y/i;this._onRemoveTextureAtlasCanvas.fire(o.canvas);const a=this._activePages.indexOf(o);-1!==a&&this._activePages.splice(a,1)}return s}_deletePage(e){this._pages.splice(e,1);for(let t=e;t=this._config.colors.ansi.length)throw new Error("No color found for idx "+e);return this._config.colors.ansi[e]}_getBackgroundColor(e,t,i,s){if(this._config.allowTransparency)return n.NULL_COLOR;let r;switch(e){case 16777216:case 33554432:r=this._getColorFromAnsiIndex(t);break;case 50331648:const e=c.AttributeData.toColorRGB(t);r=n.channels.toColor(e[0],e[1],e[2]);break;default:r=i?n.color.opaque(this._config.colors.foreground):this._config.colors.background}return r}_getForegroundColor(e,t,i,r,o,a,h,l,d,_){const u=this._getMinimumContrastColor(e,t,i,r,o,a,h,d,l,_);if(u)return u;let g;switch(o){case 16777216:case 33554432:this._config.drawBoldTextInBrightColors&&d&&a<8&&(a+=8),g=this._getColorFromAnsiIndex(a);break;case 50331648:const e=c.AttributeData.toColorRGB(a);g=n.channels.toColor(e[0],e[1],e[2]);break;default:g=h?this._config.colors.background:this._config.colors.foreground}return this._config.allowTransparency&&(g=n.color.opaque(g)),l&&(g=n.color.multiplyOpacity(g,s.DIM_OPACITY)),g}_resolveBackgroundRgba(e,t,i){switch(e){case 16777216:case 33554432:return this._getColorFromAnsiIndex(t).rgba;case 50331648:return t<<8;default:return i?this._config.colors.foreground.rgba:this._config.colors.background.rgba}}_resolveForegroundRgba(e,t,i,s){switch(e){case 16777216:case 33554432:return this._config.drawBoldTextInBrightColors&&s&&t<8&&(t+=8),this._getColorFromAnsiIndex(t).rgba;case 50331648:return t<<8;default:return i?this._config.colors.background.rgba:this._config.colors.foreground.rgba}}_getMinimumContrastColor(e,t,i,s,r,o,a,h,l,c){if(1===this._config.minimumContrastRatio||c)return;const d=this._getContrastCache(l),_=d.getColor(e,s);if(void 0!==_)return _||void 0;const u=this._resolveBackgroundRgba(t,i,a),g=this._resolveForegroundRgba(r,o,a,h),v=n.rgba.ensureContrastRatio(u,g,this._config.minimumContrastRatio/(l?2:1));if(!v)return void d.setColor(e,s,null);const f=n.channels.toColor(v>>24&255,v>>16&255,v>>8&255);return d.setColor(e,s,f),f}_getContrastCache(e){return e?this._config.colors.halfContrastCache:this._config.colors.contrastCache}_drawToCache(e,t,i,n,a=!1){const h="number"==typeof e?String.fromCharCode(e):e,l=Math.min(this._config.deviceCellWidth*Math.max(h.length,2)+4,this._textureSize);this._tmpCanvas.width=e?2*e-l:e-l;!1==!(l>=e)||0===u?(this._tmpCtx.setLineDash([Math.round(e),Math.round(e)]),this._tmpCtx.moveTo(h+u,s),this._tmpCtx.lineTo(c,s)):(this._tmpCtx.setLineDash([Math.round(e),Math.round(e)]),this._tmpCtx.moveTo(h,s),this._tmpCtx.lineTo(h+u,s),this._tmpCtx.moveTo(h+u+e,s),this._tmpCtx.lineTo(c,s)),l=(0,o.computeNextVariantOffset)(c-h,e,l);break;case 5:const g=.6,v=.3,f=c-h,p=Math.floor(g*f),C=Math.floor(v*f),m=f-p-C;this._tmpCtx.setLineDash([p,C,m]),this._tmpCtx.moveTo(h,s),this._tmpCtx.lineTo(c,s);break;default:this._tmpCtx.moveTo(h,s),this._tmpCtx.lineTo(c,s)}this._tmpCtx.stroke(),this._tmpCtx.restore()}if(this._tmpCtx.restore(),!F&&this._config.fontSize>=12&&!this._config.allowTransparency&&" "!==h){this._tmpCtx.save(),this._tmpCtx.textBaseline="alphabetic";const t=this._tmpCtx.measureText(h);if(this._tmpCtx.restore(),"actualBoundingBoxDescent"in t&&t.actualBoundingBoxDescent>0){this._tmpCtx.save();const t=new Path2D;t.rect(i,s-Math.ceil(e/2),this._config.deviceCellWidth*P,n-s+Math.ceil(e/2)),this._tmpCtx.clip(t),this._tmpCtx.lineWidth=3*this._config.devicePixelRatio,this._tmpCtx.strokeStyle=y.css,this._tmpCtx.strokeText(h,B,B+this._config.deviceCharHeight),this._tmpCtx.restore()}}}if(x){const e=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/15)),t=e%2==1?.5:0;this._tmpCtx.lineWidth=e,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(B,B+t),this._tmpCtx.lineTo(B+this._config.deviceCharWidth*P,B+t),this._tmpCtx.stroke()}if(F||this._tmpCtx.fillText(h,B,B+this._config.deviceCharHeight),"_"===h&&!this._config.allowTransparency){let e=f(this._tmpCtx.getImageData(B,B,this._config.deviceCellWidth,this._config.deviceCellHeight),y,D,I);if(e)for(let t=1;t<=5&&(this._tmpCtx.save(),this._tmpCtx.fillStyle=y.css,this._tmpCtx.fillRect(0,0,this._tmpCanvas.width,this._tmpCanvas.height),this._tmpCtx.restore(),this._tmpCtx.fillText(h,B,B+this._config.deviceCharHeight-t),e=f(this._tmpCtx.getImageData(B,B,this._config.deviceCellWidth,this._config.deviceCellHeight),y,D,I),e);t++);}if(L){const e=Math.max(1,Math.floor(this._config.fontSize*this._config.devicePixelRatio/10)),t=this._tmpCtx.lineWidth%2==1?.5:0;this._tmpCtx.lineWidth=e,this._tmpCtx.strokeStyle=this._tmpCtx.fillStyle,this._tmpCtx.beginPath(),this._tmpCtx.moveTo(B,B+Math.floor(this._config.deviceCharHeight/2)-t),this._tmpCtx.lineTo(B+this._config.deviceCharWidth*P,B+Math.floor(this._config.deviceCharHeight/2)-t),this._tmpCtx.stroke()}this._tmpCtx.restore();const O=this._tmpCtx.getImageData(0,0,this._tmpCanvas.width,this._tmpCanvas.height);let k;if(k=this._config.allowTransparency?function(e){for(let t=0;t0)return!1;return!0}(O):f(O,y,D,I),k)return _;const $=this._findGlyphBoundingBox(O,this._workBoundingBox,l,T,F,B);let U,N;for(;;){if(0===this._activePages.length){const e=this._createNewPage();U=e,N=e.currentRow,N.height=$.size.y;break}U=this._activePages[this._activePages.length-1],N=U.currentRow;for(const e of this._activePages)$.size.y<=e.currentRow.height&&(U=e,N=e.currentRow);for(let e=this._activePages.length-1;e>=0;e--)for(const t of this._activePages[e].fixedRows)t.height<=N.height&&$.size.y<=t.height&&(U=this._activePages[e],N=t);if(N.y+$.size.y>=U.canvas.height||N.height>$.size.y+2){let e=!1;if(U.currentRow.y+U.currentRow.height+$.size.y>=U.canvas.height){let t;for(const e of this._activePages)if(e.currentRow.y+e.currentRow.height+$.size.y=g.maxAtlasPages&&N.y+$.size.y<=U.canvas.height&&N.height>=$.size.y&&N.x+$.size.x<=U.canvas.width)e=!0;else{const t=this._createNewPage();U=t,N=t.currentRow,N.height=$.size.y,e=!0}}e||(U.currentRow.height>0&&U.fixedRows.push(U.currentRow),N={x:0,y:U.currentRow.y+U.currentRow.height,height:$.size.y},U.fixedRows.push(N),U.currentRow={x:0,y:N.y+N.height,height:0})}if(N.x+$.size.x<=U.canvas.width)break;N===U.currentRow?(N.x=0,N.y+=N.height,N.height=0):U.fixedRows.splice(U.fixedRows.indexOf(N),1)}return $.texturePage=this._pages.indexOf(U),$.texturePosition.x=N.x,$.texturePosition.y=N.y,$.texturePositionClipSpace.x=N.x/U.canvas.width,$.texturePositionClipSpace.y=N.y/U.canvas.height,$.sizeClipSpace.x/=U.canvas.width,$.sizeClipSpace.y/=U.canvas.height,N.height=Math.max(N.height,$.size.y),N.x+=$.size.x,U.ctx.putImageData(O,$.texturePosition.x-this._workBoundingBox.left,$.texturePosition.y-this._workBoundingBox.top,this._workBoundingBox.left,this._workBoundingBox.top,$.size.x,$.size.y),U.addGlyph($),U.version++,$}_findGlyphBoundingBox(e,t,i,s,r,o){t.top=0;const n=s?this._config.deviceCellHeight:this._tmpCanvas.height,a=s?this._config.deviceCellWidth:i;let h=!1;for(let i=0;i=o;i--){for(let s=0;s=0;i--){for(let s=0;s>>24,o=t.rgba>>>16&255,n=t.rgba>>>8&255,a=i.rgba>>>24,h=i.rgba>>>16&255,l=i.rgba>>>8&255,c=Math.floor((Math.abs(r-a)+Math.abs(o-h)+Math.abs(n-l))/12);let d=!0;for(let t=0;t{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;let i=0,s=0,r=0,o=0;var n,a,h,l,c;function d(e){const t=e.toString(16);return t.length<2?"0"+t:t}function _(e,t){return e>>0},e.toColor=function(t,i,s,r){return{css:e.toCss(t,i,s,r),rgba:e.toRgba(t,i,s,r)}}}(n||(t.channels=n={})),function(e){function t(e,t){return o=Math.round(255*t),[i,s,r]=c.toChannels(e.rgba),{css:n.toCss(i,s,r,o),rgba:n.toRgba(i,s,r,o)}}e.blend=function(e,t){if(o=(255&t.rgba)/255,1===o)return{css:t.css,rgba:t.rgba};const a=t.rgba>>24&255,h=t.rgba>>16&255,l=t.rgba>>8&255,c=e.rgba>>24&255,d=e.rgba>>16&255,_=e.rgba>>8&255;return i=c+Math.round((a-c)*o),s=d+Math.round((h-d)*o),r=_+Math.round((l-_)*o),{css:n.toCss(i,s,r),rgba:n.toRgba(i,s,r)}},e.isOpaque=function(e){return 255==(255&e.rgba)},e.ensureContrastRatio=function(e,t,i){const s=c.ensureContrastRatio(e.rgba,t.rgba,i);if(s)return n.toColor(s>>24&255,s>>16&255,s>>8&255)},e.opaque=function(e){const t=(255|e.rgba)>>>0;return[i,s,r]=c.toChannels(t),{css:n.toCss(i,s,r),rgba:t}},e.opacity=t,e.multiplyOpacity=function(e,i){return o=255&e.rgba,t(e,o*i/255)},e.toColorRGB=function(e){return[e.rgba>>24&255,e.rgba>>16&255,e.rgba>>8&255]}}(a||(t.color=a={})),function(e){let t,a;try{const e=document.createElement("canvas");e.width=1,e.height=1;const i=e.getContext("2d",{willReadFrequently:!0});i&&(t=i,t.globalCompositeOperation="copy",a=t.createLinearGradient(0,0,1,1))}catch{}e.toColor=function(e){if(e.match(/#[\da-f]{3,8}/i))switch(e.length){case 4:return i=parseInt(e.slice(1,2).repeat(2),16),s=parseInt(e.slice(2,3).repeat(2),16),r=parseInt(e.slice(3,4).repeat(2),16),n.toColor(i,s,r);case 5:return i=parseInt(e.slice(1,2).repeat(2),16),s=parseInt(e.slice(2,3).repeat(2),16),r=parseInt(e.slice(3,4).repeat(2),16),o=parseInt(e.slice(4,5).repeat(2),16),n.toColor(i,s,r,o);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 h=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(h)return i=parseInt(h[1]),s=parseInt(h[2]),r=parseInt(h[3]),o=Math.round(255*(void 0===h[5]?1:parseFloat(h[5]))),n.toColor(i,s,r,o);if(!t||!a)throw new Error("css.toColor: Unsupported css format");if(t.fillStyle=a,t.fillStyle=e,"string"!=typeof t.fillStyle)throw new Error("css.toColor: Unsupported css format");if(t.fillRect(0,0,1,1),[i,s,r,o]=t.getImageData(0,0,1,1).data,255!==o)throw new Error("css.toColor: Unsupported css format");return{rgba:n.toRgba(i,s,r,o),css:e}}}(h||(t.css=h={})),function(e){function t(e,t,i){const s=e/255,r=t/255,o=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*(o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4))}e.relativeLuminance=function(e){return t(e>>16&255,e>>8&255,255&e)},e.relativeLuminance2=t}(l||(t.rgb=l={})),function(e){function t(e,t,i){const s=e>>24&255,r=e>>16&255,o=e>>8&255;let n=t>>24&255,a=t>>16&255,h=t>>8&255,c=_(l.relativeLuminance2(n,a,h),l.relativeLuminance2(s,r,o));for(;c0||a>0||h>0);)n-=Math.max(0,Math.ceil(.1*n)),a-=Math.max(0,Math.ceil(.1*a)),h-=Math.max(0,Math.ceil(.1*h)),c=_(l.relativeLuminance2(n,a,h),l.relativeLuminance2(s,r,o));return(n<<24|a<<16|h<<8|255)>>>0}function a(e,t,i){const s=e>>24&255,r=e>>16&255,o=e>>8&255;let n=t>>24&255,a=t>>16&255,h=t>>8&255,c=_(l.relativeLuminance2(n,a,h),l.relativeLuminance2(s,r,o));for(;c>>0}e.blend=function(e,t){if(o=(255&t)/255,1===o)return t;const a=t>>24&255,h=t>>16&255,l=t>>8&255,c=e>>24&255,d=e>>16&255,_=e>>8&255;return i=c+Math.round((a-c)*o),s=d+Math.round((h-d)*o),r=_+Math.round((l-_)*o),n.toRgba(i,s,r)},e.ensureContrastRatio=function(e,i,s){const r=l.relativeLuminance(e>>8),o=l.relativeLuminance(i>>8);if(_(r,o)>8));if(n_(r,l.relativeLuminance(t>>8))?o:t}return o}const n=a(e,i,s),h=_(r,l.relativeLuminance(n>>8));if(h_(r,l.relativeLuminance(o>>8))?n:o}return n}},e.reduceLuminance=t,e.increaseLuminance=a,e.toChannels=function(e){return[e>>24&255,e>>16&255,e>>8&255,255&e]}}(c||(t.rgba=c={})),t.toPaddedHex=d,t.contrastRatio=_},345:(e,t)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.runAndSubscribe=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)))},t.runAndSubscribe=function(e,t){return t(void 0),e((e=>t(e)))}},859:(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){this._isDisposed||e===this._value||(this._value?.dispose(),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,this._value?.dispose(),this._value=void 0}},t.toDisposable=function(e){return{dispose:e}},t.disposeArray=i,t.getDisposeArrayDisposable=function(e){return{dispose:()=>i(e)}}},485:(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,o){this._data.get(e,t)||this._data.set(e,t,new i),this._data.get(e,t).set(s,r,o)}get(e,t,i,s){return this._data.get(e,t)?.get(i,s)}clear(){this._data.clear()}}},399:(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 process&&"title"in process;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)},385:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.DebouncedIdleTask=t.IdleTaskQueue=t.PriorityTaskQueue=void 0;const s=i(399);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 o 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=o,t.IdleTaskQueue=!s.isNode&&"requestIdleCallback"in window?class extends r{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}}:o,t.DebouncedIdleTask=class{constructor(){this._queue=new t.IdleTaskQueue}set(e){this._queue.clear(),this._queue.enqueue(e)}flush(){this._queue.flush()}}},147:(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}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}}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}get underlineVariantOffset(){const e=(3758096384&this._ext)>>29;return e<0?4294967288^e:e}set underlineVariantOffset(e){this._ext&=536870911,this._ext|=e<<29&3758096384}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},782:(e,t,i)=>{Object.defineProperty(t,"__esModule",{value:!0}),t.CellData=void 0;const s=i(133),r=i(855),o=i(147);class n extends o.AttributeData{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new o.ExtendedAttrs,this.combinedData=""}static fromCharData(e){const t=new n;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=n},855:(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},133:(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 o=r;o=i)return this._interim=r,s;const n=e.charCodeAt(o);56320<=n&&n<=57343?t[s++]=1024*(r-55296)+n-56320+65536:(t[s++]=r,t[s++]=n)}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,o,n,a=0,h=0,l=0;if(this.interim[0]){let s=!1,r=this.interim[0];r&=192==(224&r)?31:224==(240&r)?15:7;let o,n=0;for(;(o=63&this.interim[++n])&&n<4;)r<<=6,r|=o;const h=192==(224&this.interim[0])?2:224==(240&this.interim[0])?3:4,c=h-n;for(;l=i)return 0;if(o=e[l++],128!=(192&o)){l--,s=!0;break}this.interim[n++]=o,r<<=6,r|=63&o}s||(2===h?r<128?l--: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 c=i-4;let d=l;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(o=e[d++],128!=(192&o)){d--;continue}if(h=(15&s)<<12|(63&r)<<6|63&o,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(o=e[d++],128!=(192&o)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,this.interim[2]=o,a;if(n=e[d++],128!=(192&n)){d--;continue}if(h=(7&s)<<18|(63&r)<<12|(63&o)<<6|63&n,h<65536||h>1114111)continue;t[a++]=h}}return a}}},776:function(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},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 o=i(859),n=i(97),a={trace:n.LogLevelEnum.TRACE,debug:n.LogLevelEnum.DEBUG,info:n.LogLevelEnum.INFO,warn:n.LogLevelEnum.WARN,error:n.LogLevelEnum.ERROR,off:n.LogLevelEnum.OFF};let h,l=t.LogService=class extends o.Disposable{get logLevel(){return this._logLevel}constructor(e){super(),this._optionsService=e,this._logLevel=n.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}}},726:(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,o){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,o)};return r.toString=()=>e,t.serviceRegistry.set(e,r),r}},97:(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(726);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")}},t={};function i(s){var r=t[s];if(void 0!==r)return r.exports;var o=t[s]={exports:{}};return e[s].call(o.exports,o,o.exports,i),o.exports}var s={};return(()=>{var e=s;Object.defineProperty(e,"__esModule",{value:!0}),e.WebglAddon=void 0;const t=i(345),r=i(859),o=i(399),n=i(666),a=i(776);class h extends r.Disposable{constructor(e){if(o.isSafari&&(0,o.getSafariVersion)()<16){const e={antialias:!1,depth:!1,preserveDrawingBuffer:!0};if(!document.createElement("canvas").getContext("webgl2",e))throw new Error("Webgl2 is only supported on Safari 16 and above")}super(),this._preserveDrawingBuffer=e,this._onChangeTextureAtlas=this.register(new t.EventEmitter),this.onChangeTextureAtlas=this._onChangeTextureAtlas.event,this._onAddTextureAtlasCanvas=this.register(new t.EventEmitter),this.onAddTextureAtlasCanvas=this._onAddTextureAtlasCanvas.event,this._onRemoveTextureAtlasCanvas=this.register(new t.EventEmitter),this.onRemoveTextureAtlasCanvas=this._onRemoveTextureAtlasCanvas.event,this._onContextLoss=this.register(new t.EventEmitter),this.onContextLoss=this._onContextLoss.event}activate(e){const i=e._core;if(!e.element)return void this.register(i.onWillOpen((()=>this.activate(e))));this._terminal=e;const s=i.coreService,o=i.optionsService,h=i,l=h._renderService,c=h._characterJoinerService,d=h._charSizeService,_=h._coreBrowserService,u=h._decorationService,g=h._logService,v=h._themeService;(0,a.setTraceLogger)(g),this._renderer=this.register(new n.WebglRenderer(e,c,d,_,s,u,o,v,this._preserveDrawingBuffer)),this.register((0,t.forwardEvent)(this._renderer.onContextLoss,this._onContextLoss)),this.register((0,t.forwardEvent)(this._renderer.onChangeTextureAtlas,this._onChangeTextureAtlas)),this.register((0,t.forwardEvent)(this._renderer.onAddTextureAtlasCanvas,this._onAddTextureAtlasCanvas)),this.register((0,t.forwardEvent)(this._renderer.onRemoveTextureAtlasCanvas,this._onRemoveTextureAtlasCanvas)),l.setRenderer(this._renderer),this.register((0,r.toDisposable)((()=>{const t=this._terminal._core._renderService;t.setRenderer(this._terminal._core._createRenderer()),t.handleResize(e.cols,e.rows)})))}get textureAtlas(){return this._renderer?.textureAtlas}clearTextureAtlas(){this._renderer?.clearTextureAtlas()}}e.WebglAddon=h})(),s})()));
+//# sourceMappingURL=addon-webgl.js.map
\ No newline at end of file
diff --git a/web/chess-client-wasm/lib/xterm.css b/web/chess-client-wasm/lib/xterm.css
deleted file mode 100644
index acb1235..0000000
--- a/web/chess-client-wasm/lib/xterm.css
+++ /dev/null
@@ -1,218 +0,0 @@
-/**
- * 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:not(.debug),
-.xterm .xterm-message {
- position: absolute;
- left: 0;
- top: 0;
- bottom: 0;
- right: 0;
- z-index: 10;
- color: transparent;
- pointer-events: none;
-}
-
-.xterm .xterm-accessibility-tree:not(.debug) *::selection {
- color: transparent;
-}
-
-.xterm .xterm-accessibility-tree {
- user-select: text;
- white-space: pre;
-}
-
-.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/web/chess-client-wasm/lib/xterm.min.css b/web/chess-client-wasm/lib/xterm.min.css
new file mode 100644
index 0000000..c85ed5f
--- /dev/null
+++ b/web/chess-client-wasm/lib/xterm.min.css
@@ -0,0 +1,8 @@
+/**
+ * Minified by jsDelivr using clean-css v5.3.3.
+ * Original file: /npm/@xterm/xterm@5.5.0/css/xterm.css
+ *
+ * Do NOT use SRI with dynamically generated files! More information: https://www.jsdelivr.com/using-sri-with-dynamic-files
+ */
+.xterm{cursor:text;position:relative;user-select:none;-ms-user-select:none;-webkit-user-select:none}.xterm.focus,.xterm:focus{outline:0}.xterm .xterm-helpers{position:absolute;top:0;z-index:5}.xterm .xterm-helper-textarea{padding:0;border:0;margin:0;position:absolute;opacity:0;left:-9999em;top:0;width:0;height:0;z-index:-5;white-space:nowrap;overflow:hidden;resize:none}.xterm .composition-view{background:#000;color:#fff;display:none;position:absolute;white-space:nowrap;z-index:1}.xterm .composition-view.active{display:block}.xterm .xterm-viewport{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{cursor:default}.xterm .xterm-cursor-pointer,.xterm.xterm-cursor-pointer{cursor:pointer}.xterm.column-select.focus{cursor:crosshair}.xterm .xterm-accessibility:not(.debug),.xterm .xterm-message{position:absolute;left:0;top:0;bottom:0;right:0;z-index:10;color:transparent;pointer-events:none}.xterm .xterm-accessibility-tree:not(.debug) ::selection{color:transparent}.xterm .xterm-accessibility-tree{user-select:text;white-space:pre}.xterm .live-region{position:absolute;left:-9999px;width:1px;height:1px;overflow:hidden}.xterm-dim{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}
+/*# sourceMappingURL=/sm/97377c0c258e109358121823f5790146c714989366481f90e554c42277efb500.map */
\ No newline at end of file
diff --git a/web/chess-client-wasm/terminal.css b/web/chess-client-wasm/terminal.css
index 964928b..de9db97 100644
--- a/web/chess-client-wasm/terminal.css
+++ b/web/chess-client-wasm/terminal.css
@@ -3,15 +3,24 @@
--host-surface: #1a1b26;
}
-body {
+* {
+ box-sizing: border-box;
+}
+
+html, body {
margin: 0;
padding: 0;
- background: var(--host-bg);
+ width: 100%;
+ height: 100%;
overflow: hidden;
- font-family: monospace;
+ background: var(--host-bg);
}
#terminal {
- width: 100vw;
- height: 100vh;
+ position: absolute;
+ top: 0;
+ left: 0;
+ right: 0;
+ bottom: 0;
+ overflow: hidden;
}
\ No newline at end of file
diff --git a/web/chess-client-wasm/terminal.js b/web/chess-client-wasm/terminal.js
index fdb12c2..f684c8a 100644
--- a/web/chess-client-wasm/terminal.js
+++ b/web/chess-client-wasm/terminal.js
@@ -1,20 +1,41 @@
-// FILE: terminal.js
const term = new Terminal({
cursorBlink: true,
convertEol: true,
fontFamily: 'Menlo, Monaco, "Courier New", monospace',
fontSize: 14,
+ allowProposedApi: true,
theme: {
background: '#1a1b26',
foreground: '#a9b1d6',
cursor: '#a9b1d6',
selection: 'rgba(169, 177, 214, 0.3)'
- },
- cols: 120,
- rows: 40
+ }
});
+// Load addons
+const fitAddon = new FitAddon.FitAddon();
+const webglAddon = new WebglAddon.WebglAddon();
+const webLinksAddon = new WebLinksAddon.WebLinksAddon();
+const unicode11Addon = new Unicode11Addon.Unicode11Addon();
+
+term.loadAddon(fitAddon);
+term.loadAddon(webLinksAddon);
+term.loadAddon(unicode11Addon);
+term.unicode.activeVersion = '11';
+
term.open(document.getElementById('terminal'));
+
+// WebGL addon must load after open()
+try {
+ term.loadAddon(webglAddon);
+ webglAddon.onContextLoss(() => {
+ webglAddon.dispose();
+ });
+} catch (e) {
+ console.warn('WebGL addon failed, using canvas renderer:', e);
+}
+
+fitAddon.fit();
term.focus();
let inputBuffer = '';
@@ -51,17 +72,15 @@ term.onData(data => {
const encoder = new TextEncoder();
const decoder = new TextDecoder();
-// FIXED: Override GLOBAL fs, not go.fs
if (!globalThis.fs) {
globalThis.fs = {};
}
-// Store original methods if they exist
const originalWrite = globalThis.fs.write;
const originalRead = globalThis.fs.read;
globalThis.fs.write = function(fd, buf, offset, length, position, callback) {
- if (fd === 1 || fd === 2) { // stdout/stderr
+ if (fd === 1 || fd === 2) {
const text = decoder.decode(buf.slice(offset, offset + length));
term.write(text);
callback(null, length);
@@ -73,7 +92,7 @@ globalThis.fs.write = function(fd, buf, offset, length, position, callback) {
};
globalThis.fs.read = function(fd, buf, offset, length, position, callback) {
- if (fd === 0) { // stdin
+ if (fd === 0) {
const promise = new Promise(resolve => {
inputResolver = resolve;
});
@@ -91,7 +110,6 @@ globalThis.fs.read = function(fd, buf, offset, length, position, callback) {
}
};
-// Create Go runtime AFTER fs override
const go = new Go();
WebAssembly.instantiateStreaming(fetch('chess-client.wasm'), go.importObject)
@@ -103,8 +121,10 @@ WebAssembly.instantiateStreaming(fetch('chess-client.wasm'), go.importObject)
console.error('WASM load error:', err);
});
-window.addEventListener('resize', () => {
- const cols = Math.floor(window.innerWidth / 8);
- const rows = Math.floor(window.innerHeight / 17);
- term.resize(cols, rows);
-});
\ No newline at end of file
+// Resize handling with debounce for performance
+let resizeTimeout;
+const resizeObserver = new ResizeObserver(() => {
+ clearTimeout(resizeTimeout);
+ resizeTimeout = setTimeout(() => fitAddon.fit(), 16);
+});
+resizeObserver.observe(document.getElementById('terminal'));
\ No newline at end of file