From 139c43b07a4563ffec2e575096bc3b8e1c53be0d Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Mon, 19 May 2025 21:13:50 +1000 Subject: [PATCH 1/4] chore: delete cmd/tsconnect directory tsconnect contains a package.json and yarn.lock file which can trigger dependabot. Since we don't use this binary or have plans on using Tailscale with WASM we can remove this for now. --- cmd/tsconnect/.gitignore | 3 - cmd/tsconnect/README.md | 49 -- cmd/tsconnect/README.pkg.md | 3 - cmd/tsconnect/build-pkg.go | 97 --- cmd/tsconnect/build.go | 112 ---- cmd/tsconnect/common.go | 325 ---------- cmd/tsconnect/dev-pkg.go | 16 - cmd/tsconnect/dev.go | 16 - cmd/tsconnect/dist/placeholder | 2 - cmd/tsconnect/index.html | 20 - cmd/tsconnect/package.json | 25 - cmd/tsconnect/package.json.tmpl | 16 - cmd/tsconnect/serve.go | 142 ---- cmd/tsconnect/src/app/app.tsx | 147 ----- cmd/tsconnect/src/app/go-panic-display.tsx | 20 - cmd/tsconnect/src/app/header.tsx | 37 -- cmd/tsconnect/src/app/index.css | 74 --- cmd/tsconnect/src/app/index.ts | 36 -- cmd/tsconnect/src/app/ssh.tsx | 157 ----- cmd/tsconnect/src/app/url-display.tsx | 31 - cmd/tsconnect/src/lib/js-state-store.ts | 13 - cmd/tsconnect/src/lib/ssh.ts | 88 --- cmd/tsconnect/src/pkg/pkg.css | 8 - cmd/tsconnect/src/pkg/pkg.ts | 40 -- cmd/tsconnect/src/types/esbuild.d.ts | 14 - cmd/tsconnect/src/types/wasm_js.d.ts | 103 --- cmd/tsconnect/tailwind.config.js | 8 - cmd/tsconnect/tsconfig.json | 15 - cmd/tsconnect/tsconnect.go | 69 -- cmd/tsconnect/wasm/wasm_js.go | 659 ------------------- cmd/tsconnect/yarn.lock | 713 --------------------- 31 files changed, 3058 deletions(-) delete mode 100644 cmd/tsconnect/.gitignore delete mode 100644 cmd/tsconnect/README.md delete mode 100644 cmd/tsconnect/README.pkg.md delete mode 100644 cmd/tsconnect/build-pkg.go delete mode 100644 cmd/tsconnect/build.go delete mode 100644 cmd/tsconnect/common.go delete mode 100644 cmd/tsconnect/dev-pkg.go delete mode 100644 cmd/tsconnect/dev.go delete mode 100644 cmd/tsconnect/dist/placeholder delete mode 100644 cmd/tsconnect/index.html delete mode 100644 cmd/tsconnect/package.json delete mode 100644 cmd/tsconnect/package.json.tmpl delete mode 100644 cmd/tsconnect/serve.go delete mode 100644 cmd/tsconnect/src/app/app.tsx delete mode 100644 cmd/tsconnect/src/app/go-panic-display.tsx delete mode 100644 cmd/tsconnect/src/app/header.tsx delete mode 100644 cmd/tsconnect/src/app/index.css delete mode 100644 cmd/tsconnect/src/app/index.ts delete mode 100644 cmd/tsconnect/src/app/ssh.tsx delete mode 100644 cmd/tsconnect/src/app/url-display.tsx delete mode 100644 cmd/tsconnect/src/lib/js-state-store.ts delete mode 100644 cmd/tsconnect/src/lib/ssh.ts delete mode 100644 cmd/tsconnect/src/pkg/pkg.css delete mode 100644 cmd/tsconnect/src/pkg/pkg.ts delete mode 100644 cmd/tsconnect/src/types/esbuild.d.ts delete mode 100644 cmd/tsconnect/src/types/wasm_js.d.ts delete mode 100644 cmd/tsconnect/tailwind.config.js delete mode 100644 cmd/tsconnect/tsconfig.json delete mode 100644 cmd/tsconnect/tsconnect.go delete mode 100644 cmd/tsconnect/wasm/wasm_js.go delete mode 100644 cmd/tsconnect/yarn.lock diff --git a/cmd/tsconnect/.gitignore b/cmd/tsconnect/.gitignore deleted file mode 100644 index 13615d1213d63..0000000000000 --- a/cmd/tsconnect/.gitignore +++ /dev/null @@ -1,3 +0,0 @@ -node_modules/ -/dist -/pkg diff --git a/cmd/tsconnect/README.md b/cmd/tsconnect/README.md deleted file mode 100644 index 536cd7bbf562c..0000000000000 --- a/cmd/tsconnect/README.md +++ /dev/null @@ -1,49 +0,0 @@ -# tsconnect - -The tsconnect command builds and serves the static site that is generated for -the Tailscale Connect JS/WASM client. - -## Development - -To start the development server: - -``` -./tool/go run ./cmd/tsconnect dev -``` - -The site is served at http://localhost:9090/. JavaScript, CSS and Go `wasm` package changes can be picked up with a browser reload. Server-side Go changes require the server to be stopped and restarted. In development mode the state the Tailscale client state is stored in `sessionStorage` and will thus survive page reloads (but not the tab being closed). - -## Deployment - -To build the static assets necessary for serving, run: - -``` -./tool/go run ./cmd/tsconnect build -``` - -To serve them, run: - -``` -./tool/go run ./cmd/tsconnect serve -``` - -By default the build output is placed in the `dist/` directory and embedded in the binary, but this can be controlled by the `-distdir` flag. The `-addr` flag controls the interface and port that the serve listens on. - -# Library / NPM Package - -The client is also available as [an NPM package](https://www.npmjs.com/package/@tailscale/connect). To build it, run: - -``` -./tool/go run ./cmd/tsconnect build-pkg -``` - -That places the output in the `pkg/` directory, which may then be uploaded to a package registry (or installed from the file path directly). - -To do two-sided development (on both the NPM package and code that uses it), run: - -``` -./tool/go run ./cmd/tsconnect dev-pkg - -``` - -This serves the module at http://localhost:9090/pkg/pkg.js and the generated wasm file at http://localhost:9090/pkg/main.wasm. The two files can be used as drop-in replacements for normal imports of the NPM module. diff --git a/cmd/tsconnect/README.pkg.md b/cmd/tsconnect/README.pkg.md deleted file mode 100644 index df8d66789894d..0000000000000 --- a/cmd/tsconnect/README.pkg.md +++ /dev/null @@ -1,3 +0,0 @@ -# @tailscale/connect - -NPM package that contains a WebAssembly-based Tailscale client, see [the `cmd/tsconnect` directory in the tailscale repo](https://github.com/tailscale/tailscale/tree/main/cmd/tsconnect#library--npm-package) for more details. diff --git a/cmd/tsconnect/build-pkg.go b/cmd/tsconnect/build-pkg.go deleted file mode 100644 index 1b93e5ba06741..0000000000000 --- a/cmd/tsconnect/build-pkg.go +++ /dev/null @@ -1,97 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "encoding/json" - "fmt" - "log" - "os" - "path" - - "github.com/tailscale/hujson" - "tailscale.com/util/precompress" - "tailscale.com/version" -) - -func runBuildPkg() { - buildOptions, err := commonPkgSetup(prodMode) - if err != nil { - log.Fatalf("Cannot setup: %v", err) - } - - log.Printf("Linting...\n") - if err := runYarn("lint"); err != nil { - log.Fatalf("Linting failed: %v", err) - } - - if err := cleanDir(*pkgDir); err != nil { - log.Fatalf("Cannot clean %s: %v", *pkgDir, err) - } - - buildOptions.Write = true - buildOptions.MinifyWhitespace = true - buildOptions.MinifyIdentifiers = true - buildOptions.MinifySyntax = true - - runEsbuild(*buildOptions) - - if err := precompressWasm(); err != nil { - log.Fatalf("Could not pre-recompress wasm: %v", err) - } - - log.Printf("Generating types...\n") - if err := runYarn("pkg-types"); err != nil { - log.Fatalf("Type generation failed: %v", err) - } - - if err := updateVersion(); err != nil { - log.Fatalf("Cannot update version: %v", err) - } - - if err := copyReadme(); err != nil { - log.Fatalf("Cannot copy readme: %v", err) - } - - log.Printf("Built package version %s", version.Long()) -} - -func precompressWasm() error { - log.Printf("Pre-compressing main.wasm...\n") - return precompress.Precompress(path.Join(*pkgDir, "main.wasm"), precompress.Options{ - FastCompression: *fastCompression, - }) -} - -func updateVersion() error { - packageJSONBytes, err := os.ReadFile("package.json.tmpl") - if err != nil { - return fmt.Errorf("Could not read package.json: %w", err) - } - - var packageJSON map[string]any - packageJSONBytes, err = hujson.Standardize(packageJSONBytes) - if err != nil { - return fmt.Errorf("Could not standardize template package.json: %w", err) - } - if err := json.Unmarshal(packageJSONBytes, &packageJSON); err != nil { - return fmt.Errorf("Could not unmarshal package.json: %w", err) - } - packageJSON["version"] = version.Long() - - packageJSONBytes, err = json.MarshalIndent(packageJSON, "", " ") - if err != nil { - return fmt.Errorf("Could not marshal package.json: %w", err) - } - - return os.WriteFile(path.Join(*pkgDir, "package.json"), packageJSONBytes, 0644) -} - -func copyReadme() error { - readmeBytes, err := os.ReadFile("README.pkg.md") - if err != nil { - return fmt.Errorf("Could not read README.pkg.md: %w", err) - } - return os.WriteFile(path.Join(*pkgDir, "README.md"), readmeBytes, 0644) -} diff --git a/cmd/tsconnect/build.go b/cmd/tsconnect/build.go deleted file mode 100644 index 6178ddb9b9ea2..0000000000000 --- a/cmd/tsconnect/build.go +++ /dev/null @@ -1,112 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "encoding/json" - "fmt" - "log" - "os" - "path" - "path/filepath" - - "tailscale.com/util/precompress" -) - -func runBuild() { - buildOptions, err := commonSetup(prodMode) - if err != nil { - log.Fatalf("Cannot setup: %v", err) - } - - log.Printf("Linting...\n") - if err := runYarn("lint"); err != nil { - log.Fatalf("Linting failed: %v", err) - } - - if err := cleanDir(*distDir, "placeholder"); err != nil { - log.Fatalf("Cannot clean %s: %v", *distDir, err) - } - - buildOptions.Write = true - buildOptions.MinifyWhitespace = true - buildOptions.MinifyIdentifiers = true - buildOptions.MinifySyntax = true - - buildOptions.EntryNames = "[dir]/[name]-[hash]" - buildOptions.AssetNames = "[name]-[hash]" - buildOptions.Metafile = true - - result := runEsbuild(*buildOptions) - - // Preserve build metadata so we can extract hashed file names for serving. - metadataBytes, err := fixEsbuildMetadataPaths(result.Metafile) - if err != nil { - log.Fatalf("Cannot fix esbuild metadata paths: %v", err) - } - if err := os.WriteFile(path.Join(*distDir, "/esbuild-metadata.json"), metadataBytes, 0666); err != nil { - log.Fatalf("Cannot write metadata: %v", err) - } - - if er := precompressDist(*fastCompression); err != nil { - log.Fatalf("Cannot precompress resources: %v", er) - } -} - -// fixEsbuildMetadataPaths re-keys the esbuild metadata file to use paths -// relative to the dist directory (it normally uses paths relative to the cwd, -// which are awkward if we're running with a different cwd at serving time). -func fixEsbuildMetadataPaths(metadataStr string) ([]byte, error) { - var metadata EsbuildMetadata - if err := json.Unmarshal([]byte(metadataStr), &metadata); err != nil { - return nil, fmt.Errorf("Cannot parse metadata: %w", err) - } - distAbsPath, err := filepath.Abs(*distDir) - if err != nil { - return nil, fmt.Errorf("Cannot get absolute path from %s: %w", *distDir, err) - } - for outputPath, output := range metadata.Outputs { - outputAbsPath, err := filepath.Abs(outputPath) - if err != nil { - return nil, fmt.Errorf("Cannot get absolute path from %s: %w", outputPath, err) - } - outputRelPath, err := filepath.Rel(distAbsPath, outputAbsPath) - if err != nil { - return nil, fmt.Errorf("Cannot get relative path from %s: %w", outputRelPath, err) - } - delete(metadata.Outputs, outputPath) - metadata.Outputs[outputRelPath] = output - } - return json.Marshal(metadata) -} - -func cleanDist() error { - log.Printf("Cleaning %s...\n", *distDir) - files, err := os.ReadDir(*distDir) - if err != nil { - if os.IsNotExist(err) { - return os.MkdirAll(*distDir, 0755) - } - return err - } - - for _, file := range files { - if file.Name() != "placeholder" { - if err := os.Remove(filepath.Join(*distDir, file.Name())); err != nil { - return err - } - } - } - return nil -} - -func precompressDist(fastCompression bool) error { - log.Printf("Pre-compressing files in %s/...\n", *distDir) - return precompress.PrecompressDir(*distDir, precompress.Options{ - FastCompression: fastCompression, - ProgressFn: func(path string) { - log.Printf("Pre-compressing %v\n", path) - }, - }) -} diff --git a/cmd/tsconnect/common.go b/cmd/tsconnect/common.go deleted file mode 100644 index 437393b1d63e9..0000000000000 --- a/cmd/tsconnect/common.go +++ /dev/null @@ -1,325 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "fmt" - "log" - "net" - "os" - "os/exec" - "path" - "path/filepath" - "runtime" - "strconv" - "time" - - esbuild "github.com/evanw/esbuild/pkg/api" - "golang.org/x/exp/slices" -) - -const ( - devMode = true - prodMode = false -) - -// commonSetup performs setup that is common to both dev and build modes. -func commonSetup(dev bool) (*esbuild.BuildOptions, error) { - // Change cwd to to where this file lives -- that's where all inputs for - // esbuild and other build steps live. - root, err := findRepoRoot() - if err != nil { - return nil, err - } - tsConnectDir := filepath.Join(root, "cmd", "tsconnect") - if err := os.Chdir(tsConnectDir); err != nil { - return nil, fmt.Errorf("Cannot change cwd: %w", err) - } - if err := installJSDeps(); err != nil { - return nil, fmt.Errorf("Cannot install JS deps: %w", err) - } - - return &esbuild.BuildOptions{ - EntryPoints: []string{"src/app/index.ts", "src/app/index.css"}, - Outdir: *distDir, - Bundle: true, - Sourcemap: esbuild.SourceMapLinked, - LogLevel: esbuild.LogLevelInfo, - Define: map[string]string{"DEBUG": strconv.FormatBool(dev)}, - Target: esbuild.ES2017, - Plugins: []esbuild.Plugin{ - { - Name: "tailscale-tailwind", - Setup: func(build esbuild.PluginBuild) { - setupEsbuildTailwind(build, dev) - }, - }, - { - Name: "tailscale-go-wasm-exec-js", - Setup: setupEsbuildWasmExecJS, - }, - { - Name: "tailscale-wasm", - Setup: func(build esbuild.PluginBuild) { - setupEsbuildWasm(build, dev) - }, - }, - }, - JSXMode: esbuild.JSXModeAutomatic, - }, nil -} - -func findRepoRoot() (string, error) { - if *rootDir != "" { - return *rootDir, nil - } - cwd, err := os.Getwd() - if err != nil { - return "", err - } - for { - if _, err := os.Stat(path.Join(cwd, "go.mod")); err == nil { - return cwd, nil - } - if cwd == "/" { - return "", fmt.Errorf("Cannot find repo root") - } - cwd = path.Dir(cwd) - } -} - -func commonPkgSetup(dev bool) (*esbuild.BuildOptions, error) { - buildOptions, err := commonSetup(dev) - if err != nil { - return nil, err - } - buildOptions.EntryPoints = []string{"src/pkg/pkg.ts", "src/pkg/pkg.css"} - buildOptions.Outdir = *pkgDir - buildOptions.Format = esbuild.FormatESModule - buildOptions.AssetNames = "[name]" - return buildOptions, nil -} - -// cleanDir removes files from dirPath, except the ones specified by -// preserveFiles. -func cleanDir(dirPath string, preserveFiles ...string) error { - log.Printf("Cleaning %s...\n", dirPath) - files, err := os.ReadDir(dirPath) - if err != nil { - if os.IsNotExist(err) { - return os.MkdirAll(dirPath, 0755) - } - return err - } - - for _, file := range files { - if !slices.Contains(preserveFiles, file.Name()) { - if err := os.Remove(filepath.Join(dirPath, file.Name())); err != nil { - return err - } - } - } - return nil -} - -func runEsbuildServe(buildOptions esbuild.BuildOptions) { - host, portStr, err := net.SplitHostPort(*addr) - if err != nil { - log.Fatalf("Cannot parse addr: %v", err) - } - port, err := strconv.ParseUint(portStr, 10, 16) - if err != nil { - log.Fatalf("Cannot parse port: %v", err) - } - result, err := esbuild.Serve(esbuild.ServeOptions{ - Port: uint16(port), - Host: host, - Servedir: "./", - }, buildOptions) - if err != nil { - log.Fatalf("Cannot start esbuild server: %v", err) - } - log.Printf("Listening on http://%s:%d\n", result.Host, result.Port) - result.Wait() -} - -func runEsbuild(buildOptions esbuild.BuildOptions) esbuild.BuildResult { - log.Printf("Running esbuild...\n") - result := esbuild.Build(buildOptions) - if len(result.Errors) > 0 { - log.Printf("ESBuild Error:\n") - for _, e := range result.Errors { - log.Printf("%v", e) - } - log.Fatal("Build failed") - } - if len(result.Warnings) > 0 { - log.Printf("ESBuild Warnings:\n") - for _, w := range result.Warnings { - log.Printf("%v", w) - } - } - return result -} - -// setupEsbuildWasmExecJS generates an esbuild plugin that serves the current -// wasm_exec.js runtime helper library from the Go toolchain. -func setupEsbuildWasmExecJS(build esbuild.PluginBuild) { - wasmExecSrcPath := filepath.Join(runtime.GOROOT(), "misc", "wasm", "wasm_exec.js") - build.OnResolve(esbuild.OnResolveOptions{ - Filter: "./wasm_exec$", - }, func(args esbuild.OnResolveArgs) (esbuild.OnResolveResult, error) { - return esbuild.OnResolveResult{Path: wasmExecSrcPath}, nil - }) -} - -// setupEsbuildWasm generates an esbuild plugin that builds the Tailscale wasm -// binary and serves it as a file that the JS can load. -func setupEsbuildWasm(build esbuild.PluginBuild, dev bool) { - // Add a resolve hook to convince esbuild that the path exists. - build.OnResolve(esbuild.OnResolveOptions{ - Filter: "./main.wasm$", - }, func(args esbuild.OnResolveArgs) (esbuild.OnResolveResult, error) { - return esbuild.OnResolveResult{ - Path: "./src/main.wasm", - Namespace: "generated", - }, nil - }) - build.OnLoad(esbuild.OnLoadOptions{ - Filter: "./src/main.wasm$", - }, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) { - contents, err := buildWasm(dev) - if err != nil { - return esbuild.OnLoadResult{}, fmt.Errorf("Cannot build main.wasm: %w", err) - } - contentsStr := string(contents) - return esbuild.OnLoadResult{ - Contents: &contentsStr, - Loader: esbuild.LoaderFile, - }, nil - }) -} - -func buildWasm(dev bool) ([]byte, error) { - start := time.Now() - outputFile, err := os.CreateTemp("", "main.*.wasm") - if err != nil { - return nil, fmt.Errorf("Cannot create main.wasm output file: %w", err) - } - outputPath := outputFile.Name() - - defer os.Remove(outputPath) - // Running defer (*os.File).Close() in defer order before os.Remove - // because on some systems like Windows, it is possible for os.Remove - // to fail for unclosed files. - defer outputFile.Close() - - args := []string{"build", "-tags", "tailscale_go,osusergo,netgo,nethttpomithttp2,omitidna,omitpemdecrypt"} - if !dev { - if *devControl != "" { - return nil, fmt.Errorf("Development control URL can only be used in dev mode.") - } - // Omit long paths and debug symbols in release builds, to reduce the - // generated WASM binary size. - args = append(args, "-trimpath", "-ldflags", "-s -w") - } else if *devControl != "" { - args = append(args, "-ldflags", fmt.Sprintf("-X 'main.ControlURL=%v'", *devControl)) - } - - args = append(args, "-o", outputPath, "./wasm") - cmd := exec.Command(filepath.Join(runtime.GOROOT(), "bin", "go"), args...) - cmd.Env = append(os.Environ(), "GOOS=js", "GOARCH=wasm") - cmd.Stdin = os.Stdin - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err = cmd.Run() - if err != nil { - return nil, fmt.Errorf("Cannot build main.wasm: %w", err) - } - log.Printf("Built wasm in %v\n", time.Since(start).Round(time.Millisecond)) - - if !dev { - err := runWasmOpt(outputPath) - if err != nil { - return nil, fmt.Errorf("Cannot run wasm-opt: %w", err) - } - } - - return os.ReadFile(outputPath) -} - -func runWasmOpt(path string) error { - start := time.Now() - stat, err := os.Stat(path) - if err != nil { - return fmt.Errorf("Cannot stat %v: %w", path, err) - } - startSize := stat.Size() - cmd := exec.Command("../../tool/wasm-opt", "--enable-bulk-memory", "-Oz", path, "-o", path) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - err = cmd.Run() - if err != nil { - return fmt.Errorf("Cannot run wasm-opt: %w", err) - } - stat, err = os.Stat(path) - if err != nil { - return fmt.Errorf("Cannot stat %v: %w", path, err) - } - endSize := stat.Size() - log.Printf("Ran wasm-opt in %v, size dropped by %dK\n", time.Since(start).Round(time.Millisecond), (startSize-endSize)/1024) - return nil -} - -// installJSDeps installs the JavaScript dependencies specified by package.json -func installJSDeps() error { - log.Printf("Installing JS deps...\n") - return runYarn() -} - -func runYarn(args ...string) error { - cmd := exec.Command(*yarnPath, args...) - cmd.Stdout = os.Stdout - cmd.Stderr = os.Stderr - return cmd.Run() -} - -// EsbuildMetadata is the subset of metadata struct (described by -// https://esbuild.github.io/api/#metafile) that we care about for mapping -// from entry points to hashed file names. -type EsbuildMetadata struct { - Outputs map[string]struct { - Inputs map[string]struct { - BytesInOutput int64 `json:"bytesInOutput"` - } `json:"inputs,omitempty"` - EntryPoint string `json:"entryPoint,omitempty"` - } `json:"outputs,omitempty"` -} - -func setupEsbuildTailwind(build esbuild.PluginBuild, dev bool) { - build.OnLoad(esbuild.OnLoadOptions{ - Filter: "./src/.*\\.css$", - }, func(args esbuild.OnLoadArgs) (esbuild.OnLoadResult, error) { - start := time.Now() - yarnArgs := []string{"--silent", "tailwind", "-i", args.Path} - if !dev { - yarnArgs = append(yarnArgs, "--minify") - } - cmd := exec.Command(*yarnPath, yarnArgs...) - tailwindOutput, err := cmd.Output() - log.Printf("Ran tailwind in %v\n", time.Since(start).Round(time.Millisecond)) - if err != nil { - if exitErr, ok := err.(*exec.ExitError); ok { - log.Printf("Tailwind stderr: %s", exitErr.Stderr) - } - return esbuild.OnLoadResult{}, fmt.Errorf("Cannot run tailwind: %w", err) - } - tailwindOutputStr := string(tailwindOutput) - return esbuild.OnLoadResult{ - Contents: &tailwindOutputStr, - Loader: esbuild.LoaderCSS, - }, nil - - }) -} diff --git a/cmd/tsconnect/dev-pkg.go b/cmd/tsconnect/dev-pkg.go deleted file mode 100644 index b23e323e433ac..0000000000000 --- a/cmd/tsconnect/dev-pkg.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "log" -) - -func runDevPkg() { - buildOptions, err := commonPkgSetup(devMode) - if err != nil { - log.Fatalf("Cannot setup: %v", err) - } - runEsbuildServe(*buildOptions) -} diff --git a/cmd/tsconnect/dev.go b/cmd/tsconnect/dev.go deleted file mode 100644 index 60e747270d418..0000000000000 --- a/cmd/tsconnect/dev.go +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "log" -) - -func runDev() { - buildOptions, err := commonSetup(devMode) - if err != nil { - log.Fatalf("Cannot setup: %v", err) - } - runEsbuildServe(*buildOptions) -} diff --git a/cmd/tsconnect/dist/placeholder b/cmd/tsconnect/dist/placeholder deleted file mode 100644 index 4af99d997207f..0000000000000 --- a/cmd/tsconnect/dist/placeholder +++ /dev/null @@ -1,2 +0,0 @@ -This is here to make sure the dist/ directory exists for the go:embed command -in serve.go. diff --git a/cmd/tsconnect/index.html b/cmd/tsconnect/index.html deleted file mode 100644 index 3db45fdef2bca..0000000000000 --- a/cmd/tsconnect/index.html +++ /dev/null @@ -1,20 +0,0 @@ - - - - - - Tailscale Connect - - - - - -
-
-

Tailscale Connect

-
Loading…
-
-
- - diff --git a/cmd/tsconnect/package.json b/cmd/tsconnect/package.json deleted file mode 100644 index bf4eb7c099aac..0000000000000 --- a/cmd/tsconnect/package.json +++ /dev/null @@ -1,25 +0,0 @@ -{ - "name": "tsconnect", - "version": "0.0.1", - "license": "BSD-3-Clause", - "devDependencies": { - "@types/golang-wasm-exec": "^1.15.0", - "@types/qrcode": "^1.4.2", - "dts-bundle-generator": "^6.12.0", - "preact": "^10.10.0", - "qrcode": "^1.5.0", - "tailwindcss": "^3.1.6", - "typescript": "^4.7.4", - "xterm": "^5.1.0", - "xterm-addon-fit": "^0.7.0", - "xterm-addon-web-links": "^0.8.0" - }, - "scripts": { - "lint": "tsc --noEmit", - "pkg-types": "dts-bundle-generator --inline-declare-global=true --no-banner -o pkg/pkg.d.ts src/pkg/pkg.ts" - }, - "prettier": { - "semi": false, - "printWidth": 80 - } -} diff --git a/cmd/tsconnect/package.json.tmpl b/cmd/tsconnect/package.json.tmpl deleted file mode 100644 index 404b896eaf89e..0000000000000 --- a/cmd/tsconnect/package.json.tmpl +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Template for the package.json that is generated by the build-pkg command. -// The version number will be replaced by the current Tailscale client version -// number. -{ - "author": "Tailscale Inc.", - "description": "Tailscale Connect SDK", - "license": "BSD-3-Clause", - "name": "@tailscale/connect", - "type": "module", - "main": "./pkg.js", - "types": "./pkg.d.ts", - "version": "AUTO_GENERATED" -} diff --git a/cmd/tsconnect/serve.go b/cmd/tsconnect/serve.go deleted file mode 100644 index 49e7d3135267b..0000000000000 --- a/cmd/tsconnect/serve.go +++ /dev/null @@ -1,142 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -package main - -import ( - "bytes" - "embed" - "encoding/json" - "fmt" - "io" - "io/fs" - "log" - "net/http" - "os" - "path" - "time" - - "tailscale.com/tsweb" - "tailscale.com/util/precompress" -) - -//go:embed index.html -var embeddedFS embed.FS - -//go:embed dist/* -var embeddedDistFS embed.FS - -var serveStartTime = time.Now() - -func runServe() { - mux := http.NewServeMux() - - var distFS fs.FS - if *distDir == "./dist" { - var err error - distFS, err = fs.Sub(embeddedDistFS, "dist") - if err != nil { - log.Fatalf("Could not drop dist/ prefix from embedded FS: %v", err) - } - } else { - distFS = os.DirFS(*distDir) - } - - indexBytes, err := generateServeIndex(distFS) - if err != nil { - log.Fatalf("Could not generate index.html: %v", err) - } - mux.Handle("/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - http.ServeContent(w, r, "index.html", serveStartTime, bytes.NewReader(indexBytes)) - })) - mux.Handle("/dist/", http.StripPrefix("/dist/", http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - handleServeDist(w, r, distFS) - }))) - tsweb.Debugger(mux) - - log.Printf("Listening on %s", *addr) - err = http.ListenAndServe(*addr, mux) - if err != nil { - log.Fatal(err) - } -} - -func generateServeIndex(distFS fs.FS) ([]byte, error) { - log.Printf("Generating index.html...\n") - rawIndexBytes, err := embeddedFS.ReadFile("index.html") - if err != nil { - return nil, fmt.Errorf("Could not read index.html: %w", err) - } - - esbuildMetadataFile, err := distFS.Open("esbuild-metadata.json") - if err != nil { - return nil, fmt.Errorf("Could not open esbuild-metadata.json: %w", err) - } - defer esbuildMetadataFile.Close() - esbuildMetadataBytes, err := io.ReadAll(esbuildMetadataFile) - if err != nil { - return nil, fmt.Errorf("Could not read esbuild-metadata.json: %w", err) - } - var esbuildMetadata EsbuildMetadata - if err := json.Unmarshal(esbuildMetadataBytes, &esbuildMetadata); err != nil { - return nil, fmt.Errorf("Could not parse esbuild-metadata.json: %w", err) - } - entryPointsToHashedDistPaths := make(map[string]string) - mainWasmPath := "" - for outputPath, output := range esbuildMetadata.Outputs { - if output.EntryPoint != "" { - entryPointsToHashedDistPaths[output.EntryPoint] = path.Join("dist", outputPath) - } - if path.Ext(outputPath) == ".wasm" { - for input := range output.Inputs { - if input == "src/main.wasm" { - mainWasmPath = path.Join("dist", outputPath) - break - } - } - } - } - - indexBytes := rawIndexBytes - for entryPointPath, defaultDistPath := range entryPointsToDefaultDistPaths { - hashedDistPath := entryPointsToHashedDistPaths[entryPointPath] - if hashedDistPath != "" { - indexBytes = bytes.ReplaceAll(indexBytes, []byte(defaultDistPath), []byte(hashedDistPath)) - } - } - if mainWasmPath != "" { - mainWasmPrefetch := fmt.Sprintf("\n", mainWasmPath) - indexBytes = bytes.ReplaceAll(indexBytes, []byte(""), []byte(mainWasmPrefetch)) - } - - return indexBytes, nil -} - -var entryPointsToDefaultDistPaths = map[string]string{ - "src/app/index.css": "dist/index.css", - "src/app/index.ts": "dist/index.js", -} - -func handleServeDist(w http.ResponseWriter, r *http.Request, distFS fs.FS) { - path := r.URL.Path - f, err := precompress.OpenPrecompressedFile(w, r, path, distFS) - if err != nil { - http.Error(w, err.Error(), http.StatusNotFound) - return - } - defer f.Close() - - // fs.File does not claim to implement Seeker, but in practice it does. - fSeeker, ok := f.(io.ReadSeeker) - if !ok { - http.Error(w, "Not seekable", http.StatusInternalServerError) - return - } - - // Aggressively cache static assets, since we cache-bust our assets with - // hashed filenames. - w.Header().Set("Cache-Control", "public, max-age=31535996") - w.Header().Set("Vary", "Accept-Encoding") - - http.ServeContent(w, r, path, serveStartTime, fSeeker) -} diff --git a/cmd/tsconnect/src/app/app.tsx b/cmd/tsconnect/src/app/app.tsx deleted file mode 100644 index ee538eaeac506..0000000000000 --- a/cmd/tsconnect/src/app/app.tsx +++ /dev/null @@ -1,147 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -import { render, Component } from "preact" -import { URLDisplay } from "./url-display" -import { Header } from "./header" -import { GoPanicDisplay } from "./go-panic-display" -import { SSH } from "./ssh" - -type AppState = { - ipn?: IPN - ipnState: IPNState - netMap?: IPNNetMap - browseToURL?: string - goPanicError?: string -} - -class App extends Component<{}, AppState> { - state: AppState = { ipnState: "NoState" } - #goPanicTimeout?: number - - render() { - const { ipn, ipnState, goPanicError, netMap, browseToURL } = this.state - - let goPanicDisplay - if (goPanicError) { - goPanicDisplay = ( - - ) - } - - let urlDisplay - if (browseToURL) { - urlDisplay = - } - - let machineAuthInstructions - if (ipnState === "NeedsMachineAuth") { - machineAuthInstructions = ( -
- An administrator needs to approve this device. -
- ) - } - - const lockedOut = netMap?.lockedOut - let lockedOutInstructions - if (lockedOut) { - lockedOutInstructions = ( -
-

This instance of Tailscale Connect needs to be signed, due to - {" "}tailnet lock{" "} - being enabled on this domain. -

- -

- Run the following command on a device with a trusted tailnet lock key: -

tailscale lock sign {netMap.self.nodeKey}
-

-
- ) - } - - let ssh - if (ipn && ipnState === "Running" && netMap && !lockedOut) { - ssh = - } - - return ( - <> -
- {goPanicDisplay} -
- {urlDisplay} - {machineAuthInstructions} - {lockedOutInstructions} - {ssh} -
- - ) - } - - runWithIPN(ipn: IPN) { - this.setState({ ipn }, () => { - ipn.run({ - notifyState: this.handleIPNState, - notifyNetMap: this.handleNetMap, - notifyBrowseToURL: this.handleBrowseToURL, - notifyPanicRecover: this.handleGoPanic, - }) - }) - } - - handleIPNState = (state: IPNState) => { - const { ipn } = this.state - this.setState({ ipnState: state }) - if (state === "NeedsLogin") { - ipn?.login() - } else if (["Running", "NeedsMachineAuth"].includes(state)) { - this.setState({ browseToURL: undefined }) - } - } - - handleNetMap = (netMapStr: string) => { - const netMap = JSON.parse(netMapStr) as IPNNetMap - if (DEBUG) { - console.log("Received net map: " + JSON.stringify(netMap, null, 2)) - } - this.setState({ netMap }) - } - - handleBrowseToURL = (url: string) => { - if (this.state.ipnState === "Running") { - // Ignore URL requests if we're already running -- it's most likely an - // SSH check mode trigger and we already linkify the displayed URL - // in the terminal. - return - } - this.setState({ browseToURL: url }) - } - - handleGoPanic = (error: string) => { - if (DEBUG) { - console.error("Go panic", error) - } - this.setState({ goPanicError: error }) - if (this.#goPanicTimeout) { - window.clearTimeout(this.#goPanicTimeout) - } - this.#goPanicTimeout = window.setTimeout(this.clearGoPanic, 10000) - } - - clearGoPanic = () => { - window.clearTimeout(this.#goPanicTimeout) - this.#goPanicTimeout = undefined - this.setState({ goPanicError: undefined }) - } -} - -export function renderApp(): Promise { - return new Promise((resolve) => { - render( - (app ? resolve(app) : undefined)} />, - document.body - ) - }) -} diff --git a/cmd/tsconnect/src/app/go-panic-display.tsx b/cmd/tsconnect/src/app/go-panic-display.tsx deleted file mode 100644 index 5dd7095a27c7d..0000000000000 --- a/cmd/tsconnect/src/app/go-panic-display.tsx +++ /dev/null @@ -1,20 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -export function GoPanicDisplay({ - error, - dismiss, -}: { - error: string - dismiss: () => void -}) { - return ( -
- Tailscale has encountered an error. -
Click to reload
-
- ) -} diff --git a/cmd/tsconnect/src/app/header.tsx b/cmd/tsconnect/src/app/header.tsx deleted file mode 100644 index 099ff2f8c2f7d..0000000000000 --- a/cmd/tsconnect/src/app/header.tsx +++ /dev/null @@ -1,37 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -export function Header({ state, ipn }: { state: IPNState; ipn?: IPN }) { - const stateText = STATE_LABELS[state] - - let logoutButton - if (state === "Running") { - logoutButton = ( - - ) - } - return ( -
-
-

Tailscale Connect

-
{stateText}
- {logoutButton} -
-
- ) -} - -const STATE_LABELS = { - NoState: "Initializing…", - InUseOtherUser: "In-use by another user", - NeedsLogin: "Needs login", - NeedsMachineAuth: "Needs approval", - Stopped: "Stopped", - Starting: "Starting…", - Running: "Running", -} as const diff --git a/cmd/tsconnect/src/app/index.css b/cmd/tsconnect/src/app/index.css deleted file mode 100644 index 751b313d9f362..0000000000000 --- a/cmd/tsconnect/src/app/index.css +++ /dev/null @@ -1,74 +0,0 @@ -/* Copyright (c) Tailscale Inc & AUTHORS */ -/* SPDX-License-Identifier: BSD-3-Clause */ - -@import "xterm/css/xterm.css"; - -@tailwind base; -@tailwind components; -@tailwind utilities; - -.link { - @apply text-blue-600; -} - -.link:hover { - @apply underline; -} - -.button { - @apply font-medium py-1 px-2 rounded-md border border-transparent text-center cursor-pointer; - transition-property: background-color, border-color, color, box-shadow; - transition-duration: 120ms; - box-shadow: 0 1px 1px rgba(0, 0, 0, 0.04); - min-width: 80px; -} -.button:focus { - @apply outline-none ring; -} -.button:disabled { - @apply pointer-events-none select-none; -} - -.input { - @apply appearance-none leading-tight rounded-md bg-white border border-gray-300 hover:border-gray-400 transition-colors px-3; - height: 2.375rem; -} - -.input::placeholder { - @apply text-gray-400; -} - -.input:disabled { - @apply border-gray-200; - @apply bg-gray-50; - @apply cursor-not-allowed; -} - -.input:focus { - @apply outline-none ring border-transparent; -} - -.select { - @apply appearance-none py-2 px-3 leading-tight rounded-md bg-white border border-gray-300; -} - -.select-with-arrow { - @apply relative; -} - -.select-with-arrow .select { - width: 100%; -} - -.select-with-arrow::after { - @apply absolute; - content: ""; - top: 50%; - right: 0.5rem; - transform: translate(-0.3em, -0.15em); - width: 0.6em; - height: 0.4em; - opacity: 0.6; - background-color: currentColor; - clip-path: polygon(100% 0%, 0 0%, 50% 100%); -} diff --git a/cmd/tsconnect/src/app/index.ts b/cmd/tsconnect/src/app/index.ts deleted file mode 100644 index 24ca4543921ae..0000000000000 --- a/cmd/tsconnect/src/app/index.ts +++ /dev/null @@ -1,36 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -import "../wasm_exec" -import wasmUrl from "./main.wasm" -import { sessionStateStorage } from "../lib/js-state-store" -import { renderApp } from "./app" - -async function main() { - const app = await renderApp() - const go = new Go() - const wasmInstance = await WebAssembly.instantiateStreaming( - fetch(`./dist/${wasmUrl}`), - go.importObject - ) - // The Go process should never exit, if it does then it's an unhandled panic. - go.run(wasmInstance.instance).then(() => - app.handleGoPanic("Unexpected shutdown") - ) - - const params = new URLSearchParams(window.location.search) - const authKey = params.get("authkey") ?? undefined - - const ipn = newIPN({ - // Persist IPN state in sessionStorage in development, so that we don't need - // to re-authorize every time we reload the page. - stateStorage: DEBUG ? sessionStateStorage : undefined, - // authKey allows for an auth key to be - // specified as a url param which automatically - // authorizes the client for use. - authKey: DEBUG ? authKey : undefined, - }) - app.runWithIPN(ipn) -} - -main() diff --git a/cmd/tsconnect/src/app/ssh.tsx b/cmd/tsconnect/src/app/ssh.tsx deleted file mode 100644 index df81745bd3fd7..0000000000000 --- a/cmd/tsconnect/src/app/ssh.tsx +++ /dev/null @@ -1,157 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -import { useState, useCallback, useMemo, useEffect, useRef } from "preact/hooks" -import { createPortal } from "preact/compat" -import type { VNode } from "preact" -import { runSSHSession, SSHSessionDef } from "../lib/ssh" - -export function SSH({ netMap, ipn }: { netMap: IPNNetMap; ipn: IPN }) { - const [sshSessionDef, setSSHSessionDef] = useState( - null - ) - const clearSSHSessionDef = useCallback(() => setSSHSessionDef(null), []) - if (sshSessionDef) { - const sshSession = ( - - ) - if (sshSessionDef.newWindow) { - return {sshSession} - } - return sshSession - } - const sshPeers = netMap.peers.filter( - (p) => p.tailscaleSSHEnabled && p.online !== false - ) - - if (sshPeers.length == 0) { - return - } - - return -} - -type SSHFormSessionDef = SSHSessionDef & { newWindow?: boolean } - -function SSHSession({ - def, - ipn, - onDone, -}: { - def: SSHSessionDef - ipn: IPN - onDone: () => void -}) { - const ref = useRef(null) - useEffect(() => { - if (ref.current) { - runSSHSession(ref.current, def, ipn, { - onConnectionProgress: (p) => console.log("Connection progress", p), - onConnected() {}, - onError: (err) => console.error(err), - onDone, - }) - } - }, [ref]) - - return
-} - -function NoSSHPeers() { - return ( -
- None of your machines have{" "} - - Tailscale SSH - - {" "}enabled. Give it a try! -
- ) -} - -function SSHForm({ - sshPeers, - onSubmit, -}: { - sshPeers: IPNNetMapPeerNode[] - onSubmit: (def: SSHFormSessionDef) => void -}) { - sshPeers = sshPeers.slice().sort((a, b) => a.name.localeCompare(b.name)) - const [username, setUsername] = useState("") - const [hostname, setHostname] = useState(sshPeers[0].name) - return ( -
{ - e.preventDefault() - onSubmit({ username, hostname }) - }} - > - setUsername(e.currentTarget.value)} - /> -
- -
- { - if (e.altKey) { - e.preventDefault() - e.stopPropagation() - onSubmit({ username, hostname, newWindow: true }) - } - }} - /> -
- ) -} - -const NewWindow = ({ - children, - close, -}: { - children: VNode - close: () => void -}) => { - const newWindow = useMemo(() => { - const newWindow = window.open(undefined, undefined, "width=600,height=400") - if (newWindow) { - const containerNode = newWindow.document.createElement("div") - containerNode.className = "h-screen flex flex-col overflow-hidden" - newWindow.document.body.appendChild(containerNode) - - for (const linkNode of document.querySelectorAll( - "head link[rel=stylesheet]" - )) { - const newLink = document.createElement("link") - newLink.rel = "stylesheet" - newLink.href = (linkNode as HTMLLinkElement).href - newWindow.document.head.appendChild(newLink) - } - } - return newWindow - }, []) - if (!newWindow) { - console.error("Could not open window") - return null - } - newWindow.onbeforeunload = () => { - close() - } - - useEffect(() => () => newWindow.close(), []) - return createPortal(children, newWindow.document.body.lastChild as Element) -} diff --git a/cmd/tsconnect/src/app/url-display.tsx b/cmd/tsconnect/src/app/url-display.tsx deleted file mode 100644 index fc82c7fb91b3c..0000000000000 --- a/cmd/tsconnect/src/app/url-display.tsx +++ /dev/null @@ -1,31 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -import { useState } from "preact/hooks" -import * as qrcode from "qrcode" - -export function URLDisplay({ url }: { url: string }) { - const [dataURL, setDataURL] = useState("") - qrcode.toDataURL(url, { width: 512 }, (err, dataURL) => { - if (err) { - console.error("Error generating QR code", err) - } else { - setDataURL(dataURL) - } - }) - - return ( - - ) -} diff --git a/cmd/tsconnect/src/lib/js-state-store.ts b/cmd/tsconnect/src/lib/js-state-store.ts deleted file mode 100644 index e57dfd98efabd..0000000000000 --- a/cmd/tsconnect/src/lib/js-state-store.ts +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -/** @fileoverview Callbacks used by jsStateStore to persist IPN state. */ - -export const sessionStateStorage: IPNStateStorage = { - setState(id, value) { - window.sessionStorage[`ipn-state-${id}`] = value - }, - getState(id) { - return window.sessionStorage[`ipn-state-${id}`] || "" - }, -} diff --git a/cmd/tsconnect/src/lib/ssh.ts b/cmd/tsconnect/src/lib/ssh.ts deleted file mode 100644 index afbcdb895a3ba..0000000000000 --- a/cmd/tsconnect/src/lib/ssh.ts +++ /dev/null @@ -1,88 +0,0 @@ -import { Terminal, ITerminalOptions } from "xterm" -import { FitAddon } from "xterm-addon-fit" -import { WebLinksAddon } from "xterm-addon-web-links" - -export type SSHSessionDef = { - username: string - hostname: string - /** Defaults to 5 seconds */ - timeoutSeconds?: number -} - -export type SSHSessionCallbacks = { - onConnectionProgress: (messsage: string) => void - onConnected: () => void - onDone: () => void - onError?: (err: string) => void -} - -export function runSSHSession( - termContainerNode: HTMLDivElement, - def: SSHSessionDef, - ipn: IPN, - callbacks: SSHSessionCallbacks, - terminalOptions?: ITerminalOptions -) { - const parentWindow = termContainerNode.ownerDocument.defaultView ?? window - const term = new Terminal({ - cursorBlink: true, - allowProposedApi: true, - ...terminalOptions, - }) - - const fitAddon = new FitAddon() - term.loadAddon(fitAddon) - term.open(termContainerNode) - fitAddon.fit() - - const webLinksAddon = new WebLinksAddon((event, uri) => - event.view?.open(uri, "_blank", "noopener") - ) - term.loadAddon(webLinksAddon) - - let onDataHook: ((data: string) => void) | undefined - term.onData((e) => { - onDataHook?.(e) - }) - - term.focus() - - let resizeObserver: ResizeObserver | undefined - let handleUnload: ((e: Event) => void) | undefined - - const sshSession = ipn.ssh(def.hostname, def.username, { - writeFn(input) { - term.write(input) - }, - writeErrorFn(err) { - callbacks.onError?.(err) - term.write(err) - }, - setReadFn(hook) { - onDataHook = hook - }, - rows: term.rows, - cols: term.cols, - onConnectionProgress: callbacks.onConnectionProgress, - onConnected: callbacks.onConnected, - onDone() { - resizeObserver?.disconnect() - term.dispose() - if (handleUnload) { - parentWindow.removeEventListener("unload", handleUnload) - } - callbacks.onDone() - }, - timeoutSeconds: def.timeoutSeconds, - }) - - // Make terminal and SSH session track the size of the containing DOM node. - resizeObserver = new parentWindow.ResizeObserver(() => fitAddon.fit()) - resizeObserver.observe(termContainerNode) - term.onResize(({ rows, cols }) => sshSession.resize(rows, cols)) - - // Close the session if the user closes the window without an explicit - // exit. - handleUnload = () => sshSession.close() - parentWindow.addEventListener("unload", handleUnload) -} diff --git a/cmd/tsconnect/src/pkg/pkg.css b/cmd/tsconnect/src/pkg/pkg.css deleted file mode 100644 index 76ea21f5b53b2..0000000000000 --- a/cmd/tsconnect/src/pkg/pkg.css +++ /dev/null @@ -1,8 +0,0 @@ -/* Copyright (c) Tailscale Inc & AUTHORS */ -/* SPDX-License-Identifier: BSD-3-Clause */ - -@import "xterm/css/xterm.css"; - -@tailwind base; -@tailwind components; -@tailwind utilities; diff --git a/cmd/tsconnect/src/pkg/pkg.ts b/cmd/tsconnect/src/pkg/pkg.ts deleted file mode 100644 index 4d535cb404015..0000000000000 --- a/cmd/tsconnect/src/pkg/pkg.ts +++ /dev/null @@ -1,40 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// Type definitions need to be manually imported for dts-bundle-generator to -// discover them. -/// -/// - -import "../wasm_exec" -import wasmURL from "./main.wasm" - -/** - * Superset of the IPNConfig type, with additional configuration that is - * needed for the package to function. - */ -type IPNPackageConfig = IPNConfig & { - // Auth key used to initialize the Tailscale client (required) - authKey: string - // URL of the main.wasm file that is included in the page, if it is not - // accessible via a relative URL. - wasmURL?: string - // Function invoked if the Go process panics or unexpectedly exits. - panicHandler: (err: string) => void -} - -export async function createIPN(config: IPNPackageConfig): Promise { - const go = new Go() - const wasmInstance = await WebAssembly.instantiateStreaming( - fetch(config.wasmURL ?? wasmURL), - go.importObject - ) - // The Go process should never exit, if it does then it's an unhandled panic. - go.run(wasmInstance.instance).then(() => - config.panicHandler("Unexpected shutdown") - ) - - return newIPN(config) -} - -export { runSSHSession } from "../lib/ssh" diff --git a/cmd/tsconnect/src/types/esbuild.d.ts b/cmd/tsconnect/src/types/esbuild.d.ts deleted file mode 100644 index ef28f7b1cf556..0000000000000 --- a/cmd/tsconnect/src/types/esbuild.d.ts +++ /dev/null @@ -1,14 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -/** - * @fileoverview Type definitions for types generated by the esbuild build - * process. - */ - -declare module "*.wasm" { - const path: string - export default path -} - -declare const DEBUG: boolean diff --git a/cmd/tsconnect/src/types/wasm_js.d.ts b/cmd/tsconnect/src/types/wasm_js.d.ts deleted file mode 100644 index 492197ccb1a9b..0000000000000 --- a/cmd/tsconnect/src/types/wasm_js.d.ts +++ /dev/null @@ -1,103 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -/** - * @fileoverview Type definitions for types exported by the wasm_js.go Go - * module. - */ - -declare global { - function newIPN(config: IPNConfig): IPN - - interface IPN { - run(callbacks: IPNCallbacks): void - login(): void - logout(): void - ssh( - host: string, - username: string, - termConfig: { - writeFn: (data: string) => void - writeErrorFn: (err: string) => void - setReadFn: (readFn: (data: string) => void) => void - rows: number - cols: number - /** Defaults to 5 seconds */ - timeoutSeconds?: number - onConnectionProgress: (message: string) => void - onConnected: () => void - onDone: () => void - } - ): IPNSSHSession - fetch(url: string): Promise<{ - status: number - statusText: string - text: () => Promise - }> - } - - interface IPNSSHSession { - resize(rows: number, cols: number): boolean - close(): boolean - } - - interface IPNStateStorage { - setState(id: string, value: string): void - getState(id: string): string - } - - type IPNConfig = { - stateStorage?: IPNStateStorage - authKey?: string - controlURL?: string - hostname?: string - } - - type IPNCallbacks = { - notifyState: (state: IPNState) => void - notifyNetMap: (netMapStr: string) => void - notifyBrowseToURL: (url: string) => void - notifyPanicRecover: (err: string) => void - } - - type IPNNetMap = { - self: IPNNetMapSelfNode - peers: IPNNetMapPeerNode[] - lockedOut: boolean - } - - type IPNNetMapNode = { - name: string - addresses: string[] - machineKey: string - nodeKey: string - } - - type IPNNetMapSelfNode = IPNNetMapNode & { - machineStatus: IPNMachineStatus - } - - type IPNNetMapPeerNode = IPNNetMapNode & { - online?: boolean - tailscaleSSHEnabled: boolean - } - - /** Mirrors values from ipn/backend.go */ - type IPNState = - | "NoState" - | "InUseOtherUser" - | "NeedsLogin" - | "NeedsMachineAuth" - | "Stopped" - | "Starting" - | "Running" - - /** Mirrors values from MachineStatus in tailcfg.go */ - type IPNMachineStatus = - | "MachineUnknown" - | "MachineUnauthorized" - | "MachineAuthorized" - | "MachineInvalid" -} - -export {} diff --git a/cmd/tsconnect/tailwind.config.js b/cmd/tsconnect/tailwind.config.js deleted file mode 100644 index 31823000b6139..0000000000000 --- a/cmd/tsconnect/tailwind.config.js +++ /dev/null @@ -1,8 +0,0 @@ -/** @type {import('tailwindcss').Config} */ -module.exports = { - content: ["./index.html", "./src/**/*.ts", "./src/**/*.tsx"], - theme: { - extend: {}, - }, - plugins: [], -} diff --git a/cmd/tsconnect/tsconfig.json b/cmd/tsconnect/tsconfig.json deleted file mode 100644 index 52c25c7271f7c..0000000000000 --- a/cmd/tsconnect/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "compilerOptions": { - "target": "ES2017", - "module": "ES2020", - "moduleResolution": "node", - "isolatedModules": true, - "strict": true, - "forceConsistentCasingInFileNames": true, - "sourceMap": true, - "jsx": "react-jsx", - "jsxImportSource": "preact" - }, - "include": ["src/**/*"], - "exclude": ["node_modules"] -} diff --git a/cmd/tsconnect/tsconnect.go b/cmd/tsconnect/tsconnect.go deleted file mode 100644 index 1ab13b3551a4c..0000000000000 --- a/cmd/tsconnect/tsconnect.go +++ /dev/null @@ -1,69 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// The tsconnect command builds and serves the static site that is generated for -// the Tailscale Connect JS/WASM client. Can be run in 3 modes: -// - dev: builds the site and serves it. JS and CSS changes can be picked up -// with a reload. -// - build: builds the site and writes it to dist/ -// - serve: serves the site from dist/ (embedded in the binary) -package main // import "tailscale.com/cmd/tsconnect" - -import ( - "flag" - "fmt" - "log" - "os" -) - -var ( - addr = flag.String("addr", ":9090", "address to listen on") - distDir = flag.String("distdir", "./dist", "path of directory to place build output in") - pkgDir = flag.String("pkgdir", "./pkg", "path of directory to place NPM package build output in") - yarnPath = flag.String("yarnpath", "../../tool/yarn", "path yarn executable used to install JavaScript dependencies") - fastCompression = flag.Bool("fast-compression", false, "Use faster compression when building, to speed up build time. Meant to iterative/debugging use only.") - devControl = flag.String("dev-control", "", "URL of a development control server to be used with dev. If provided without specifying dev, an error will be returned.") - rootDir = flag.String("rootdir", "", "Root directory of repo. If not specified, will be inferred from the cwd.") -) - -func main() { - flag.Usage = usage - flag.Parse() - if len(flag.Args()) != 1 { - flag.Usage() - } - - switch flag.Arg(0) { - case "dev": - runDev() - case "dev-pkg": - runDevPkg() - case "build": - runBuild() - case "build-pkg": - runBuildPkg() - case "serve": - runServe() - default: - log.Printf("Unknown command: %s", flag.Arg(0)) - flag.Usage() - } -} - -func usage() { - fmt.Fprintf(os.Stderr, ` -usage: tsconnect {dev|build|serve} -`[1:]) - - flag.PrintDefaults() - fmt.Fprintf(os.Stderr, ` - -tsconnect implements development/build/serving workflows for Tailscale Connect. -It can be invoked with one of three subcommands: - -- dev: Run in development mode, allowing JS and CSS changes to be picked up without a rebuilt or restart. -- build: Run in production build mode (generating static assets) -- serve: Run in production serve mode (serving static assets) -`[1:]) - os.Exit(2) -} diff --git a/cmd/tsconnect/wasm/wasm_js.go b/cmd/tsconnect/wasm/wasm_js.go deleted file mode 100644 index e8d6b665c40eb..0000000000000 --- a/cmd/tsconnect/wasm/wasm_js.go +++ /dev/null @@ -1,659 +0,0 @@ -// Copyright (c) Tailscale Inc & AUTHORS -// SPDX-License-Identifier: BSD-3-Clause - -// The wasm package builds a WebAssembly module that provides a subset of -// Tailscale APIs to JavaScript. -// -// When run in the browser, a newIPN(config) function is added to the global JS -// namespace. When called it returns an ipn object with the methods -// run(callbacks), login(), logout(), and ssh(...). -package main - -import ( - "bytes" - "context" - "encoding/hex" - "encoding/json" - "fmt" - "log" - "math/rand" - "net" - "net/http" - "net/netip" - "strings" - "syscall/js" - "time" - - "golang.org/x/crypto/ssh" - "tailscale.com/control/controlclient" - "tailscale.com/ipn" - "tailscale.com/ipn/ipnlocal" - "tailscale.com/ipn/ipnserver" - "tailscale.com/ipn/store/mem" - "tailscale.com/logpolicy" - "tailscale.com/logtail" - "tailscale.com/net/netns" - "tailscale.com/net/tsdial" - "tailscale.com/safesocket" - "tailscale.com/smallzstd" - "tailscale.com/tailcfg" - "tailscale.com/tsd" - "tailscale.com/wgengine" - "tailscale.com/wgengine/netstack" - "tailscale.com/words" -) - -// ControlURL defines the URL to be used for connection to Control. -var ControlURL = ipn.DefaultControlURL - -func main() { - js.Global().Set("newIPN", js.FuncOf(func(this js.Value, args []js.Value) any { - if len(args) != 1 { - log.Fatal("Usage: newIPN(config)") - return nil - } - return newIPN(args[0]) - })) - // Keep Go runtime alive, otherwise it will be shut down before newIPN gets - // called. - <-make(chan bool) -} - -func newIPN(jsConfig js.Value) map[string]any { - netns.SetEnabled(false) - - var store ipn.StateStore - if jsStateStorage := jsConfig.Get("stateStorage"); !jsStateStorage.IsUndefined() { - store = &jsStateStore{jsStateStorage} - } else { - store = new(mem.Store) - } - - controlURL := ControlURL - if jsControlURL := jsConfig.Get("controlURL"); jsControlURL.Type() == js.TypeString { - controlURL = jsControlURL.String() - } - - var authKey string - if jsAuthKey := jsConfig.Get("authKey"); jsAuthKey.Type() == js.TypeString { - authKey = jsAuthKey.String() - } - - var hostname string - if jsHostname := jsConfig.Get("hostname"); jsHostname.Type() == js.TypeString { - hostname = jsHostname.String() - } else { - hostname = generateHostname() - } - - lpc := getOrCreateLogPolicyConfig(store) - c := logtail.Config{ - Collection: lpc.Collection, - PrivateID: lpc.PrivateID, - // NewZstdEncoder is intentionally not passed in, compressed requests - // set HTTP headers that are not supported by the no-cors fetching mode. - HTTPC: &http.Client{Transport: &noCORSTransport{http.DefaultTransport}}, - } - logtail := logtail.NewLogger(c, log.Printf) - logf := logtail.Logf - - sys := new(tsd.System) - sys.Set(store) - dialer := &tsdial.Dialer{Logf: logf} - eng, err := wgengine.NewUserspaceEngine(logf, wgengine.Config{ - Dialer: dialer, - SetSubsystem: sys.Set, - }) - if err != nil { - log.Fatal(err) - } - sys.Set(eng) - - ns, err := netstack.Create(logf, sys.Tun.Get(), eng, sys.MagicSock.Get(), dialer, sys.DNSManager.Get()) - if err != nil { - log.Fatalf("netstack.Create: %v", err) - } - ns.ProcessLocalIPs = true - ns.ProcessSubnets = true - - dialer.UseNetstackForIP = func(ip netip.Addr) bool { - return true - } - dialer.NetstackDialTCP = func(ctx context.Context, dst netip.AddrPort) (net.Conn, error) { - return ns.DialContextTCP(ctx, dst) - } - sys.NetstackRouter.Set(true) - - logid := lpc.PublicID - srv := ipnserver.New(logf, logid, nil /* no netMon */) - lb, err := ipnlocal.NewLocalBackend(logf, logid, sys, controlclient.LoginEphemeral) - if err != nil { - log.Fatalf("ipnlocal.NewLocalBackend: %v", err) - } - if err := ns.Start(lb); err != nil { - log.Fatalf("failed to start netstack: %v", err) - } - lb.SetDecompressor(func() (controlclient.Decompressor, error) { - return smallzstd.NewDecoder(nil) - }) - srv.SetLocalBackend(lb) - - jsIPN := &jsIPN{ - dialer: dialer, - srv: srv, - lb: lb, - controlURL: controlURL, - authKey: authKey, - hostname: hostname, - } - - return map[string]any{ - "run": js.FuncOf(func(this js.Value, args []js.Value) any { - if len(args) != 1 { - log.Fatal(`Usage: run({ - notifyState(state: int): void, - notifyNetMap(netMap: object): void, - notifyBrowseToURL(url: string): void, - notifyPanicRecover(err: string): void, - })`) - return nil - } - jsIPN.run(args[0]) - return nil - }), - "login": js.FuncOf(func(this js.Value, args []js.Value) any { - if len(args) != 0 { - log.Printf("Usage: login()") - return nil - } - jsIPN.login() - return nil - }), - "logout": js.FuncOf(func(this js.Value, args []js.Value) any { - if len(args) != 0 { - log.Printf("Usage: logout()") - return nil - } - jsIPN.logout() - return nil - }), - "ssh": js.FuncOf(func(this js.Value, args []js.Value) any { - if len(args) != 3 { - log.Printf("Usage: ssh(hostname, userName, termConfig)") - return nil - } - return jsIPN.ssh( - args[0].String(), - args[1].String(), - args[2]) - }), - "fetch": js.FuncOf(func(this js.Value, args []js.Value) any { - if len(args) != 1 { - log.Printf("Usage: fetch(url)") - return nil - } - - url := args[0].String() - return jsIPN.fetch(url) - }), - } -} - -type jsIPN struct { - dialer *tsdial.Dialer - srv *ipnserver.Server - lb *ipnlocal.LocalBackend - controlURL string - authKey string - hostname string -} - -var jsIPNState = map[ipn.State]string{ - ipn.NoState: "NoState", - ipn.InUseOtherUser: "InUseOtherUser", - ipn.NeedsLogin: "NeedsLogin", - ipn.NeedsMachineAuth: "NeedsMachineAuth", - ipn.Stopped: "Stopped", - ipn.Starting: "Starting", - ipn.Running: "Running", -} - -var jsMachineStatus = map[tailcfg.MachineStatus]string{ - tailcfg.MachineUnknown: "MachineUnknown", - tailcfg.MachineUnauthorized: "MachineUnauthorized", - tailcfg.MachineAuthorized: "MachineAuthorized", - tailcfg.MachineInvalid: "MachineInvalid", -} - -func (i *jsIPN) run(jsCallbacks js.Value) { - notifyState := func(state ipn.State) { - jsCallbacks.Call("notifyState", jsIPNState[state]) - } - notifyState(ipn.NoState) - - i.lb.SetNotifyCallback(func(n ipn.Notify) { - // Panics in the notify callback are likely due to be due to bugs in - // this bridging module (as opposed to actual bugs in Tailscale) and - // thus may be recoverable. Let the UI know, and allow the user to - // choose if they want to reload the page. - defer func() { - if r := recover(); r != nil { - fmt.Println("Panic recovered:", r) - jsCallbacks.Call("notifyPanicRecover", fmt.Sprint(r)) - } - }() - log.Printf("NOTIFY: %+v", n) - if n.State != nil { - notifyState(*n.State) - } - if nm := n.NetMap; nm != nil { - jsNetMap := jsNetMap{ - Self: jsNetMapSelfNode{ - jsNetMapNode: jsNetMapNode{ - Name: nm.Name, - Addresses: mapSlice(nm.Addresses, func(a netip.Prefix) string { return a.Addr().String() }), - NodeKey: nm.NodeKey.String(), - MachineKey: nm.MachineKey.String(), - }, - MachineStatus: jsMachineStatus[nm.MachineStatus], - }, - Peers: mapSlice(nm.Peers, func(p *tailcfg.Node) jsNetMapPeerNode { - name := p.Name - if name == "" { - // In practice this should only happen for Hello. - name = p.Hostinfo.Hostname() - } - return jsNetMapPeerNode{ - jsNetMapNode: jsNetMapNode{ - Name: name, - Addresses: mapSlice(p.Addresses, func(a netip.Prefix) string { return a.Addr().String() }), - MachineKey: p.Machine.String(), - NodeKey: p.Key.String(), - }, - Online: p.Online, - TailscaleSSHEnabled: p.Hostinfo.TailscaleSSHEnabled(), - } - }), - LockedOut: nm.TKAEnabled && len(nm.SelfNode.KeySignature) == 0, - } - if jsonNetMap, err := json.Marshal(jsNetMap); err == nil { - jsCallbacks.Call("notifyNetMap", string(jsonNetMap)) - } else { - log.Printf("Could not generate JSON netmap: %v", err) - } - } - if n.BrowseToURL != nil { - jsCallbacks.Call("notifyBrowseToURL", *n.BrowseToURL) - } - }) - - go func() { - err := i.lb.Start(ipn.Options{ - UpdatePrefs: &ipn.Prefs{ - ControlURL: i.controlURL, - RouteAll: false, - AllowSingleHosts: true, - WantRunning: true, - Hostname: i.hostname, - }, - AuthKey: i.authKey, - }) - if err != nil { - log.Printf("Start error: %v", err) - } - }() - - go func() { - ln, err := safesocket.Listen("") - if err != nil { - log.Fatalf("safesocket.Listen: %v", err) - } - - err = i.srv.Run(context.Background(), ln) - log.Fatalf("ipnserver.Run exited: %v", err) - }() -} - -func (i *jsIPN) login() { - go i.lb.StartLoginInteractive() -} - -func (i *jsIPN) logout() { - if i.lb.State() == ipn.NoState { - log.Printf("Backend not running") - } - go i.lb.Logout() -} - -func (i *jsIPN) ssh(host, username string, termConfig js.Value) map[string]any { - jsSSHSession := &jsSSHSession{ - jsIPN: i, - host: host, - username: username, - termConfig: termConfig, - } - - go jsSSHSession.Run() - - return map[string]any{ - "close": js.FuncOf(func(this js.Value, args []js.Value) any { - return jsSSHSession.Close() != nil - }), - "resize": js.FuncOf(func(this js.Value, args []js.Value) any { - rows := args[0].Int() - cols := args[1].Int() - return jsSSHSession.Resize(rows, cols) != nil - }), - } -} - -type jsSSHSession struct { - jsIPN *jsIPN - host string - username string - termConfig js.Value - session *ssh.Session - - pendingResizeRows int - pendingResizeCols int -} - -func (s *jsSSHSession) Run() { - writeFn := s.termConfig.Get("writeFn") - writeErrorFn := s.termConfig.Get("writeErrorFn") - setReadFn := s.termConfig.Get("setReadFn") - rows := s.termConfig.Get("rows").Int() - cols := s.termConfig.Get("cols").Int() - timeoutSeconds := 5.0 - if jsTimeoutSeconds := s.termConfig.Get("timeoutSeconds"); jsTimeoutSeconds.Type() == js.TypeNumber { - timeoutSeconds = jsTimeoutSeconds.Float() - } - onConnectionProgress := s.termConfig.Get("onConnectionProgress") - onConnected := s.termConfig.Get("onConnected") - onDone := s.termConfig.Get("onDone") - defer onDone.Invoke() - - writeError := func(label string, err error) { - writeErrorFn.Invoke(fmt.Sprintf("%s Error: %v\r\n", label, err)) - } - reportProgress := func(message string) { - onConnectionProgress.Invoke(message) - } - - ctx, cancel := context.WithTimeout(context.Background(), time.Duration(timeoutSeconds*float64(time.Second))) - defer cancel() - reportProgress(fmt.Sprintf("Connecting to %s…", strings.Split(s.host, ".")[0])) - c, err := s.jsIPN.dialer.UserDial(ctx, "tcp", net.JoinHostPort(s.host, "22")) - if err != nil { - writeError("Dial", err) - return - } - defer c.Close() - - config := &ssh.ClientConfig{ - HostKeyCallback: func(hostname string, remote net.Addr, key ssh.PublicKey) error { - // Host keys are not used with Tailscale SSH, but we can use this - // callback to know that the connection has been established. - reportProgress("SSH connection established…") - return nil - }, - User: s.username, - } - - reportProgress("Starting SSH client…") - sshConn, _, _, err := ssh.NewClientConn(c, s.host, config) - if err != nil { - writeError("SSH Connection", err) - return - } - defer sshConn.Close() - - sshClient := ssh.NewClient(sshConn, nil, nil) - defer sshClient.Close() - - session, err := sshClient.NewSession() - if err != nil { - writeError("SSH Session", err) - return - } - s.session = session - defer session.Close() - - stdin, err := session.StdinPipe() - if err != nil { - writeError("SSH Stdin", err) - return - } - - session.Stdout = termWriter{writeFn} - session.Stderr = termWriter{writeFn} - - setReadFn.Invoke(js.FuncOf(func(this js.Value, args []js.Value) any { - input := args[0].String() - _, err := stdin.Write([]byte(input)) - if err != nil { - writeError("Write Input", err) - } - return nil - })) - - // We might have gotten a resize notification since we started opening the - // session, pick up the latest size. - if s.pendingResizeRows != 0 { - rows = s.pendingResizeRows - } - if s.pendingResizeCols != 0 { - cols = s.pendingResizeCols - } - err = session.RequestPty("xterm", rows, cols, ssh.TerminalModes{}) - - if err != nil { - writeError("Pseudo Terminal", err) - return - } - - err = session.Shell() - if err != nil { - writeError("Shell", err) - return - } - - onConnected.Invoke() - err = session.Wait() - if err != nil { - writeError("Wait", err) - return - } -} - -func (s *jsSSHSession) Close() error { - if s.session == nil { - // We never had a chance to open the session, ignore the close request. - return nil - } - return s.session.Close() -} - -func (s *jsSSHSession) Resize(rows, cols int) error { - if s.session == nil { - s.pendingResizeRows = rows - s.pendingResizeCols = cols - return nil - } - return s.session.WindowChange(rows, cols) -} - -func (i *jsIPN) fetch(url string) js.Value { - return makePromise(func() (any, error) { - c := &http.Client{ - Transport: &http.Transport{ - DialContext: i.dialer.UserDial, - }, - } - res, err := c.Get(url) - if err != nil { - return nil, err - } - - return map[string]any{ - "status": res.StatusCode, - "statusText": res.Status, - "text": js.FuncOf(func(this js.Value, args []js.Value) any { - return makePromise(func() (any, error) { - defer res.Body.Close() - buf := new(bytes.Buffer) - if _, err := buf.ReadFrom(res.Body); err != nil { - return nil, err - } - return buf.String(), nil - }) - }), - // TODO: populate a more complete JS Response object - }, nil - }) -} - -type termWriter struct { - f js.Value -} - -func (w termWriter) Write(p []byte) (n int, err error) { - r := bytes.Replace(p, []byte("\n"), []byte("\n\r"), -1) - w.f.Invoke(string(r)) - return len(p), nil -} - -type jsNetMap struct { - Self jsNetMapSelfNode `json:"self"` - Peers []jsNetMapPeerNode `json:"peers"` - LockedOut bool `json:"lockedOut"` -} - -type jsNetMapNode struct { - Name string `json:"name"` - Addresses []string `json:"addresses"` - MachineKey string `json:"machineKey"` - NodeKey string `json:"nodeKey"` -} - -type jsNetMapSelfNode struct { - jsNetMapNode - MachineStatus string `json:"machineStatus"` -} - -type jsNetMapPeerNode struct { - jsNetMapNode - Online *bool `json:"online,omitempty"` - TailscaleSSHEnabled bool `json:"tailscaleSSHEnabled"` -} - -type jsStateStore struct { - jsStateStorage js.Value -} - -func (s *jsStateStore) ReadState(id ipn.StateKey) ([]byte, error) { - jsValue := s.jsStateStorage.Call("getState", string(id)) - if jsValue.String() == "" { - return nil, ipn.ErrStateNotExist - } - return hex.DecodeString(jsValue.String()) -} - -func (s *jsStateStore) WriteState(id ipn.StateKey, bs []byte) error { - s.jsStateStorage.Call("setState", string(id), hex.EncodeToString(bs)) - return nil -} - -func mapSlice[T any, M any](a []T, f func(T) M) []M { - n := make([]M, len(a)) - for i, e := range a { - n[i] = f(e) - } - return n -} - -func filterSlice[T any](a []T, f func(T) bool) []T { - n := make([]T, 0, len(a)) - for _, e := range a { - if f(e) { - n = append(n, e) - } - } - return n -} - -func generateHostname() string { - tails := words.Tails() - scales := words.Scales() - if rand.Int()%2 == 0 { - // JavaScript - tails = filterSlice(tails, func(s string) bool { return strings.HasPrefix(s, "j") }) - scales = filterSlice(scales, func(s string) bool { return strings.HasPrefix(s, "s") }) - } else { - // WebAssembly - tails = filterSlice(tails, func(s string) bool { return strings.HasPrefix(s, "w") }) - scales = filterSlice(scales, func(s string) bool { return strings.HasPrefix(s, "a") }) - } - - tail := tails[rand.Intn(len(tails))] - scale := scales[rand.Intn(len(scales))] - return fmt.Sprintf("%s-%s", tail, scale) -} - -// makePromise handles the boilerplate of wrapping goroutines with JS promises. -// f is run on a goroutine and its return value is used to resolve the promise -// (or reject it if an error is returned). -func makePromise(f func() (any, error)) js.Value { - handler := js.FuncOf(func(this js.Value, args []js.Value) any { - resolve := args[0] - reject := args[1] - go func() { - if res, err := f(); err == nil { - resolve.Invoke(res) - } else { - reject.Invoke(err.Error()) - } - }() - return nil - }) - - promiseConstructor := js.Global().Get("Promise") - return promiseConstructor.New(handler) -} - -const logPolicyStateKey = "log-policy" - -func getOrCreateLogPolicyConfig(state ipn.StateStore) *logpolicy.Config { - if configBytes, err := state.ReadState(logPolicyStateKey); err == nil { - if config, err := logpolicy.ConfigFromBytes(configBytes); err == nil { - return config - } else { - log.Printf("Could not parse log policy config: %v", err) - } - } else if err != ipn.ErrStateNotExist { - log.Printf("Could not get log policy config from state store: %v", err) - } - config := logpolicy.NewConfig(logtail.CollectionNode) - if err := state.WriteState(logPolicyStateKey, config.ToBytes()); err != nil { - log.Printf("Could not save log policy config to state store: %v", err) - } - return config -} - -// noCORSTransport wraps a RoundTripper and forces the no-cors mode on requests, -// so that we can use it with non-CORS-aware servers. -type noCORSTransport struct { - http.RoundTripper -} - -func (t *noCORSTransport) RoundTrip(req *http.Request) (*http.Response, error) { - req.Header.Set("js.fetch:mode", "no-cors") - resp, err := t.RoundTripper.RoundTrip(req) - if err == nil { - // In no-cors mode no response properties are returned. Populate just - // the status so that callers do not think this was an error. - resp.StatusCode = http.StatusOK - resp.Status = http.StatusText(http.StatusOK) - } - return resp, err -} diff --git a/cmd/tsconnect/yarn.lock b/cmd/tsconnect/yarn.lock deleted file mode 100644 index 663a1244ebf69..0000000000000 --- a/cmd/tsconnect/yarn.lock +++ /dev/null @@ -1,713 +0,0 @@ -# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. -# yarn lockfile v1 - - -"@nodelib/fs.scandir@2.1.5": - version "2.1.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" - integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g== - dependencies: - "@nodelib/fs.stat" "2.0.5" - run-parallel "^1.1.9" - -"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2": - version "2.0.5" - resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b" - integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A== - -"@nodelib/fs.walk@^1.2.3": - version "1.2.8" - resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a" - integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg== - dependencies: - "@nodelib/fs.scandir" "2.1.5" - fastq "^1.6.0" - -"@types/golang-wasm-exec@^1.15.0": - version "1.15.0" - resolved "https://registry.yarnpkg.com/@types/golang-wasm-exec/-/golang-wasm-exec-1.15.0.tgz#d0aafbb2b0dc07eaf45dfb83bfb6cdd5b2b3c55c" - integrity sha512-FrL97mp7WW8LqNinVkzTVKOIQKuYjQqgucnh41+1vRQ+bf1LT8uh++KRf9otZPXsa6H1p8ruIGz1BmCGttOL6Q== - -"@types/node@*": - version "18.6.1" - resolved "https://registry.yarnpkg.com/@types/node/-/node-18.6.1.tgz#828e4785ccca13f44e2fb6852ae0ef11e3e20ba5" - integrity sha512-z+2vB6yDt1fNwKOeGbckpmirO+VBDuQqecXkgeIqDlaOtmKn6hPR/viQ8cxCfqLU4fTlvM3+YjM367TukWdxpg== - -"@types/qrcode@^1.4.2": - version "1.4.2" - resolved "https://registry.yarnpkg.com/@types/qrcode/-/qrcode-1.4.2.tgz#7d7142d6fa9921f195db342ed08b539181546c74" - integrity sha512-7uNT9L4WQTNJejHTSTdaJhfBSCN73xtXaHFyBJ8TSwiLhe4PRuTue7Iph0s2nG9R/ifUaSnGhLUOZavlBEqDWQ== - dependencies: - "@types/node" "*" - -acorn-node@^1.8.2: - version "1.8.2" - resolved "https://registry.yarnpkg.com/acorn-node/-/acorn-node-1.8.2.tgz#114c95d64539e53dede23de8b9d96df7c7ae2af8" - integrity sha512-8mt+fslDufLYntIoPAaIMUe/lrbrehIiwmR3t2k9LljIzoigEPF27eLk2hy8zSGzmR/ogr7zbRKINMo1u0yh5A== - dependencies: - acorn "^7.0.0" - acorn-walk "^7.0.0" - xtend "^4.0.2" - -acorn-walk@^7.0.0: - version "7.2.0" - resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc" - integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA== - -acorn@^7.0.0: - version "7.4.1" - resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa" - integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A== - -ansi-regex@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" - integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== - -ansi-styles@^4.0.0: - version "4.3.0" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" - integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg== - dependencies: - color-convert "^2.0.1" - -anymatch@~3.1.2: - version "3.1.2" - resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" - integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== - dependencies: - normalize-path "^3.0.0" - picomatch "^2.0.4" - -arg@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/arg/-/arg-5.0.2.tgz#c81433cc427c92c4dcf4865142dbca6f15acd59c" - integrity sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg== - -binary-extensions@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" - integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== - -braces@^3.0.2, braces@~3.0.2: - version "3.0.2" - resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" - integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== - dependencies: - fill-range "^7.0.1" - -camelcase-css@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/camelcase-css/-/camelcase-css-2.0.1.tgz#ee978f6947914cc30c6b44741b6ed1df7f043fd5" - integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== - -camelcase@^5.0.0: - version "5.3.1" - resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320" - integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg== - -chokidar@^3.5.3: - version "3.5.3" - resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" - integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== - dependencies: - anymatch "~3.1.2" - braces "~3.0.2" - glob-parent "~5.1.2" - is-binary-path "~2.1.0" - is-glob "~4.0.1" - normalize-path "~3.0.0" - readdirp "~3.6.0" - optionalDependencies: - fsevents "~2.3.2" - -cliui@^6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-6.0.0.tgz#511d702c0c4e41ca156d7d0e96021f23e13225b1" - integrity sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^6.2.0" - -cliui@^7.0.2: - version "7.0.4" - resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f" - integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ== - dependencies: - string-width "^4.2.0" - strip-ansi "^6.0.0" - wrap-ansi "^7.0.0" - -color-convert@^2.0.1: - version "2.0.1" - resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" - integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ== - dependencies: - color-name "~1.1.4" - -color-name@^1.1.4, color-name@~1.1.4: - version "1.1.4" - resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" - integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== - -cssesc@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/cssesc/-/cssesc-3.0.0.tgz#37741919903b868565e1c09ea747445cd18983ee" - integrity sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg== - -decamelize@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290" - integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA= - -defined@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/defined/-/defined-1.0.0.tgz#c98d9bcef75674188e110969151199e39b1fa693" - integrity sha512-Y2caI5+ZwS5c3RiNDJ6u53VhQHv+hHKwhkI1iHvceKUHw9Df6EK2zRLfjejRgMuCuxK7PfSWIMwWecceVvThjQ== - -detective@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/detective/-/detective-5.2.1.tgz#6af01eeda11015acb0e73f933242b70f24f91034" - integrity sha512-v9XE1zRnz1wRtgurGu0Bs8uHKFSTdteYZNbIPFVhUZ39L/S79ppMpdmVOZAnoz1jfEFodc48n6MX483Xo3t1yw== - dependencies: - acorn-node "^1.8.2" - defined "^1.0.0" - minimist "^1.2.6" - -didyoumean@^1.2.2: - version "1.2.2" - resolved "https://registry.yarnpkg.com/didyoumean/-/didyoumean-1.2.2.tgz#989346ffe9e839b4555ecf5666edea0d3e8ad037" - integrity sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw== - -dijkstrajs@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/dijkstrajs/-/dijkstrajs-1.0.2.tgz#2e48c0d3b825462afe75ab4ad5e829c8ece36257" - integrity sha512-QV6PMaHTCNmKSeP6QoXhVTw9snc9VD8MulTT0Bd99Pacp4SS1cjcrYPgBPmibqKVtMJJfqC6XvOXgPMEEPH/fg== - -dlv@^1.1.3: - version "1.1.3" - resolved "https://registry.yarnpkg.com/dlv/-/dlv-1.1.3.tgz#5c198a8a11453596e751494d49874bc7732f2e79" - integrity sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA== - -dts-bundle-generator@^6.12.0: - version "6.12.0" - resolved "https://registry.yarnpkg.com/dts-bundle-generator/-/dts-bundle-generator-6.12.0.tgz#0a221bdce5fdd309a56c8556e645f16ed87ab07d" - integrity sha512-k/QAvuVaLIdyWRUHduDrWBe4j8PcE6TDt06+f32KHbW7/SmUPbX1O23fFtQgKwUyTBkbIjJFOFtNrF97tJcKug== - dependencies: - typescript ">=3.0.1" - yargs "^17.2.1" - -emoji-regex@^8.0.0: - version "8.0.0" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" - integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== - -encode-utf8@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/encode-utf8/-/encode-utf8-1.0.3.tgz#f30fdd31da07fb596f281beb2f6b027851994cda" - integrity sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw== - -escalade@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40" - integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw== - -fast-glob@^3.2.11: - version "3.2.11" - resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.2.11.tgz#a1172ad95ceb8a16e20caa5c5e56480e5129c1d9" - integrity sha512-xrO3+1bxSo3ZVHAnqzyuewYT6aMFHRAd4Kcs92MAonjwQZLsK9d0SF1IyQ3k5PoirxTW0Oe/RqFgMQ6TcNE5Ew== - dependencies: - "@nodelib/fs.stat" "^2.0.2" - "@nodelib/fs.walk" "^1.2.3" - glob-parent "^5.1.2" - merge2 "^1.3.0" - micromatch "^4.0.4" - -fastq@^1.6.0: - version "1.13.0" - resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c" - integrity sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw== - dependencies: - reusify "^1.0.4" - -fill-range@^7.0.1: - version "7.0.1" - resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40" - integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ== - dependencies: - to-regex-range "^5.0.1" - -find-up@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" - integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw== - dependencies: - locate-path "^5.0.0" - path-exists "^4.0.0" - -fsevents@~2.3.2: - version "2.3.2" - resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" - integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== - -function-bind@^1.1.1: - version "1.1.1" - resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" - integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A== - -get-caller-file@^2.0.1, get-caller-file@^2.0.5: - version "2.0.5" - resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e" - integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg== - -glob-parent@^5.1.2, glob-parent@~5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" - integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== - dependencies: - is-glob "^4.0.1" - -glob-parent@^6.0.2: - version "6.0.2" - resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3" - integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A== - dependencies: - is-glob "^4.0.3" - -has@^1.0.3: - version "1.0.3" - resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796" - integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw== - dependencies: - function-bind "^1.1.1" - -is-binary-path@~2.1.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" - integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== - dependencies: - binary-extensions "^2.0.0" - -is-core-module@^2.9.0: - version "2.9.0" - resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.9.0.tgz#e1c34429cd51c6dd9e09e0799e396e27b19a9c69" - integrity sha512-+5FPy5PnwmO3lvfMb0AsoPaBG+5KHUI0wYFXOtYPnVVVspTFUuMZNfNaNVRt3FZadstu2c8x23vykRW/NBoU6A== - dependencies: - has "^1.0.3" - -is-extglob@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" - integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== - -is-fullwidth-code-point@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d" - integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg== - -is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: - version "4.0.3" - resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" - integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== - dependencies: - is-extglob "^2.1.1" - -is-number@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" - integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng== - -lilconfig@^2.0.5: - version "2.0.6" - resolved "https://registry.yarnpkg.com/lilconfig/-/lilconfig-2.0.6.tgz#32a384558bd58af3d4c6e077dd1ad1d397bc69d4" - integrity sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg== - -locate-path@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0" - integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g== - dependencies: - p-locate "^4.1.0" - -merge2@^1.3.0: - version "1.4.1" - resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" - integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg== - -micromatch@^4.0.4: - version "4.0.5" - resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" - integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA== - dependencies: - braces "^3.0.2" - picomatch "^2.3.1" - -minimist@^1.2.6: - version "1.2.6" - resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" - integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== - -nanoid@^3.3.4: - version "3.3.4" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.4.tgz#730b67e3cd09e2deacf03c027c81c9d9dbc5e8ab" - integrity sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw== - -normalize-path@^3.0.0, normalize-path@~3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" - integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== - -object-hash@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/object-hash/-/object-hash-3.0.0.tgz#73f97f753e7baffc0e2cc9d6e079079744ac82e9" - integrity sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw== - -p-limit@^2.2.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1" - integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w== - dependencies: - p-try "^2.0.0" - -p-locate@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07" - integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A== - dependencies: - p-limit "^2.2.0" - -p-try@^2.0.0: - version "2.2.0" - resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" - integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== - -path-exists@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3" - integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w== - -path-parse@^1.0.7: - version "1.0.7" - resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" - integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== - -picocolors@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c" - integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ== - -picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: - version "2.3.1" - resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" - integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== - -pify@^2.3.0: - version "2.3.0" - resolved "https://registry.yarnpkg.com/pify/-/pify-2.3.0.tgz#ed141a6ac043a849ea588498e7dca8b15330e90c" - integrity sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog== - -pngjs@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/pngjs/-/pngjs-5.0.0.tgz#e79dd2b215767fd9c04561c01236df960bce7fbb" - integrity sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw== - -postcss-import@^14.1.0: - version "14.1.0" - resolved "https://registry.yarnpkg.com/postcss-import/-/postcss-import-14.1.0.tgz#a7333ffe32f0b8795303ee9e40215dac922781f0" - integrity sha512-flwI+Vgm4SElObFVPpTIT7SU7R3qk2L7PyduMcokiaVKuWv9d/U+Gm/QAd8NDLuykTWTkcrjOeD2Pp1rMeBTGw== - dependencies: - postcss-value-parser "^4.0.0" - read-cache "^1.0.0" - resolve "^1.1.7" - -postcss-js@^4.0.0: - version "4.0.0" - resolved "https://registry.yarnpkg.com/postcss-js/-/postcss-js-4.0.0.tgz#31db79889531b80dc7bc9b0ad283e418dce0ac00" - integrity sha512-77QESFBwgX4irogGVPgQ5s07vLvFqWr228qZY+w6lW599cRlK/HmnlivnnVUxkjHnCu4J16PDMHcH+e+2HbvTQ== - dependencies: - camelcase-css "^2.0.1" - -postcss-load-config@^3.1.4: - version "3.1.4" - resolved "https://registry.yarnpkg.com/postcss-load-config/-/postcss-load-config-3.1.4.tgz#1ab2571faf84bb078877e1d07905eabe9ebda855" - integrity sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg== - dependencies: - lilconfig "^2.0.5" - yaml "^1.10.2" - -postcss-nested@5.0.6: - version "5.0.6" - resolved "https://registry.yarnpkg.com/postcss-nested/-/postcss-nested-5.0.6.tgz#466343f7fc8d3d46af3e7dba3fcd47d052a945bc" - integrity sha512-rKqm2Fk0KbA8Vt3AdGN0FB9OBOMDVajMG6ZCf/GoHgdxUJ4sBFp0A/uMIRm+MJUdo33YXEtjqIz8u7DAp8B7DA== - dependencies: - postcss-selector-parser "^6.0.6" - -postcss-selector-parser@^6.0.10, postcss-selector-parser@^6.0.6: - version "6.0.10" - resolved "https://registry.yarnpkg.com/postcss-selector-parser/-/postcss-selector-parser-6.0.10.tgz#79b61e2c0d1bfc2602d549e11d0876256f8df88d" - integrity sha512-IQ7TZdoaqbT+LCpShg46jnZVlhWD2w6iQYAcYXfHARZ7X1t/UGhhceQDs5X0cGqKvYlHNOuv7Oa1xmb0oQuA3w== - dependencies: - cssesc "^3.0.0" - util-deprecate "^1.0.2" - -postcss-value-parser@^4.0.0, postcss-value-parser@^4.2.0: - version "4.2.0" - resolved "https://registry.yarnpkg.com/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz#723c09920836ba6d3e5af019f92bc0971c02e514" - integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== - -postcss@^8.4.14: - version "8.4.14" - resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.14.tgz#ee9274d5622b4858c1007a74d76e42e56fd21caf" - integrity sha512-E398TUmfAYFPBSdzgeieK2Y1+1cpdxJx8yXbK/m57nRhKSmk1GB2tO4lbLBtlkfPQTDKfe4Xqv1ASWPpayPEig== - dependencies: - nanoid "^3.3.4" - picocolors "^1.0.0" - source-map-js "^1.0.2" - -preact@^10.10.0: - version "10.10.0" - resolved "https://registry.yarnpkg.com/preact/-/preact-10.10.0.tgz#7434750a24b59dae1957d95dc0aa47a4a8e9a180" - integrity sha512-fszkg1iJJjq68I4lI8ZsmBiaoQiQHbxf1lNq+72EmC/mZOsFF5zn3k1yv9QGoFgIXzgsdSKtYymLJsrJPoamjQ== - -qrcode@^1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/qrcode/-/qrcode-1.5.0.tgz#95abb8a91fdafd86f8190f2836abbfc500c72d1b" - integrity sha512-9MgRpgVc+/+47dFvQeD6U2s0Z92EsKzcHogtum4QB+UNd025WOJSHvn/hjk9xmzj7Stj95CyUAs31mrjxliEsQ== - dependencies: - dijkstrajs "^1.0.1" - encode-utf8 "^1.0.3" - pngjs "^5.0.0" - yargs "^15.3.1" - -queue-microtask@^1.2.2: - version "1.2.3" - resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" - integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== - -quick-lru@^5.1.1: - version "5.1.1" - resolved "https://registry.yarnpkg.com/quick-lru/-/quick-lru-5.1.1.tgz#366493e6b3e42a3a6885e2e99d18f80fb7a8c932" - integrity sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA== - -read-cache@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/read-cache/-/read-cache-1.0.0.tgz#e664ef31161166c9751cdbe8dbcf86b5fb58f774" - integrity sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA== - dependencies: - pify "^2.3.0" - -readdirp@~3.6.0: - version "3.6.0" - resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" - integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== - dependencies: - picomatch "^2.2.1" - -require-directory@^2.1.1: - version "2.1.1" - resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42" - integrity sha1-jGStX9MNqxyXbiNE/+f3kqam30I= - -require-main-filename@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" - integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== - -resolve@^1.1.7, resolve@^1.22.1: - version "1.22.1" - resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" - integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== - dependencies: - is-core-module "^2.9.0" - path-parse "^1.0.7" - supports-preserve-symlinks-flag "^1.0.0" - -reusify@^1.0.4: - version "1.0.4" - resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" - integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== - -run-parallel@^1.1.9: - version "1.2.0" - resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee" - integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA== - dependencies: - queue-microtask "^1.2.2" - -set-blocking@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" - integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= - -source-map-js@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c" - integrity sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw== - -string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: - version "4.2.3" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" - integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== - dependencies: - emoji-regex "^8.0.0" - is-fullwidth-code-point "^3.0.0" - strip-ansi "^6.0.1" - -strip-ansi@^6.0.0, strip-ansi@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" - integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== - dependencies: - ansi-regex "^5.0.1" - -supports-preserve-symlinks-flag@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" - integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w== - -tailwindcss@^3.1.6: - version "3.1.6" - resolved "https://registry.yarnpkg.com/tailwindcss/-/tailwindcss-3.1.6.tgz#bcb719357776c39e6376a8d84e9834b2b19a49f1" - integrity sha512-7skAOY56erZAFQssT1xkpk+kWt2NrO45kORlxFPXUt3CiGsVPhH1smuH5XoDH6sGPXLyBv+zgCKA2HWBsgCytg== - dependencies: - arg "^5.0.2" - chokidar "^3.5.3" - color-name "^1.1.4" - detective "^5.2.1" - didyoumean "^1.2.2" - dlv "^1.1.3" - fast-glob "^3.2.11" - glob-parent "^6.0.2" - is-glob "^4.0.3" - lilconfig "^2.0.5" - normalize-path "^3.0.0" - object-hash "^3.0.0" - picocolors "^1.0.0" - postcss "^8.4.14" - postcss-import "^14.1.0" - postcss-js "^4.0.0" - postcss-load-config "^3.1.4" - postcss-nested "5.0.6" - postcss-selector-parser "^6.0.10" - postcss-value-parser "^4.2.0" - quick-lru "^5.1.1" - resolve "^1.22.1" - -to-regex-range@^5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4" - integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ== - dependencies: - is-number "^7.0.0" - -typescript@>=3.0.1, typescript@^4.7.4: - version "4.7.4" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235" - integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ== - -util-deprecate@^1.0.2: - version "1.0.2" - resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf" - integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw== - -which-module@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/which-module/-/which-module-2.0.0.tgz#d9ef07dce77b9902b8a3a8fa4b31c3e3f7e6e87a" - integrity sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho= - -wrap-ansi@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-6.2.0.tgz#e9393ba07102e6c91a3b221478f0257cd2856e53" - integrity sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -wrap-ansi@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" - integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== - dependencies: - ansi-styles "^4.0.0" - string-width "^4.1.0" - strip-ansi "^6.0.0" - -xtend@^4.0.2: - version "4.0.2" - resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" - integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== - -xterm-addon-fit@^0.7.0: - version "0.7.0" - resolved "https://registry.yarnpkg.com/xterm-addon-fit/-/xterm-addon-fit-0.7.0.tgz#b8ade6d96e63b47443862088f6670b49fb752c6a" - integrity sha512-tQgHGoHqRTgeROPnvmtEJywLKoC/V9eNs4bLLz7iyJr1aW/QFzRwfd3MGiJ6odJd9xEfxcW36/xRU47JkD5NKQ== - -xterm-addon-web-links@^0.8.0: - version "0.8.0" - resolved "https://registry.yarnpkg.com/xterm-addon-web-links/-/xterm-addon-web-links-0.8.0.tgz#2cb1d57129271022569208578b0bf4774e7e6ea9" - integrity sha512-J4tKngmIu20ytX9SEJjAP3UGksah7iALqBtfTwT9ZnmFHVplCumYQsUJfKuS+JwMhjsjH61YXfndenLNvjRrEw== - -xterm@^5.1.0: - version "5.1.0" - resolved "https://registry.yarnpkg.com/xterm/-/xterm-5.1.0.tgz#3e160d60e6801c864b55adf19171c49d2ff2b4fc" - integrity sha512-LovENH4WDzpwynj+OTkLyZgJPeDom9Gra4DMlGAgz6pZhIDCQ+YuO7yfwanY+gVbn/mmZIStNOnVRU/ikQuAEQ== - -y18n@^4.0.0: - version "4.0.3" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf" - integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ== - -y18n@^5.0.5: - version "5.0.8" - resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" - integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== - -yaml@^1.10.2: - version "1.10.2" - resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b" - integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg== - -yargs-parser@^18.1.2: - version "18.1.3" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-18.1.3.tgz#be68c4975c6b2abf469236b0c870362fab09a7b0" - integrity sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ== - dependencies: - camelcase "^5.0.0" - decamelize "^1.2.0" - -yargs-parser@^21.0.0: - version "21.1.1" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-21.1.1.tgz#9096bceebf990d21bb31fa9516e0ede294a77d35" - integrity sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw== - -yargs@^15.3.1: - version "15.4.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-15.4.1.tgz#0d87a16de01aee9d8bec2bfbf74f67851730f4f8" - integrity sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A== - dependencies: - cliui "^6.0.0" - decamelize "^1.2.0" - find-up "^4.1.0" - get-caller-file "^2.0.1" - require-directory "^2.1.1" - require-main-filename "^2.0.0" - set-blocking "^2.0.0" - string-width "^4.2.0" - which-module "^2.0.0" - y18n "^4.0.0" - yargs-parser "^18.1.2" - -yargs@^17.2.1: - version "17.5.1" - resolved "https://registry.yarnpkg.com/yargs/-/yargs-17.5.1.tgz#e109900cab6fcb7fd44b1d8249166feb0b36e58e" - integrity sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA== - dependencies: - cliui "^7.0.2" - escalade "^3.1.1" - get-caller-file "^2.0.5" - require-directory "^2.1.1" - string-width "^4.2.3" - y18n "^5.0.5" - yargs-parser "^21.0.0" From 2a935aa6c5ddcd5e91b80498603da0ff5a8d613a Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Mon, 19 May 2025 21:19:23 +1000 Subject: [PATCH 2/4] Update Makefile --- Makefile | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/Makefile b/Makefile index 4a9acb0b45d7b..8adef020fb40e 100644 --- a/Makefile +++ b/Makefile @@ -33,16 +33,13 @@ build386: ## Build tailscale CLI for linux/386 buildlinuxarm: ## Build tailscale CLI for linux/arm GOOS=linux GOARCH=arm ./tool/go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled -buildwasm: ## Build tailscale CLI for js/wasm - GOOS=js GOARCH=wasm ./tool/go install ./cmd/tsconnect/wasm ./cmd/tailscale/cli - buildlinuxloong64: ## Build tailscale CLI for linux/loong64 GOOS=linux GOARCH=loong64 ./tool/go install tailscale.com/cmd/tailscale tailscale.com/cmd/tailscaled buildmultiarchimage: ## Build (and optionally push) multiarch docker image ./build_docker.sh -check: staticcheck vet depaware buildwindows build386 buildlinuxarm buildwasm ## Perform basic checks and compilation tests +check: staticcheck vet depaware buildwindows build386 buildlinuxarm ## Perform basic checks and compilation tests staticcheck: ## Run staticcheck.io checks ./tool/go run honnef.co/go/tools/cmd/staticcheck -- $$(./tool/go list ./... | grep -v tempfork) From 59b93ac9a0c742cd1e7da52ecc8b116988196aeb Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Mon, 19 May 2025 22:02:02 +1000 Subject: [PATCH 3/4] Update test.yml --- .github/workflows/test.yml | 38 -------------------------------------- 1 file changed, 38 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 2c30fb0d21c2b..b56922c9f459c 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -280,44 +280,6 @@ jobs: GOOS: android GOARCH: arm64 - wasm: # builds tsconnect, which is the only wasm build we support - runs-on: ubuntu-22.04 - steps: - - name: checkout - uses: actions/checkout@v3 - - uses: actions/setup-go@v5 - with: - go-version-file: go.mod - cache: false - - name: Restore Cache - uses: actions/cache@v3 - with: - # Note: unlike the other setups, this is only grabbing the mod download - # cache, rather than the whole mod directory, as the download cache - # contains zips that can be unpacked in parallel faster than they can be - # fetched and extracted by tar - path: | - ~/.cache/go-build - ~/go/pkg/mod/cache - ~\AppData\Local\go-build - # The -2- here should be incremented when the scheme of data to be - # cached changes (e.g. path above changes). - key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }} - restore-keys: | - ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }} - ${{ github.job }}-${{ runner.os }}-go-2- - - name: build tsconnect client - run: ./tool/go build ./cmd/tsconnect/wasm ./cmd/tailscale/cli - env: - GOOS: js - GOARCH: wasm - - name: build tsconnect server - # Note, no GOOS/GOARCH in env on this build step, we're running a build - # tool that handles the build itself. - run: | - ./tool/go run ./cmd/tsconnect --fast-compression build - ./tool/go run ./cmd/tsconnect --fast-compression build-pkg - fuzz: # This target periodically breaks (see TS_FUZZ_CURRENTLY_BROKEN at the top # of the file), so it's more complex than usual: the 'build fuzzers' step From 73657549525d7c3e5cfe70e470af13603a50105a Mon Sep 17 00:00:00 2001 From: Dean Sheather Date: Mon, 19 May 2025 22:03:35 +1000 Subject: [PATCH 4/4] Update test.yml --- .github/workflows/test.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index b56922c9f459c..c00f94a5e8382 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -429,7 +429,6 @@ jobs: - vm - cross - ios - - wasm - fuzz - depaware - go_generate @@ -473,7 +472,6 @@ jobs: - vm - cross - ios - - wasm - fuzz - depaware - go_generate