From 820ad7eb279fe897deea90dfed5348ec1f8d6b2fb725404a4fad2591e8a96d56 Mon Sep 17 00:00:00 2001 From: Lixen Wraith Date: Sat, 7 Feb 2026 15:37:52 -0500 Subject: [PATCH] v0.9.0 user and session management improvement, xterm.js addons --- Makefile | 27 ++- cmd/chess-client-cli/exit_native.go | 1 - cmd/chess-client-cli/exit_wasm.go | 1 - cmd/chess-client-cli/main.go | 1 - cmd/chess-server/cli/cli.go | 33 ++- cmd/chess-server/main.go | 7 +- cmd/chess-server/pid.go | 1 - go.mod | 26 +-- go.sum | 26 +++ internal/client/api/client.go | 5 +- internal/client/api/types.go | 9 +- internal/client/command/auth.go | 13 +- internal/client/command/debug.go | 1 - internal/client/command/game.go | 1 - internal/client/command/pass_native.go | 1 - internal/client/command/pass_wasm.go | 1 - internal/client/command/registry.go | 1 - internal/client/display/board.go | 1 - internal/client/display/colors.go | 1 - internal/client/display/format.go | 1 - internal/client/session/session.go | 1 - internal/server/board/board.go | 1 - internal/server/core/api.go | 1 - internal/server/core/error.go | 3 +- internal/server/core/player.go | 16 +- internal/server/core/state.go | 1 - internal/server/engine/engine.go | 1 - internal/server/game/game.go | 44 +++- internal/server/http/auth.go | 60 +++-- internal/server/http/handler.go | 21 +- internal/server/http/middleware.go | 12 +- internal/server/http/validator.go | 1 - internal/server/processor/command.go | 1 - internal/server/processor/processor.go | 67 ++++-- internal/server/processor/queue.go | 1 - internal/server/service/game.go | 22 +- internal/server/service/service.go | 111 ++++++++- internal/server/service/user.go | 217 ++++++++++++----- internal/server/service/waiter.go | 1 - internal/server/storage/game.go | 1 - internal/server/storage/schema.go | 28 ++- internal/server/storage/session.go | 92 ++++++++ internal/server/storage/storage.go | 1 - internal/server/storage/user.go | 103 +++++++-- .../server/webserver/chess-client-web/app.js | 9 +- .../webserver/chess-client-web/index.html | 1 - .../webserver/chess-client-web/style.css | 1 - internal/server/webserver/server.go | 1 - test/README.md | 5 +- test/run-test-server.sh | 1 - test/test-api.sh | 1 - test/test-db.sh | 1 - test/test-longpoll.sh | 1 - web/chess-client-wasm/index.html | 10 +- web/chess-client-wasm/lib/addon-fit.min.js | 8 + .../lib/addon-unicode11.min.js | 8 + .../lib/addon-web-links.min.js | 8 + web/chess-client-wasm/lib/addon-webgl.min.js | 8 + web/chess-client-wasm/lib/xterm.css | 218 ------------------ web/chess-client-wasm/lib/xterm.min.css | 8 + web/chess-client-wasm/terminal.css | 19 +- web/chess-client-wasm/terminal.js | 48 ++-- 62 files changed, 875 insertions(+), 446 deletions(-) create mode 100644 internal/server/storage/session.go create mode 100644 web/chess-client-wasm/lib/addon-fit.min.js create mode 100644 web/chess-client-wasm/lib/addon-unicode11.min.js create mode 100644 web/chess-client-wasm/lib/addon-web-links.min.js create mode 100644 web/chess-client-wasm/lib/addon-webgl.min.js delete mode 100644 web/chess-client-wasm/lib/xterm.css create mode 100644 web/chess-client-wasm/lib/xterm.min.css 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