commit 51cf581361182403ce19627b7468a724c67f3875 Author: William Petit Date: Thu Feb 9 12:16:36 2023 +0100 feat: initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4ab03dd --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/node_modules +/bin +/pkg/app/sdk/client/dist +/.env +/tools \ No newline at end of file diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..769d920 --- /dev/null +++ b/Makefile @@ -0,0 +1,76 @@ +LINT_ARGS ?= --timeout 5m +GITCHLOG_ARGS ?= +SHELL := /bin/bash + +GOTEST_ARGS ?= -short + +ESBUILD_VERSION ?= v0.17.5 + +GIT_VERSION := $(shell git describe --always) + +build: build-edge-cli + +watch: + go run -mod=readonly github.com/cortesi/modd/cmd/modd@latest + +.PHONY: test +test: test-go + +test-go: + go test -v -count=1 $(GOTEST_ARGS) ./... + +lint: + golangci-lint run --enable-all $(LINT_ARGS) + +build-edge-cli: build-sdk + CGO_ENABLED=0 go build \ + -v \ + -o ./bin/cli \ + ./cmd/cli + +install-git-hooks: + git config core.hooksPath .githooks + + +tools/esbuild/bin/esbuild: + mkdir -p tools/esbuild/bin + curl -fsSL https://esbuild.github.io/dl/$(ESBUILD_VERSION) | sh + mv -f esbuild tools/esbuild/bin/esbuild + +build-sdk: pkg/sdk/client/dist/client.js + +.PHONY: pkg/sdk/client/dist/client.js +pkg/sdk/client/dist/client.js: tools/esbuild/bin/esbuild node_modules + mkdir -p pkg/sdk/client/dist + tools/esbuild/bin/esbuild \ + pkg/sdk/client/src/index.ts \ + --bundle \ + --sourcemap \ + --target=es2020 \ + --format=iife \ + --global-name=Arcad \ + --define:global=window \ + --platform=browser \ + --footer:js="Arcad=Arcad.default;" \ + --outfile=pkg/sdk/client/dist/client.js + +node_modules: + npm ci + +gitea-release: tools/gitea-release/bin/gitea-release.sh + GITEA_RELEASE_PROJECT="edge" \ + GITEA_RELEASE_ORG="arcad" \ + GITEA_RELEASE_BASE_URL="https://forge.cadoles.com" \ + GITEA_RELEASE_VERSION="$(GIT_VERSION)" \ + GITEA_RELEASE_NAME="$(GIT_VERSION)" \ + GITEA_RELEASE_COMMITISH_TARGET="$(GIT_VERSION)" \ + GITEA_RELEASE_IS_DRAFT="false" \ + GITEA_RELEASE_IS_PRERELEASE="true" \ + GITEA_RELEASE_BODY="" \ + GITEA_RELEASE_ATTACHMENTS="bin/cli" \ + tools/gitea-release/bin/gitea-release.sh + +tools/gitea-release/bin/gitea-release.sh: + mkdir -p tools/gitea-release/bin + curl --output tools/gitea-release/bin/gitea-release.sh https://forge.cadoles.com/Cadoles/Jenkins/raw/branch/master/resources/com/cadoles/gitea/gitea-release.sh + chmod +x tools/gitea-release/bin/gitea-release.sh \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..3f2c5af --- /dev/null +++ b/README.md @@ -0,0 +1,5 @@ +# `forge.cadoles/arcad/edge` + +## Documentation + +[Voir la documentation](./doc/README.md) \ No newline at end of file diff --git a/cmd/cli/command/app/package.go b/cmd/cli/command/app/package.go new file mode 100644 index 0000000..263d7e6 --- /dev/null +++ b/cmd/cli/command/app/package.go @@ -0,0 +1,149 @@ +package app + +import ( + "archive/zip" + "fmt" + "io" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "forge.cadoles.com/arcad/edge/pkg/app" + "forge.cadoles.com/arcad/edge/pkg/bundle" + "github.com/pkg/errors" + "github.com/urfave/cli/v2" +) + +func PackageCommand() *cli.Command { + return &cli.Command{ + Name: "package", + Usage: "Generate a new app package from given directory", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "directory", + Usage: "use source directory `DIR`", + Aliases: []string{"d"}, + Required: true, + }, + &cli.StringFlag{ + Name: "output-dir", + Aliases: []string{"o"}, + Usage: "use `DIR` as generated package destination", + }, + }, + Action: func(ctx *cli.Context) error { + appDir := ctx.String("directory") + outputDir := ctx.String("output-dir") + + if outputDir == "" { + workdir, err := os.Getwd() + if err != nil { + return errors.Wrap(err, "could not retrieve current working directory") + } + + outputDir = workdir + } + + bundle := bundle.NewDirectoryBundle(appDir) + + manifest, err := app.LoadAppManifest(bundle) + if err != nil { + return errors.Wrap(err, "could not load app manifest") + } + + if err := os.MkdirAll(outputDir, 0o755); err != nil { + return errors.Wrapf(err, "could not create directory ''%s'", outputDir) + } + + archiveName := fmt.Sprintf( + "%s_%s%s", + strings.ToLower(string(manifest.ID)), + string(manifest.Version), + ".zip", + ) + packagePath := filepath.Join(outputDir, archiveName) + + if err := zipDirectory(appDir, packagePath); err != nil { + return errors.Wrapf(err, "could not zip directory ''%s'", appDir) + } + + return nil + }, + } +} + +func zipDirectory(baseDir string, outputFile string) error { + outFile, err := os.Create(outputFile) + if err != nil { + return errors.WithStack(err) + } + + defer func() { + if err := outFile.Close(); err != nil { + panic(errors.WithStack(err)) + } + }() + + w := zip.NewWriter(outFile) + + if err := copyDir(w, baseDir+"/", ""); err != nil { + return errors.WithStack(err) + } + + if err := w.Close(); err != nil { + return errors.WithStack(err) + } + + return nil +} + +func copyDir(writer *zip.Writer, baseDir string, zipBasePath string) error { + files, err := ioutil.ReadDir(baseDir) + if err != nil { + return errors.WithStack(err) + } + + for _, file := range files { + if !file.IsDir() { + srcPath := baseDir + file.Name() + zipPath := zipBasePath + file.Name() + + if err := copyFile(writer, srcPath, zipPath); err != nil { + return errors.WithStack(err) + } + } else if file.IsDir() { + newBase := baseDir + file.Name() + "/" + + if err := copyDir(writer, newBase, zipBasePath+file.Name()+"/"); err != nil { + return errors.WithStack(err) + } + } + } + + return nil +} + +func copyFile(writer *zip.Writer, srcPath string, zipPath string) error { + r, err := os.Open(srcPath) + if err != nil { + return errors.WithStack(err) + } + + defer func() { + if err := r.Close(); err != nil { + panic(errors.WithStack(err)) + } + }() + + f, err := writer.Create(zipPath) + if err != nil { + return errors.WithStack(err) + } + + if _, err = io.Copy(f, r); err != nil { + return errors.WithStack(err) + } + + return nil +} diff --git a/cmd/cli/command/app/root.go b/cmd/cli/command/app/root.go new file mode 100644 index 0000000..cfd7309 --- /dev/null +++ b/cmd/cli/command/app/root.go @@ -0,0 +1,16 @@ +package app + +import ( + "github.com/urfave/cli/v2" +) + +func Root() *cli.Command { + return &cli.Command{ + Name: "app", + Usage: "App related commands", + Subcommands: []*cli.Command{ + RunCommand(), + PackageCommand(), + }, + } +} diff --git a/cmd/cli/command/app/run.go b/cmd/cli/command/app/run.go new file mode 100644 index 0000000..be4def4 --- /dev/null +++ b/cmd/cli/command/app/run.go @@ -0,0 +1,119 @@ +package app + +import ( + "database/sql" + "net/http" + "path/filepath" + + "forge.cadoles.com/arcad/edge/pkg/bus/memory" + appHTTP "forge.cadoles.com/arcad/edge/pkg/http" + "forge.cadoles.com/arcad/edge/pkg/module" + "forge.cadoles.com/arcad/edge/pkg/storage/sqlite" + "gitlab.com/wpetit/goweb/logger" + + "forge.cadoles.com/arcad/edge/pkg/bundle" + "github.com/go-chi/chi/v5" + "github.com/go-chi/chi/v5/middleware" + "github.com/pkg/errors" + "github.com/urfave/cli/v2" + + _ "modernc.org/sqlite" +) + +func RunCommand() *cli.Command { + return &cli.Command{ + Name: "run", + Usage: "Run the specified app bundle", + Flags: []cli.Flag{ + &cli.StringFlag{ + Name: "path", + Usage: "use `PATH` as app bundle (zipped bundle or directory)", + Aliases: []string{"p"}, + Value: ".", + }, + &cli.StringFlag{ + Name: "address", + Usage: "use `ADDRESS` as http server listening address", + Aliases: []string{"a"}, + Value: ":8080", + }, + &cli.StringFlag{ + Name: "log-format", + Usage: "use `LOG-FORMAT` ('json' or 'human')", + Value: "human", + }, + &cli.IntFlag{ + Name: "log-level", + Usage: "use `LOG-LEVEL` (0: debug -> 5: fatal)", + Value: 0, + }, + &cli.StringFlag{ + Name: "storage-dsn", + Usage: "use `DSN` for SQLite storage database", + Value: ":memory:", + }, + }, + Action: func(ctx *cli.Context) error { + address := ctx.String("address") + path := ctx.String("path") + logFormat := ctx.String("log-format") + logLevel := ctx.Int("log-level") + storageDSN := ctx.String("storage-dsn") + + logger.SetFormat(logger.Format(logFormat)) + logger.SetLevel(logger.Level(logLevel)) + + cmdCtx := ctx.Context + + absPath, err := filepath.Abs(path) + if err != nil { + return errors.Wrapf(err, "could not resolve path '%s'", path) + } + + logger.Info(cmdCtx, "opening app bundle", logger.F("path", absPath)) + + bundle, err := bundle.FromPath(path) + if err != nil { + return errors.Wrapf(err, "could not open path '%s' as an app bundle", path) + } + + mux := chi.NewMux() + + mux.Use(middleware.Logger) + + bus := memory.NewBus() + + db, err := sql.Open("sqlite", storageDSN) + if err != nil { + return errors.Wrapf(err, "could not open database with path '%s'", storageDSN) + } + + documentStore := sqlite.NewDocumentStoreWithDB(db) + + handler := appHTTP.NewHandler( + appHTTP.WithBus(bus), + appHTTP.WithServerModules( + module.ConsoleModuleFactory(), + module.LifecycleModuleFactory(bus), + module.NetModuleFactory(bus), + module.RPCModuleFactory(bus), + module.StoreModuleFactory(documentStore), + // module.FileModuleFactory(conf.App.DataDir), + ), + ) + if err := handler.Load(bundle); err != nil { + return errors.Wrap(err, "could not load app bundle") + } + + mux.Handle("/*", handler) + + logger.Info(cmdCtx, "listening", logger.F("address", address)) + + if err := http.ListenAndServe(address, mux); err != nil { + return errors.WithStack(err) + } + + return nil + }, + } +} diff --git a/cmd/cli/command/main.go b/cmd/cli/command/main.go new file mode 100644 index 0000000..d65bd3e --- /dev/null +++ b/cmd/cli/command/main.go @@ -0,0 +1,48 @@ +package command + +import ( + "context" + "fmt" + "os" + "sort" + + "github.com/urfave/cli/v2" +) + +func Main(commands ...*cli.Command) { + ctx := context.Background() + + app := &cli.App{ + Name: "edge-cli", + Usage: "Arcad edge cli", + Commands: commands, + Flags: []cli.Flag{ + &cli.BoolFlag{ + Name: "debug", + EnvVars: []string{"DEBUG"}, + Value: false, + }, + }, + } + + app.ExitErrHandler = func(ctx *cli.Context, err error) { + if err == nil { + return + } + + debug := ctx.Bool("debug") + + if !debug { + fmt.Printf("[ERROR] %v\n", err) + } else { + fmt.Printf("%+v", err) + } + } + + sort.Sort(cli.FlagsByName(app.Flags)) + sort.Sort(cli.CommandsByName(app.Commands)) + + if err := app.RunContext(ctx, os.Args); err != nil { + os.Exit(1) + } +} diff --git a/cmd/cli/main.go b/cmd/cli/main.go new file mode 100644 index 0000000..34c47a0 --- /dev/null +++ b/cmd/cli/main.go @@ -0,0 +1,10 @@ +package main + +import ( + "forge.cadoles.com/arcad/edge/cmd/cli/command" + "forge.cadoles.com/arcad/edge/cmd/cli/command/app" +) + +func main() { + command.Main(app.Root()) +} diff --git a/doc/README.md b/doc/README.md new file mode 100644 index 0000000..e533b51 --- /dev/null +++ b/doc/README.md @@ -0,0 +1,15 @@ +# Documentation + +## Edge Apps + +Une **Edge App** est une application capable de s'exécuter sur la plateforme Arcad. À contrario d'une **Cloud App**, celles ci peuvent continuer à fonctioner dans le mode "déconnecté" d'un serveur "edge" appairé avec la plateforme. + +### Référence + +- [API Client](./apps/client-api/README.md) +- [API Serveur](./apps/server-api/README.md) + +### Tutoriels + +- [Créer sa première application](./apps/my-first-app.md) +- [Empaqueter une application](./apps/package-app.md) \ No newline at end of file diff --git a/doc/apps/app-manifest.md b/doc/apps/app-manifest.md new file mode 100644 index 0000000..1cf2b38 --- /dev/null +++ b/doc/apps/app-manifest.md @@ -0,0 +1,3 @@ +# Le manifeste d'application (`arcad.yml`) + +> `TODO` \ No newline at end of file diff --git a/doc/apps/client-api/README.md b/doc/apps/client-api/README.md new file mode 100644 index 0000000..140335d --- /dev/null +++ b/doc/apps/client-api/README.md @@ -0,0 +1,3 @@ +# API Client + +> `TODO` \ No newline at end of file diff --git a/doc/apps/my-first-app.md b/doc/apps/my-first-app.md new file mode 100644 index 0000000..a6fa829 --- /dev/null +++ b/doc/apps/my-first-app.md @@ -0,0 +1,74 @@ +# Créer ma première application + +## 1. Télécharger le CLI + +1. Se rendre à l'adresse https://forge.cadoles.com/arcad/edge/releases + +2. Télécharger la dernière version du binaire `cli` disponible dans la page. + +## 2. Créer l'arborescence de son application + +L'arborescence d'une "Edge App" doit correspondre à une structure prédéfinie. + +```bash +my-app + |-> arcad.yml # Le fichier "manifeste" décrivant votre application + |-> public # Répertoire contenant tous les fichiers accessibles publiquement + |-> server + |-> main.js # Le point d'entrée pour le code "serveur" de votre application +``` + +## 3. Compléter le fichier `arcad.yml` + +Ce fichier est le manifeste de votre application. Il permet au serveur Arcad d'identifier celle ci et d'afficher ses informations dans la page d'accueil. + +```yaml +--- +# L'identifiant de votre application. Il doit être globalement unique. +# Un identifiant du type nom de domaine inversé est en général conseillé (ex: tld.mycompany.myapp) +id: tld.mycompany.myapp + +# Le titre de votre application. +title: My App + +# Les mots-clés associés à votre applications. +tags: ["chat"] + +# La description de votre application. +# Vous pouvez utiliser la syntaxe Markdown pour la mettre en forme. +description: |> + A simple demo application +``` + +## 4. Créer la page d'accueil + +Créer le fichier `my-app/public/index.html`: + +```html + + + My App + + +

My App

+ + +``` + +## 5. Créer le fichier `server/main.js` + +Ce fichier est nécessaire, même vide. + +```bash +touch server/main.js +``` + +## 6. Exécuter votre application en local + +Utiliser le CLI téléchargé préalablement pour lancer votre nouvelle application localement. + +```bash +cli app run -p ./chemin/vers/app +``` + +La page d'accueil devrait être accessible à l'adresse http://localhost:8080. \ No newline at end of file diff --git a/doc/apps/package-app.md b/doc/apps/package-app.md new file mode 100644 index 0000000..771f3e1 --- /dev/null +++ b/doc/apps/package-app.md @@ -0,0 +1,3 @@ +# Empaqueter une application + +> `TODO` \ No newline at end of file diff --git a/doc/apps/server-api/README.md b/doc/apps/server-api/README.md new file mode 100644 index 0000000..851b0da --- /dev/null +++ b/doc/apps/server-api/README.md @@ -0,0 +1,3 @@ +# API Serveur + +> `TODO` \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f49056c --- /dev/null +++ b/go.mod @@ -0,0 +1,55 @@ +module forge.cadoles.com/arcad/edge + +go 1.19 + +require modernc.org/sqlite v1.20.4 + +require ( + cdr.dev/slog v1.4.0 // indirect + github.com/alecthomas/chroma v0.7.0 // indirect + github.com/cpuguy83/go-md2man/v2 v2.0.2 // indirect + github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 // indirect + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/dlclark/regexp2 v1.7.0 // indirect + github.com/dop251/goja v0.0.0-20230203172422-5460598cfa32 // indirect + github.com/dop251/goja_nodejs v0.0.0-20230207183254-2229640ea097 // indirect + github.com/dustin/go-humanize v1.0.0 // indirect + github.com/fatih/color v1.7.0 // indirect + github.com/go-chi/chi/v5 v5.0.8 // indirect + github.com/go-sourcemap/sourcemap v2.1.3+incompatible // indirect + github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e // indirect + github.com/google/uuid v1.3.0 // indirect + github.com/gorilla/websocket v1.4.2 // indirect + github.com/igm/sockjs-go/v3 v3.0.2 // indirect + github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect + github.com/mattn/go-colorable v0.1.4 // indirect + github.com/mattn/go-isatty v0.0.16 // indirect + github.com/mitchellh/mapstructure v1.5.0 // indirect + github.com/oklog/ulid/v2 v2.1.0 // indirect + github.com/orcaman/concurrent-map v1.0.0 // indirect + github.com/pkg/errors v0.9.1 // indirect + github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 // indirect + github.com/russross/blackfriday/v2 v2.1.0 // indirect + github.com/spf13/afero v1.9.3 // indirect + github.com/urfave/cli/v2 v2.24.3 // indirect + github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 // indirect + gitlab.com/wpetit/goweb v0.0.0-20230206085656-dec695f0e2e9 // indirect + go.opencensus.io v0.22.5 // indirect + golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa // indirect + golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect + golang.org/x/sys v0.3.0 // indirect + golang.org/x/term v0.3.0 // indirect + golang.org/x/text v0.5.0 // indirect + golang.org/x/tools v0.1.12 // indirect + golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + lukechampine.com/uint128 v1.2.0 // indirect + modernc.org/cc/v3 v3.40.0 // indirect + modernc.org/ccgo/v3 v3.16.13 // indirect + modernc.org/libc v1.22.2 // indirect + modernc.org/mathutil v1.5.0 // indirect + modernc.org/memory v1.4.0 // indirect + modernc.org/opt v0.1.3 // indirect + modernc.org/strutil v1.1.3 // indirect + modernc.org/token v1.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..2d949c6 --- /dev/null +++ b/go.sum @@ -0,0 +1,598 @@ +cdr.dev/slog v1.4.0 h1:tLXQJ/hZ5Q051h0MBHSd2Ha8xzdXj7CjtzmG/8dUvUk= +cdr.dev/slog v1.4.0/go.mod h1:C5OL99WyuOK8YHZdYY57dAPN1jK2WJlCdq2VP6xeQns= +cloud.google.com/go v0.26.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.34.0/go.mod h1:aQUYkXzVsufM+DwF1aE+0xfcU+56JwCaLick0ClmMTw= +cloud.google.com/go v0.38.0/go.mod h1:990N+gfupTy94rShfmMCWGDn0LpTmnzTp2qbd1dvSRU= +cloud.google.com/go v0.44.1/go.mod h1:iSa0KzasP4Uvy3f1mN/7PiObzGgflwredwwASm/v6AU= +cloud.google.com/go v0.44.2/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.44.3/go.mod h1:60680Gw3Yr4ikxnPRS/oxxkBccT6SA1yMk63TGekxKY= +cloud.google.com/go v0.45.1/go.mod h1:RpBamKRgapWJb87xiFSdk4g1CME7QZg3uwTez+TSTjc= +cloud.google.com/go v0.46.3/go.mod h1:a6bKKbmY7er1mI7TEI4lsAkts/mkhTSZK8w33B4RAg0= +cloud.google.com/go v0.49.0/go.mod h1:hGvAdzcWNbyuxS3nWhD7H2cIJxjRRTRLQVB0bdputVY= +cloud.google.com/go v0.50.0/go.mod h1:r9sluTvynVuxRIOHXQEHMFffphuXHOMZMycpNR5e6To= +cloud.google.com/go v0.52.0/go.mod h1:pXajvRH/6o3+F9jDHZWQ5PbGhn+o8w9qiu/CffaVdO4= +cloud.google.com/go v0.53.0/go.mod h1:fp/UouUEsRkN6ryDKNW/Upv/JBKnv6WDthjR6+vze6M= +cloud.google.com/go v0.54.0/go.mod h1:1rq2OEkV3YMf6n/9ZvGWI3GWw0VoqH/1x2nd8Is/bPc= +cloud.google.com/go v0.56.0/go.mod h1:jr7tqZxxKOVYizybht9+26Z/gUq7tiRzu+ACVAMbKVk= +cloud.google.com/go v0.57.0/go.mod h1:oXiQ6Rzq3RAkkY7N6t3TcE6jE+CIBBbA36lwQ1JyzZs= +cloud.google.com/go v0.62.0/go.mod h1:jmCYTdRCQuc1PHIIJ/maLInMho30T/Y0M4hTdTShOYc= +cloud.google.com/go v0.65.0/go.mod h1:O5N8zS7uWy9vkA9vayVHs65eM1ubvY4h553ofrNHObY= +cloud.google.com/go v0.72.0/go.mod h1:M+5Vjvlc2wnp6tjzE102Dw08nGShTscUx2nZMufOKPI= +cloud.google.com/go v0.74.0/go.mod h1:VV1xSbzvo+9QJOxLDaJfTjx5e+MePCpCWwvftOeQmWk= +cloud.google.com/go v0.75.0/go.mod h1:VGuuCn7PG0dwsd5XPVm2Mm3wlh3EL55/79EKB6hlPTY= +cloud.google.com/go/bigquery v1.0.1/go.mod h1:i/xbL2UlR5RvWAURpBYZTtm/cXjCha9lbfbpx4poX+o= +cloud.google.com/go/bigquery v1.3.0/go.mod h1:PjpwJnslEMmckchkHFfq+HTD2DmtT67aNFKH1/VBDHE= +cloud.google.com/go/bigquery v1.4.0/go.mod h1:S8dzgnTigyfTmLBfrtrhyYhwRxG72rYxvftPBK2Dvzc= +cloud.google.com/go/bigquery v1.5.0/go.mod h1:snEHRnqQbz117VIFhE8bmtwIDY80NLUZUMb4Nv6dBIg= +cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4gLoIoXIAPc= +cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= +cloud.google.com/go/datastore v1.0.0/go.mod h1:LXYbyblFSglQ5pkeyhO+Qmw7ukd3C+pD7TKLgZqpHYE= +cloud.google.com/go/datastore v1.1.0/go.mod h1:umbIZjpQpHh4hmRpGhH4tLFup+FVzqBi1b3c64qFpCk= +cloud.google.com/go/pubsub v1.0.1/go.mod h1:R0Gpsv3s54REJCy4fxDixWD93lHJMoZTyQ2kNxGRt3I= +cloud.google.com/go/pubsub v1.1.0/go.mod h1:EwwdRX2sKPjnvnqCa270oGRyludottCI76h+R3AArQw= +cloud.google.com/go/pubsub v1.2.0/go.mod h1:jhfEVHT8odbXTkndysNHCcx0awwzvfOlguIAii9o8iA= +cloud.google.com/go/pubsub v1.3.1/go.mod h1:i+ucay31+CNRpDW4Lu78I4xXG+O1r/MAHgjpRVR+TSU= +cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= +cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= +cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= +cloud.google.com/go/storage v1.8.0/go.mod h1:Wv1Oy7z6Yz3DshWRJFhqM/UCfaWIRTdp0RXyy7KQOVs= +cloud.google.com/go/storage v1.10.0/go.mod h1:FLPqc6j+Ki4BU591ie1oL6qBQGu2Bl/tZ9ullr3+Kg0= +cloud.google.com/go/storage v1.14.0/go.mod h1:GrKmX003DSIwi9o29oFT7YDnHYwZoctc3fOKtUw0Xmo= +dmitri.shuralyov.com/gpu/mtl v0.0.0-20190408044501-666a987793e9/go.mod h1:H6x//7gZCb22OMCxBHrMx7a5I7Hp++hsVxbQ4BYO7hU= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/BurntSushi/xgb v0.0.0-20160522181843-27f122750802/go.mod h1:IVnqGOEym/WlBOVXweHU+Q+/VP0lqqI8lqeDx9IjBqo= +github.com/GeertJohan/go.incremental v1.0.0/go.mod h1:6fAjUhbVuX1KcMD3c8TEgVUqmo4seqhv0i0kdATSkM0= +github.com/GeertJohan/go.rice v1.0.0/go.mod h1:eH6gbSOAUv07dQuZVnBmoDP8mgsM1rtixis4Tib9if0= +github.com/akavel/rsrc v0.8.0/go.mod h1:uLoCtb9J+EyAqh+26kdrTgmzRBFPGOolLWKpdxkKq+c= +github.com/alecthomas/assert v0.0.0-20170929043011-405dbfeb8e38/go.mod h1:r7bzyVFMNntcxPZXK3/+KdruV1H5KSlyVY0gc+NgInI= +github.com/alecthomas/chroma v0.7.0 h1:z+0HgTUmkpRDRz0SRSdMaqOLfJV4F+N1FPDZUZIDUzw= +github.com/alecthomas/chroma v0.7.0/go.mod h1:1U/PfCsTALWWYHDnsIQkxEBM0+6LLe0v8+RSVMOwxeY= +github.com/alecthomas/colour v0.0.0-20160524082231-60882d9e2721/go.mod h1:QO9JBoKquHd+jz9nshCh40fOfO+JzsoXy8qTHF68zU0= +github.com/alecthomas/kong v0.1.17-0.20190424132513-439c674f7ae0/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= +github.com/alecthomas/kong v0.2.1-0.20190708041108-0548c6b1afae/go.mod h1:+inYUSluD+p4L8KdviBSgzcqEjUQOfC5fQDRFuc36lI= +github.com/alecthomas/kong-hcl v0.1.8-0.20190615233001-b21fea9723c8/go.mod h1:MRgZdU3vrFd05IQ89AxUZ0aYdF39BYoNFa324SodPCA= +github.com/alecthomas/repr v0.0.0-20180818092828-117648cd9897/go.mod h1:xTS7Pm1pD1mvyM075QCDSRqH6qRLXylzS24ZTpRiSzQ= +github.com/census-instrumentation/opencensus-proto v0.2.1/go.mod h1:f6KPmirojxKA12rnyqOA5BBL4O983OfeGPqjHWSTneU= +github.com/chzyer/logex v1.1.10/go.mod h1:+Ywpsq7O8HXn0nuIou7OrIPyXbp3wmkHB+jjWRnGsAI= +github.com/chzyer/readline v0.0.0-20180603132655-2972be24d48e/go.mod h1:nSuG5e5PlCu98SY8svDHJxuZscDgtXS6KTTbou5AhLI= +github.com/chzyer/test v0.0.0-20180213035817-a1ea475d72b1/go.mod h1:Q3SI9o4m/ZMnBNeIyt5eFwwo7qiLfzFZmjNmxjkiQlU= +github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= +github.com/cncf/udpa/go v0.0.0-20200629203442-efcf912fb354/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cncf/udpa/go v0.0.0-20201120205902-5459f2c99403/go.mod h1:WmhPx2Nbnhtbo57+VJT5O0JRkEi1Wbu0z5j0R8u5Hbk= +github.com/cpuguy83/go-md2man/v2 v2.0.2 h1:p1EgwI/C7NhT0JmVkwCD2ZBK8j4aeHQX2pMHHBfMQ6w= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/daaku/go.zipexe v1.0.0/go.mod h1:z8IiR6TsVLEYKwXAoE/I+8ys/sDkgTzSL0CLnGVd57E= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964 h1:y5HC9v93H5EPKqaS1UYVg1uYah5Xf51mBfIoWehClUQ= +github.com/danwakefield/fnmatch v0.0.0-20160403171240-cbb64ac3d964/go.mod h1:Xd9hchkHSWYkEqJwUGisez3G1QY8Ryz0sdWrLPMGjLk= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/dlclark/regexp2 v1.1.6/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.2.0 h1:8sAhBGEM0dRWogWqWyQeIJnxjWO6oIjl8FKqREDsGfk= +github.com/dlclark/regexp2 v1.2.0/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.4.1-0.20201116162257-a2a8dda75c91/go.mod h1:2pZnwuY/m+8K6iRw6wQdMtk+rH5tNGR1i55kozfMjCc= +github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo= +github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dop251/goja v0.0.0-20211022113120-dc8c55024d06/go.mod h1:R9ET47fwRVRPZnOGvHxxhuZcbrMCuiqOz3Rlrh4KSnk= +github.com/dop251/goja v0.0.0-20221118162653-d4bf6fde1b86/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs= +github.com/dop251/goja v0.0.0-20230203172422-5460598cfa32 h1:audXtK7nV3y4W9ckAxRBE+eQV5Bljf5Non4NTa9kLVE= +github.com/dop251/goja v0.0.0-20230203172422-5460598cfa32/go.mod h1:yRkwfj0CBpOGre+TwBsqPV0IH0Pk73e4PXJOeNDboGs= +github.com/dop251/goja_nodejs v0.0.0-20210225215109-d91c329300e7/go.mod h1:hn7BA7c8pLvoGndExHudxTDKZ84Pyvv+90pbBjbTz0Y= +github.com/dop251/goja_nodejs v0.0.0-20211022123610-8dd9abb0616d/go.mod h1:DngW8aVqWbuLRMHItjPUyqdj+HWPvnQe8V8y1nDpIbM= +github.com/dop251/goja_nodejs v0.0.0-20230207183254-2229640ea097 h1:WsLyDk8yHsVT1puf/32883ZxEb6Pgqd19AlQH9mxVK0= +github.com/dop251/goja_nodejs v0.0.0-20230207183254-2229640ea097/go.mod h1:0tlktQL7yHfYEtjcRGi/eiOkbDR5XF7gyFFvbC5//E0= +github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo= +github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk= +github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.1-0.20191026205805-5f8ba28d4473/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= +github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= +github.com/envoyproxy/go-control-plane v0.9.7/go.mod h1:cwu0lG7PUMfa9snN8LXBig5ynNVH9qI8YYLbd1fK2po= +github.com/envoyproxy/go-control-plane v0.9.9-0.20201210154907-fd9021fe5dad/go.mod h1:cXg6YxExXjJnVBQHBLXeUAgxn2UodCpnH306RInaBQk= +github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= +github.com/fatih/color v1.7.0 h1:DkWD4oS2D8LGGgTQ6IvwJJXSL5Vp2ffcQg58nFV38Ys= +github.com/fatih/color v1.7.0/go.mod h1:Zm6kSWBoL9eyXnKyktHP6abPY2pDugNf5KwzbycvMj4= +github.com/go-chi/chi v4.0.2+incompatible/go.mod h1:eB3wogJHnLi3x/kFX2A+IbTBlXxmMeXJVKy9tTv1XzQ= +github.com/go-chi/chi/v5 v5.0.8 h1:lD+NLqFcAi1ovnVZpsnObHGW4xb4J8lNmoYVfECH1Y0= +github.com/go-chi/chi/v5 v5.0.8/go.mod h1:DslCQbL2OYiznFReuXYUmQ2hGd1aDpCnlMNITLSKoi8= +github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20191125211704-12ad95a8df72/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-gl/glfw/v3.3/glfw v0.0.0-20200222043503-6f7a984d4dc4/go.mod h1:tQ2UAYgL5IevRw8kRxooKSPJfGvJ9fJQFa0TUsXzTg8= +github.com/go-playground/locales v0.12.1/go.mod h1:IUMDtCfWo/w/mtMfIE/IG2K+Ey3ygWanZIBtBW0W2TM= +github.com/go-playground/universal-translator v0.16.0/go.mod h1:1AnU7NaIRDWWzGEKwgtJRd2xk99HeFyHw3yid4rvQIY= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible h1:W1iEw64niKVGogNgBN3ePyLFfuisuzeidWPMPWmECqU= +github.com/go-sourcemap/sourcemap v2.1.3+incompatible/go.mod h1:F8jJfvm2KbVjc5NqelyYJmf/v5J0dwNLS2mL4sNA1Jg= +github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q= +github.com/golang/groupcache v0.0.0-20190702054246-869f871628b6/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191027212112-611e8accdfc9/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20191227052852-215e87163ea7/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e h1:1r7pUrabqp18hOBcwBwiTsbnFeTZHV9eER/QT5JVZxY= +github.com/golang/groupcache v0.0.0-20200121045136-8c9f03a8e57e/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc= +github.com/golang/mock v1.1.1/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.2.0/go.mod h1:oTYuIxOrZwtPieC+H1uAHpcLFnEyAGVDL/k47Jfbm0A= +github.com/golang/mock v1.3.1/go.mod h1:sBzyDLLjw3U8JLTeZvSv8jJB+tU5PVekmnlKIyFUx0Y= +github.com/golang/mock v1.4.0/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.1/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.3/go.mod h1:UOMv5ysSaYNkG+OFQykRIcU/QvvxJf3p21QfJ2Bt3cw= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.1/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.2/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.4/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= +github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.1/go.mod h1:U8fpvMrcmy5pZrNK1lt4xCsGvpyWQ/VVv6QDs8UjoX8= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/protobuf v1.4.3/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/google/btree v0.0.0-20180813153112-4030bb1f1f0c/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/btree v1.0.0/go.mod h1:lNA+9X1NB3Zf8V7Ke586lFgjr2dZNuvo3lPJSGZ5JPQ= +github.com/google/go-cmp v0.2.0/go.mod h1:oXzfMopK8JAjlY9xF4vHSVASa0yLyX7SntLO5aqRK0M= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.2-0.20191216170541-340f1ebe299e/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.4.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.1/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.4/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/martian v2.1.0+incompatible/go.mod h1:9I4somxYTbIHy5NJKHRl3wXiIaQGbYVAs8BPL6v8lEs= +github.com/google/martian/v3 v3.0.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/martian/v3 v3.1.0/go.mod h1:y5Zk1BBys9G+gd6Jrk0W3cC1+ELVxBWuIGO+w/tUAp0= +github.com/google/pprof v0.0.0-20181206194817-3ea8567a2e57/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20190515194954-54271f7e092f/go.mod h1:zfwlbNMJ+OItoe0UupaVj+oy1omPYYDuagoSzA8v9mc= +github.com/google/pprof v0.0.0-20191218002539-d4f498aebedc/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200212024743-f11f1df84d12/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200229191704-1ebb73c60ed3/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200430221834-fc25d7d30c6d/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20200708004538-1a94d8640e99/go.mod h1:ZgVRPoUq/hfqzAqh7sHMqb3I9Rq5C59dIz2SbBwJ4eM= +github.com/google/pprof v0.0.0-20201023163331-3e6fc7fc9c4c/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201203190320-1bf35d6f28c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/pprof v0.0.0-20201218002935-b9804c9f04c2/go.mod h1:kpwsk12EmLew5upagYY7GY0pfYCcupk39gWOCRROcvE= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= +github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= +github.com/googleapis/google-cloud-go-testing v0.0.0-20200911160855-bcd43fbb19e8/go.mod h1:dvDLG8qkwmyD9a/MJJN3XJcT3xFxOKAvTZGvuZmac9g= +github.com/gorilla/csrf v1.6.0/go.mod h1:7tSf8kmjNYr7IWDCYhd3U8Ck34iQ/Yw5CJu7bAkHEGI= +github.com/gorilla/handlers v1.4.1/go.mod h1:Qkdc/uu4tH4g6mTK6auzZ766c4CA0Ng8+o/OAirnOIQ= +github.com/gorilla/mux v1.7.3/go.mod h1:1lud6UwP+6orDFRuTfBEV8e9/aOM/c4fVVCaMa2zaAs= +github.com/gorilla/securecookie v1.1.1/go.mod h1:ra0sb63/xPlUeL+yeDciTfxMRAA+MP+HVt/4epWDjd4= +github.com/gorilla/sessions v1.2.0/go.mod h1:dk2InVEVJ0sfLlnXv9EAgkf6ecYs/i80K/zI+bUmuGM= +github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0Ufc= +github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= +github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8= +github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ= +github.com/ianlancetaylor/demangle v0.0.0-20181102032728-5e5cf60278f6/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/ianlancetaylor/demangle v0.0.0-20200824232613-28f6c0f3b639/go.mod h1:aSSvb/t6k1mPoxDqO4vJh6VOCGPwU4O0C2/Eqndh1Sc= +github.com/igm/sockjs-go/v3 v3.0.2 h1:2m0k53w0DBiGozeQUIEPR6snZFmpFpYvVsGnfLPNXbE= +github.com/igm/sockjs-go/v3 v3.0.2/go.mod h1:UqchsOjeagIBFHvd+RZpLaVRbCwGilEC08EDHsD1jYE= +github.com/jessevdk/go-flags v1.4.0/go.mod h1:4FA24M0QyGHXBuZZK/XkWh8h0e1EYbRYJSGM75WSRxI= +github.com/jstemmer/go-junit-report v0.0.0-20190106144839-af01ea7f8024/go.mod h1:6v2b51hI/fHJwM22ozAgKL4VKDeJcHhJFhtBdhmNjmU= +github.com/jstemmer/go-junit-report v0.9.1/go.mod h1:Brl9GWCQeLvo8nXZwPNNblvFj/XSXhF0NWZEnDohbsk= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/kr/fs v0.1.0/go.mod h1:FFnZGqtBN9Gxj7eW1uZ42v5BccTP0vu6NEaFoC2HwRg= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.1.0/go.mod h1:+cyI34gQWZcE1eQU7NVgKkkzdXDQHr1dBMtdAPozLkw= +github.com/mattn/go-colorable v0.0.9/go.mod h1:9vuHe8Xs5qXnSaW/c/ABM9alt+Vo+STaOChaDxuIBZU= +github.com/mattn/go-colorable v0.1.4 h1:snbPLB8fVfU9iwbbo30TPtbLRzwWu6aJS6Xh4eaaviA= +github.com/mattn/go-colorable v0.1.4/go.mod h1:U0ppj6V5qS13XJ6of8GYAs25YV2eR4EVcfRqFIhoBtE= +github.com/mattn/go-isatty v0.0.4/go.mod h1:M+lRXTBqGeGNdLjl/ufCoiOlB5xdOkqRJdNxMWT7Zi4= +github.com/mattn/go-isatty v0.0.8/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.11 h1:FxPOTFNqGkuDUGi3H/qkUbQO4ZiBa2brKq5r0l8TGeM= +github.com/mattn/go-isatty v0.0.11/go.mod h1:PhnuNfih5lzO57/f3n+odYbM4JtupLOxQOAqxQCu2WE= +github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= +github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/mitchellh/mapstructure v1.1.2/go.mod h1:FVVH3fgwuzCH5S8UJGiWEs2h04kUh9fWfEaFds41c1Y= +github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY= +github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo= +github.com/nkovacs/streamquote v0.0.0-20170412213628-49af9bddb229/go.mod h1:0aYXnNPJ8l7uZxf45rWW1a/uME32OF0rhiYGNQ2oF2E= +github.com/oklog/ulid/v2 v2.1.0 h1:+9lhoxAP56we25tyYETBBY1YLA2SaoLvUFgrP2miPJU= +github.com/oklog/ulid/v2 v2.1.0/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= +github.com/orcaman/concurrent-map v1.0.0 h1:I/2A2XPCb4IuQWcQhBhSwGfiuybl/J0ev9HDbW65HOY= +github.com/orcaman/concurrent-map v1.0.0/go.mod h1:Lu3tH6HLW3feq74c2GC+jIMS/K2CFcDWnWD9XkenwhI= +github.com/oxtoacart/bpool v0.0.0-20190530202638-03653db5a59c/go.mod h1:X07ZCGwUbLaax7L0S3Tw4hpejzu63ZrrQiUe6W0hcy0= +github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= +github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/sftp v1.13.1/go.mod h1:3HaPG6Dq1ILlpPZRO0HVMrsydcdLt6HRDccSgb87qRg= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0 h1:OdAsTTz6OkFY5QxjkYwrChwuRruF69c169dPK26NUlk= +github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/sergi/go-diff v1.0.0/go.mod h1:0CfEIISq7TuYL3j771MWULgwwjU+GofnZX9QAmXWZgo= +github.com/spf13/afero v1.9.3 h1:41FoI0fD7OR7mGcKE/aOiLkGreyf8ifIOQmJANWogMk= +github.com/spf13/afero v1.9.3/go.mod h1:iUV7ddyEEZPO5gA3zD4fJt6iStLlL+Lg4m2cihcDf8Y= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/urfave/cli/v2 v2.24.3 h1:7Q1w8VN8yE0MJEHP06bv89PjYsN4IHWED2s1v/Zlfm0= +github.com/urfave/cli/v2 v2.24.3/go.mod h1:GHupkWPMM0M/sj1a2b4wUrWBPzazNrIjouW6fmdJLxc= +github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= +github.com/valyala/fasttemplate v1.0.1/go.mod h1:UQGH1tvbgY+Nz5t2n7tXsz52dQxojPUpymEIMZ47gx8= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673 h1:bAn7/zixMGCfxrRTfdpNzjtPYqr8smhKouy9mxVdGPU= +github.com/xrash/smetrics v0.0.0-20201216005158-039620a65673/go.mod h1:N3UwUGtsrSj3ccvlPHLoLsHnpR27oXr4ZE984MbSER8= +github.com/yuin/goldmark v1.1.25/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.27/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.1.32/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +gitlab.com/wpetit/goweb v0.0.0-20230206085656-dec695f0e2e9 h1:6JlkcdjYVQglPWYuemK2MoZAtRE4vFx85zLXflGIyI8= +gitlab.com/wpetit/goweb v0.0.0-20230206085656-dec695f0e2e9/go.mod h1:3sus4zjoUv1GB7eDLL60QaPkUnXJCWBpjvbe0jWifeY= +go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= +go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= +go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.3/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.4/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= +go.opencensus.io v0.22.5 h1:dntmOdLpSpHlVqbW5Eay97DelsZHe+55D+xC6i0dDS0= +go.opencensus.io v0.22.5/go.mod h1:5pWMHQbX5EPX2/62yrJeAkowc+lfs/XD7Uxpq3pI6kk= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191206172530-e9b2fee46413/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20210421170649-83a5a9bb288b/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa h1:idItI2DDfCokpg0N51B2VtiLdJ4vAuXC9fnCb2gACo4= +golang.org/x/crypto v0.0.0-20211108221036-ceb1ce70b4fa/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= +golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= +golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= +golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY= +golang.org/x/exp v0.0.0-20191129062945-2f5052295587/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20191227195350-da58074b4299/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200119233911-0405dc783f0a/go.mod h1:2RIsYlXP63K8oxa1u096TMicItID8zy7Y6sNkU49FU4= +golang.org/x/exp v0.0.0-20200207192155-f17229e696bd/go.mod h1:J/WKrq2StrnmMY6+EHIKF9dgMWnmCNThgcyBT1FY9mM= +golang.org/x/exp v0.0.0-20200224162631-6cc2880d07d6/go.mod h1:3jZMyOhIsHpP37uCMkUooju7aAi5cS1Q23tOzKc+0MU= +golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js= +golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= +golang.org/x/lint v0.0.0-20190301231843-5614ed5bae6f/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= +golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190409202823-959b441ac422/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190909230951-414d861bb4ac/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/lint v0.0.0-20191125180803-fdd1cda4f05f/go.mod h1:5qLYkcX4OjUUV8bRuDixDT3tpyyb+LUpUlRWLxfhWrs= +golang.org/x/lint v0.0.0-20200130185559-910be7a94367/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20200302205851-738671d3881b/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/lint v0.0.0-20201208152925-83fdc39ff7b5/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mobile v0.0.0-20190312151609-d3739f865fa6/go.mod h1:z+o9i4GpDbdi3rU15maQ/Ox0txvL9dWGYEHz965HBQE= +golang.org/x/mobile v0.0.0-20190719004257-d2bd2a29d028/go.mod h1:E/iHnbuqvinMTCcRqshq8CkpyQDoeVncDDYHnLhea+o= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.0/go.mod h1:0QHyrYULN0/3qlju5TqG8bIK38QM8yzMo5ekMj3DlcY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.1.1-0.20191107180719-034126e5016b/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.2.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 h1:6zppjxzCulZykYSLyVDYbneBfbaBIQPYMevg0bEwv2s= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190501004415-9ce7a6920f09/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190503192946-f4e77d36d62c/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190603091049-60506f45cf65/go.mod h1:HSz+uSET+XFnRR8LxR5pz3Of3rY3CfYBVs4xY44aLks= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190628185345-da137c7871d7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190724013045-ca1201d0de80/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20191209160850-c0dbc17a3553/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200114155413-6afb5195e5aa/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200202094626-16171245cfb2/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200222125558-5a598a2470a0/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200301022130-244492dfa37a/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200324143707-d3edc9973b7e/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200501053045-e0ff5e5a1de5/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200506145744-7e3656a0809f/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200513185701-a91f0712d120/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200520182314-0ba52f642ac2/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20200625001655-4c5254603344/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200707034311-ab3426394381/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20200822124328-c89045814202/go.mod h1:/O7V0waA8r7cgGh81Ro3o1hOxt32SMVPicZroKQ2sZA= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201031054903-ff519b6c9102/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20201209123823-ac852fbbde11/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20201224014010-6772e930b67b/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.4.0/go.mod h1:MBQ8lrhLObU/6UmLb4fmbmk5OcyYmqtbGd/9yIeKjEE= +golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= +golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20191202225959-858c2ad4c8b6/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200107190931-bf48bf16ab8d/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= +golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201109201403-9fd604954f58/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20201208152858-08078c50e5b5/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/oauth2 v0.0.0-20210218202405-ba52d332ba99/go.mod h1:KelEdhl1UZF7XfJ4dDtk6s++YSgaE7mD/BuKKDLBl4A= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181108010431-42b317875d0f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190227155943-e225da77a7e6/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200317015054-43a5402ce75a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20200625203802-6e8e738ad208/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201207232520-09787c993a3a/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20180830151530-49385e6e1522/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20181128092732-4ed8d59d0b35/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190502145724-3ef323f4f1fd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190606165138-5da285871e9c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190624142023-c5567b49c5d0/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190726091711-fc99dfbffb4e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191001151750-bb3f8db39f24/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191204072324-ce4227a45e2e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191210023423-ac6580df4449/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191228213918-04cbcbbfeed8/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200113162924-86b910548bc1/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200122134326-e047566fdf82/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200202164722-d101bd2416d5/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200212091648-12a6c2dcc1e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200331124033-c3d80250170d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200501052902-10377860bb8e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200511232937-7e40ca221e25/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200515095857-1151b9dac4a9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200523222454-059865788121/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200803210538-64077c9b5642/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200905004654-be1d3432aa8f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201201145000-ef89a241ccb3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210104204734-6f8348627aad/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210225134936-a50acf3fe073/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423185535-09eb48e85fd7/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1 h1:SrN+KX8Art/Sf4HNj6Zcz06G7VEz+7w9tdXTPOZ7+l4= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.3.0 h1:w8ZOecv6NaNa/zC8944JTU3vz4u6Lagfk4RPQxv92NQ= +golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1 h1:v+OssWQX+hTHEmOBgwxdZxK4zHq3yOs8F9J7mk0PY8E= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.3.0 h1:qoo4akIqOcDME5bhc/NgxUdovd6BSS2uMsVjB56q1xI= +golang.org/x/term v0.3.0/go.mod h1:q750SLmJuPmVoN1blW3UFBPREJfb1KmY3vwxfr+nFDA= +golang.org/x/text v0.0.0-20170915032832-14c0d48ead0c/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.1-0.20180807135948-17ff2d5776d2/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4 h1:0YWbFKbhXG/wIiuHDSKpS0Iy7FSA+u45VtBMfQcFTTc= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.5.0 h1:OLmvp0KP+FVG99Ct/qFiL/Fhk4zp4QQnZ7b2U+5piUM= +golang.org/x/text v0.5.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190114222345-bf090417da8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190226205152-f727befe758c/go.mod h1:9Yl7xja0Znq3iFh3HoIrodX9oNMXvdceNzlUR8zjMvY= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312151545-0bb0c0a6e846/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190312170243-e65039ee4138/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190506145303-2d16b83fe98c/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190524140312-2c0ae7006135/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190606124116-d0a3d012864b/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190628153133-6cdbf07be9d0/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190816200558-6889da9d5479/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20190911174233-4f2ddba30aff/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191012152004-8de300cfc20a/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191113191852-77e3bb0ad9e7/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191115202509-3a792d9c32b2/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191125144606-a911d9008d1f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191130070609-6e064ea0cf2d/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191216173652-a0e659d51361/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20191227053925-7b8e75db28f4/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200117161641-43d50277825c/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200122220014-bf1340f18c4a/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200204074204-1cc6d1ef6c74/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200207183749-b753a1ba74fa/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200212150539-ea181f53ac56/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200224181240-023911ca70b2/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200227222343-706bc42d1f0d/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20200304193943-95d2e580d8eb/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200312045724-11d5b4c81c7d/go.mod h1:o4KQGtdN14AW+yjsvvwRTJJuXz8XRtIHtEnmAXLyFUw= +golang.org/x/tools v0.0.0-20200331025713-a30bf2db82d4/go.mod h1:Sl4aGygMT6LrqrWclx+PTx3U+LnKx/seiNR+3G19Ar8= +golang.org/x/tools v0.0.0-20200501065659-ab2804fb9c9d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200512131952-2bc93b1c0c88/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200515010526-7d3b6ebf133d/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200618134242-20370b0cb4b2/go.mod h1:EkVYQZoAsY45+roYkvgYkIh4xh/qjgUK9TdY2XT94GE= +golang.org/x/tools v0.0.0-20200729194436-6467de6f59a7/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200804011535-6c149bb5ef0d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200825202427-b303f430e36d/go.mod h1:njjCfa9FT2d7l9Bc6FUM5FLjQPp3cFF28FI3qnDFljA= +golang.org/x/tools v0.0.0-20200904185747-39188db58858/go.mod h1:Cj7w3i3Rnn0Xh82ur9kSqwfTHTeVxaDqrfMjpcNT6bE= +golang.org/x/tools v0.0.0-20201110124207-079ba7bd75cd/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201201161351-ac6f37ff4c2a/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20201208233053-a543418bbed2/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210105154028-b0ab187a4818/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.0.0-20210108195828-e2f9c7f1fc8e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.12 h1:VveCTK38A2rkS8ZqFY25HIDFscX5X9OoEhJd3quQmXU= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/api v0.4.0/go.mod h1:8k5glujaEP+g9n7WNsDg8QP6cUVNI86fCNMcbazEtwE= +google.golang.org/api v0.7.0/go.mod h1:WtwebWUNSVBH/HAw79HIFXZNqEvBhG+Ra+ax0hx3E3M= +google.golang.org/api v0.8.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.9.0/go.mod h1:o4eAsZoiT+ibD93RtjEohWalFOjRDx6CVaqeizhEnKg= +google.golang.org/api v0.13.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.14.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.15.0/go.mod h1:iLdEw5Ide6rF15KTC1Kkl0iskquN2gFfn9o9XIsbkAI= +google.golang.org/api v0.17.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.18.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.19.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.20.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.22.0/go.mod h1:BwFmGc8tA3vsd7r/7kR8DY7iEEGSU04BFxCo5jP/sfE= +google.golang.org/api v0.24.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.28.0/go.mod h1:lIXQywCXRcnZPGlsd8NbLnOjtAoL6em04bJ9+z0MncE= +google.golang.org/api v0.29.0/go.mod h1:Lcubydp8VUV7KeIHD9z2Bys/sm/vGKnG1UHuDBSrHWM= +google.golang.org/api v0.30.0/go.mod h1:QGmEvQ87FHZNiUVJkT14jQNYJ4ZJjdRF23ZXz5138Fc= +google.golang.org/api v0.35.0/go.mod h1:/XrVsuzM0rZmrsbjJutiuftIzeuTQcEeaYcSk/mQ1dg= +google.golang.org/api v0.36.0/go.mod h1:+z5ficQTmoYpPn8LCUNVpK5I7hwkpjbcgqA7I34qYtE= +google.golang.org/api v0.40.0/go.mod h1:fYKFpnQN0DsDSKRVRcQSDQNtqWPfM9i+zNPxepjRCQ8= +google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= +google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= +google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= +google.golang.org/appengine v1.6.5/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.6/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/appengine v1.6.7/go.mod h1:8WjMMxjGQR8xUklV/ARdw2HLXBOI7O7uCIDZVag1xfc= +google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= +google.golang.org/genproto v0.0.0-20190307195333-5fe7a883aa19/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190418145605-e7d98fc518a7/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190425155659-357c62f0e4bb/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190502173448-54afdca5d873/go.mod h1:VzzqZJRnGkLBvHegQrXjBqPurQTc5/KpmUdxsrq26oE= +google.golang.org/genproto v0.0.0-20190801165951-fa694d86fc64/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= +google.golang.org/genproto v0.0.0-20190911173649-1774047e7e51/go.mod h1:IbNlFCBrqXvoKpeg0TB2l7cyZUmoaFKYIwrEpbDKLA8= +google.golang.org/genproto v0.0.0-20191108220845-16a3f7862a1a/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191115194625-c23dd37a84c9/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191216164720-4f79533eabd1/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20191230161307-f3c370f40bfb/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200115191322-ca5a22157cba/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200122232147-0452cf42e150/go.mod h1:n3cpQtvxv34hfy77yVDNjmbRyujviMdxYliBSkLhpCc= +google.golang.org/genproto v0.0.0-20200204135345-fa8e72b47b90/go.mod h1:GmwEX6Z4W5gMy59cAlVYjN9JhxgbQH6Gn+gFDQe2lzA= +google.golang.org/genproto v0.0.0-20200212174721-66ed5ce911ce/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200224152610-e50cd9704f63/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200228133532-8c2c7df3a383/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200305110556-506484158171/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200312145019-da6875a35672/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200331122359-1ee6d9798940/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200430143042-b979b6f78d84/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200511104702-f5ebc3bea380/go.mod h1:55QSHmfGQM9UVYDPBsyGGes0y52j32PQ3BqQfXhyH3c= +google.golang.org/genproto v0.0.0-20200515170657-fc4c6c6a6587/go.mod h1:YsZOwe1myG/8QRHRsmBRE1LrgQY60beZKjly0O1fX9U= +google.golang.org/genproto v0.0.0-20200526211855-cb27e3aa2013/go.mod h1:NbSheEEYHJ7i3ixzK3sjbqSGDJWnxyFXZblF3eUsNvo= +google.golang.org/genproto v0.0.0-20200618031413-b414f8b61790/go.mod h1:jDfRM7FcilCzHH/e9qn6dsT145K34l5v+OpcnNgKAAA= +google.golang.org/genproto v0.0.0-20200729003335-053ba62fc06f/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200804131852-c06518451d9c/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200825200019-8632dd797987/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20200904004341-0bd0a958aa1d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201109203340-2640f1f9cdfb/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201201144952-b05cb90ed32e/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201210142538-e3217bee35cc/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20201214200347-8c77b98c765d/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210108203827-ffc7fda8c3d7/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/genproto v0.0.0-20210226172003-ab064af71705/go.mod h1:FWY/as6DDZQgahTzZj3fqbO1CbirC29ZNUFHwi0/+no= +google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= +google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= +google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= +google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= +google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= +google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.27.1/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= +google.golang.org/grpc v1.28.0/go.mod h1:rpkK4SK4GF4Ach/+MFLZUBavHOvF2JJB5uozKKal+60= +google.golang.org/grpc v1.29.1/go.mod h1:itym6AZVZYACWQqET3MqgPpjcuV5QH3BxFS3IjizoKk= +google.golang.org/grpc v1.30.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.31.1/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= +google.golang.org/grpc v1.33.2/go.mod h1:JMHMWHQWaTccqQQlmk3MJZS+GWXOdAesneDmEnv2fbc= +google.golang.org/grpc v1.34.0/go.mod h1:WotjhfgOW/POjDeRt8vscBtXq+2VjORFy659qA51WJ8= +google.golang.org/grpc v1.35.0/go.mod h1:qjiiYl8FncCW8feJPdyg3v6XW24KsRHe+dy9BAGRRjU= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.23.1-0.20200526195155-81db48ad09cc/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +google.golang.org/protobuf v1.24.0/go.mod h1:r/3tXBNzIEhYS9I1OUVjXDlt8tc493IdKGjtUeSXeh4= +google.golang.org/protobuf v1.25.0/go.mod h1:9JNX74DMeImyA3h4bdi1ymwjUzf21/xIlbajtzgsN7c= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/go-playground/assert.v1 v1.2.1/go.mod h1:9RXL0bg/zibRAgZUYszZSwO/z8Y/a8bDuhia5mkpMnE= +gopkg.in/go-playground/validator.v9 v9.29.1/go.mod h1:+c9/zcJMFNgbLvly1L1V+PpxWdVbfP1avr/N00E2vyQ= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190106161140-3f1c8253044a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +honnef.co/go/tools v0.0.1-2020.1.3/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +honnef.co/go/tools v0.0.1-2020.1.4/go.mod h1:X/FiERA/W4tHapMX5mGpAtMSVEeEUOyHaw9vFzvIQ3k= +lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI= +lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw= +modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0= +modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw= +modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY= +modernc.org/libc v1.22.2 h1:4U7v51GyhlWqQmwCHj28Rdq2Yzwk55ovjFrdPjs8Hb0= +modernc.org/libc v1.22.2/go.mod h1:uvQavJ1pZ0hIoC/jfqNoMLURIMhKzINIWypNM17puug= +modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ= +modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E= +modernc.org/memory v1.4.0 h1:crykUfNSnMAXaOJnnxcSzbUGMqkLWjklJKkBK2nwZwk= +modernc.org/memory v1.4.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU= +modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/sqlite v1.20.4 h1:J8+m2trkN+KKoE7jglyHYYYiaq5xmz2HoHJIiBlRzbE= +modernc.org/sqlite v1.20.4/go.mod h1:zKcGyrICaxNTMEHSr1HQ2GUraP0j+845GYw37+EyT6A= +modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY= +modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw= +modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg= +modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= +rsc.io/quote/v3 v3.1.0/go.mod h1:yEA65RcK8LyAZtP9Kv3t0HmxON59tX3rD+tICJqUlj0= +rsc.io/sampler v1.3.0/go.mod h1:T1hPZKmBbMNahiBKFy5HrXp6adAjACjK9JXDnKaTXpA= diff --git a/misc/client-sdk-testsuite/.gitignore b/misc/client-sdk-testsuite/.gitignore new file mode 100644 index 0000000..b2d59d1 --- /dev/null +++ b/misc/client-sdk-testsuite/.gitignore @@ -0,0 +1,2 @@ +/node_modules +/dist \ No newline at end of file diff --git a/misc/client-sdk-testsuite/Makefile b/misc/client-sdk-testsuite/Makefile new file mode 100644 index 0000000..5bd250c --- /dev/null +++ b/misc/client-sdk-testsuite/Makefile @@ -0,0 +1,15 @@ +dist: node_modules + mkdir -p dist + cp -rf src/* dist/ + mkdir -p dist/public/vendor + cp -f node_modules/mocha/mocha.js dist/public/vendor/mocha.js + cp -f node_modules/mocha/mocha.css dist/public/vendor/mocha.css + cp -f node_modules/chai/chai.js dist/public/vendor/chai.js + +node_modules: + npm ci + +clean: + rm -rf dist node_modules + +.PHONY: dist \ No newline at end of file diff --git a/misc/client-sdk-testsuite/README.md b/misc/client-sdk-testsuite/README.md new file mode 100644 index 0000000..e5e9579 --- /dev/null +++ b/misc/client-sdk-testsuite/README.md @@ -0,0 +1 @@ +# Client SDK Test suite \ No newline at end of file diff --git a/misc/client-sdk-testsuite/package-lock.json b/misc/client-sdk-testsuite/package-lock.json new file mode 100644 index 0000000..4d7ac7c --- /dev/null +++ b/misc/client-sdk-testsuite/package-lock.json @@ -0,0 +1,2559 @@ +{ + "name": "client-sdk-testsuite", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "client-sdk-testsuite", + "version": "0.0.0", + "license": "AGPL-3.0", + "dependencies": { + "chai": "^4.2.0", + "mocha": "^6.2.2" + } + }, + "node_modules/ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==", + "engines": { + "node": ">=6" + } + }, + "node_modules/ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/array.prototype.reduce": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", + "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "engines": { + "node": "*" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "engines": { + "node": ">=6" + } + }, + "node_modules/chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==", + "engines": { + "node": "*" + } + }, + "node_modules/cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "dependencies": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "node_modules/es-abstract": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-abstract/node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, + "node_modules/es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "dependencies": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "dependencies": { + "locate-path": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/flat": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "dependencies": { + "is-buffer": "~2.0.3" + }, + "bin": { + "flat": "cli.js" + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "node_modules/function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==", + "engines": { + "node": "*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + } + }, + "node_modules/globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "dependencies": { + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==", + "engines": { + "node": ">=4.x" + } + }, + "node_modules/has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "bin": { + "he": "bin/he" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/internal-slot": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "dependencies": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "engines": { + "node": ">=4" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", + "engines": { + "node": ">=4" + } + }, + "node_modules/is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "dependencies": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "dependencies": { + "chalk": "^2.0.1" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "dependencies": { + "get-func-name": "^2.0.0" + } + }, + "node_modules/minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", + "dependencies": { + "minimist": "^1.2.5" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mocha": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", + "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", + "dependencies": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.4", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + }, + "bin": { + "_mocha": "bin/_mocha", + "mocha": "bin/mocha" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node_modules/node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "dependencies": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "dependencies": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.getownpropertydescriptors": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz", + "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==", + "dependencies": { + "array.prototype.reduce": "^1.0.5", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "dependencies": { + "p-limit": "^2.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==", + "engines": { + "node": ">=4" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "engines": { + "node": "*" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "node_modules/safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "dependencies": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "dependencies": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "which": "bin/which" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==" + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "dependencies": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrap-ansi/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "node_modules/yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "dependencies": { + "cliui": "^5.0.0", + "find-up": "^3.0.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": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + } + }, + "node_modules/yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "node_modules/yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "dependencies": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==", + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "dependencies": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "dependencies": { + "ansi-regex": "^4.1.0" + }, + "engines": { + "node": ">=6" + } + } + }, + "dependencies": { + "ansi-colors": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz", + "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==" + }, + "ansi-regex": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", + "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==" + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "array.prototype.reduce": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/array.prototype.reduce/-/array.prototype.reduce-1.0.5.tgz", + "integrity": "sha512-kDdugMl7id9COE8R7MHF5jWk7Dqt/fs4Pv+JXoICnYwqpjjjbUurz6w5fT5IG6brLdJhv6/VoHB0H7oyIBXd+Q==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4", + "es-array-method-boxes-properly": "^1.0.0", + "is-string": "^1.0.7" + } + }, + "assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==" + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "browser-stdout": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz", + "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==" + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==" + }, + "chai": { + "version": "4.3.7", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.3.7.tgz", + "integrity": "sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==", + "requires": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.2", + "deep-eql": "^4.1.2", + "get-func-name": "^2.0.0", + "loupe": "^2.3.1", + "pathval": "^1.1.1", + "type-detect": "^4.0.5" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "check-error": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", + "integrity": "sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==" + }, + "cliui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz", + "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==", + "requires": { + "string-width": "^3.1.0", + "strip-ansi": "^5.2.0", + "wrap-ansi": "^5.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==" + }, + "deep-eql": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.3.tgz", + "integrity": "sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==", + "requires": { + "type-detect": "^4.0.0" + } + }, + "define-properties": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz", + "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==", + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "diff": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==" + }, + "emoji-regex": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz", + "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==" + }, + "es-abstract": { + "version": "1.21.1", + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.21.1.tgz", + "integrity": "sha512-QudMsPOz86xYz/1dG1OuGBKOELjCh99IIWHLzy5znUB6j8xG2yMA7bfTV86VSqKF+Y/H08vQPR+9jyXpuC6hfg==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "es-set-tostringtag": "^2.0.1", + "es-to-primitive": "^1.2.1", + "function-bind": "^1.1.1", + "function.prototype.name": "^1.1.5", + "get-intrinsic": "^1.1.3", + "get-symbol-description": "^1.0.0", + "globalthis": "^1.0.3", + "gopd": "^1.0.1", + "has": "^1.0.3", + "has-property-descriptors": "^1.0.0", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "internal-slot": "^1.0.4", + "is-array-buffer": "^3.0.1", + "is-callable": "^1.2.7", + "is-negative-zero": "^2.0.2", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "is-string": "^1.0.7", + "is-typed-array": "^1.1.10", + "is-weakref": "^1.0.2", + "object-inspect": "^1.12.2", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.4.3", + "safe-regex-test": "^1.0.0", + "string.prototype.trimend": "^1.0.6", + "string.prototype.trimstart": "^1.0.6", + "typed-array-length": "^1.0.4", + "unbox-primitive": "^1.0.2", + "which-typed-array": "^1.1.9" + }, + "dependencies": { + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + } + } + }, + "es-array-method-boxes-properly": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-array-method-boxes-properly/-/es-array-method-boxes-properly-1.0.0.tgz", + "integrity": "sha512-wd6JXUmyHmt8T5a2xreUwKcGPq6f1f+WwIJkijUqiGcJz1qqnZgP6XIK+QyIWU5lT7imeNxUll48bziG+TSYcA==" + }, + "es-set-tostringtag": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz", + "integrity": "sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==", + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "has-tostringtag": "^1.0.0" + } + }, + "es-to-primitive": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz", + "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==", + "requires": { + "is-callable": "^1.1.4", + "is-date-object": "^1.0.1", + "is-symbol": "^1.0.2" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==" + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==" + }, + "find-up": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz", + "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==", + "requires": { + "locate-path": "^3.0.0" + } + }, + "flat": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.1.tgz", + "integrity": "sha512-FmTtBsHskrU6FJ2VxCnsDb84wu9zhmO3cUX2kGFb5tuwhfXxGciiT0oRY+cck35QmG+NmGh5eLz6lLCpWTqwpA==", + "requires": { + "is-buffer": "~2.0.3" + } + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "requires": { + "is-callable": "^1.1.3" + } + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==" + }, + "function.prototype.name": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz", + "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "es-abstract": "^1.19.0", + "functions-have-names": "^1.2.2" + } + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==" + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==" + }, + "get-func-name": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz", + "integrity": "sha512-Hm0ixYtaSZ/V7C8FJrtZIuBBI+iSgL+1Aq82zSu8VQNB4S3Gk8e7Qs3VwBDJAhmRZcFqkl3tQu36g/Foh5I5ig==" + }, + "get-intrinsic": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.0.tgz", + "integrity": "sha512-L049y6nFOuom5wGyRc3/gdTLO94dySVKRACj1RmJZBQXlbTMhtNIgkWkUHq+jYmZvKf14EW1EoJnnjbmoHij0Q==", + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.3" + } + }, + "get-symbol-description": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz", + "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "glob": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", + "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globalthis": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.3.tgz", + "integrity": "sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==", + "requires": { + "define-properties": "^1.1.3" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "growl": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz", + "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==" + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==" + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==" + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "internal-slot": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.4.tgz", + "integrity": "sha512-tA8URYccNzMo94s5MQZgH8NB/XTa6HsOo0MLfXTKKEnHVVdegzaQoFZ7Jp44bdvLvY2waT5dc+j5ICEswhi7UQ==", + "requires": { + "get-intrinsic": "^1.1.3", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-array-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.1.tgz", + "integrity": "sha512-ASfLknmY8Xa2XtB4wmbz13Wu202baeA18cJBCeCy0wXUHZF0IPyVEXqKEcd+t2fNSLLL1vC6k7lxZEojNbISXQ==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-typed-array": "^1.1.10" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-buffer": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.5.tgz", + "integrity": "sha512-i2R6zNFDwgEHJyQUtJEk0XFi1i0dPFn/oqjK3/vPCcDeJvW5NQ83V8QbicfF1SupOaB0h8ntgBC2YiE7dfyctQ==" + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==" + }, + "is-negative-zero": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz", + "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==" + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-weakref": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz", + "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==", + "requires": { + "call-bind": "^1.0.2" + } + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "js-yaml": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz", + "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==", + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "locate-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz", + "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==", + "requires": { + "p-locate": "^3.0.0", + "path-exists": "^3.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "log-symbols": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz", + "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==", + "requires": { + "chalk": "^2.0.1" + } + }, + "loupe": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.6.tgz", + "integrity": "sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==", + "requires": { + "get-func-name": "^2.0.0" + } + }, + "minimatch": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", + "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.7.tgz", + "integrity": "sha512-bzfL1YUZsP41gmu/qjrEk0Q6i2ix/cVeAhbCbqH9u3zYutS1cLg00qhrD0M2MVdCcx4Sc0UpP2eBWo9rotpq6g==" + }, + "mkdirp": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.4.tgz", + "integrity": "sha512-iG9AK/dJLtJ0XNgTuDbSyNS3zECqDlAhnQW4CsNxBG3LQJBbHmRX1egw39DmtOdCAqY+dKXV+sgPgilNWUKMVw==", + "requires": { + "minimist": "^1.2.5" + } + }, + "mocha": { + "version": "6.2.3", + "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.2.3.tgz", + "integrity": "sha512-0R/3FvjIGH3eEuG17ccFPk117XL2rWxatr81a57D+r/x2uTYZRbdZ4oVidEUMh2W2TJDa7MdAb12Lm2/qrKajg==", + "requires": { + "ansi-colors": "3.2.3", + "browser-stdout": "1.3.1", + "debug": "3.2.6", + "diff": "3.5.0", + "escape-string-regexp": "1.0.5", + "find-up": "3.0.0", + "glob": "7.1.3", + "growl": "1.10.5", + "he": "1.2.0", + "js-yaml": "3.13.1", + "log-symbols": "2.2.0", + "minimatch": "3.0.4", + "mkdirp": "0.5.4", + "ms": "2.1.1", + "node-environment-flags": "1.0.5", + "object.assign": "4.1.0", + "strip-json-comments": "2.0.1", + "supports-color": "6.0.0", + "which": "1.3.1", + "wide-align": "1.1.3", + "yargs": "13.3.2", + "yargs-parser": "13.1.2", + "yargs-unparser": "1.6.0" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==" + }, + "node-environment-flags": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz", + "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==", + "requires": { + "object.getownpropertydescriptors": "^2.0.3", + "semver": "^5.7.0" + } + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==" + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.5.tgz", + "integrity": "sha512-yDNzckpM6ntyQiGTik1fKV1DcVDRS+w8bvpWNCBanvH5LfRX9O8WTHqQzG4RZwRAM4I0oU7TV11Lj5v0g20ibw==", + "requires": { + "array.prototype.reduce": "^1.0.5", + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "path-exists": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz", + "integrity": "sha512-bpC7GYwiDYQ4wYLe+FA8lhRjhQCMcQGuSgGGqDkg/QerRWw9CmGRT0iSOVRSZJ29NMLZgIzqaljJ63oaL4NIJQ==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==" + }, + "regexp.prototype.flags": { + "version": "1.4.3", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz", + "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3", + "functions-have-names": "^1.2.2" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==" + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==" + }, + "safe-regex-test": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz", + "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==", + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "is-regex": "^1.1.4" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==" + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==" + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "string.prototype.trimend": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz", + "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "string.prototype.trimstart": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz", + "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==", + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "es-abstract": "^1.20.4" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==" + }, + "supports-color": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz", + "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==", + "requires": { + "has-flag": "^3.0.0" + } + }, + "type-detect": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", + "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==" + }, + "typed-array-length": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.4.tgz", + "integrity": "sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==", + "requires": { + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "is-typed-array": "^1.1.9" + } + }, + "unbox-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz", + "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==", + "requires": { + "call-bind": "^1.0.2", + "has-bigints": "^1.0.2", + "has-symbols": "^1.0.3", + "which-boxed-primitive": "^1.0.2" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-module": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz", + "integrity": "sha512-B+enWhmw6cjfVC7kS8Pj9pCrKSc5txArRyaYGe088shv/FGWH+0Rjx/xPgtsWfsUtS27FkP697E4DDhgrgoc0Q==" + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "requires": { + "string-width": "^1.0.2 || 2" + } + }, + "wrap-ansi": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz", + "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==", + "requires": { + "ansi-styles": "^3.2.0", + "string-width": "^3.0.0", + "strip-ansi": "^5.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==" + }, + "yargs": { + "version": "13.3.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.3.2.tgz", + "integrity": "sha512-AX3Zw5iPruN5ie6xGRIDgqkT+ZhnRlZMLMHAs8tg7nRruy2Nb+i5o9bwghAogtM08q1dpr2LVoS8KSTMYpWXUw==", + "requires": { + "cliui": "^5.0.0", + "find-up": "^3.0.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": "^3.0.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^13.1.2" + }, + "dependencies": { + "ansi-regex": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz", + "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==" + }, + "string-width": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz", + "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==", + "requires": { + "emoji-regex": "^7.0.1", + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^5.1.0" + } + }, + "strip-ansi": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz", + "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==", + "requires": { + "ansi-regex": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.2.tgz", + "integrity": "sha512-3lbsNRf/j+A4QuSZfDRA7HRSfWrzO0YjqTJd5kjAq37Zep1CEgaYmrH9Q3GwPiB9cHyd1Y1UwggGhJGoxipbzg==", + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yargs-unparser": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.6.0.tgz", + "integrity": "sha512-W9tKgmSn0DpSatfri0nx52Joq5hVXgeLiqR/5G0sZNDoLZFOr/xjBUDcShCOGNsBnEMNo1KAMBkTej1Hm62HTw==", + "requires": { + "flat": "^4.1.0", + "lodash": "^4.17.15", + "yargs": "^13.3.0" + } + } + } +} diff --git a/misc/client-sdk-testsuite/package.json b/misc/client-sdk-testsuite/package.json new file mode 100644 index 0000000..ac9f418 --- /dev/null +++ b/misc/client-sdk-testsuite/package.json @@ -0,0 +1,16 @@ +{ + "name": "client-sdk-testsuite", + "version": "0.0.0", + "description": "Client SDK Test suite", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "repository": {}, + "author": "", + "license": "AGPL-3.0", + "homepage": "", + "dependencies": { + "chai": "^4.2.0", + "mocha": "^6.2.2" + } +} diff --git a/misc/client-sdk-testsuite/src/arcad.yml b/misc/client-sdk-testsuite/src/arcad.yml new file mode 100644 index 0000000..e275410 --- /dev/null +++ b/misc/client-sdk-testsuite/src/arcad.yml @@ -0,0 +1,7 @@ +--- +id: app.arcad.edge.client-sdk-test +title: SDK Test +version: 0.0.0 +description: | + Suite de tests pour le SDK client +tags: ["test"] \ No newline at end of file diff --git a/misc/client-sdk-testsuite/src/public/index.html b/misc/client-sdk-testsuite/src/public/index.html new file mode 100644 index 0000000..ff4a938 --- /dev/null +++ b/misc/client-sdk-testsuite/src/public/index.html @@ -0,0 +1,28 @@ + + + + + Client SDK Test suite + + + + + +
+ + + + + + + + \ No newline at end of file diff --git a/misc/client-sdk-testsuite/src/public/test/client-sdk.js b/misc/client-sdk-testsuite/src/public/test/client-sdk.js new file mode 100644 index 0000000..ad8a153 --- /dev/null +++ b/misc/client-sdk-testsuite/src/public/test/client-sdk.js @@ -0,0 +1,147 @@ +Arcad.debug = true; + +describe('Arcad', function() { + + describe('#connect()', function() { + after(() => { + Arcad.disconnect(); + }); + + it('should open the connection', function() { + return Arcad.connect() + .then(() => { + chai.assert.isNotNull(Arcad._conn); + }); + }); + }); + + describe('#disconnect()', function() { + it('should close the connection', function() { + Arcad.disconnect(); + chai.assert.isNull(Arcad._conn); + }); + }); + + describe('#send()', function() { + this.timeout(5000); + + before(() => { + return Arcad.connect(); + }); + + after(() => { + Arcad.disconnect(); + }); + + it('should send a message to the backend and echo back', function(done) { + const now = new Date(); + const handler = evt => { + chai.assert.equal(evt.detail.now, now.toJSON()); + Arcad.removeEventListener('message', handler); + done(); + } + + // Server should echo back message + Arcad.addEventListener('message', handler); + + // Send message to backend + Arcad.send({ now }); + }); + }); + +}); + +describe('Remote Procedure Call', function() { + + before(() => { + return Arcad.connect(); + }); + + after(() => { + Arcad.disconnect(); + }); + + it('should call the remote echo() method and resolve the returned value', function() { + const foo = "bar"; + + return Arcad.rpc('echo', { foo }) + .then(result => { + chai.assert.equal(result.foo, foo); + }); + }); + + it('should call the remote throwError() method and reject with an error', function() { + return Arcad.rpc('throwError') + .catch(err => { + // Assert that it's an "internal" error + // See https://www.jsonrpc.org/specification#error_object + chai.assert.equal(err.code, -32603); + }); + }); + + it('should call an unregistered method and reject with an error', function() { + return Arcad.rpc('unregisteredMethod') + .catch(err => { + // Assert that it's an "method not found" error + // See https://www.jsonrpc.org/specification#error_object + chai.assert.equal(err.code, -32601); + }); + }); + + + it('should call the add() method repetitively and keep count of the sent values', function() { + this.timeout(5000); + + const values = []; + for(let i = 0; i <= 1000; i++) { + values.push((Math.random() * 1000 | 0)); + } + return Arcad.rpc('reset') + .then(() => { + return Promise.all(values.map(v => Arcad.rpc("add", {value: v}))); + }) + .then(() => Arcad.rpc('total')) + .then(remoteTotal => { + const localTotal = values.reduce((t, v) => t+v); + console.log("Remote total:", remoteTotal, "Local total:", localTotal); + chai.assert.equal(remoteTotal, localTotal) + }) + }); + +}); + +describe('File Module', function() { + + before(() => { + return Arcad.connect(); + }); + + after(() => { + Arcad.disconnect(); + }); + + it('should upload then download a file', function() { + const content = "file "+(new Date()).toJSON(); + const file = new Blob([content], {type: "text/plain"}); + + return Arcad.upload(file) + .then(upload => upload.result()) + .then(fileId => { + + chai.assert.isNotEmpty(fileId); + + const fileUrl = Arcad.fileUrl(fileId); + chai.assert.isNotEmpty(fileUrl); + + return fetch(fileUrl) + .then(res => res.text()) + .then(fileContent => { + chai.assert.equal(content, fileContent); + }); + }) + .catch(err => { + chai.assert.fail(err); + }) + }); + +}); diff --git a/misc/client-sdk-testsuite/src/server/main.js b/misc/client-sdk-testsuite/src/server/main.js new file mode 100644 index 0000000..7aa7866 --- /dev/null +++ b/misc/client-sdk-testsuite/src/server/main.js @@ -0,0 +1,70 @@ + +// Called on backend initialization +function onInit() { + console.log("backend started"); + + // Register RPC exposed methods + rpc.register("echo"); + rpc.register("throwError"); + + rpc.register("add"); + rpc.register("reset"); + rpc.register("total"); +} + +// Called for each user connection +function onUserConnect() { + console.log("onUserConnect") +} + +// Called for each user disconnection +function onUserDisconnect() { + console.log("onUserDisconnect") +} + +// Called for each user message +function onUserMessage(data) { + console.log("onUserMessage", data.now); + net.send({ now: data.now }); +} + +// Called for each file upload request +function onFileUpload(fileId, fileInfo) { + console.log("onFileUpload", fileId, fileInfo); + if (fileInfo.contentType !== "text/plain") return false; + if (fileInfo.filename !== 'blob') return false; + return true; +} + +// Called for each file download request +function onFileDownload(fileId) { + console.log("onFileDownload", fileId); + return true; +} + +// RPC methods + +function echo(params) { + console.log("echoing", params); + return params; +} + +function throwError() { + throw new Error("oh no !"); +} + +var count = 0; + +function add(params) { + console.log("add", params); + count += params.value; + return count; +} + +function reset() { + count = 0; +} + +function total() { + return count; +} \ No newline at end of file diff --git a/modd.conf b/modd.conf new file mode 100644 index 0000000..6be9faf --- /dev/null +++ b/modd.conf @@ -0,0 +1,11 @@ +**/*.go +pkg/app/sdk/client/src/**/*.js +pkg/app/sdk/client/src/**/*.ts +misc/client-sdk-testsuite/src/**/* +modd.conf +{ + prep: make GOTEST_ARGS="-short" test + prep: make build + prep: cd misc/client-sdk-testsuite && make dist + daemon: bin/cli app run -p misc/client-sdk-testsuite/dist +} diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..7a1ff25 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,235 @@ +{ + "name": "edge", + "version": "1.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "edge", + "version": "1.0.0", + "license": "AGPL-3.0", + "dependencies": { + "@types/sockjs-client": "^1.5.1", + "sockjs-client": "^1.6.1" + } + }, + "node_modules/@types/sockjs-client": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@types/sockjs-client/-/sockjs-client-1.5.1.tgz", + "integrity": "sha512-bmZM6A1GPdjF0bcuIUC+50hZEMGkzMsiG9by6X9U+7IZFOiPtz7MJ9h05FSpPVxlj4i+TzzoG3ESo1FJlbLb6A==" + }, + "node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dependencies": { + "ms": "^2.1.1" + } + }, + "node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "dependencies": { + "websocket-driver": ">=0.5.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "node_modules/querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "node_modules/requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/sockjs-client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.1.tgz", + "integrity": "sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==", + "dependencies": { + "debug": "^3.2.7", + "eventsource": "^2.0.2", + "faye-websocket": "^0.11.4", + "inherits": "^2.0.4", + "url-parse": "^1.5.10" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://tidelift.com/funding/github/npm/sockjs-client" + } + }, + "node_modules/url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "dependencies": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "node_modules/websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "dependencies": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + }, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==", + "engines": { + "node": ">=0.8.0" + } + } + }, + "dependencies": { + "@types/sockjs-client": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@types/sockjs-client/-/sockjs-client-1.5.1.tgz", + "integrity": "sha512-bmZM6A1GPdjF0bcuIUC+50hZEMGkzMsiG9by6X9U+7IZFOiPtz7MJ9h05FSpPVxlj4i+TzzoG3ESo1FJlbLb6A==" + }, + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "requires": { + "ms": "^2.1.1" + } + }, + "eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==" + }, + "faye-websocket": { + "version": "0.11.4", + "resolved": "https://registry.npmjs.org/faye-websocket/-/faye-websocket-0.11.4.tgz", + "integrity": "sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==", + "requires": { + "websocket-driver": ">=0.5.1" + } + }, + "http-parser-js": { + "version": "0.5.8", + "resolved": "https://registry.npmjs.org/http-parser-js/-/http-parser-js-0.5.8.tgz", + "integrity": "sha512-SGeBX54F94Wgu5RH3X5jsDtf4eHyRogWX1XGT3b4HuW3tQPM4AaBzoUji/4AAJNXCEOWZ5O0DgZmJw1947gD5Q==" + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + }, + "querystringify": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/querystringify/-/querystringify-2.2.0.tgz", + "integrity": "sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==" + }, + "requires-port": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz", + "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==" + }, + "safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" + }, + "sockjs-client": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/sockjs-client/-/sockjs-client-1.6.1.tgz", + "integrity": "sha512-2g0tjOR+fRs0amxENLi/q5TiJTqY+WXFOzb5UwXndlK6TO3U/mirZznpx6w34HVMoc3g7cY24yC/ZMIYnDlfkw==", + "requires": { + "debug": "^3.2.7", + "eventsource": "^2.0.2", + "faye-websocket": "^0.11.4", + "inherits": "^2.0.4", + "url-parse": "^1.5.10" + } + }, + "url-parse": { + "version": "1.5.10", + "resolved": "https://registry.npmjs.org/url-parse/-/url-parse-1.5.10.tgz", + "integrity": "sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==", + "requires": { + "querystringify": "^2.1.1", + "requires-port": "^1.0.0" + } + }, + "websocket-driver": { + "version": "0.7.4", + "resolved": "https://registry.npmjs.org/websocket-driver/-/websocket-driver-0.7.4.tgz", + "integrity": "sha512-b17KeDIQVjvb0ssuSDF2cYXSg2iztliJ4B9WdsuB6J952qCPKmnVq4DyW5motImXHDC1cBT/1UezrJVsKw5zjg==", + "requires": { + "http-parser-js": ">=0.5.1", + "safe-buffer": ">=5.1.0", + "websocket-extensions": ">=0.1.1" + } + }, + "websocket-extensions": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/websocket-extensions/-/websocket-extensions-0.1.4.tgz", + "integrity": "sha512-OqedPIGOfsDlo31UNwYbCFMSaO9m9G/0faIHj5/dZFDMFqPTcx6UwqyOy3COEaEOg/9VsGIpdqn62W5KhoKSpg==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..3845639 --- /dev/null +++ b/package.json @@ -0,0 +1,15 @@ +{ + "name": "edge", + "version": "1.0.0", + "description": "", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "Cadoles ", + "license": "AGPL-3.0", + "dependencies": { + "@types/sockjs-client": "^1.5.1", + "sockjs-client": "^1.6.1" + } +} diff --git a/pkg/app/app.go b/pkg/app/app.go new file mode 100644 index 0000000..699a315 --- /dev/null +++ b/pkg/app/app.go @@ -0,0 +1,16 @@ +package app + +type ID string + +type Manifest struct { + ID ID `yaml:"id"` + Version string `yaml:"version"` + Title string `yaml:"title"` + Description string `yaml:"description"` + Tags []string `yaml:"tags"` +} + +type App struct { + ID ID + Manifest *Manifest +} diff --git a/pkg/app/crypto.go b/pkg/app/crypto.go new file mode 100644 index 0000000..5fb5e51 --- /dev/null +++ b/pkg/app/crypto.go @@ -0,0 +1,25 @@ +package app + +import ( + "crypto/rand" + "encoding/binary" + + "github.com/pkg/errors" +) + +type cryptoSource struct{} + +func (s cryptoSource) Seed(seed int64) {} + +func (s cryptoSource) Int63() int64 { + return int64(s.Uint64() & ^uint64(1<<63)) +} + +func (s cryptoSource) Uint64() (v uint64) { + err := binary.Read(rand.Reader, binary.BigEndian, &v) + if err != nil { + panic(errors.Wrap(err, "could not read number for random source")) + } + + return v +} diff --git a/pkg/app/error.go b/pkg/app/error.go new file mode 100644 index 0000000..8d04ac1 --- /dev/null +++ b/pkg/app/error.go @@ -0,0 +1,5 @@ +package app + +import "github.com/pkg/errors" + +var ErrUnknownBundleArchiveFormat = errors.New("unknown bundle archive format") diff --git a/pkg/app/loader.go b/pkg/app/loader.go new file mode 100644 index 0000000..9220d0a --- /dev/null +++ b/pkg/app/loader.go @@ -0,0 +1,101 @@ +package app + +import ( + "context" + "path/filepath" + + "gitlab.com/wpetit/goweb/logger" + "gopkg.in/yaml.v2" + + "forge.cadoles.com/arcad/edge/pkg/bundle" + "github.com/pkg/errors" +) + +type FilesystemLoader struct { + searchPatterns []string +} + +type LoadedApp struct { + App *App + Bundle bundle.Bundle +} + +func (l *FilesystemLoader) Load(ctx context.Context) ([]*LoadedApp, error) { + apps := make([]*LoadedApp, 0) + + for _, seachPattern := range l.searchPatterns { + absSearchPattern, err := filepath.Abs(seachPattern) + if err != nil { + return nil, errors.Wrapf(err, "could not generate absolute path for '%s'", seachPattern) + } + + logger.Debug(ctx, "searching apps in filesystem", logger.F("searchPattern", absSearchPattern)) + + files, err := filepath.Glob(absSearchPattern) + if err != nil { + return nil, errors.Wrapf(err, "could not search files with pattern '%s'", absSearchPattern) + } + + for _, f := range files { + loopCtx := logger.With(ctx, logger.F("file", f)) + + logger.Debug(loopCtx, "found app bundle") + + b, err := bundle.FromPath(f) + if err != nil { + logger.Error(loopCtx, "could not load bundle", logger.E(errors.WithStack(err))) + + continue + } + + logger.Debug(loopCtx, "loading app manifest") + + appManifest, err := LoadAppManifest(b) + if err != nil { + logger.Error(loopCtx, "could not load app manifest", logger.E(errors.WithStack(err))) + + continue + } + + g := &App{ + ID: appManifest.ID, + Manifest: appManifest, + } + + apps = append(apps, &LoadedApp{ + App: g, + Bundle: b, + }) + } + } + + return apps, nil +} + +func NewFilesystemLoader(searchPatterns ...string) *FilesystemLoader { + return &FilesystemLoader{ + searchPatterns: searchPatterns, + } +} + +func LoadAppManifest(b bundle.Bundle) (*Manifest, error) { + reader, _, err := b.File("arcad.yml") + if err != nil { + return nil, errors.Wrap(err, "could not read arcad.yml") + } + + defer func() { + if err := reader.Close(); err != nil { + panic(errors.WithStack(err)) + } + }() + + manifest := &Manifest{} + + decoder := yaml.NewDecoder(reader) + if err := decoder.Decode(manifest); err != nil { + return nil, errors.Wrap(err, "could not decode arcad.yml") + } + + return manifest, nil +} diff --git a/pkg/app/promise_proxy.go b/pkg/app/promise_proxy.go new file mode 100644 index 0000000..b9a1b85 --- /dev/null +++ b/pkg/app/promise_proxy.go @@ -0,0 +1,41 @@ +package app + +import ( + "sync" + + "github.com/dop251/goja" +) + +type PromiseProxy struct { + *goja.Promise + wg sync.WaitGroup + resolve func(result interface{}) + reject func(reason interface{}) +} + +func (p *PromiseProxy) Resolve(result interface{}) { + defer p.wg.Done() + p.resolve(result) +} + +func (p *PromiseProxy) Reject(reason interface{}) { + defer p.wg.Done() + p.resolve(reason) +} + +func (p *PromiseProxy) Wait() { + p.wg.Wait() +} + +func NewPromiseProxy(promise *goja.Promise, resolve func(result interface{}), reject func(reason interface{})) *PromiseProxy { + proxy := &PromiseProxy{ + Promise: promise, + wg: sync.WaitGroup{}, + resolve: resolve, + reject: reject, + } + + proxy.wg.Add(1) + + return proxy +} diff --git a/pkg/app/server.go b/pkg/app/server.go new file mode 100644 index 0000000..1430e30 --- /dev/null +++ b/pkg/app/server.go @@ -0,0 +1,182 @@ +package app + +import ( + "math/rand" + "sync" + + "github.com/dop251/goja" + "github.com/dop251/goja_nodejs/eventloop" + "github.com/pkg/errors" +) + +var ErrFuncDoesNotExist = errors.New("function does not exist") + +type Server struct { + runtime *goja.Runtime + loop *eventloop.EventLoop + modules []ServerModule +} + +func (s *Server) Load(name string, src string) error { + _, err := s.runtime.RunScript(name, src) + if err != nil { + return errors.Wrap(err, "could not run js script") + } + + return nil +} + +func (s *Server) ExecFuncByName(funcName string, args ...interface{}) (goja.Value, error) { + callable, ok := goja.AssertFunction(s.runtime.Get(funcName)) + if !ok { + return nil, errors.WithStack(ErrFuncDoesNotExist) + } + + return s.Exec(callable, args...) +} + +func (s *Server) Exec(callable goja.Callable, args ...interface{}) (goja.Value, error) { + var ( + wg sync.WaitGroup + value goja.Value + err error + ) + + wg.Add(1) + + s.loop.RunOnLoop(func(vm *goja.Runtime) { + jsArgs := make([]goja.Value, 0, len(args)) + for _, a := range args { + jsArgs = append(jsArgs, vm.ToValue(a)) + } + + value, err = callable(nil, jsArgs...) + if err != nil { + err = errors.WithStack(err) + } + + wg.Done() + }) + + wg.Wait() + + return value, err +} + +func (s *Server) IsPromise(v goja.Value) (*goja.Promise, bool) { + promise, ok := v.Export().(*goja.Promise) + return promise, ok +} + +func (s *Server) WaitForPromise(promise *goja.Promise) goja.Value { + var ( + wg sync.WaitGroup + value goja.Value + ) + + wg.Add(1) + + // Wait for promise completion + go func() { + for { + var loopWait sync.WaitGroup + loopWait.Add(1) + + breakLoop := false + + s.loop.RunOnLoop(func(vm *goja.Runtime) { + defer loopWait.Done() + + if promise.State() == goja.PromiseStatePending { + return + } + + value = promise.Result() + + breakLoop = true + }) + + loopWait.Wait() + + if breakLoop { + wg.Done() + + return + } + } + }() + + wg.Wait() + + return value +} + +func (s *Server) NewPromise() *PromiseProxy { + promise, resolve, reject := s.runtime.NewPromise() + + return NewPromiseProxy(promise, resolve, reject) +} + +func (s *Server) ToValue(v interface{}) goja.Value { + return s.runtime.ToValue(v) +} + +func (s *Server) Start() error { + s.loop.Start() + + for _, mod := range s.modules { + initMod, ok := mod.(InitializableModule) + if !ok { + continue + } + + if err := initMod.OnInit(); err != nil { + return errors.WithStack(err) + } + } + + return nil +} + +func (s *Server) Stop() { + s.loop.Stop() +} + +func (s *Server) initModules(factories ...ServerModuleFactory) { + runtime := goja.New() + + runtime.SetFieldNameMapper(goja.UncapFieldNameMapper()) + runtime.SetRandSource(createRandomSource()) + + modules := make([]ServerModule, 0, len(factories)) + + for _, moduleFactory := range factories { + mod := moduleFactory(s) + export := runtime.NewObject() + mod.Export(export) + runtime.Set(mod.Name(), export) + + modules = append(modules, mod) + } + + s.runtime = runtime + s.modules = modules +} + +func NewServer(factories ...ServerModuleFactory) *Server { + backend := &Server{ + loop: eventloop.NewEventLoop( + eventloop.EnableConsole(false), + ), + } + + backend.initModules(factories...) + + return backend +} + +func createRandomSource() goja.RandSource { + rnd := rand.New(&cryptoSource{}) + + return rnd.Float64 +} diff --git a/pkg/app/server_module.go b/pkg/app/server_module.go new file mode 100644 index 0000000..d331bf2 --- /dev/null +++ b/pkg/app/server_module.go @@ -0,0 +1,17 @@ +package app + +import ( + "github.com/dop251/goja" +) + +type ServerModuleFactory func(*Server) ServerModule + +type ServerModule interface { + Name() string + Export(*goja.Object) +} + +type InitializableModule interface { + ServerModule + OnInit() error +} diff --git a/pkg/bundle/bundle.go b/pkg/bundle/bundle.go new file mode 100644 index 0000000..ec85940 --- /dev/null +++ b/pkg/bundle/bundle.go @@ -0,0 +1,11 @@ +package bundle + +import ( + "io" + "os" +) + +type Bundle interface { + File(string) (io.ReadCloser, os.FileInfo, error) + Dir(string) ([]os.FileInfo, error) +} diff --git a/pkg/bundle/bundle_test.go b/pkg/bundle/bundle_test.go new file mode 100644 index 0000000..694e6b3 --- /dev/null +++ b/pkg/bundle/bundle_test.go @@ -0,0 +1,70 @@ +package bundle + +import ( + "fmt" + "testing" + + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +func TestBundle(t *testing.T) { + t.Parallel() + + logger.SetLevel(logger.LevelDebug) + + bundles := []Bundle{ + NewDirectoryBundle("testdata/bundle"), + NewTarBundle("testdata/bundle.tar.gz"), + NewZipBundle("testdata/bundle.zip"), + } + + for _, b := range bundles { + func(b Bundle) { + t.Run(fmt.Sprintf("'%T'", b), func(t *testing.T) { + t.Parallel() + + reader, info, err := b.File("data/test/foo.txt") + if err != nil { + t.Error(err) + } + + if reader == nil { + t.Fatal("File(data/test/foo.txt): reader should not be nil") + } + + defer func() { + if err := reader.Close(); err != nil { + t.Error(errors.WithStack(err)) + } + }() + + if info == nil { + t.Error("File(data/test/foo.txt): info should not be nil") + } + + files, err := b.Dir("data") + if err != nil { + t.Error(err) + } + + if e, g := 1, len(files); e != g { + t.Errorf("len(files): expected '%v', got '%v'", e, g) + } + + files, err = b.Dir("data/test") + if err != nil { + t.Error(err) + } + + if e, g := 1, len(files); e != g { + t.Fatalf("len(files): expected '%v', got '%v'", e, g) + } + + if e, g := "foo.txt", files[0].Name(); e != g { + t.Errorf("files[0].Name(): expected '%v', got '%v'", e, g) + } + }) + }(b) + } +} diff --git a/pkg/bundle/directory_bundle.go b/pkg/bundle/directory_bundle.go new file mode 100644 index 0000000..47d0312 --- /dev/null +++ b/pkg/bundle/directory_bundle.go @@ -0,0 +1,54 @@ +package bundle + +import ( + "context" + "io" + "io/ioutil" + "os" + "path" + + "gitlab.com/wpetit/goweb/logger" + + "github.com/pkg/errors" +) + +type DirectoryBundle struct { + baseDir string +} + +func (b *DirectoryBundle) File(filename string) (io.ReadCloser, os.FileInfo, error) { + ctx := context.Background() + + fullPath := path.Join(b.baseDir, filename) + + logger.Debug(ctx, "accessing bundle file", logger.F("file", fullPath)) + + info, err := os.Stat(fullPath) + if err != nil { + if os.IsNotExist(err) { + return nil, nil, err + } + + return nil, nil, errors.Wrapf(err, "stat '%s'", fullPath) + } + + reader, err := os.Open(fullPath) + if err != nil { + return nil, nil, errors.Wrapf(err, "open '%s'", fullPath) + } + + return reader, info, nil +} + +func (b *DirectoryBundle) Dir(dirname string) ([]os.FileInfo, error) { + fullPath := path.Join(b.baseDir, dirname) + ctx := context.Background() + logger.Debug(ctx, "accessing bundle directory", logger.F("file", fullPath)) + return ioutil.ReadDir(fullPath) +} + +func NewDirectoryBundle(baseDir string) *DirectoryBundle { + return &DirectoryBundle{ + baseDir: baseDir, + } +} diff --git a/pkg/bundle/error.go b/pkg/bundle/error.go new file mode 100644 index 0000000..8f148f9 --- /dev/null +++ b/pkg/bundle/error.go @@ -0,0 +1,5 @@ +package bundle + +import "errors" + +var ErrUnknownBundleArchiveExt = errors.New("unknown bundle archive extension") diff --git a/pkg/bundle/filesystem.go b/pkg/bundle/filesystem.go new file mode 100644 index 0000000..776996f --- /dev/null +++ b/pkg/bundle/filesystem.go @@ -0,0 +1,101 @@ +package bundle + +import ( + "bytes" + "context" + "io/ioutil" + "net/http" + "os" + "path" + "path/filepath" + "strings" + + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +type FileSystem struct { + prefix string + bundle Bundle +} + +func (fs *FileSystem) Open(name string) (http.File, error) { + if filepath.Separator != '/' && strings.ContainsRune(name, filepath.Separator) || + strings.Contains(name, "\x00") { + return nil, errors.New("http: invalid character in file path") + } + + p := path.Join(fs.prefix, strings.TrimPrefix(name, "/")) + + ctx := logger.With( + context.Background(), + logger.F("filename", name), + ) + + logger.Debug(ctx, "opening file") + + readCloser, fileInfo, err := fs.bundle.File(p) + if err != nil { + if os.IsNotExist(err) { + return nil, err + } + + logger.Error(ctx, "could not open bundle file", logger.E(err)) + + return nil, errors.Wrapf(err, "could not open bundle file '%s'", p) + } + defer readCloser.Close() + + file := &File{ + fi: fileInfo, + } + + if fileInfo.IsDir() { + files, err := fs.bundle.Dir(p) + if err != nil { + logger.Error(ctx, "could not read bundle directory", logger.E(err)) + + return nil, errors.Wrapf(err, "could not read bundle directory '%s'", p) + } + + file.files = files + } else { + data, err := ioutil.ReadAll(readCloser) + if err != nil { + logger.Error(ctx, "could not read bundle file", logger.E(err)) + + return nil, errors.Wrapf(err, "could not read bundle file '%s'", p) + } + + file.Reader = bytes.NewReader(data) + } + + return file, nil +} + +func NewFileSystem(prefix string, bundle Bundle) *FileSystem { + return &FileSystem{prefix, bundle} +} + +type File struct { + *bytes.Reader + fi os.FileInfo + files []os.FileInfo +} + +// A noop-closer. +func (f *File) Close() error { + return nil +} + +func (f *File) Readdir(count int) ([]os.FileInfo, error) { + if f.fi.IsDir() && f.files != nil { + return f.files, nil + } + + return nil, os.ErrNotExist +} + +func (f *File) Stat() (os.FileInfo, error) { + return f.fi, nil +} diff --git a/pkg/bundle/from_path.go b/pkg/bundle/from_path.go new file mode 100644 index 0000000..89448fd --- /dev/null +++ b/pkg/bundle/from_path.go @@ -0,0 +1,60 @@ +package bundle + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/pkg/errors" +) + +type ArchiveExt string + +const ( + ExtZip ArchiveExt = "zip" + ExtTarGz ArchiveExt = "tar.gz" +) + +func FromPath(path string) (Bundle, error) { + stat, err := os.Stat(path) + if err != nil { + return nil, errors.Wrapf(err, "could not stat file '%s'", path) + } + + var b Bundle + + if stat.IsDir() { + b = NewDirectoryBundle(path) + } else { + b, err = matchArchivePattern(path) + if err != nil { + return nil, errors.WithStack(err) + } + } + + return b, nil +} + +func matchArchivePattern(archivePath string) (Bundle, error) { + base := filepath.Base(archivePath) + + matches, err := filepath.Match(fmt.Sprintf("*.%s", ExtTarGz), base) + if err != nil { + return nil, errors.Wrapf(err, "could not match file archive '%s'", archivePath) + } + + if matches { + return NewTarBundle(archivePath), nil + } + + matches, err = filepath.Match(fmt.Sprintf("*.%s", ExtZip), base) + if err != nil { + return nil, errors.Wrapf(err, "could not match file archive '%s'", archivePath) + } + + if matches { + return NewZipBundle(archivePath), nil + } + + return nil, errors.WithStack(ErrUnknownBundleArchiveExt) +} diff --git a/pkg/bundle/tar_bundle.go b/pkg/bundle/tar_bundle.go new file mode 100644 index 0000000..e8a7ae5 --- /dev/null +++ b/pkg/bundle/tar_bundle.go @@ -0,0 +1,146 @@ +package bundle + +import ( + "archive/tar" + "compress/gzip" + "context" + "io" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +type TarBundle struct { + archivePath string +} + +func (b *TarBundle) File(filename string) (io.ReadCloser, os.FileInfo, error) { + reader, archive, err := b.openArchive() + if err != nil { + return nil, nil, err + } + + ctx := logger.With( + context.Background(), + logger.F("filename", filename), + ) + + logger.Debug(ctx, "opening file") + + for { + header, err := reader.Next() + + if err == io.EOF { + break + } + + if err != nil { + return nil, nil, errors.Wrap(err, "could not get next tar file") + } + + p := strings.TrimPrefix(strings.TrimSuffix(header.Name, "/"), "./") + + logger.Debug(ctx, "reading archive file", logger.F("path", p)) + + if filename != p { + continue + } + + if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeDir { + continue + } + + rc := &archiveFile{reader, archive} + + return rc, header.FileInfo(), nil + } + + return nil, nil, os.ErrNotExist +} + +func (b *TarBundle) Dir(dirname string) ([]os.FileInfo, error) { + reader, archive, err := b.openArchive() + if err != nil { + return nil, err + } + defer archive.Close() + + files := make([]os.FileInfo, 0) + ctx := context.Background() + + for { + header, err := reader.Next() + + if errors.Is(err, io.EOF) { + break + } + + if err != nil { + return nil, errors.Wrap(err, "could not get next tar file") + } + + if header.Typeflag != tar.TypeReg && header.Typeflag != tar.TypeDir { + continue + } + + if !strings.HasPrefix(header.Name, dirname) { + continue + } + + relPath, err := filepath.Rel(dirname, header.Name) + if err != nil { + return nil, errors.Wrap(err, "could not get relative path") + } + + logger.Debug( + ctx, "checking file prefix", + logger.F("dirname", dirname), + logger.F("filename", header.Name), + logger.F("relpath", relPath), + ) + + if relPath == filepath.Base(header.Name) { + files = append(files, header.FileInfo()) + } + } + + return files, nil +} + +func (b *TarBundle) openArchive() (*tar.Reader, *os.File, error) { + f, err := os.Open(b.archivePath) + if err != nil { + return nil, nil, errors.Wrapf(err, "could not open '%v'", b.archivePath) + } + + gzf, err := gzip.NewReader(f) + if err != nil { + return nil, nil, errors.Wrapf(err, "could not decompress '%v'", b.archivePath) + } + + tr := tar.NewReader(gzf) + + return tr, f, nil +} + +func NewTarBundle(archivePath string) *TarBundle { + return &TarBundle{ + archivePath: archivePath, + } +} + +type archiveFile struct { + reader io.Reader + closer io.Closer +} + +func (f *archiveFile) Read(p []byte) (n int, err error) { + return f.reader.Read(p) +} + +func (f *archiveFile) Close() error { + return f.closer.Close() +} diff --git a/pkg/bundle/testdata/bundle.tar.gz b/pkg/bundle/testdata/bundle.tar.gz new file mode 100644 index 0000000..30227ea Binary files /dev/null and b/pkg/bundle/testdata/bundle.tar.gz differ diff --git a/pkg/bundle/testdata/bundle.zip b/pkg/bundle/testdata/bundle.zip new file mode 100644 index 0000000..2c54c1d Binary files /dev/null and b/pkg/bundle/testdata/bundle.zip differ diff --git a/pkg/bundle/testdata/bundle/data/test/foo.txt b/pkg/bundle/testdata/bundle/data/test/foo.txt new file mode 100644 index 0000000..ba0e162 --- /dev/null +++ b/pkg/bundle/testdata/bundle/data/test/foo.txt @@ -0,0 +1 @@ +bar \ No newline at end of file diff --git a/pkg/bundle/zip_bundle.go b/pkg/bundle/zip_bundle.go new file mode 100644 index 0000000..76d8ea0 --- /dev/null +++ b/pkg/bundle/zip_bundle.go @@ -0,0 +1,98 @@ +package bundle + +import ( + "archive/zip" + "context" + "io" + "os" + "path/filepath" + "strings" + + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +type ZipBundle struct { + archivePath string +} + +func (b *ZipBundle) File(filename string) (io.ReadCloser, os.FileInfo, error) { + reader, err := b.openArchive() + if err != nil { + return nil, nil, err + } + + ctx := logger.With( + context.Background(), + logger.F("filename", filename), + ) + + logger.Debug(ctx, "opening file") + + f, err := reader.Open(filename) + if err != nil { + return nil, nil, errors.WithStack(err) + } + + stat, err := f.Stat() + if err != nil { + return nil, nil, errors.WithStack(err) + } + + return f, stat, nil +} + +func (b *ZipBundle) Dir(dirname string) ([]os.FileInfo, error) { + reader, err := b.openArchive() + if err != nil { + return nil, err + } + + defer func() { + if err := reader.Close(); err != nil { + panic(errors.WithStack(err)) + } + }() + + files := make([]os.FileInfo, 0) + ctx := context.Background() + + for _, f := range reader.File { + if !strings.HasPrefix(f.Name, dirname) { + continue + } + + relPath, err := filepath.Rel(dirname, f.Name) + if err != nil { + return nil, errors.Wrap(err, "could not get relative path") + } + + logger.Debug( + ctx, "checking file prefix", + logger.F("dirname", dirname), + logger.F("filename", f.Name), + logger.F("relpath", relPath), + ) + + if relPath == filepath.Base(f.Name) { + files = append(files, f.FileInfo()) + } + } + + return files, nil +} + +func (b *ZipBundle) openArchive() (*zip.ReadCloser, error) { + zr, err := zip.OpenReader(b.archivePath) + if err != nil { + return nil, errors.Wrapf(err, "could not decompress '%v'", b.archivePath) + } + + return zr, nil +} + +func NewZipBundle(archivePath string) *ZipBundle { + return &ZipBundle{ + archivePath: archivePath, + } +} diff --git a/pkg/bus/bus.go b/pkg/bus/bus.go new file mode 100644 index 0000000..a02d437 --- /dev/null +++ b/pkg/bus/bus.go @@ -0,0 +1,13 @@ +package bus + +import "context" + +type Bus interface { + Subscribe(ctx context.Context, ns MessageNamespace) (<-chan Message, error) + Unsubscribe(ctx context.Context, ns MessageNamespace, ch <-chan Message) + Publish(ctx context.Context, msg Message) error + Request(ctx context.Context, msg Message) (Message, error) + Reply(ctx context.Context, ns MessageNamespace, h RequestHandler) error +} + +type RequestHandler func(msg Message) (Message, error) diff --git a/pkg/bus/error.go b/pkg/bus/error.go new file mode 100644 index 0000000..ead8d31 --- /dev/null +++ b/pkg/bus/error.go @@ -0,0 +1,9 @@ +package bus + +import "github.com/pkg/errors" + +var ( + ErrPublishTimeout = errors.New("publish timeout") + ErrUnexpectedMessage = errors.New("unexpected message") + ErrNoResponse = errors.New("no response") +) diff --git a/pkg/bus/memory/bus.go b/pkg/bus/memory/bus.go new file mode 100644 index 0000000..9ac7f3a --- /dev/null +++ b/pkg/bus/memory/bus.go @@ -0,0 +1,91 @@ +package memory + +import ( + "context" + + "forge.cadoles.com/arcad/edge/pkg/bus" + cmap "github.com/orcaman/concurrent-map" + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +type Bus struct { + opt *Option + dispatchers cmap.ConcurrentMap + nextRequestID uint64 +} + +func (b *Bus) Subscribe(ctx context.Context, ns bus.MessageNamespace) (<-chan bus.Message, error) { + logger.Debug( + ctx, "subscribing to messages", + logger.F("messageNamespace", ns), + ) + + dispatchers := b.getDispatchers(ns) + d := newEventDispatcher(b.opt.BufferSize) + + go d.Run() + + dispatchers.Add(d) + + return d.Out(), nil +} + +func (b *Bus) Unsubscribe(ctx context.Context, ns bus.MessageNamespace, ch <-chan bus.Message) { + logger.Debug( + ctx, "unsubscribing from messages", + logger.F("messageNamespace", ns), + ) + + dispatchers := b.getDispatchers(ns) + dispatchers.RemoveByOutChannel(ch) +} + +func (b *Bus) Publish(ctx context.Context, msg bus.Message) error { + dispatchers := b.getDispatchers(msg.MessageNamespace()) + dispatchersList := dispatchers.List() + + logger.Debug( + ctx, "publishing message", + logger.F("dispatchers", len(dispatchersList)), + logger.F("messageNamespace", msg.MessageNamespace()), + ) + + for _, d := range dispatchersList { + if err := d.In(msg); err != nil { + return errors.WithStack(err) + } + } + + return nil +} + +func (b *Bus) getDispatchers(namespace bus.MessageNamespace) *eventDispatcherSet { + strNamespace := string(namespace) + + rawDispatchers, exists := b.dispatchers.Get(strNamespace) + dispatchers, ok := rawDispatchers.(*eventDispatcherSet) + + if !exists || !ok { + dispatchers = newEventDispatcherSet() + b.dispatchers.Set(strNamespace, dispatchers) + } + + return dispatchers +} + +func NewBus(funcs ...OptionFunc) *Bus { + opt := DefaultOption() + + for _, fn := range funcs { + fn(opt) + } + + return &Bus{ + opt: opt, + dispatchers: cmap.New(), + } +} + +// Check bus implementation. +var _ bus.Bus = NewBus() diff --git a/pkg/bus/memory/bus_test.go b/pkg/bus/memory/bus_test.go new file mode 100644 index 0000000..ac79f69 --- /dev/null +++ b/pkg/bus/memory/bus_test.go @@ -0,0 +1,29 @@ +package memory + +import ( + "testing" + + busTesting "forge.cadoles.com/arcad/edge/pkg/bus/testing" +) + +func TestMemoryBus(t *testing.T) { + if testing.Short() { + t.Skip("Test disabled when -short flag is set") + } + + t.Parallel() + + t.Run("PublishSubscribe", func(t *testing.T) { + t.Parallel() + + b := NewBus() + busTesting.TestPublishSubscribe(t, b) + }) + + t.Run("RequestReply", func(t *testing.T) { + t.Parallel() + + b := NewBus() + busTesting.TestRequestReply(t, b) + }) +} diff --git a/pkg/bus/memory/event_dispatcher.go b/pkg/bus/memory/event_dispatcher.go new file mode 100644 index 0000000..fd789f8 --- /dev/null +++ b/pkg/bus/memory/event_dispatcher.go @@ -0,0 +1,117 @@ +package memory + +import ( + "context" + "sync" + "time" + + "forge.cadoles.com/arcad/edge/pkg/bus" + "gitlab.com/wpetit/goweb/logger" +) + +type eventDispatcherSet struct { + mutex sync.Mutex + items map[*eventDispatcher]struct{} +} + +func (s *eventDispatcherSet) Add(d *eventDispatcher) { + s.mutex.Lock() + defer s.mutex.Unlock() + + s.items[d] = struct{}{} +} + +func (s *eventDispatcherSet) RemoveByOutChannel(out <-chan bus.Message) { + s.mutex.Lock() + defer s.mutex.Unlock() + + for d := range s.items { + if d.IsOut(out) { + d.Close() + delete(s.items, d) + } + } +} + +func (s *eventDispatcherSet) List() []*eventDispatcher { + s.mutex.Lock() + defer s.mutex.Unlock() + + dispatchers := make([]*eventDispatcher, 0, len(s.items)) + + for d := range s.items { + dispatchers = append(dispatchers, d) + } + + return dispatchers +} + +func newEventDispatcherSet() *eventDispatcherSet { + return &eventDispatcherSet{ + items: make(map[*eventDispatcher]struct{}), + } +} + +type eventDispatcher struct { + in chan bus.Message + out chan bus.Message + mutex sync.RWMutex + closed bool +} + +func (d *eventDispatcher) Close() { + d.mutex.Lock() + defer d.mutex.Unlock() + + d.closed = true + close(d.in) +} + +func (d *eventDispatcher) In(msg bus.Message) (err error) { + d.mutex.RLock() + defer d.mutex.RUnlock() + + if d.closed { + return + } + + d.in <- msg + + return nil +} + +func (d *eventDispatcher) Out() <-chan bus.Message { + return d.out +} + +func (d *eventDispatcher) IsOut(out <-chan bus.Message) bool { + return d.out == out +} + +func (d *eventDispatcher) Run() { + ctx := context.Background() + + for { + msg, ok := <-d.in + if !ok { + close(d.out) + + return + } + + timeout := time.After(2 * time.Second) + select { + case d.out <- msg: + case <-timeout: + logger.Error(ctx, "message out chan timed out", logger.F("message", msg)) + } + } +} + +func newEventDispatcher(bufferSize int64) *eventDispatcher { + return &eventDispatcher{ + in: make(chan bus.Message, bufferSize), + out: make(chan bus.Message, bufferSize), + closed: false, + } +} diff --git a/pkg/bus/memory/option.go b/pkg/bus/memory/option.go new file mode 100644 index 0000000..e9f3eaf --- /dev/null +++ b/pkg/bus/memory/option.go @@ -0,0 +1,19 @@ +package memory + +type Option struct { + BufferSize int64 +} + +type OptionFunc func(*Option) + +func DefaultOption() *Option { + return &Option{ + BufferSize: 16, // nolint: gomnd + } +} + +func WithBufferSize(size int64) OptionFunc { + return func(o *Option) { + o.BufferSize = size + } +} diff --git a/pkg/bus/memory/request_reply.go b/pkg/bus/memory/request_reply.go new file mode 100644 index 0000000..aaa9390 --- /dev/null +++ b/pkg/bus/memory/request_reply.go @@ -0,0 +1,151 @@ +package memory + +import ( + "context" + "strconv" + "sync/atomic" + + "forge.cadoles.com/arcad/edge/pkg/bus" + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +const ( + MessageNamespaceRequest bus.MessageNamespace = "reqrep/request" + MessageNamespaceReply bus.MessageNamespace = "reqrep/reply" +) + +type RequestMessage struct { + RequestID uint64 + + Message bus.Message + + ns bus.MessageNamespace +} + +func (m *RequestMessage) MessageNamespace() bus.MessageNamespace { + return m.ns +} + +type ReplyMessage struct { + RequestID uint64 + Message bus.Message + Error error + + ns bus.MessageNamespace +} + +func (m *ReplyMessage) MessageNamespace() bus.MessageNamespace { + return m.ns +} + +func (b *Bus) Request(ctx context.Context, msg bus.Message) (bus.Message, error) { + requestID := atomic.AddUint64(&b.nextRequestID, 1) + + req := &RequestMessage{ + RequestID: requestID, + Message: msg, + ns: msg.MessageNamespace(), + } + + replyNamespace := createReplyNamespace(requestID) + + replies, err := b.Subscribe(ctx, replyNamespace) + if err != nil { + return nil, errors.WithStack(err) + } + + defer func() { + b.Unsubscribe(ctx, replyNamespace, replies) + }() + + logger.Debug(ctx, "publishing request", logger.F("request", req)) + + if err := b.Publish(ctx, req); err != nil { + return nil, errors.WithStack(err) + } + + for { + select { + case <-ctx.Done(): + return nil, errors.WithStack(ctx.Err()) + + case msg, ok := <-replies: + if !ok { + return nil, errors.WithStack(bus.ErrNoResponse) + } + + reply, ok := msg.(*ReplyMessage) + if !ok { + return nil, errors.WithStack(bus.ErrUnexpectedMessage) + } + + if reply.Error != nil { + return nil, errors.WithStack(err) + } + + return reply.Message, nil + } + } +} + +type RequestHandler func(evt bus.Message) (bus.Message, error) + +func (b *Bus) Reply(ctx context.Context, msgNamespace bus.MessageNamespace, h bus.RequestHandler) error { + requests, err := b.Subscribe(ctx, msgNamespace) + if err != nil { + return errors.WithStack(err) + } + + defer func() { + b.Unsubscribe(ctx, msgNamespace, requests) + }() + + for { + select { + case <-ctx.Done(): + return errors.WithStack(ctx.Err()) + + case msg, ok := <-requests: + if !ok { + return nil + } + + request, ok := msg.(*RequestMessage) + if !ok { + return errors.WithStack(bus.ErrUnexpectedMessage) + } + + logger.Debug(ctx, "handling request", logger.F("request", request)) + + msg, err := h(request.Message) + + reply := &ReplyMessage{ + RequestID: request.RequestID, + Message: nil, + Error: nil, + + ns: createReplyNamespace(request.RequestID), + } + + if err != nil { + reply.Error = errors.WithStack(err) + } else { + reply.Message = msg + } + + logger.Debug(ctx, "publishing reply", logger.F("reply", reply)) + + if err := b.Publish(ctx, reply); err != nil { + return errors.WithStack(err) + } + } + } +} + +func createReplyNamespace(requestID uint64) bus.MessageNamespace { + return bus.NewMessageNamespace( + MessageNamespaceReply, + bus.MessageNamespace(strconv.FormatUint(requestID, 10)), + ) +} diff --git a/pkg/bus/message.go b/pkg/bus/message.go new file mode 100644 index 0000000..3a470d1 --- /dev/null +++ b/pkg/bus/message.go @@ -0,0 +1,33 @@ +package bus + +import ( + "strings" + + "github.com/pkg/errors" +) + +type ( + MessageNamespace string +) + +type Message interface { + MessageNamespace() MessageNamespace +} + +func NewMessageNamespace(namespaces ...MessageNamespace) MessageNamespace { + var sb strings.Builder + + for i, ns := range namespaces { + if i != 0 { + if _, err := sb.WriteString(":"); err != nil { + panic(errors.Wrap(err, "could not build new message namespace")) + } + } + + if _, err := sb.WriteString(string(ns)); err != nil { + panic(errors.Wrap(err, "could not build new message namespace")) + } + } + + return MessageNamespace(sb.String()) +} diff --git a/pkg/bus/testing/publish_subscribe.go b/pkg/bus/testing/publish_subscribe.go new file mode 100644 index 0000000..6db69e3 --- /dev/null +++ b/pkg/bus/testing/publish_subscribe.go @@ -0,0 +1,96 @@ +package testing + +import ( + "context" + "sync" + "sync/atomic" + "testing" + "time" + + "forge.cadoles.com/arcad/edge/pkg/bus" + "github.com/pkg/errors" +) + +const ( + testNamespace bus.MessageNamespace = "testNamespace" +) + +type testMessage struct{} + +func (e *testMessage) MessageNamespace() bus.MessageNamespace { + return testNamespace +} + +func TestPublishSubscribe(t *testing.T, b bus.Bus) { + ctx, cancel := context.WithTimeout(context.Background(), time.Minute) + defer cancel() + + t.Log("subscribe") + + messages, err := b.Subscribe(ctx, testNamespace) + if err != nil { + t.Fatal(errors.WithStack(err)) + } + + var wg sync.WaitGroup + + wg.Add(5) + + go func() { + // 5 events should be received + t.Log("publish 0") + + if err := b.Publish(ctx, &testMessage{}); err != nil { + t.Error(errors.WithStack(err)) + } + + t.Log("publish 1") + + if err := b.Publish(ctx, &testMessage{}); err != nil { + t.Error(errors.WithStack(err)) + } + + t.Log("publish 2") + + if err := b.Publish(ctx, &testMessage{}); err != nil { + t.Error(errors.WithStack(err)) + } + + t.Log("publish 3") + + if err := b.Publish(ctx, &testMessage{}); err != nil { + t.Error(errors.WithStack(err)) + } + + t.Log("publish 4") + + if err := b.Publish(ctx, &testMessage{}); err != nil { + t.Error(errors.WithStack(err)) + } + }() + + var count int32 = 0 + + go func() { + t.Log("range for events") + + for msg := range messages { + t.Logf("received msg %d", atomic.LoadInt32(&count)) + atomic.AddInt32(&count, 1) + + if e, g := testNamespace, msg.MessageNamespace(); e != g { + t.Errorf("evt.MessageNamespace(): expected '%v', got '%v'", e, g) + } + + wg.Done() + } + }() + + wg.Wait() + + b.Unsubscribe(ctx, testNamespace, messages) + + if e, g := int32(5), count; e != g { + t.Errorf("message received count: expected '%v', got '%v'", e, g) + } +} diff --git a/pkg/bus/testing/request_reply.go b/pkg/bus/testing/request_reply.go new file mode 100644 index 0000000..22ceddd --- /dev/null +++ b/pkg/bus/testing/request_reply.go @@ -0,0 +1,110 @@ +package testing + +import ( + "context" + "sync" + "testing" + "time" + + "forge.cadoles.com/arcad/edge/pkg/bus" + "github.com/pkg/errors" +) + +const ( + testTypeReqRes bus.MessageNamespace = "testNamspaceReqRes" +) + +type testReqResMessage struct { + i int +} + +func (m *testReqResMessage) MessageNamespace() bus.MessageNamespace { + return testNamespace +} + +func TestRequestReply(t *testing.T, b bus.Bus) { + expectedRoundTrips := 256 + timeout := time.Now().Add(time.Duration(expectedRoundTrips) * time.Second) + + var ( + initWaitGroup sync.WaitGroup + resWaitGroup sync.WaitGroup + ) + + initWaitGroup.Add(1) + + go func() { + repondCtx, cancelRespond := context.WithDeadline(context.Background(), timeout) + defer cancelRespond() + + initWaitGroup.Done() + + err := b.Reply(repondCtx, testNamespace, func(msg bus.Message) (bus.Message, error) { + defer resWaitGroup.Done() + + req, ok := msg.(*testReqResMessage) + if !ok { + return nil, errors.WithStack(bus.ErrUnexpectedMessage) + } + + result := &testReqResMessage{req.i} + + // Simulate random work + time.Sleep(time.Millisecond * 100) + + t.Logf("[RES] sending res #%d", req.i) + + return result, nil + }) + if err != nil { + t.Error(err) + } + }() + + initWaitGroup.Wait() + + var reqWaitGroup sync.WaitGroup + + for i := 0; i < expectedRoundTrips; i++ { + resWaitGroup.Add(1) + reqWaitGroup.Add(1) + + go func(i int) { + defer reqWaitGroup.Done() + + requestCtx, cancelRequest := context.WithDeadline(context.Background(), timeout) + defer cancelRequest() + + req := &testReqResMessage{i} + + t.Logf("[REQ] sending req #%d", i) + + result, err := b.Request(requestCtx, req) + if err != nil { + t.Error(err) + } + + t.Logf("[REQ] received req #%d reply", i) + + if result == nil { + t.Error("result should not be nil") + + return + } + + res, ok := result.(*testReqResMessage) + if !ok { + t.Error(errors.WithStack(bus.ErrUnexpectedMessage)) + + return + } + + if e, g := req.i, res.i; e != g { + t.Errorf("res.i: expected '%v', got '%v'", e, g) + } + }(i) + } + + reqWaitGroup.Wait() + resWaitGroup.Wait() +} diff --git a/pkg/http/file.go b/pkg/http/file.go new file mode 100644 index 0000000..1367ad7 --- /dev/null +++ b/pkg/http/file.go @@ -0,0 +1,51 @@ +package http + +import ( + "io" + "io/fs" + "net/http" + "os" + + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +func serveFile(w http.ResponseWriter, r *http.Request, fs fs.FS, path string) { + ctx := logger.With(r.Context(), logger.F("path", path)) + + file, err := fs.Open(path) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + http.Error(w, http.StatusText(http.StatusNotFound), http.StatusNotFound) + + return + } + + logger.Error(ctx, "error while opening fs file", logger.E(errors.WithStack(err))) + http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError) + + return + } + + defer func() { + if err := file.Close(); err != nil { + logger.Error(ctx, "error while closing fs file", logger.E(errors.WithStack(err))) + } + }() + + info, err := file.Stat() + if err != nil { + logger.Error(ctx, "error while retrieving fs file stat", logger.E(errors.WithStack(err))) + + return + } + + reader, ok := file.(io.ReadSeeker) + if !ok { + logger.Error(ctx, "could not convert file to readseeker", logger.E(errors.WithStack(err))) + + return + } + + http.ServeContent(w, r, path, info.ModTime(), reader) +} diff --git a/pkg/http/handler.go b/pkg/http/handler.go new file mode 100644 index 0000000..09a4eda --- /dev/null +++ b/pkg/http/handler.go @@ -0,0 +1,106 @@ +package http + +import ( + "io/ioutil" + "net/http" + "strings" + "sync" + + "forge.cadoles.com/arcad/edge/pkg/app" + "forge.cadoles.com/arcad/edge/pkg/bundle" + "forge.cadoles.com/arcad/edge/pkg/bus" + "forge.cadoles.com/arcad/edge/pkg/sdk" + "github.com/igm/sockjs-go/v3/sockjs" + "github.com/pkg/errors" +) + +const ( + sockJSPathPrefix = "/sock" + clientJSPath = "/client.js" + serverMainScript = "server/main.js" +) + +type Handler struct { + bundle bundle.Bundle + public http.Handler + + sockjs http.Handler + bus bus.Bus + sockjsOpts sockjs.Options + + server *app.Server + serverModuleFactories []app.ServerModuleFactory + + mutex sync.RWMutex +} + +func (h *Handler) ServeHTTP(w http.ResponseWriter, r *http.Request) { + h.mutex.RLock() + defer h.mutex.RUnlock() + + switch { + case r.URL.Path == clientJSPath: + serveFile(w, r, &sdk.FS, "client/dist/client.js") + case r.URL.Path == clientJSPath+".map": + serveFile(w, r, &sdk.FS, "client/dist/client.js.map") + case strings.HasPrefix(r.URL.Path, sockJSPathPrefix): + h.sockjs.ServeHTTP(w, r) + default: + h.public.ServeHTTP(w, r) + } +} + +func (h *Handler) Load(bdle bundle.Bundle) error { + h.mutex.Lock() + defer h.mutex.Unlock() + + file, _, err := bdle.File(serverMainScript) + if err != nil { + return errors.Wrap(err, "could not open server main script") + } + + mainScript, err := ioutil.ReadAll(file) + if err != nil { + return errors.Wrap(err, "could not read server main script") + } + + server := app.NewServer(h.serverModuleFactories...) + + if err := server.Load(serverMainScript, string(mainScript)); err != nil { + return errors.WithStack(err) + } + + fs := bundle.NewFileSystem("public", bdle) + public := http.FileServer(fs) + sockjs := sockjs.NewHandler(sockJSPathPrefix, h.sockjsOpts, h.handleSockJSSession) + + if h.server != nil { + h.server.Stop() + } + + if err := server.Start(); err != nil { + return errors.WithStack(err) + } + + h.bundle = bdle + h.server = server + h.public = public + h.sockjs = sockjs + + return nil +} + +func NewHandler(funcs ...HandlerOptionFunc) *Handler { + opts := defaultHandlerOptions() + for _, fn := range funcs { + fn(opts) + } + + handler := &Handler{ + sockjsOpts: opts.SockJS, + serverModuleFactories: opts.ServerModuleFactories, + bus: opts.Bus, + } + + return handler +} diff --git a/pkg/http/options.go b/pkg/http/options.go new file mode 100644 index 0000000..f0fac3f --- /dev/null +++ b/pkg/http/options.go @@ -0,0 +1,49 @@ +package http + +import ( + "time" + + "forge.cadoles.com/arcad/edge/pkg/app" + "forge.cadoles.com/arcad/edge/pkg/bus" + "forge.cadoles.com/arcad/edge/pkg/bus/memory" + "github.com/igm/sockjs-go/v3/sockjs" +) + +type HandlerOptions struct { + Bus bus.Bus + SockJS sockjs.Options + ServerModuleFactories []app.ServerModuleFactory +} + +func defaultHandlerOptions() *HandlerOptions { + sockjsOptions := func() sockjs.Options { + return sockjs.DefaultOptions + }() + sockjsOptions.DisconnectDelay = 10 * time.Second + + return &HandlerOptions{ + Bus: memory.NewBus(), + SockJS: sockjsOptions, + ServerModuleFactories: make([]app.ServerModuleFactory, 0), + } +} + +type HandlerOptionFunc func(*HandlerOptions) + +func WithServerModules(factories ...app.ServerModuleFactory) HandlerOptionFunc { + return func(opts *HandlerOptions) { + opts.ServerModuleFactories = factories + } +} + +func WithSockJS(options sockjs.Options) HandlerOptionFunc { + return func(opts *HandlerOptions) { + opts.SockJS = options + } +} + +func WithBus(bus bus.Bus) HandlerOptionFunc { + return func(opts *HandlerOptions) { + opts.Bus = bus + } +} diff --git a/pkg/http/sockjs.go b/pkg/http/sockjs.go new file mode 100644 index 0000000..6b8a2bc --- /dev/null +++ b/pkg/http/sockjs.go @@ -0,0 +1,214 @@ +package http + +import ( + "context" + "encoding/json" + + "forge.cadoles.com/arcad/edge/pkg/module" + "github.com/igm/sockjs-go/v3/sockjs" + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +const ( + statusChannelClosed = iota +) + +func (h *Handler) handleSockJSSession(sess sockjs.Session) { + ctx := logger.With(sess.Request().Context(), + logger.F("sessionID", sess.ID()), + ) + + logger.Debug(ctx, "new sockjs session") + + defer func() { + if sess.GetSessionState() == sockjs.SessionActive { + if err := sess.Close(statusChannelClosed, "channel closed"); err != nil { + logger.Error(ctx, "could not close sockjs session", logger.E(errors.WithStack(err))) + } + } + }() + + go h.handleServerMessages(ctx, sess) + h.handleClientMessages(ctx, sess) +} + +func (h *Handler) handleServerMessages(ctx context.Context, sess sockjs.Session) { + messages, err := h.bus.Subscribe(ctx, module.MessageNamespaceServer) + if err != nil { + panic(errors.WithStack(err)) + } + + defer func() { + // Close messages subscriber + h.bus.Unsubscribe(ctx, module.MessageNamespaceServer, messages) + + logger.Debug(ctx, "unsubscribed") + + if sess.GetSessionState() != sockjs.SessionActive { + return + } + + if err := sess.Close(statusChannelClosed, "channel closed"); err != nil { + logger.Error(ctx, "could not close sockjs session", logger.E(errors.WithStack(err))) + } + }() + + for { + select { + case <-ctx.Done(): + return + + case msg := <-messages: + backendMessage, ok := msg.(*module.ServerMessage) + if !ok { + logger.Error( + ctx, + "unexpected backend message", + logger.F("message", msg), + ) + + continue + } + + payload, err := json.Marshal(backendMessage.Data) + if err != nil { + logger.Error( + ctx, + "could not encode message", + logger.E(err), + ) + + continue + } + + message := NewWebsocketMessage( + WebsocketMessageTypeMessage, + json.RawMessage(payload), + ) + + data, err := json.Marshal(message) + if err != nil { + logger.Error( + ctx, + "could not encode message", + logger.E(err), + ) + + continue + } + + logger.Debug(ctx, "sending message") + + // Send message + if err := sess.Send(string(data)); err != nil { + logger.Error( + ctx, + "could not send message", + logger.E(err), + ) + } + } + } +} + +func (h *Handler) handleClientMessages(ctx context.Context, sess sockjs.Session) { + for { + select { + case <-ctx.Done(): + logger.Debug(ctx, "context done") + + return + + default: + logger.Debug(ctx, "waiting for websocket data") + + data, err := sess.RecvCtx(ctx) + if err != nil { + if errors.Is(err, sockjs.ErrSessionNotOpen) { + break + } + + logger.Error( + ctx, + "could not read message", + logger.E(errors.WithStack(err)), + ) + + break + } + + logger.Debug(ctx, "websocket data received", logger.F("data", data)) + + message := &WebsocketMessage{} + if err := json.Unmarshal([]byte(data), message); err != nil { + logger.Error( + ctx, + "could not decode message", + logger.E(errors.WithStack(err)), + ) + + break + } + + switch { + + case message.Type == WebsocketMessageTypeMessage: + var payload map[string]interface{} + if err := json.Unmarshal(message.Payload, &payload); err != nil { + logger.Error( + ctx, + "could not decode payload", + logger.E(errors.WithStack(err)), + ) + + return + } + + ctx := logger.With(ctx, logger.F("payload", payload)) + + frontendMessage := module.NewFrontendMessage(payload) + + logger.Debug(ctx, "publishing new frontend message", logger.F("message", frontendMessage)) + + if err := h.bus.Publish(ctx, frontendMessage); err != nil { + logger.Error(ctx, "could not publish message", + logger.E(errors.WithStack(err)), + logger.F("message", frontendMessage), + ) + + return + } + + logger.Debug(ctx, "new frontend message published", logger.F("message", frontendMessage)) + + default: + logger.Error( + ctx, + "unsupported message type", + logger.F("messageType", message.Type), + ) + } + } + } +} + +const ( + WebsocketMessageTypeMessage = "message" +) + +type WebsocketMessage struct { + Type string `json:"t"` + Payload json.RawMessage `json:"p"` +} + +type WebsocketMessagePayload struct { + Data map[string]interface{} `json:"d"` +} + +func NewWebsocketMessage(dataType string, payload json.RawMessage) *WebsocketMessage { + return &WebsocketMessage{ + Type: dataType, + Payload: payload, + } +} diff --git a/pkg/module/.gitignore b/pkg/module/.gitignore new file mode 100644 index 0000000..6a91a43 --- /dev/null +++ b/pkg/module/.gitignore @@ -0,0 +1 @@ +*.sqlite \ No newline at end of file diff --git a/pkg/module/authorization.go b/pkg/module/authorization.go new file mode 100644 index 0000000..4bc2650 --- /dev/null +++ b/pkg/module/authorization.go @@ -0,0 +1,109 @@ +package module + +// import ( +// "context" +// "sync" + +// "forge.cadoles.com/arcad/edge/pkg/app" +// "forge.cadoles.com/arcad/edge/pkg/bus" +// "forge.cadoles.com/arcad/edge/pkg/repository" +// "github.com/dop251/goja" +// "github.com/pkg/errors" +// "gitlab.com/wpetit/goweb/logger" +// ) + +// type AuthorizationModule struct { +// appID app.ID +// bus bus.Bus +// backend *app.Server +// admins sync.Map +// } + +// func (m *AuthorizationModule) Name() string { +// return "authorization" +// } + +// func (m *AuthorizationModule) Export(export *goja.Object) { +// if err := export.Set("isAdmin", m.isAdmin); err != nil { +// panic(errors.Wrap(err, "could not set 'register' function")) +// } +// } + +// func (m *AuthorizationModule) isAdmin(call goja.FunctionCall) goja.Value { +// userID := call.Argument(0).String() +// if userID == "" { +// panic(errors.New("first argument must be a user id")) +// } + +// rawValue, exists := m.admins.Load(repository.UserID(userID)) +// if !exists { +// return m.backend.ToValue(false) +// } + +// isAdmin, ok := rawValue.(bool) +// if !ok { +// return m.backend.ToValue(false) +// } + +// return m.backend.ToValue(isAdmin) +// } + +// func (m *AuthorizationModule) handleEvents() { +// ctx := logger.With(context.Background(), logger.F("moduleAppID", m.appID)) + +// ns := AppMessageNamespace(m.appID) + +// userConnectedMessages, err := m.bus.Subscribe(ctx, ns, MessageTypeUserConnected) +// if err != nil { +// panic(errors.WithStack(err)) +// } + +// userDisconnectedMessages, err := m.bus.Subscribe(ctx, ns, MessageTypeUserDisconnected) +// if err != nil { +// panic(errors.WithStack(err)) +// } + +// defer func() { +// m.bus.Unsubscribe(ctx, ns, MessageTypeUserConnected, userConnectedMessages) +// m.bus.Unsubscribe(ctx, ns, MessageTypeUserDisconnected, userDisconnectedMessages) +// }() + +// for { +// select { +// case msg := <-userConnectedMessages: +// userConnectedMsg, ok := msg.(*MessageUserConnected) +// if !ok { +// continue +// } + +// logger.Debug(ctx, "user connected", logger.F("msg", userConnectedMsg)) + +// m.admins.Store(userConnectedMsg.UserID, userConnectedMsg.IsAdmin) + +// case msg := <-userDisconnectedMessages: +// userDisconnectedMsg, ok := msg.(*MessageUserDisconnected) +// if !ok { +// continue +// } + +// logger.Debug(ctx, "user disconnected", logger.F("msg", userDisconnectedMsg)) + +// m.admins.Delete(userDisconnectedMsg.UserID) +// } +// } +// } + +// func AuthorizationModuleFactory(b bus.Bus) app.ServerModuleFactory { +// return func(appID app.ID, backend *app.Server) app.ServerModule { +// mod := &AuthorizationModule{ +// appID: appID, +// bus: b, +// backend: backend, +// admins: sync.Map{}, +// } + +// go mod.handleEvents() + +// return mod +// } +// } diff --git a/pkg/module/authorization_test.go b/pkg/module/authorization_test.go new file mode 100644 index 0000000..bd3990b --- /dev/null +++ b/pkg/module/authorization_test.go @@ -0,0 +1,103 @@ +package module + +// import ( +// "context" +// "io/ioutil" +// "testing" +// "time" + +// "forge.cadoles.com/arcad/edge/pkg/app" +// "forge.cadoles.com/arcad/edge/pkg/bus/memory" +// ) + +// func TestAuthorizationModule(t *testing.T) { +// t.Parallel() + +// testAppID := app.ID("test-app") + +// b := memory.NewBus() + +// backend := app.NewServer(testAppID, +// ConsoleModuleFactory(), +// AuthorizationModuleFactory(b), +// ) + +// data, err := ioutil.ReadFile("testdata/authorization.js") +// if err != nil { +// t.Fatal(err) +// } + +// if err := backend.Load(string(data)); err != nil { +// t.Fatal(err) +// } + +// backend.Start() +// defer backend.Stop() + +// if err := backend.OnInit(); err != nil { +// t.Error(err) +// } + +// // Test non connected user + +// retValue, err := backend.ExecFuncByName("isAdmin", testUserID) +// if err != nil { +// t.Error(err) +// } + +// isAdmin := retValue.ToBoolean() + +// if e, g := false, isAdmin; e != g { +// t.Errorf("isAdmin: expected '%v', got '%v'", e, g) +// } + +// // Test user connection as normal user + +// ctx := context.Background() + +// b.Publish(ctx, NewMessageUserConnected(testAppID, testUserID, false)) +// time.Sleep(2 * time.Second) + +// retValue, err = backend.ExecFuncByName("isAdmin", testUserID) +// if err != nil { +// t.Error(err) +// } + +// isAdmin = retValue.ToBoolean() + +// if e, g := false, isAdmin; e != g { +// t.Errorf("isAdmin: expected '%v', got '%v'", e, g) +// } + +// // Test user connection as admin + +// b.Publish(ctx, NewMessageUserConnected(testAppID, testUserID, true)) +// time.Sleep(2 * time.Second) + +// retValue, err = backend.ExecFuncByName("isAdmin", testUserID) +// if err != nil { +// t.Error(err) +// } + +// isAdmin = retValue.ToBoolean() + +// if e, g := true, isAdmin; e != g { +// t.Errorf("isAdmin: expected '%v', got '%v'", e, g) +// } + +// // Test user disconnection + +// b.Publish(ctx, NewMessageUserDisconnected(testAppID, testUserID)) +// time.Sleep(2 * time.Second) + +// retValue, err = backend.ExecFuncByName("isAdmin", testUserID) +// if err != nil { +// t.Error(err) +// } + +// isAdmin = retValue.ToBoolean() + +// if e, g := false, isAdmin; e != g { +// t.Errorf("isAdmin: expected '%v', got '%v'", e, g) +// } +// } diff --git a/pkg/module/console.go b/pkg/module/console.go new file mode 100644 index 0000000..42872b5 --- /dev/null +++ b/pkg/module/console.go @@ -0,0 +1,44 @@ +package module + +import ( + "context" + "fmt" + "strings" + + "gitlab.com/wpetit/goweb/logger" + + "forge.cadoles.com/arcad/edge/pkg/app" + "github.com/dop251/goja" + "github.com/pkg/errors" +) + +type ConsoleModule struct{} + +func (m *ConsoleModule) Name() string { + return "console" +} + +func (m *ConsoleModule) log(call goja.FunctionCall) goja.Value { + var sb strings.Builder + + for _, arg := range call.Arguments { + sb.WriteString(fmt.Sprintf("%+v", arg.Export())) + sb.WriteString(" ") + } + + logger.Debug(context.Background(), sb.String()) + + return nil +} + +func (m *ConsoleModule) Export(export *goja.Object) { + if err := export.Set("log", m.log); err != nil { + panic(errors.Wrap(err, "could not set 'log' function")) + } +} + +func ConsoleModuleFactory() app.ServerModuleFactory { + return func(backend *app.Server) app.ServerModule { + return &ConsoleModule{} + } +} diff --git a/pkg/module/context.go b/pkg/module/context.go new file mode 100644 index 0000000..87d2ffb --- /dev/null +++ b/pkg/module/context.go @@ -0,0 +1,55 @@ +package module + +import ( + "context" + "sync" + + "forge.cadoles.com/arcad/edge/pkg/app" + "github.com/dop251/goja" + "github.com/pkg/errors" +) + +func assertContext(v goja.Value, r *goja.Runtime) context.Context { + if c, ok := v.Export().(context.Context); ok { + return c + } + + panic(r.NewTypeError("value should be a context")) +} + +type ContextModule struct { + ctx ServerContext + mutex sync.RWMutex +} + +func (m *ContextModule) Name() string { + return "context" +} + +func (m *ContextModule) get(call goja.FunctionCall, rt *goja.Runtime) goja.Value { + m.mutex.RLock() + defer m.mutex.RUnlock() + + return rt.ToValue(m.ctx) +} + +func (m *ContextModule) Export(export *goja.Object) { + if err := export.Set("get", m.get); err != nil { + panic(errors.Wrap(err, "could not set 'get' function")) + } +} + +func ContextModuleFactory() app.ServerModuleFactory { + return func(backend *app.Server) app.ServerModule { + return &ContextModule{ + ctx: ServerContext{ + Context: context.Background(), + }, + mutex: sync.RWMutex{}, + } + } +} + +type ServerContext struct { + context.Context +} diff --git a/pkg/module/error.go b/pkg/module/error.go new file mode 100644 index 0000000..431c009 --- /dev/null +++ b/pkg/module/error.go @@ -0,0 +1,5 @@ +package module + +import "github.com/pkg/errors" + +var ErrUnexpectedArgumentsNumber = errors.New("unexpected number of arguments") diff --git a/pkg/module/file.go b/pkg/module/file.go new file mode 100644 index 0000000..23b1e44 --- /dev/null +++ b/pkg/module/file.go @@ -0,0 +1,293 @@ +package module + +// import ( +// "context" +// "fmt" +// "io" +// "mime/multipart" +// "os" +// "path/filepath" +// "time" + +// "forge.cadoles.com/arcad/edge/pkg/app" +// "forge.cadoles.com/arcad/edge/pkg/bus" +// "github.com/dop251/goja" +// "github.com/genjidb/genji" +// "github.com/genjidb/genji/database" +// "github.com/genjidb/genji/document" +// "github.com/google/uuid" +// "github.com/pkg/errors" +// "github.com/spf13/afero" +// "gitlab.com/wpetit/goweb/logger" +// ) + +// const ( +// fileKeyID = "id" +// collectionFiles = "files" +// ) + +// type FileEntry struct { +// ID string +// Filename string +// Size int64 +// ContentType string +// CreatedAt string +// Metadata map[string]interface{} +// } + +// type FileModule struct { +// appID app.ID +// backend *app.Server +// bus bus.Bus +// db *AppDatabaseMixin +// dataDir string +// fs afero.Fs +// } + +// func (m *FileModule) Name() string { +// return "file" +// } + +// func (m *FileModule) Export(export *goja.Object) { +// // if err := export.Set("ls", m.ls); err != nil { +// // panic(errors.Wrap(err, "could not set 'save' function")) +// // } +// } + +// func (m *FileModule) logAndPanic(msg string, err error) { +// err = errors.Wrap(err, msg) +// logger.Error(context.Background(), msg, logger.E(err)) +// panic(errors.WithStack(err)) +// } + +// func (m *FileModule) handleMessages() { +// ctx := context.Background() + +// ns := createAppMessageNamespace(m.appID) + +// go func() { +// err := m.bus.Reply(ctx, ns, MessageTypeUploadRequest, func(msg bus.Message) (bus.Message, error) { +// uploadRequest, ok := msg.(*MessageUploadRequest) +// if !ok { +// return nil, errors.Wrapf(bus.ErrUnexpectedMessage, "expected message upload request, got '%s'", msg.MessageType()) +// } + +// res, err := m.handleUploadRequest(uploadRequest) +// if err != nil { +// logger.Error(ctx, "could not handle upload request", logger.E(errors.WithStack(err))) + +// return nil, errors.WithStack(err) +// } + +// logger.Debug(ctx, "upload request response", logger.F("response", res)) + +// return res, nil +// }) +// if err != nil { +// panic(errors.WithStack(err)) +// } +// }() + +// err := m.bus.Reply(ctx, ns, MessageTypeDownloadRequest, func(msg bus.Message) (bus.Message, error) { +// downloadRequest, ok := msg.(*MessageDownloadRequest) +// if !ok { +// return nil, errors.Wrapf(bus.ErrUnexpectedMessage, "expected message download request, got '%s'", msg.MessageType()) +// } + +// res, err := m.handleDownloadRequest(downloadRequest) +// if err != nil { +// logger.Error(ctx, "could not handle download request", logger.E(errors.WithStack(err))) + +// return nil, errors.WithStack(err) +// } + +// return res, nil +// }) +// if err != nil { +// panic(errors.WithStack(err)) +// } +// } + +// func (m *FileModule) handleUploadRequest(req *MessageUploadRequest) (*MessageUploadResponse, error) { +// fileInfo := map[string]interface{}{ +// "filename": req.Header.Filename, +// "contentType": req.Header.Header.Get("Content-Type"), +// "size": req.Header.Size, +// "metadata": req.Metadata, +// } + +// res := NewMessageUploadResponse(req.AppID, req.UserID, req.RequestID) +// fileID := uuid.New().String() + +// result, err := m.backend.ExecFuncByName("onFileUpload", req.UserID, fileID, fileInfo) +// if err != nil { +// if errors.Is(err, app.ErrFuncDoesNotExist) { +// res.Allow = false + +// return res, nil +// } + +// return nil, errors.WithStack(err) +// } + +// res.Allow = result.ToBoolean() + +// if res.Allow { +// if err := m.saveFile(fileID, req.UserID, req.Header, req.File, req.Metadata); err != nil { +// return nil, errors.WithStack(err) +// } + +// res.FileID = fileID +// } + +// return res, nil +// } + +// func (m *FileModule) saveFile(fileID string, header *multipart.FileHeader, file multipart.File, metadata map[string]interface{}) error { +// err := m.db.WithCollectionTx(collectionFiles, func(tx *genji.Tx) error { +// entry := &FileEntry{ +// ID: fileID, +// Filename: header.Filename, +// Size: header.Size, +// ContentType: header.Header.Get("Content-Type"), +// CreatedAt: time.Now().UTC().String(), +// Metadata: metadata, +// } + +// insertQuery := fmt.Sprintf("INSERT INTO `%s` VALUES ?", collectionFiles) +// if err := tx.Exec(insertQuery, &entry); err != nil { +// return errors.WithStack(err) +// } + +// fileDir := m.getFileDir(fileID) + +// if err := m.fs.MkdirAll(fileDir, 0o755); err != nil { +// return errors.WithStack(err) +// } + +// filePath := filepath.Join(fileDir, fileID) + +// newFile, err := m.fs.Create(filePath) +// if err != nil { +// return errors.WithStack(err) +// } + +// defer newFile.Close() + +// if _, err := io.Copy(newFile, file); err != nil { +// return errors.WithStack(err) +// } + +// return nil +// }) +// if err != nil { +// return errors.WithStack(err) +// } + +// return nil +// } + +// func (m *FileModule) handleDownloadRequest(req *MessageDownloadRequest) (*MessageDownloadResponse, error) { +// res := NewMessageDownloadResponse(req.AppID, req.UserID, req.RequestID) + +// result, err := m.backend.ExecFuncByName("onFileDownload", req.UserID, req.FileID) +// if err != nil { +// if errors.Is(err, app.ErrFuncDoesNotExist) { +// res.Allow = false + +// return res, nil +// } + +// return nil, errors.WithStack(err) +// } + +// res.Allow = result.ToBoolean() + +// file, fileEntry, err := m.openFile(req.FileID) +// if err != nil && !os.IsNotExist(errors.Cause(err)) { +// return nil, errors.WithStack(err) +// } + +// if file != nil { +// res.File = file +// } + +// if fileEntry != nil { +// res.Filename = fileEntry.Filename +// res.ContentType = fileEntry.ContentType +// res.Size = fileEntry.Size +// } + +// return res, nil +// } + +// func (m *FileModule) openFile(fileID string) (afero.File, *FileEntry, error) { +// var ( +// fileEntry *FileEntry +// file afero.File +// ) + +// err := m.db.WithCollectionTx(collectionFiles, func(tx *genji.Tx) error { +// selectQuery := fmt.Sprintf("SELECT * FROM `%s` WHERE id = ?", collectionFiles) + +// doc, err := tx.QueryDocument(selectQuery, fileID) +// if err != nil { +// if errors.Is(err, database.ErrDocumentNotFound) { +// return nil +// } + +// return errors.WithStack(err) +// } + +// fileEntry = &FileEntry{} + +// if err := document.StructScan(doc, fileEntry); err != nil { +// return errors.WithStack(err) +// } + +// fileDir := m.getFileDir(fileID) +// filePath := filepath.Join(fileDir, fileID) + +// file, err = m.fs.Open(filePath) +// if err != nil { +// file = nil + +// return errors.WithStack(err) +// } + +// return nil +// }) +// if err != nil { +// return nil, nil, errors.WithStack(err) +// } + +// return file, fileEntry, nil +// } + +// func (m *FileModule) getFileDir(fileID string) string { +// return filepath.Join(m.dataDir, string(m.appID), "files", fileID[0:2], fileID[2:4], fileID[4:6]) +// } + +// func FileModuleFactory(dataDir string, bus bus.Bus) app.ServerModuleFactory { +// return func(appID app.ID, backend *app.Server) app.ServerModule { +// var fs afero.Fs +// if dataDir == inMemory { +// fs = afero.NewMemMapFs() +// } else { +// fs = afero.NewOsFs() +// } + +// mod := &FileModule{ +// dataDir: dataDir, +// appID: appID, +// bus: bus, +// backend: backend, +// db: NewAppDatabaseMixin(dataDir, "file.db", appID), +// fs: fs, +// } + +// go mod.handleMessages() + +// return mod +// } +// } diff --git a/pkg/module/file_message.go b/pkg/module/file_message.go new file mode 100644 index 0000000..10abea7 --- /dev/null +++ b/pkg/module/file_message.go @@ -0,0 +1,127 @@ +package module + +// import ( +// "io" +// "mime/multipart" + +// "forge.cadoles.com/arcad/edge/pkg/app" +// "forge.cadoles.com/arcad/edge/pkg/bus" +// "github.com/google/uuid" +// ) + +// const ( +// MessageTypeUploadRequest bus.MessageType = "uploadRequest" +// MessageTypeUploadResponse bus.MessageType = "uploadResponse" +// MessageTypeDownloadRequest bus.MessageType = "downloadRequest" +// MessageTypeDownloadResponse bus.MessageType = "downloadResponse" +// ) + +// type MessageUploadRequest struct { +// AppID app.ID +// RequestID string +// Header *multipart.FileHeader +// File multipart.File +// Metadata map[string]interface{} + +// ns bus.MessageNamespace +// } + +// func (m *MessageUploadRequest) MessageNamespace() bus.MessageNamespace { +// return m.ns +// } + +// func (m *MessageUploadRequest) MessageType() bus.MessageType { +// return MessageTypeUploadRequest +// } + +// func NewMessageUploadRequest(appID app.ID, header *multipart.FileHeader, file multipart.File, metadata map[string]interface{}) *MessageUploadRequest { +// return &MessageUploadRequest{ +// AppID: appID, +// RequestID: uuid.New().String(), +// Header: header, +// File: file, +// Metadata: metadata, + +// ns: AppMessageNamespace(appID), +// } +// } + +// type MessageUploadResponse struct { +// AppID app.ID +// RequestID string +// FileID string +// Allow bool + +// ns bus.MessageNamespace +// } + +// func (m *MessageUploadResponse) MessageNamespace() bus.MessageNamespace { +// return m.ns +// } + +// func (m *MessageUploadResponse) MessageType() bus.MessageType { +// return MessageTypeUploadResponse +// } + +// func NewMessageUploadResponse(appID app.ID, requestID string) *MessageUploadResponse { +// return &MessageUploadResponse{ +// AppID: appID, +// RequestID: requestID, + +// ns: AppMessageNamespace(appID), +// } +// } + +// type MessageDownloadRequest struct { +// AppID app.ID +// RequestID string +// FileID string + +// ns bus.MessageNamespace +// } + +// func (m *MessageDownloadRequest) MessageNamespace() bus.MessageNamespace { +// return m.ns +// } + +// func (m *MessageDownloadRequest) MessageType() bus.MessageType { +// return MessageTypeDownloadRequest +// } + +// func NewMessageDownloadRequest(appID app.ID, fileID string) *MessageDownloadRequest { +// return &MessageDownloadRequest{ +// AppID: appID, +// RequestID: uuid.New().String(), +// FileID: fileID, +// ns: AppMessageNamespace(appID), +// } +// } + +// type MessageDownloadResponse struct { +// AppID app.ID +// RequestID string +// Allow bool +// File io.ReadCloser +// ContentType string +// Filename string +// Size int64 + +// ns bus.MessageNamespace +// } + +// func (m *MessageDownloadResponse) MessageNamespace() bus.MessageNamespace { +// return m.ns +// } + +// func (e *MessageDownloadResponse) MessageType() bus.MessageType { +// return MessageTypeDownloadResponse +// } + +// func NewMessageDownloadResponse(appID app.ID, requestID string) *MessageDownloadResponse { +// return &MessageDownloadResponse{ +// AppID: appID, +// RequestID: requestID, + +// ns: AppMessageNamespace(appID), +// } +// } diff --git a/pkg/module/lifecycle.go b/pkg/module/lifecycle.go new file mode 100644 index 0000000..a95abf4 --- /dev/null +++ b/pkg/module/lifecycle.go @@ -0,0 +1,191 @@ +package module + +import ( + "context" + + "forge.cadoles.com/arcad/edge/pkg/app" + "forge.cadoles.com/arcad/edge/pkg/bus" + "github.com/dop251/goja" + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +type LifecycleModule struct { + backend *app.Server + bus bus.Bus +} + +func (m *LifecycleModule) Name() string { + return "lifecycle" +} + +func (m *LifecycleModule) Export(export *goja.Object) { +} + +func (m *LifecycleModule) OnInit() error { + if _, err := m.backend.ExecFuncByName("onInit"); err != nil { + if errors.Is(err, app.ErrFuncDoesNotExist) { + logger.Warn(context.Background(), "could not find onInit() function", logger.E(errors.WithStack(err))) + + return errors.WithStack(err) + } + } + + return nil +} + +func (m *LifecycleModule) handleMessages() { + ctx := context.Background() + + logger.Debug( + ctx, + "subscribing to bus messages", + ) + + userConnectedMessages, err := m.bus.Subscribe(ctx, MessageNamespaceUserConnected) + if err != nil { + panic(errors.WithStack(err)) + } + + userDisconnectedMessages, err := m.bus.Subscribe(ctx, MessageNamespaceUserDisconnected) + if err != nil { + panic(errors.WithStack(err)) + } + + frontendMessageMessages, err := m.bus.Subscribe(ctx, MessageNamespaceFrontend) + if err != nil { + panic(errors.WithStack(err)) + } + + defer func() { + logger.Debug( + ctx, + "unsubscribing from bus messages", + ) + + m.bus.Unsubscribe(ctx, MessageNamespaceFrontend, frontendMessageMessages) + m.bus.Unsubscribe(ctx, MessageNamespaceUserDisconnected, userDisconnectedMessages) + m.bus.Unsubscribe(ctx, MessageNamespaceUserConnected, userConnectedMessages) + }() + + for { + logger.Debug( + ctx, + "waiting for next message", + ) + select { + case <-ctx.Done(): + logger.Debug( + ctx, + "context done", + ) + + return + + case msg := <-userConnectedMessages: + userConnected, ok := msg.(*UserConnectedMessage) + if !ok { + logger.Error( + ctx, + "unexpected message type", + logger.F("message", msg), + ) + + continue + } + + logger.Debug( + ctx, + "received user connected message", + logger.F("message", userConnected), + ) + + if _, err := m.backend.ExecFuncByName("onUserConnect"); err != nil { + if errors.Is(err, app.ErrFuncDoesNotExist) { + continue + } + + logger.Error( + ctx, + "on user connected error", + logger.E(err), + ) + } + + case msg := <-userDisconnectedMessages: + userDisconnected, ok := msg.(*UserDisconnectedMessage) + if !ok { + logger.Error( + ctx, + "unexpected message type", + logger.F("message", msg), + ) + + continue + } + + logger.Debug( + ctx, + "received user disconnected message", + logger.F("message", userDisconnected), + ) + + if _, err := m.backend.ExecFuncByName("onUserDisconnect"); err != nil { + if errors.Is(err, app.ErrFuncDoesNotExist) { + continue + } + + logger.Error( + ctx, + "on user disconnected error", + logger.E(err), + ) + } + + case msg := <-frontendMessageMessages: + frontendMessage, ok := msg.(*FrontendMessage) + if !ok { + logger.Error( + ctx, + "unexpected message type", + logger.F("message", msg), + ) + + continue + } + + logger.Debug( + ctx, + "received frontend message", + logger.F("message", frontendMessage), + ) + + if _, err := m.backend.ExecFuncByName("onUserMessage", frontendMessage.Data); err != nil { + if errors.Is(err, app.ErrFuncDoesNotExist) { + continue + } + + logger.Error( + ctx, + "on user message error", + logger.E(err), + ) + } + } + } +} + +func LifecycleModuleFactory(bus bus.Bus) app.ServerModuleFactory { + return func(backend *app.Server) app.ServerModule { + module := &LifecycleModule{ + backend: backend, + bus: bus, + } + + go module.handleMessages() + + return module + } +} + +var _ app.InitializableModule = &LifecycleModule{} diff --git a/pkg/module/lifecycle_message.go b/pkg/module/lifecycle_message.go new file mode 100644 index 0000000..0262aef --- /dev/null +++ b/pkg/module/lifecycle_message.go @@ -0,0 +1,56 @@ +package module + +import ( + "forge.cadoles.com/arcad/edge/pkg/bus" +) + +const ( + MessageNamespaceFrontend bus.MessageNamespace = "frontend" + MessageNamespaceServer bus.MessageNamespace = "backend" + MessageNamespaceUserConnected bus.MessageNamespace = "userConnected" + MessageNamespaceUserDisconnected bus.MessageNamespace = "userDisconnected" +) + +type UserConnectedMessage struct{} + +func (m *UserConnectedMessage) MessageNamespace() bus.MessageNamespace { + return MessageNamespaceUserConnected +} + +func NewMessageUserConnected() *UserConnectedMessage { + return &UserConnectedMessage{} +} + +type UserDisconnectedMessage struct{} + +func (m *UserDisconnectedMessage) MessageNamespace() bus.MessageNamespace { + return MessageNamespaceUserDisconnected +} + +func NewMessageUserDisconnected() *UserDisconnectedMessage { + return &UserDisconnectedMessage{} +} + +type ServerMessage struct { + Data interface{} +} + +func (m *ServerMessage) MessageNamespace() bus.MessageNamespace { + return MessageNamespaceServer +} + +func NewServerMessage(data interface{}) *ServerMessage { + return &ServerMessage{data} +} + +type FrontendMessage struct { + Data map[string]interface{} +} + +func (m *FrontendMessage) MessageNamespace() bus.MessageNamespace { + return MessageNamespaceFrontend +} + +func NewFrontendMessage(data map[string]interface{}) *FrontendMessage { + return &FrontendMessage{data} +} diff --git a/pkg/module/net.go b/pkg/module/net.go new file mode 100644 index 0000000..4ae45c6 --- /dev/null +++ b/pkg/module/net.go @@ -0,0 +1,68 @@ +package module + +import ( + "context" + + "forge.cadoles.com/arcad/edge/pkg/app" + "forge.cadoles.com/arcad/edge/pkg/bus" + "github.com/dop251/goja" + "github.com/pkg/errors" +) + +type NetModule struct { + backend *app.Server + bus bus.Bus +} + +func (m *NetModule) Name() string { + return "net" +} + +func (m *NetModule) Export(export *goja.Object) { + if err := export.Set("broadcast", m.broadcast); err != nil { + panic(errors.Wrap(err, "could not set 'broadcast' function")) + } + + if err := export.Set("send", m.send); err != nil { + panic(errors.Wrap(err, "could not set 'send' function")) + } +} + +func (m *NetModule) broadcast(call goja.FunctionCall) goja.Value { + if len(call.Arguments) < 1 { + panic(m.backend.ToValue("invalid number of argument")) + } + + data := call.Arguments[0].Export() + + msg := NewServerMessage(data) + if err := m.bus.Publish(context.Background(), msg); err != nil { + panic(errors.WithStack(err)) + } + + return nil +} + +func (m *NetModule) send(call goja.FunctionCall) goja.Value { + if len(call.Arguments) < 1 { + panic(m.backend.ToValue("invalid number of argument")) + } + + data := call.Arguments[0].Export() + + msg := NewServerMessage(data) + if err := m.bus.Publish(context.Background(), msg); err != nil { + panic(errors.WithStack(err)) + } + + return nil +} + +func NetModuleFactory(bus bus.Bus) app.ServerModuleFactory { + return func(backend *app.Server) app.ServerModule { + return &NetModule{ + backend: backend, + bus: bus, + } + } +} diff --git a/pkg/module/rpc.go b/pkg/module/rpc.go new file mode 100644 index 0000000..f503c26 --- /dev/null +++ b/pkg/module/rpc.go @@ -0,0 +1,238 @@ +package module + +import ( + "context" + "fmt" + "sync" + + "forge.cadoles.com/arcad/edge/pkg/app" + "forge.cadoles.com/arcad/edge/pkg/bus" + "github.com/dop251/goja" + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +type RPCRequest struct { + Method string + Params interface{} + ID interface{} +} + +type RPCError struct { + Code int `json:"code"` + Message string `json:"message"` + Data interface{} `json:"data"` +} + +type RPCResponse struct { + Result interface{} + Error *RPCError + ID interface{} +} + +type RPCModule struct { + backend *app.Server + bus bus.Bus + callbacks sync.Map +} + +func (m *RPCModule) Name() string { + return "rpc" +} + +func (m *RPCModule) Export(export *goja.Object) { + if err := export.Set("register", m.register); err != nil { + panic(errors.Wrap(err, "could not set 'register' function")) + } + + if err := export.Set("unregister", m.unregister); err != nil { + panic(errors.Wrap(err, "could not set 'unregister' function")) + } +} + +func (m *RPCModule) register(call goja.FunctionCall) goja.Value { + fnName := call.Argument(0).String() + if fnName == "" { + panic(errors.New("First argument must be a function name")) + } + + ctx := context.Background() + + logger.Debug(ctx, "registering method", logger.F("method", fnName)) + + m.callbacks.Store(fnName, nil) + + return nil +} + +func (m *RPCModule) unregister(call goja.FunctionCall) goja.Value { + fnName := call.Argument(0).String() + if fnName == "" { + panic(errors.New("First argument must be a function name")) + } + + m.callbacks.Delete(fnName) + + return nil +} + +func (m *RPCModule) handleMessages() { + ctx := context.Background() + + frontendMessages, err := m.bus.Subscribe(ctx, MessageNamespaceFrontend) + if err != nil { + panic(errors.WithStack(err)) + } + + defer func() { + m.bus.Unsubscribe(ctx, MessageNamespaceFrontend, frontendMessages) + }() + + sendRes := func(ctx context.Context, req *RPCRequest, result goja.Value) { + res := &RPCResponse{ + ID: req.ID, + Result: result.Export(), + } + + logger.Debug(ctx, "sending rpc response", logger.F("response", res)) + + if err := m.sendResponse(ctx, res); err != nil { + logger.Error( + ctx, "could not send response", + logger.E(errors.WithStack(err)), + logger.F("response", res), + logger.F("request", req), + ) + } + } + + for msg := range frontendMessages { + frontendMessage, ok := msg.(*FrontendMessage) + if !ok { + logger.Warn(ctx, "unexpected bus message", logger.F("message", msg)) + + continue + } + + ok, req := m.isRPCRequest(frontendMessage) + if !ok { + continue + } + + logger.Debug(ctx, "received rpc request", logger.F("request", req)) + + if _, exists := m.callbacks.Load(req.Method); !exists { + logger.Debug(ctx, "method not found", logger.F("req", req)) + + if err := m.sendMethodNotFoundResponse(ctx, req); err != nil { + logger.Error( + ctx, "could not send method not found response", + logger.E(errors.WithStack(err)), + logger.F("request", req), + ) + } + + continue + } + + result, err := m.backend.ExecFuncByName(req.Method, req.Params) + if err != nil { + if err := m.sendErrorResponse(ctx, req, err); err != nil { + logger.Error( + ctx, "could not send error response", + logger.E(errors.WithStack(err)), + logger.F("originalError", err), + logger.F("request", req), + ) + } + + continue + } + + promise, ok := m.backend.IsPromise(result) + if ok { + go func(ctx context.Context, req *RPCRequest, promise *goja.Promise) { + result := m.backend.WaitForPromise(promise) + sendRes(ctx, req, result) + }(ctx, req, promise) + } else { + sendRes(ctx, req, result) + } + } +} + +func (m *RPCModule) sendErrorResponse(ctx context.Context, req *RPCRequest, err error) error { + return m.sendResponse(ctx, &RPCResponse{ + ID: req.ID, + Result: nil, + Error: &RPCError{ + Code: -32603, + Message: err.Error(), + }, + }) +} + +func (m *RPCModule) sendMethodNotFoundResponse(ctx context.Context, req *RPCRequest) error { + return m.sendResponse(ctx, &RPCResponse{ + ID: req.ID, + Result: nil, + Error: &RPCError{ + Code: -32601, + Message: fmt.Sprintf("method not found"), + }, + }) +} + +func (m *RPCModule) sendResponse(ctx context.Context, res *RPCResponse) error { + msg := NewServerMessage(map[string]interface{}{ + "jsonrpc": "2.0", + "id": res.ID, + "error": res.Error, + "result": res.Result, + }) + + if err := m.bus.Publish(ctx, msg); err != nil { + return errors.WithStack(err) + } + + return nil +} + +func (m *RPCModule) isRPCRequest(msg *FrontendMessage) (bool, *RPCRequest) { + jsonRPC, exists := msg.Data["jsonrpc"] + if !exists || jsonRPC != "2.0" { + return false, nil + } + + rawMethod, exists := msg.Data["method"] + if !exists { + return false, nil + } + + method, ok := rawMethod.(string) + if !ok { + return false, nil + } + + id := msg.Data["id"] + params := msg.Data["params"] + + return true, &RPCRequest{ + ID: id, + Method: method, + Params: params, + } +} + +func RPCModuleFactory(bus bus.Bus) app.ServerModuleFactory { + return func(backend *app.Server) app.ServerModule { + mod := &RPCModule{ + backend: backend, + bus: bus, + } + + go mod.handleMessages() + + return mod + } +} diff --git a/pkg/module/store.go b/pkg/module/store.go new file mode 100644 index 0000000..14295f6 --- /dev/null +++ b/pkg/module/store.go @@ -0,0 +1,200 @@ +package module + +import ( + "fmt" + + "forge.cadoles.com/arcad/edge/pkg/app" + "forge.cadoles.com/arcad/edge/pkg/storage" + "forge.cadoles.com/arcad/edge/pkg/storage/filter" + "github.com/davecgh/go-spew/spew" + "github.com/dop251/goja" + "github.com/mitchellh/mapstructure" + "github.com/pkg/errors" +) + +type StoreModule struct { + backend *app.Server + store storage.DocumentStore +} + +func (m *StoreModule) Name() string { + return "store" +} + +func (m *StoreModule) Export(export *goja.Object) { + if err := export.Set("upsert", m.upsert); err != nil { + panic(errors.Wrap(err, "could not set 'upsert' function")) + } + + if err := export.Set("get", m.get); err != nil { + panic(errors.Wrap(err, "could not set 'get' function")) + } + + if err := export.Set("query", m.query); err != nil { + panic(errors.Wrap(err, "could not set 'query' function")) + } + + if err := export.Set("delete", m.delete); err != nil { + panic(errors.Wrap(err, "could not set 'delete' function")) + } +} + +func (m *StoreModule) upsert(call goja.FunctionCall, rt *goja.Runtime) goja.Value { + ctx := assertContext(call.Argument(0), rt) + collection := m.assertCollection(call.Argument(1), rt) + document := m.assertDocument(call.Argument(2), rt) + + document, err := m.store.Upsert(ctx, collection, document) + if err != nil { + panic(errors.Wrapf(err, "error while upserting document in collection '%s'", collection)) + } + + spew.Dump(document) + + return rt.ToValue(map[string]interface{}(document)) +} + +func (m *StoreModule) get(call goja.FunctionCall, rt *goja.Runtime) goja.Value { + ctx := assertContext(call.Argument(0), rt) + collection := m.assertCollection(call.Argument(1), rt) + documentID := m.assertDocumentID(call.Argument(2), rt) + + document, err := m.store.Get(ctx, collection, documentID) + if err != nil { + if errors.Is(err, storage.ErrDocumentNotFound) { + return nil + } + + panic(errors.Wrapf(err, "error while getting document '%s' in collection '%s'", documentID, collection)) + } + + return rt.ToValue(map[string]interface{}(document)) +} + +type queryOptions struct { + Limit *int `mapstructure:"limit"` + Offset *int `mapstructure:"offset"` + OrderBy *string `mapstructure:"orderBy"` + OrderDirection *string `mapstructure:"orderDirection"` +} + +func (m *StoreModule) query(call goja.FunctionCall, rt *goja.Runtime) goja.Value { + ctx := assertContext(call.Argument(0), rt) + collection := m.assertCollection(call.Argument(1), rt) + filter := m.assertFilter(call.Argument(2), rt) + queryOptions := m.assertQueryOptions(call.Argument(3), rt) + + queryOptionsFuncs := make([]storage.QueryOptionFunc, 0) + + if queryOptions.Limit != nil { + queryOptionsFuncs = append(queryOptionsFuncs, storage.WithLimit(*queryOptions.Limit)) + } + + if queryOptions.OrderBy != nil { + queryOptionsFuncs = append(queryOptionsFuncs, storage.WithOrderBy(*queryOptions.OrderBy)) + } + + if queryOptions.Offset != nil { + queryOptionsFuncs = append(queryOptionsFuncs, storage.WithOffset(*queryOptions.Limit)) + } + + if queryOptions.OrderDirection != nil { + queryOptionsFuncs = append(queryOptionsFuncs, storage.WithOrderDirection( + storage.OrderDirection(*queryOptions.OrderDirection), + )) + } + + documents, err := m.store.Query(ctx, collection, filter, queryOptionsFuncs...) + if err != nil { + panic(errors.Wrapf(err, "error while querying documents in collection '%s'", collection)) + } + + rawDocuments := make([]map[string]interface{}, len(documents)) + for idx, doc := range documents { + rawDocuments[idx] = map[string]interface{}(doc) + } + + return rt.ToValue(rawDocuments) +} + +func (m *StoreModule) delete(call goja.FunctionCall, rt *goja.Runtime) goja.Value { + ctx := assertContext(call.Argument(0), rt) + collection := m.assertCollection(call.Argument(1), rt) + documentID := m.assertDocumentID(call.Argument(2), rt) + + if err := m.store.Delete(ctx, collection, documentID); err != nil { + panic(errors.Wrapf(err, "error while deleting document '%s' in collection '%s'", documentID, collection)) + } + + return nil +} + +func (m *StoreModule) assertCollection(value goja.Value, rt *goja.Runtime) string { + collection, ok := value.Export().(string) + if !ok { + panic(rt.NewTypeError(fmt.Sprintf("collection must be a string, got '%T'", value.Export()))) + } + + return collection +} + +func (m *StoreModule) assertFilter(value goja.Value, rt *goja.Runtime) *filter.Filter { + rawFilter, ok := value.Export().(map[string]interface{}) + if !ok { + panic(rt.NewTypeError(fmt.Sprintf("filter must be an object, got '%T'", value.Export()))) + } + + filter, err := filter.NewFrom(rawFilter) + if err != nil { + panic(errors.Wrap(err, "could not convert object to filter")) + } + + return filter +} + +func (m *StoreModule) assertDocumentID(value goja.Value, rt *goja.Runtime) storage.DocumentID { + documentID, ok := value.Export().(storage.DocumentID) + if !ok { + rawDocumentID, ok := value.Export().(string) + if !ok { + panic(rt.NewTypeError(fmt.Sprintf("document id must be a documentid or a string, got '%T'", value.Export()))) + } + + documentID = storage.DocumentID(rawDocumentID) + } + + return documentID +} + +func (m *StoreModule) assertQueryOptions(value goja.Value, rt *goja.Runtime) *queryOptions { + rawQueryOptions, ok := value.Export().(map[string]interface{}) + if !ok { + panic(rt.NewTypeError(fmt.Sprintf("query options must be an object, got '%T'", value.Export()))) + } + + queryOptions := &queryOptions{} + + if err := mapstructure.Decode(rawQueryOptions, queryOptions); err != nil { + panic(errors.Wrap(err, "could not convert object to query options")) + } + + return queryOptions +} + +func (m *StoreModule) assertDocument(value goja.Value, rt *goja.Runtime) storage.Document { + document, ok := value.Export().(map[string]interface{}) + if !ok { + panic(rt.NewTypeError("document must be an object")) + } + + return document +} + +func StoreModuleFactory(store storage.DocumentStore) app.ServerModuleFactory { + return func(backend *app.Server) app.ServerModule { + return &StoreModule{ + backend: backend, + store: store, + } + } +} diff --git a/pkg/module/store_test.go b/pkg/module/store_test.go new file mode 100644 index 0000000..d3739f7 --- /dev/null +++ b/pkg/module/store_test.go @@ -0,0 +1,41 @@ +package module + +import ( + "io/ioutil" + "testing" + + "forge.cadoles.com/arcad/edge/pkg/app" + "forge.cadoles.com/arcad/edge/pkg/storage/sqlite" + "github.com/pkg/errors" + "gitlab.com/wpetit/goweb/logger" +) + +func TestStoreModule(t *testing.T) { + logger.SetLevel(logger.LevelDebug) + + store := sqlite.NewDocumentStore(":memory:") + backend := app.NewServer( + ContextModuleFactory(), + ConsoleModuleFactory(), + StoreModuleFactory(store), + ) + + data, err := ioutil.ReadFile("testdata/store.js") + if err != nil { + t.Fatalf("%+v", errors.WithStack(err)) + } + + if err := backend.Load("testdata/store.js", string(data)); err != nil { + t.Fatalf("%+v", errors.WithStack(err)) + } + + if err := backend.Start(); err != nil { + t.Fatalf("%+v", errors.WithStack(err)) + } + + if _, err := backend.ExecFuncByName("testStore"); err != nil { + t.Fatalf("%+v", errors.WithStack(err)) + } + + backend.Stop() +} diff --git a/pkg/module/testdata/store.js b/pkg/module/testdata/store.js new file mode 100644 index 0000000..df6f942 --- /dev/null +++ b/pkg/module/testdata/store.js @@ -0,0 +1,32 @@ +var ctx = context.get(); + +function testStore() { + var obj = store.upsert(ctx, "test", {"foo": "bar"}); + var obj1 = store.get(ctx, "test", obj._id); + + console.log(obj, obj1); + + for (var key in obj) { + if (!obj.hasOwnProperty(key)) { + continue; + } + + if (obj[key].toString() !== obj1[key].toString()) { + throw new Error("obj['"+key+"'] !== obj1['"+key+"']"); + } + } + + var results = store.query(ctx, "test", { "eq": {"foo": "bar"} }, {"orderBy": "foo", "limit": 10, "skip": 0}); + + if (!results || results.length !== 1) { + throw new Error("results should contains 1 item"); + } + + store.delete(ctx, "test", obj._id); + + var obj2 = store.get(ctx, "test", obj._id); + + if (obj2 != null) { + throw new Error("obj2 should be null"); + } +} \ No newline at end of file diff --git a/pkg/module/user.go b/pkg/module/user.go new file mode 100644 index 0000000..f3aab3e --- /dev/null +++ b/pkg/module/user.go @@ -0,0 +1,59 @@ +package module + +// import ( +// "context" + +// "github.com/dop251/goja" +// "github.com/pkg/errors" +// "forge.cadoles.com/arcad/edge/pkg/app" +// "forge.cadoles.com/arcad/edge/pkg/repository" +// "gitlab.com/wpetit/goweb/logger" +// ) + +// type UserModule struct { +// appID app.ID +// repo repository.UserRepository +// backend *app.Server +// ctx context.Context +// } + +// func (m *UserModule) Name() string { +// return "user" +// } + +// func (m *UserModule) Export(export *goja.Object) { +// if err := export.Set("getUserById", m.getUserByID); err != nil { +// panic(errors.Wrap(err, "could not set 'getUserById' function")) +// } +// } + +// func (m *UserModule) getUserByID(call goja.FunctionCall) goja.Value { +// if len(call.Arguments) != 1 { +// panic(m.backend.ToValue("invalid number of arguments")) +// } + +// userID := repository.UserID(call.Arguments[0].String()) + +// user, err := m.repo.Get(userID) +// if err != nil { +// err = errors.Wrapf(err, "could not find user '%s'", userID) +// logger.Error(m.ctx, "could not find user", logger.E(err), logger.F("userID", userID)) +// panic(m.backend.ToValue(err)) +// } + +// return m.backend.ToValue(user) +// } + +// func UserModuleFactory(repo repository.UserRepository) app.ServerModuleFactory { +// return func(appID app.ID, backend *app.Server) app.ServerModule { +// return &UserModule{ +// appID: appID, +// repo: repo, +// backend: backend, +// ctx: logger.With( +// context.Background(), +// logger.F("appID", appID), +// ), +// } +// } +// } diff --git a/pkg/module/user_test.go b/pkg/module/user_test.go new file mode 100644 index 0000000..aa60974 --- /dev/null +++ b/pkg/module/user_test.go @@ -0,0 +1,70 @@ +package module + +// import ( +// "errors" +// "io/ioutil" +// "testing" + +// "gitlab.com/arcadbox/arcad/internal/app" +// "gitlab.com/arcadbox/arcad/internal/repository" +// ) + +// func TestUserModuleGetUserByID(t *testing.T) { +// repo := &fakeUserRepository{} + +// appID := app.ID("test") +// backend := app.NewServer(appID, +// ConsoleModuleFactory(), +// UserModuleFactory(repo), +// ) + +// data, err := ioutil.ReadFile("testdata/user_getbyid.js") +// if err != nil { +// t.Fatal(err) +// } + +// if err := backend.Load(string(data)); err != nil { +// t.Fatal(err) +// } + +// backend.Start() +// defer backend.Stop() + +// if err := backend.OnInit(); err != nil { +// t.Error(err) +// } +// } + +// type fakeUserRepository struct{} + +// func (r *fakeUserRepository) Create() (*repository.User, error) { +// return nil, errors.New("not implemented") +// } + +// func (r *fakeUserRepository) Save(user *repository.User) error { +// return errors.New("not implemented") +// } + +// func (r *fakeUserRepository) Get(userID repository.UserID) (*repository.User, error) { +// if userID == "0" { +// return &repository.User{}, nil +// } + +// return nil, errors.New("not implemented") +// } + +// func (r *fakeUserRepository) Delete(userID repository.UserID) error { +// return errors.New("not implemented") +// } + +// func (r *fakeUserRepository) Touch(userID repository.UserID, rawUserAgent string) error { +// return errors.New("not implemented") +// } + +// func (r *fakeUserRepository) List() ([]*repository.User, error) { +// return nil, errors.New("not implemented") +// } + +// func (r *fakeUserRepository) ListByID(userIDs ...repository.UserID) ([]*repository.User, error) { +// return nil, errors.New("not implemented") +// } diff --git a/pkg/sdk/client/dist/client.js b/pkg/sdk/client/dist/client.js new file mode 100644 index 0000000..61830ae --- /dev/null +++ b/pkg/sdk/client/dist/client.js @@ -0,0 +1,4081 @@ +var Arcad = (() => { + var __create = Object.create; + var __defProp = Object.defineProperty; + var __getOwnPropDesc = Object.getOwnPropertyDescriptor; + var __getOwnPropNames = Object.getOwnPropertyNames; + var __getProtoOf = Object.getPrototypeOf; + var __hasOwnProp = Object.prototype.hasOwnProperty; + var __commonJS = (cb, mod) => function __require() { + return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; + }; + var __export = (target, all) => { + for (var name in all) + __defProp(target, name, { get: all[name], enumerable: true }); + }; + var __copyProps = (to, from, except, desc) => { + if (from && typeof from === "object" || typeof from === "function") { + for (let key of __getOwnPropNames(from)) + if (!__hasOwnProp.call(to, key) && key !== except) + __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); + } + return to; + }; + var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod + )); + var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + + // node_modules/sockjs-client/lib/utils/browser-crypto.js + var require_browser_crypto = __commonJS({ + "node_modules/sockjs-client/lib/utils/browser-crypto.js"(exports, module) { + "use strict"; + if (window.crypto && window.crypto.getRandomValues) { + module.exports.randomBytes = function(length) { + var bytes = new Uint8Array(length); + window.crypto.getRandomValues(bytes); + return bytes; + }; + } else { + module.exports.randomBytes = function(length) { + var bytes = new Array(length); + for (var i = 0; i < length; i++) { + bytes[i] = Math.floor(Math.random() * 256); + } + return bytes; + }; + } + } + }); + + // node_modules/sockjs-client/lib/utils/random.js + var require_random = __commonJS({ + "node_modules/sockjs-client/lib/utils/random.js"(exports, module) { + "use strict"; + var crypto = require_browser_crypto(); + var _randomStringChars = "abcdefghijklmnopqrstuvwxyz012345"; + module.exports = { + string: function(length) { + var max = _randomStringChars.length; + var bytes = crypto.randomBytes(length); + var ret = []; + for (var i = 0; i < length; i++) { + ret.push(_randomStringChars.substr(bytes[i] % max, 1)); + } + return ret.join(""); + }, + number: function(max) { + return Math.floor(Math.random() * max); + }, + numberString: function(max) { + var t = ("" + (max - 1)).length; + var p = new Array(t + 1).join("0"); + return (p + this.number(max)).slice(-t); + } + }; + } + }); + + // node_modules/sockjs-client/lib/utils/event.js + var require_event = __commonJS({ + "node_modules/sockjs-client/lib/utils/event.js"(exports, module) { + "use strict"; + var random = require_random(); + var onUnload = {}; + var afterUnload = false; + var isChromePackagedApp = window.chrome && window.chrome.app && window.chrome.app.runtime; + module.exports = { + attachEvent: function(event, listener) { + if (typeof window.addEventListener !== "undefined") { + window.addEventListener(event, listener, false); + } else if (window.document && window.attachEvent) { + window.document.attachEvent("on" + event, listener); + window.attachEvent("on" + event, listener); + } + }, + detachEvent: function(event, listener) { + if (typeof window.addEventListener !== "undefined") { + window.removeEventListener(event, listener, false); + } else if (window.document && window.detachEvent) { + window.document.detachEvent("on" + event, listener); + window.detachEvent("on" + event, listener); + } + }, + unloadAdd: function(listener) { + if (isChromePackagedApp) { + return null; + } + var ref = random.string(8); + onUnload[ref] = listener; + if (afterUnload) { + setTimeout(this.triggerUnloadCallbacks, 0); + } + return ref; + }, + unloadDel: function(ref) { + if (ref in onUnload) { + delete onUnload[ref]; + } + }, + triggerUnloadCallbacks: function() { + for (var ref in onUnload) { + onUnload[ref](); + delete onUnload[ref]; + } + } + }; + var unloadTriggered = function() { + if (afterUnload) { + return; + } + afterUnload = true; + module.exports.triggerUnloadCallbacks(); + }; + if (!isChromePackagedApp) { + module.exports.attachEvent("unload", unloadTriggered); + } + } + }); + + // node_modules/requires-port/index.js + var require_requires_port = __commonJS({ + "node_modules/requires-port/index.js"(exports, module) { + "use strict"; + module.exports = function required(port, protocol) { + protocol = protocol.split(":")[0]; + port = +port; + if (!port) + return false; + switch (protocol) { + case "http": + case "ws": + return port !== 80; + case "https": + case "wss": + return port !== 443; + case "ftp": + return port !== 21; + case "gopher": + return port !== 70; + case "file": + return false; + } + return port !== 0; + }; + } + }); + + // node_modules/querystringify/index.js + var require_querystringify = __commonJS({ + "node_modules/querystringify/index.js"(exports) { + "use strict"; + var has = Object.prototype.hasOwnProperty; + var undef; + function decode(input) { + try { + return decodeURIComponent(input.replace(/\+/g, " ")); + } catch (e) { + return null; + } + } + function encode(input) { + try { + return encodeURIComponent(input); + } catch (e) { + return null; + } + } + function querystring(query) { + var parser = /([^=?#&]+)=?([^&]*)/g, result = {}, part; + while (part = parser.exec(query)) { + var key = decode(part[1]), value = decode(part[2]); + if (key === null || value === null || key in result) + continue; + result[key] = value; + } + return result; + } + function querystringify(obj, prefix) { + prefix = prefix || ""; + var pairs = [], value, key; + if ("string" !== typeof prefix) + prefix = "?"; + for (key in obj) { + if (has.call(obj, key)) { + value = obj[key]; + if (!value && (value === null || value === undef || isNaN(value))) { + value = ""; + } + key = encode(key); + value = encode(value); + if (key === null || value === null) + continue; + pairs.push(key + "=" + value); + } + } + return pairs.length ? prefix + pairs.join("&") : ""; + } + exports.stringify = querystringify; + exports.parse = querystring; + } + }); + + // node_modules/url-parse/index.js + var require_url_parse = __commonJS({ + "node_modules/url-parse/index.js"(exports, module) { + "use strict"; + var required = require_requires_port(); + var qs = require_querystringify(); + var controlOrWhitespace = /^[\x00-\x20\u00a0\u1680\u2000-\u200a\u2028\u2029\u202f\u205f\u3000\ufeff]+/; + var CRHTLF = /[\n\r\t]/g; + var slashes = /^[A-Za-z][A-Za-z0-9+-.]*:\/\//; + var port = /:\d+$/; + var protocolre = /^([a-z][a-z0-9.+-]*:)?(\/\/)?([\\/]+)?([\S\s]*)/i; + var windowsDriveLetter = /^[a-zA-Z]:/; + function trimLeft(str) { + return (str ? str : "").toString().replace(controlOrWhitespace, ""); + } + var rules = [ + ["#", "hash"], + // Extract from the back. + ["?", "query"], + // Extract from the back. + function sanitize(address, url) { + return isSpecial(url.protocol) ? address.replace(/\\/g, "/") : address; + }, + ["/", "pathname"], + // Extract from the back. + ["@", "auth", 1], + // Extract from the front. + [NaN, "host", void 0, 1, 1], + // Set left over value. + [/:(\d*)$/, "port", void 0, 1], + // RegExp the back. + [NaN, "hostname", void 0, 1, 1] + // Set left over. + ]; + var ignore = { hash: 1, query: 1 }; + function lolcation(loc) { + var globalVar; + if (typeof window !== "undefined") + globalVar = window; + else if (typeof window !== "undefined") + globalVar = window; + else if (typeof self !== "undefined") + globalVar = self; + else + globalVar = {}; + var location = globalVar.location || {}; + loc = loc || location; + var finaldestination = {}, type = typeof loc, key; + if ("blob:" === loc.protocol) { + finaldestination = new Url(unescape(loc.pathname), {}); + } else if ("string" === type) { + finaldestination = new Url(loc, {}); + for (key in ignore) + delete finaldestination[key]; + } else if ("object" === type) { + for (key in loc) { + if (key in ignore) + continue; + finaldestination[key] = loc[key]; + } + if (finaldestination.slashes === void 0) { + finaldestination.slashes = slashes.test(loc.href); + } + } + return finaldestination; + } + function isSpecial(scheme) { + return scheme === "file:" || scheme === "ftp:" || scheme === "http:" || scheme === "https:" || scheme === "ws:" || scheme === "wss:"; + } + function extractProtocol(address, location) { + address = trimLeft(address); + address = address.replace(CRHTLF, ""); + location = location || {}; + var match = protocolre.exec(address); + var protocol = match[1] ? match[1].toLowerCase() : ""; + var forwardSlashes = !!match[2]; + var otherSlashes = !!match[3]; + var slashesCount = 0; + var rest; + if (forwardSlashes) { + if (otherSlashes) { + rest = match[2] + match[3] + match[4]; + slashesCount = match[2].length + match[3].length; + } else { + rest = match[2] + match[4]; + slashesCount = match[2].length; + } + } else { + if (otherSlashes) { + rest = match[3] + match[4]; + slashesCount = match[3].length; + } else { + rest = match[4]; + } + } + if (protocol === "file:") { + if (slashesCount >= 2) { + rest = rest.slice(2); + } + } else if (isSpecial(protocol)) { + rest = match[4]; + } else if (protocol) { + if (forwardSlashes) { + rest = rest.slice(2); + } + } else if (slashesCount >= 2 && isSpecial(location.protocol)) { + rest = match[4]; + } + return { + protocol, + slashes: forwardSlashes || isSpecial(protocol), + slashesCount, + rest + }; + } + function resolve(relative, base) { + if (relative === "") + return base; + var path = (base || "/").split("/").slice(0, -1).concat(relative.split("/")), i = path.length, last = path[i - 1], unshift = false, up = 0; + while (i--) { + if (path[i] === ".") { + path.splice(i, 1); + } else if (path[i] === "..") { + path.splice(i, 1); + up++; + } else if (up) { + if (i === 0) + unshift = true; + path.splice(i, 1); + up--; + } + } + if (unshift) + path.unshift(""); + if (last === "." || last === "..") + path.push(""); + return path.join("/"); + } + function Url(address, location, parser) { + address = trimLeft(address); + address = address.replace(CRHTLF, ""); + if (!(this instanceof Url)) { + return new Url(address, location, parser); + } + var relative, extracted, parse, instruction, index, key, instructions = rules.slice(), type = typeof location, url = this, i = 0; + if ("object" !== type && "string" !== type) { + parser = location; + location = null; + } + if (parser && "function" !== typeof parser) + parser = qs.parse; + location = lolcation(location); + extracted = extractProtocol(address || "", location); + relative = !extracted.protocol && !extracted.slashes; + url.slashes = extracted.slashes || relative && location.slashes; + url.protocol = extracted.protocol || location.protocol || ""; + address = extracted.rest; + if (extracted.protocol === "file:" && (extracted.slashesCount !== 2 || windowsDriveLetter.test(address)) || !extracted.slashes && (extracted.protocol || extracted.slashesCount < 2 || !isSpecial(url.protocol))) { + instructions[3] = [/(.*)/, "pathname"]; + } + for (; i < instructions.length; i++) { + instruction = instructions[i]; + if (typeof instruction === "function") { + address = instruction(address, url); + continue; + } + parse = instruction[0]; + key = instruction[1]; + if (parse !== parse) { + url[key] = address; + } else if ("string" === typeof parse) { + index = parse === "@" ? address.lastIndexOf(parse) : address.indexOf(parse); + if (~index) { + if ("number" === typeof instruction[2]) { + url[key] = address.slice(0, index); + address = address.slice(index + instruction[2]); + } else { + url[key] = address.slice(index); + address = address.slice(0, index); + } + } + } else if (index = parse.exec(address)) { + url[key] = index[1]; + address = address.slice(0, index.index); + } + url[key] = url[key] || (relative && instruction[3] ? location[key] || "" : ""); + if (instruction[4]) + url[key] = url[key].toLowerCase(); + } + if (parser) + url.query = parser(url.query); + if (relative && location.slashes && url.pathname.charAt(0) !== "/" && (url.pathname !== "" || location.pathname !== "")) { + url.pathname = resolve(url.pathname, location.pathname); + } + if (url.pathname.charAt(0) !== "/" && isSpecial(url.protocol)) { + url.pathname = "/" + url.pathname; + } + if (!required(url.port, url.protocol)) { + url.host = url.hostname; + url.port = ""; + } + url.username = url.password = ""; + if (url.auth) { + index = url.auth.indexOf(":"); + if (~index) { + url.username = url.auth.slice(0, index); + url.username = encodeURIComponent(decodeURIComponent(url.username)); + url.password = url.auth.slice(index + 1); + url.password = encodeURIComponent(decodeURIComponent(url.password)); + } else { + url.username = encodeURIComponent(decodeURIComponent(url.auth)); + } + url.auth = url.password ? url.username + ":" + url.password : url.username; + } + url.origin = url.protocol !== "file:" && isSpecial(url.protocol) && url.host ? url.protocol + "//" + url.host : "null"; + url.href = url.toString(); + } + function set(part, value, fn) { + var url = this; + switch (part) { + case "query": + if ("string" === typeof value && value.length) { + value = (fn || qs.parse)(value); + } + url[part] = value; + break; + case "port": + url[part] = value; + if (!required(value, url.protocol)) { + url.host = url.hostname; + url[part] = ""; + } else if (value) { + url.host = url.hostname + ":" + value; + } + break; + case "hostname": + url[part] = value; + if (url.port) + value += ":" + url.port; + url.host = value; + break; + case "host": + url[part] = value; + if (port.test(value)) { + value = value.split(":"); + url.port = value.pop(); + url.hostname = value.join(":"); + } else { + url.hostname = value; + url.port = ""; + } + break; + case "protocol": + url.protocol = value.toLowerCase(); + url.slashes = !fn; + break; + case "pathname": + case "hash": + if (value) { + var char = part === "pathname" ? "/" : "#"; + url[part] = value.charAt(0) !== char ? char + value : value; + } else { + url[part] = value; + } + break; + case "username": + case "password": + url[part] = encodeURIComponent(value); + break; + case "auth": + var index = value.indexOf(":"); + if (~index) { + url.username = value.slice(0, index); + url.username = encodeURIComponent(decodeURIComponent(url.username)); + url.password = value.slice(index + 1); + url.password = encodeURIComponent(decodeURIComponent(url.password)); + } else { + url.username = encodeURIComponent(decodeURIComponent(value)); + } + } + for (var i = 0; i < rules.length; i++) { + var ins = rules[i]; + if (ins[4]) + url[ins[1]] = url[ins[1]].toLowerCase(); + } + url.auth = url.password ? url.username + ":" + url.password : url.username; + url.origin = url.protocol !== "file:" && isSpecial(url.protocol) && url.host ? url.protocol + "//" + url.host : "null"; + url.href = url.toString(); + return url; + } + function toString(stringify) { + if (!stringify || "function" !== typeof stringify) + stringify = qs.stringify; + var query, url = this, host = url.host, protocol = url.protocol; + if (protocol && protocol.charAt(protocol.length - 1) !== ":") + protocol += ":"; + var result = protocol + (url.protocol && url.slashes || isSpecial(url.protocol) ? "//" : ""); + if (url.username) { + result += url.username; + if (url.password) + result += ":" + url.password; + result += "@"; + } else if (url.password) { + result += ":" + url.password; + result += "@"; + } else if (url.protocol !== "file:" && isSpecial(url.protocol) && !host && url.pathname !== "/") { + result += "@"; + } + if (host[host.length - 1] === ":" || port.test(url.hostname) && !url.port) { + host += ":"; + } + result += host + url.pathname; + query = "object" === typeof url.query ? stringify(url.query) : url.query; + if (query) + result += "?" !== query.charAt(0) ? "?" + query : query; + if (url.hash) + result += url.hash; + return result; + } + Url.prototype = { set, toString }; + Url.extractProtocol = extractProtocol; + Url.location = lolcation; + Url.trimLeft = trimLeft; + Url.qs = qs; + module.exports = Url; + } + }); + + // node_modules/ms/index.js + var require_ms = __commonJS({ + "node_modules/ms/index.js"(exports, module) { + var s = 1e3; + var m = s * 60; + var h = m * 60; + var d = h * 24; + var w = d * 7; + var y = d * 365.25; + module.exports = function(val, options) { + options = options || {}; + var type = typeof val; + if (type === "string" && val.length > 0) { + return parse(val); + } else if (type === "number" && isFinite(val)) { + return options.long ? fmtLong(val) : fmtShort(val); + } + throw new Error( + "val is not a non-empty string or a valid number. val=" + JSON.stringify(val) + ); + }; + function parse(str) { + str = String(str); + if (str.length > 100) { + return; + } + var match = /^(-?(?:\d+)?\.?\d+) *(milliseconds?|msecs?|ms|seconds?|secs?|s|minutes?|mins?|m|hours?|hrs?|h|days?|d|weeks?|w|years?|yrs?|y)?$/i.exec( + str + ); + if (!match) { + return; + } + var n = parseFloat(match[1]); + var type = (match[2] || "ms").toLowerCase(); + switch (type) { + case "years": + case "year": + case "yrs": + case "yr": + case "y": + return n * y; + case "weeks": + case "week": + case "w": + return n * w; + case "days": + case "day": + case "d": + return n * d; + case "hours": + case "hour": + case "hrs": + case "hr": + case "h": + return n * h; + case "minutes": + case "minute": + case "mins": + case "min": + case "m": + return n * m; + case "seconds": + case "second": + case "secs": + case "sec": + case "s": + return n * s; + case "milliseconds": + case "millisecond": + case "msecs": + case "msec": + case "ms": + return n; + default: + return void 0; + } + } + function fmtShort(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return Math.round(ms / d) + "d"; + } + if (msAbs >= h) { + return Math.round(ms / h) + "h"; + } + if (msAbs >= m) { + return Math.round(ms / m) + "m"; + } + if (msAbs >= s) { + return Math.round(ms / s) + "s"; + } + return ms + "ms"; + } + function fmtLong(ms) { + var msAbs = Math.abs(ms); + if (msAbs >= d) { + return plural(ms, msAbs, d, "day"); + } + if (msAbs >= h) { + return plural(ms, msAbs, h, "hour"); + } + if (msAbs >= m) { + return plural(ms, msAbs, m, "minute"); + } + if (msAbs >= s) { + return plural(ms, msAbs, s, "second"); + } + return ms + " ms"; + } + function plural(ms, msAbs, n, name) { + var isPlural = msAbs >= n * 1.5; + return Math.round(ms / n) + " " + name + (isPlural ? "s" : ""); + } + } + }); + + // node_modules/debug/src/common.js + var require_common = __commonJS({ + "node_modules/debug/src/common.js"(exports, module) { + "use strict"; + function setup(env) { + createDebug.debug = createDebug; + createDebug.default = createDebug; + createDebug.coerce = coerce; + createDebug.disable = disable; + createDebug.enable = enable; + createDebug.enabled = enabled; + createDebug.humanize = require_ms(); + Object.keys(env).forEach(function(key) { + createDebug[key] = env[key]; + }); + createDebug.instances = []; + createDebug.names = []; + createDebug.skips = []; + createDebug.formatters = {}; + function selectColor(namespace) { + var hash = 0; + for (var i = 0; i < namespace.length; i++) { + hash = (hash << 5) - hash + namespace.charCodeAt(i); + hash |= 0; + } + return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; + } + createDebug.selectColor = selectColor; + function createDebug(namespace) { + var prevTime; + function debug() { + if (!debug.enabled) { + return; + } + for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) { + args[_key] = arguments[_key]; + } + var self2 = debug; + var curr = Number(/* @__PURE__ */ new Date()); + var ms = curr - (prevTime || curr); + self2.diff = ms; + self2.prev = prevTime; + self2.curr = curr; + prevTime = curr; + args[0] = createDebug.coerce(args[0]); + if (typeof args[0] !== "string") { + args.unshift("%O"); + } + var index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, function(match, format) { + if (match === "%%") { + return match; + } + index++; + var formatter = createDebug.formatters[format]; + if (typeof formatter === "function") { + var val = args[index]; + match = formatter.call(self2, val); + args.splice(index, 1); + index--; + } + return match; + }); + createDebug.formatArgs.call(self2, args); + var logFn = self2.log || createDebug.log; + logFn.apply(self2, args); + } + debug.namespace = namespace; + debug.enabled = createDebug.enabled(namespace); + debug.useColors = createDebug.useColors(); + debug.color = selectColor(namespace); + debug.destroy = destroy; + debug.extend = extend; + if (typeof createDebug.init === "function") { + createDebug.init(debug); + } + createDebug.instances.push(debug); + return debug; + } + function destroy() { + var index = createDebug.instances.indexOf(this); + if (index !== -1) { + createDebug.instances.splice(index, 1); + return true; + } + return false; + } + function extend(namespace, delimiter) { + return createDebug(this.namespace + (typeof delimiter === "undefined" ? ":" : delimiter) + namespace); + } + function enable(namespaces) { + createDebug.save(namespaces); + createDebug.names = []; + createDebug.skips = []; + var i; + var split = (typeof namespaces === "string" ? namespaces : "").split(/[\s,]+/); + var len = split.length; + for (i = 0; i < len; i++) { + if (!split[i]) { + continue; + } + namespaces = split[i].replace(/\*/g, ".*?"); + if (namespaces[0] === "-") { + createDebug.skips.push(new RegExp("^" + namespaces.substr(1) + "$")); + } else { + createDebug.names.push(new RegExp("^" + namespaces + "$")); + } + } + for (i = 0; i < createDebug.instances.length; i++) { + var instance = createDebug.instances[i]; + instance.enabled = createDebug.enabled(instance.namespace); + } + } + function disable() { + createDebug.enable(""); + } + function enabled(name) { + if (name[name.length - 1] === "*") { + return true; + } + var i; + var len; + for (i = 0, len = createDebug.skips.length; i < len; i++) { + if (createDebug.skips[i].test(name)) { + return false; + } + } + for (i = 0, len = createDebug.names.length; i < len; i++) { + if (createDebug.names[i].test(name)) { + return true; + } + } + return false; + } + function coerce(val) { + if (val instanceof Error) { + return val.stack || val.message; + } + return val; + } + createDebug.enable(createDebug.load()); + return createDebug; + } + module.exports = setup; + } + }); + + // node_modules/debug/src/browser.js + var require_browser = __commonJS({ + "node_modules/debug/src/browser.js"(exports, module) { + "use strict"; + function _typeof(obj) { + if (typeof Symbol === "function" && typeof Symbol.iterator === "symbol") { + _typeof = function _typeof2(obj2) { + return typeof obj2; + }; + } else { + _typeof = function _typeof2(obj2) { + return obj2 && typeof Symbol === "function" && obj2.constructor === Symbol && obj2 !== Symbol.prototype ? "symbol" : typeof obj2; + }; + } + return _typeof(obj); + } + exports.log = log; + exports.formatArgs = formatArgs; + exports.save = save; + exports.load = load; + exports.useColors = useColors; + exports.storage = localstorage(); + exports.colors = ["#0000CC", "#0000FF", "#0033CC", "#0033FF", "#0066CC", "#0066FF", "#0099CC", "#0099FF", "#00CC00", "#00CC33", "#00CC66", "#00CC99", "#00CCCC", "#00CCFF", "#3300CC", "#3300FF", "#3333CC", "#3333FF", "#3366CC", "#3366FF", "#3399CC", "#3399FF", "#33CC00", "#33CC33", "#33CC66", "#33CC99", "#33CCCC", "#33CCFF", "#6600CC", "#6600FF", "#6633CC", "#6633FF", "#66CC00", "#66CC33", "#9900CC", "#9900FF", "#9933CC", "#9933FF", "#99CC00", "#99CC33", "#CC0000", "#CC0033", "#CC0066", "#CC0099", "#CC00CC", "#CC00FF", "#CC3300", "#CC3333", "#CC3366", "#CC3399", "#CC33CC", "#CC33FF", "#CC6600", "#CC6633", "#CC9900", "#CC9933", "#CCCC00", "#CCCC33", "#FF0000", "#FF0033", "#FF0066", "#FF0099", "#FF00CC", "#FF00FF", "#FF3300", "#FF3333", "#FF3366", "#FF3399", "#FF33CC", "#FF33FF", "#FF6600", "#FF6633", "#FF9900", "#FF9933", "#FFCC00", "#FFCC33"]; + function useColors() { + if (typeof window !== "undefined" && window.process && (window.process.type === "renderer" || window.process.__nwjs)) { + return true; + } + if (typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false; + } + return typeof document !== "undefined" && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance || // Is firebug? http://stackoverflow.com/a/398120/376773 + typeof window !== "undefined" && window.console && (window.console.firebug || window.console.exception && window.console.table) || // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31 || // Double check webkit in userAgent just in case we are in a worker + typeof navigator !== "undefined" && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/); + } + function formatArgs(args) { + args[0] = (this.useColors ? "%c" : "") + this.namespace + (this.useColors ? " %c" : " ") + args[0] + (this.useColors ? "%c " : " ") + "+" + module.exports.humanize(this.diff); + if (!this.useColors) { + return; + } + var c = "color: " + this.color; + args.splice(1, 0, c, "color: inherit"); + var index = 0; + var lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, function(match) { + if (match === "%%") { + return; + } + index++; + if (match === "%c") { + lastC = index; + } + }); + args.splice(lastC, 0, c); + } + function log() { + var _console; + return (typeof console === "undefined" ? "undefined" : _typeof(console)) === "object" && console.log && (_console = console).log.apply(_console, arguments); + } + function save(namespaces) { + try { + if (namespaces) { + exports.storage.setItem("debug", namespaces); + } else { + exports.storage.removeItem("debug"); + } + } catch (error) { + } + } + function load() { + var r; + try { + r = exports.storage.getItem("debug"); + } catch (error) { + } + if (!r && typeof process !== "undefined" && "env" in process) { + r = process.env.DEBUG; + } + return r; + } + function localstorage() { + try { + return localStorage; + } catch (error) { + } + } + module.exports = require_common()(exports); + var formatters = module.exports.formatters; + formatters.j = function(v) { + try { + return JSON.stringify(v); + } catch (error) { + return "[UnexpectedJSONParseError]: " + error.message; + } + }; + } + }); + + // node_modules/sockjs-client/lib/utils/url.js + var require_url = __commonJS({ + "node_modules/sockjs-client/lib/utils/url.js"(exports, module) { + "use strict"; + var URL = require_url_parse(); + var debug = function() { + }; + if (true) { + debug = require_browser()("sockjs-client:utils:url"); + } + module.exports = { + getOrigin: function(url) { + if (!url) { + return null; + } + var p = new URL(url); + if (p.protocol === "file:") { + return null; + } + var port = p.port; + if (!port) { + port = p.protocol === "https:" ? "443" : "80"; + } + return p.protocol + "//" + p.hostname + ":" + port; + }, + isOriginEqual: function(a, b) { + var res = this.getOrigin(a) === this.getOrigin(b); + debug("same", a, b, res); + return res; + }, + isSchemeEqual: function(a, b) { + return a.split(":")[0] === b.split(":")[0]; + }, + addPath: function(url, path) { + var qs = url.split("?"); + return qs[0] + path + (qs[1] ? "?" + qs[1] : ""); + }, + addQuery: function(url, q) { + return url + (url.indexOf("?") === -1 ? "?" + q : "&" + q); + }, + isLoopbackAddr: function(addr) { + return /^127\.([0-9]{1,3})\.([0-9]{1,3})\.([0-9]{1,3})$/i.test(addr) || /^\[::1\]$/.test(addr); + } + }; + } + }); + + // node_modules/inherits/inherits_browser.js + var require_inherits_browser = __commonJS({ + "node_modules/inherits/inherits_browser.js"(exports, module) { + if (typeof Object.create === "function") { + module.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor; + ctor.prototype = Object.create(superCtor.prototype, { + constructor: { + value: ctor, + enumerable: false, + writable: true, + configurable: true + } + }); + } + }; + } else { + module.exports = function inherits(ctor, superCtor) { + if (superCtor) { + ctor.super_ = superCtor; + var TempCtor = function() { + }; + TempCtor.prototype = superCtor.prototype; + ctor.prototype = new TempCtor(); + ctor.prototype.constructor = ctor; + } + }; + } + } + }); + + // node_modules/sockjs-client/lib/event/eventtarget.js + var require_eventtarget = __commonJS({ + "node_modules/sockjs-client/lib/event/eventtarget.js"(exports, module) { + "use strict"; + function EventTarget2() { + this._listeners = {}; + } + EventTarget2.prototype.addEventListener = function(eventType, listener) { + if (!(eventType in this._listeners)) { + this._listeners[eventType] = []; + } + var arr = this._listeners[eventType]; + if (arr.indexOf(listener) === -1) { + arr = arr.concat([listener]); + } + this._listeners[eventType] = arr; + }; + EventTarget2.prototype.removeEventListener = function(eventType, listener) { + var arr = this._listeners[eventType]; + if (!arr) { + return; + } + var idx = arr.indexOf(listener); + if (idx !== -1) { + if (arr.length > 1) { + this._listeners[eventType] = arr.slice(0, idx).concat(arr.slice(idx + 1)); + } else { + delete this._listeners[eventType]; + } + return; + } + }; + EventTarget2.prototype.dispatchEvent = function() { + var event = arguments[0]; + var t = event.type; + var args = arguments.length === 1 ? [event] : Array.apply(null, arguments); + if (this["on" + t]) { + this["on" + t].apply(this, args); + } + if (t in this._listeners) { + var listeners = this._listeners[t]; + for (var i = 0; i < listeners.length; i++) { + listeners[i].apply(this, args); + } + } + }; + module.exports = EventTarget2; + } + }); + + // node_modules/sockjs-client/lib/event/emitter.js + var require_emitter = __commonJS({ + "node_modules/sockjs-client/lib/event/emitter.js"(exports, module) { + "use strict"; + var inherits = require_inherits_browser(); + var EventTarget2 = require_eventtarget(); + function EventEmitter() { + EventTarget2.call(this); + } + inherits(EventEmitter, EventTarget2); + EventEmitter.prototype.removeAllListeners = function(type) { + if (type) { + delete this._listeners[type]; + } else { + this._listeners = {}; + } + }; + EventEmitter.prototype.once = function(type, listener) { + var self2 = this, fired = false; + function g() { + self2.removeListener(type, g); + if (!fired) { + fired = true; + listener.apply(this, arguments); + } + } + this.on(type, g); + }; + EventEmitter.prototype.emit = function() { + var type = arguments[0]; + var listeners = this._listeners[type]; + if (!listeners) { + return; + } + var l = arguments.length; + var args = new Array(l - 1); + for (var ai = 1; ai < l; ai++) { + args[ai - 1] = arguments[ai]; + } + for (var i = 0; i < listeners.length; i++) { + listeners[i].apply(this, args); + } + }; + EventEmitter.prototype.on = EventEmitter.prototype.addListener = EventTarget2.prototype.addEventListener; + EventEmitter.prototype.removeListener = EventTarget2.prototype.removeEventListener; + module.exports.EventEmitter = EventEmitter; + } + }); + + // node_modules/sockjs-client/lib/transport/browser/websocket.js + var require_websocket = __commonJS({ + "node_modules/sockjs-client/lib/transport/browser/websocket.js"(exports, module) { + "use strict"; + var Driver = window.WebSocket || window.MozWebSocket; + if (Driver) { + module.exports = function WebSocketBrowserDriver(url) { + return new Driver(url); + }; + } else { + module.exports = void 0; + } + } + }); + + // node_modules/sockjs-client/lib/transport/websocket.js + var require_websocket2 = __commonJS({ + "node_modules/sockjs-client/lib/transport/websocket.js"(exports, module) { + "use strict"; + var utils = require_event(); + var urlUtils = require_url(); + var inherits = require_inherits_browser(); + var EventEmitter = require_emitter().EventEmitter; + var WebsocketDriver = require_websocket(); + var debug = function() { + }; + if (true) { + debug = require_browser()("sockjs-client:websocket"); + } + function WebSocketTransport(transUrl, ignore, options) { + if (!WebSocketTransport.enabled()) { + throw new Error("Transport created when disabled"); + } + EventEmitter.call(this); + debug("constructor", transUrl); + var self2 = this; + var url = urlUtils.addPath(transUrl, "/websocket"); + if (url.slice(0, 5) === "https") { + url = "wss" + url.slice(5); + } else { + url = "ws" + url.slice(4); + } + this.url = url; + this.ws = new WebsocketDriver(this.url, [], options); + this.ws.onmessage = function(e) { + debug("message event", e.data); + self2.emit("message", e.data); + }; + this.unloadRef = utils.unloadAdd(function() { + debug("unload"); + self2.ws.close(); + }); + this.ws.onclose = function(e) { + debug("close event", e.code, e.reason); + self2.emit("close", e.code, e.reason); + self2._cleanup(); + }; + this.ws.onerror = function(e) { + debug("error event", e); + self2.emit("close", 1006, "WebSocket connection broken"); + self2._cleanup(); + }; + } + inherits(WebSocketTransport, EventEmitter); + WebSocketTransport.prototype.send = function(data) { + var msg = "[" + data + "]"; + debug("send", msg); + this.ws.send(msg); + }; + WebSocketTransport.prototype.close = function() { + debug("close"); + var ws = this.ws; + this._cleanup(); + if (ws) { + ws.close(); + } + }; + WebSocketTransport.prototype._cleanup = function() { + debug("_cleanup"); + var ws = this.ws; + if (ws) { + ws.onmessage = ws.onclose = ws.onerror = null; + } + utils.unloadDel(this.unloadRef); + this.unloadRef = this.ws = null; + this.removeAllListeners(); + }; + WebSocketTransport.enabled = function() { + debug("enabled"); + return !!WebsocketDriver; + }; + WebSocketTransport.transportName = "websocket"; + WebSocketTransport.roundTrips = 2; + module.exports = WebSocketTransport; + } + }); + + // node_modules/sockjs-client/lib/transport/lib/buffered-sender.js + var require_buffered_sender = __commonJS({ + "node_modules/sockjs-client/lib/transport/lib/buffered-sender.js"(exports, module) { + "use strict"; + var inherits = require_inherits_browser(); + var EventEmitter = require_emitter().EventEmitter; + var debug = function() { + }; + if (true) { + debug = require_browser()("sockjs-client:buffered-sender"); + } + function BufferedSender(url, sender) { + debug(url); + EventEmitter.call(this); + this.sendBuffer = []; + this.sender = sender; + this.url = url; + } + inherits(BufferedSender, EventEmitter); + BufferedSender.prototype.send = function(message) { + debug("send", message); + this.sendBuffer.push(message); + if (!this.sendStop) { + this.sendSchedule(); + } + }; + BufferedSender.prototype.sendScheduleWait = function() { + debug("sendScheduleWait"); + var self2 = this; + var tref; + this.sendStop = function() { + debug("sendStop"); + self2.sendStop = null; + clearTimeout(tref); + }; + tref = setTimeout(function() { + debug("timeout"); + self2.sendStop = null; + self2.sendSchedule(); + }, 25); + }; + BufferedSender.prototype.sendSchedule = function() { + debug("sendSchedule", this.sendBuffer.length); + var self2 = this; + if (this.sendBuffer.length > 0) { + var payload = "[" + this.sendBuffer.join(",") + "]"; + this.sendStop = this.sender(this.url, payload, function(err) { + self2.sendStop = null; + if (err) { + debug("error", err); + self2.emit("close", err.code || 1006, "Sending error: " + err); + self2.close(); + } else { + self2.sendScheduleWait(); + } + }); + this.sendBuffer = []; + } + }; + BufferedSender.prototype._cleanup = function() { + debug("_cleanup"); + this.removeAllListeners(); + }; + BufferedSender.prototype.close = function() { + debug("close"); + this._cleanup(); + if (this.sendStop) { + this.sendStop(); + this.sendStop = null; + } + }; + module.exports = BufferedSender; + } + }); + + // node_modules/sockjs-client/lib/transport/lib/polling.js + var require_polling = __commonJS({ + "node_modules/sockjs-client/lib/transport/lib/polling.js"(exports, module) { + "use strict"; + var inherits = require_inherits_browser(); + var EventEmitter = require_emitter().EventEmitter; + var debug = function() { + }; + if (true) { + debug = require_browser()("sockjs-client:polling"); + } + function Polling(Receiver, receiveUrl, AjaxObject) { + debug(receiveUrl); + EventEmitter.call(this); + this.Receiver = Receiver; + this.receiveUrl = receiveUrl; + this.AjaxObject = AjaxObject; + this._scheduleReceiver(); + } + inherits(Polling, EventEmitter); + Polling.prototype._scheduleReceiver = function() { + debug("_scheduleReceiver"); + var self2 = this; + var poll = this.poll = new this.Receiver(this.receiveUrl, this.AjaxObject); + poll.on("message", function(msg) { + debug("message", msg); + self2.emit("message", msg); + }); + poll.once("close", function(code, reason) { + debug("close", code, reason, self2.pollIsClosing); + self2.poll = poll = null; + if (!self2.pollIsClosing) { + if (reason === "network") { + self2._scheduleReceiver(); + } else { + self2.emit("close", code || 1006, reason); + self2.removeAllListeners(); + } + } + }); + }; + Polling.prototype.abort = function() { + debug("abort"); + this.removeAllListeners(); + this.pollIsClosing = true; + if (this.poll) { + this.poll.abort(); + } + }; + module.exports = Polling; + } + }); + + // node_modules/sockjs-client/lib/transport/lib/sender-receiver.js + var require_sender_receiver = __commonJS({ + "node_modules/sockjs-client/lib/transport/lib/sender-receiver.js"(exports, module) { + "use strict"; + var inherits = require_inherits_browser(); + var urlUtils = require_url(); + var BufferedSender = require_buffered_sender(); + var Polling = require_polling(); + var debug = function() { + }; + if (true) { + debug = require_browser()("sockjs-client:sender-receiver"); + } + function SenderReceiver(transUrl, urlSuffix, senderFunc, Receiver, AjaxObject) { + var pollUrl = urlUtils.addPath(transUrl, urlSuffix); + debug(pollUrl); + var self2 = this; + BufferedSender.call(this, transUrl, senderFunc); + this.poll = new Polling(Receiver, pollUrl, AjaxObject); + this.poll.on("message", function(msg) { + debug("poll message", msg); + self2.emit("message", msg); + }); + this.poll.once("close", function(code, reason) { + debug("poll close", code, reason); + self2.poll = null; + self2.emit("close", code, reason); + self2.close(); + }); + } + inherits(SenderReceiver, BufferedSender); + SenderReceiver.prototype.close = function() { + BufferedSender.prototype.close.call(this); + debug("close"); + this.removeAllListeners(); + if (this.poll) { + this.poll.abort(); + this.poll = null; + } + }; + module.exports = SenderReceiver; + } + }); + + // node_modules/sockjs-client/lib/transport/lib/ajax-based.js + var require_ajax_based = __commonJS({ + "node_modules/sockjs-client/lib/transport/lib/ajax-based.js"(exports, module) { + "use strict"; + var inherits = require_inherits_browser(); + var urlUtils = require_url(); + var SenderReceiver = require_sender_receiver(); + var debug = function() { + }; + if (true) { + debug = require_browser()("sockjs-client:ajax-based"); + } + function createAjaxSender(AjaxObject) { + return function(url, payload, callback) { + debug("create ajax sender", url, payload); + var opt = {}; + if (typeof payload === "string") { + opt.headers = { "Content-type": "text/plain" }; + } + var ajaxUrl = urlUtils.addPath(url, "/xhr_send"); + var xo = new AjaxObject("POST", ajaxUrl, payload, opt); + xo.once("finish", function(status) { + debug("finish", status); + xo = null; + if (status !== 200 && status !== 204) { + return callback(new Error("http status " + status)); + } + callback(); + }); + return function() { + debug("abort"); + xo.close(); + xo = null; + var err = new Error("Aborted"); + err.code = 1e3; + callback(err); + }; + }; + } + function AjaxBasedTransport(transUrl, urlSuffix, Receiver, AjaxObject) { + SenderReceiver.call(this, transUrl, urlSuffix, createAjaxSender(AjaxObject), Receiver, AjaxObject); + } + inherits(AjaxBasedTransport, SenderReceiver); + module.exports = AjaxBasedTransport; + } + }); + + // node_modules/sockjs-client/lib/transport/receiver/xhr.js + var require_xhr = __commonJS({ + "node_modules/sockjs-client/lib/transport/receiver/xhr.js"(exports, module) { + "use strict"; + var inherits = require_inherits_browser(); + var EventEmitter = require_emitter().EventEmitter; + var debug = function() { + }; + if (true) { + debug = require_browser()("sockjs-client:receiver:xhr"); + } + function XhrReceiver(url, AjaxObject) { + debug(url); + EventEmitter.call(this); + var self2 = this; + this.bufferPosition = 0; + this.xo = new AjaxObject("POST", url, null); + this.xo.on("chunk", this._chunkHandler.bind(this)); + this.xo.once("finish", function(status, text) { + debug("finish", status, text); + self2._chunkHandler(status, text); + self2.xo = null; + var reason = status === 200 ? "network" : "permanent"; + debug("close", reason); + self2.emit("close", null, reason); + self2._cleanup(); + }); + } + inherits(XhrReceiver, EventEmitter); + XhrReceiver.prototype._chunkHandler = function(status, text) { + debug("_chunkHandler", status); + if (status !== 200 || !text) { + return; + } + for (var idx = -1; ; this.bufferPosition += idx + 1) { + var buf = text.slice(this.bufferPosition); + idx = buf.indexOf("\n"); + if (idx === -1) { + break; + } + var msg = buf.slice(0, idx); + if (msg) { + debug("message", msg); + this.emit("message", msg); + } + } + }; + XhrReceiver.prototype._cleanup = function() { + debug("_cleanup"); + this.removeAllListeners(); + }; + XhrReceiver.prototype.abort = function() { + debug("abort"); + if (this.xo) { + this.xo.close(); + debug("close"); + this.emit("close", null, "user"); + this.xo = null; + } + this._cleanup(); + }; + module.exports = XhrReceiver; + } + }); + + // node_modules/sockjs-client/lib/transport/browser/abstract-xhr.js + var require_abstract_xhr = __commonJS({ + "node_modules/sockjs-client/lib/transport/browser/abstract-xhr.js"(exports, module) { + "use strict"; + var EventEmitter = require_emitter().EventEmitter; + var inherits = require_inherits_browser(); + var utils = require_event(); + var urlUtils = require_url(); + var XHR = window.XMLHttpRequest; + var debug = function() { + }; + if (true) { + debug = require_browser()("sockjs-client:browser:xhr"); + } + function AbstractXHRObject(method, url, payload, opts) { + debug(method, url); + var self2 = this; + EventEmitter.call(this); + setTimeout(function() { + self2._start(method, url, payload, opts); + }, 0); + } + inherits(AbstractXHRObject, EventEmitter); + AbstractXHRObject.prototype._start = function(method, url, payload, opts) { + var self2 = this; + try { + this.xhr = new XHR(); + } catch (x) { + } + if (!this.xhr) { + debug("no xhr"); + this.emit("finish", 0, "no xhr support"); + this._cleanup(); + return; + } + url = urlUtils.addQuery(url, "t=" + +/* @__PURE__ */ new Date()); + this.unloadRef = utils.unloadAdd(function() { + debug("unload cleanup"); + self2._cleanup(true); + }); + try { + this.xhr.open(method, url, true); + if (this.timeout && "timeout" in this.xhr) { + this.xhr.timeout = this.timeout; + this.xhr.ontimeout = function() { + debug("xhr timeout"); + self2.emit("finish", 0, ""); + self2._cleanup(false); + }; + } + } catch (e) { + debug("exception", e); + this.emit("finish", 0, ""); + this._cleanup(false); + return; + } + if ((!opts || !opts.noCredentials) && AbstractXHRObject.supportsCORS) { + debug("withCredentials"); + this.xhr.withCredentials = true; + } + if (opts && opts.headers) { + for (var key in opts.headers) { + this.xhr.setRequestHeader(key, opts.headers[key]); + } + } + this.xhr.onreadystatechange = function() { + if (self2.xhr) { + var x = self2.xhr; + var text, status; + debug("readyState", x.readyState); + switch (x.readyState) { + case 3: + try { + status = x.status; + text = x.responseText; + } catch (e) { + } + debug("status", status); + if (status === 1223) { + status = 204; + } + if (status === 200 && text && text.length > 0) { + debug("chunk"); + self2.emit("chunk", status, text); + } + break; + case 4: + status = x.status; + debug("status", status); + if (status === 1223) { + status = 204; + } + if (status === 12005 || status === 12029) { + status = 0; + } + debug("finish", status, x.responseText); + self2.emit("finish", status, x.responseText); + self2._cleanup(false); + break; + } + } + }; + try { + self2.xhr.send(payload); + } catch (e) { + self2.emit("finish", 0, ""); + self2._cleanup(false); + } + }; + AbstractXHRObject.prototype._cleanup = function(abort) { + debug("cleanup"); + if (!this.xhr) { + return; + } + this.removeAllListeners(); + utils.unloadDel(this.unloadRef); + this.xhr.onreadystatechange = function() { + }; + if (this.xhr.ontimeout) { + this.xhr.ontimeout = null; + } + if (abort) { + try { + this.xhr.abort(); + } catch (x) { + } + } + this.unloadRef = this.xhr = null; + }; + AbstractXHRObject.prototype.close = function() { + debug("close"); + this._cleanup(true); + }; + AbstractXHRObject.enabled = !!XHR; + var axo = ["Active"].concat("Object").join("X"); + if (!AbstractXHRObject.enabled && axo in window) { + debug("overriding xmlhttprequest"); + XHR = function() { + try { + return new window[axo]("Microsoft.XMLHTTP"); + } catch (e) { + return null; + } + }; + AbstractXHRObject.enabled = !!new XHR(); + } + var cors = false; + try { + cors = "withCredentials" in new XHR(); + } catch (ignored) { + } + AbstractXHRObject.supportsCORS = cors; + module.exports = AbstractXHRObject; + } + }); + + // node_modules/sockjs-client/lib/transport/sender/xhr-cors.js + var require_xhr_cors = __commonJS({ + "node_modules/sockjs-client/lib/transport/sender/xhr-cors.js"(exports, module) { + "use strict"; + var inherits = require_inherits_browser(); + var XhrDriver = require_abstract_xhr(); + function XHRCorsObject(method, url, payload, opts) { + XhrDriver.call(this, method, url, payload, opts); + } + inherits(XHRCorsObject, XhrDriver); + XHRCorsObject.enabled = XhrDriver.enabled && XhrDriver.supportsCORS; + module.exports = XHRCorsObject; + } + }); + + // node_modules/sockjs-client/lib/transport/sender/xhr-local.js + var require_xhr_local = __commonJS({ + "node_modules/sockjs-client/lib/transport/sender/xhr-local.js"(exports, module) { + "use strict"; + var inherits = require_inherits_browser(); + var XhrDriver = require_abstract_xhr(); + function XHRLocalObject(method, url, payload) { + XhrDriver.call(this, method, url, payload, { + noCredentials: true + }); + } + inherits(XHRLocalObject, XhrDriver); + XHRLocalObject.enabled = XhrDriver.enabled; + module.exports = XHRLocalObject; + } + }); + + // node_modules/sockjs-client/lib/utils/browser.js + var require_browser2 = __commonJS({ + "node_modules/sockjs-client/lib/utils/browser.js"(exports, module) { + "use strict"; + module.exports = { + isOpera: function() { + return window.navigator && /opera/i.test(window.navigator.userAgent); + }, + isKonqueror: function() { + return window.navigator && /konqueror/i.test(window.navigator.userAgent); + }, + hasDomain: function() { + if (!window.document) { + return true; + } + try { + return !!window.document.domain; + } catch (e) { + return false; + } + } + }; + } + }); + + // node_modules/sockjs-client/lib/transport/xhr-streaming.js + var require_xhr_streaming = __commonJS({ + "node_modules/sockjs-client/lib/transport/xhr-streaming.js"(exports, module) { + "use strict"; + var inherits = require_inherits_browser(); + var AjaxBasedTransport = require_ajax_based(); + var XhrReceiver = require_xhr(); + var XHRCorsObject = require_xhr_cors(); + var XHRLocalObject = require_xhr_local(); + var browser = require_browser2(); + function XhrStreamingTransport(transUrl) { + if (!XHRLocalObject.enabled && !XHRCorsObject.enabled) { + throw new Error("Transport created when disabled"); + } + AjaxBasedTransport.call(this, transUrl, "/xhr_streaming", XhrReceiver, XHRCorsObject); + } + inherits(XhrStreamingTransport, AjaxBasedTransport); + XhrStreamingTransport.enabled = function(info) { + if (info.nullOrigin) { + return false; + } + if (browser.isOpera()) { + return false; + } + return XHRCorsObject.enabled; + }; + XhrStreamingTransport.transportName = "xhr-streaming"; + XhrStreamingTransport.roundTrips = 2; + XhrStreamingTransport.needBody = !!window.document; + module.exports = XhrStreamingTransport; + } + }); + + // node_modules/sockjs-client/lib/transport/sender/xdr.js + var require_xdr = __commonJS({ + "node_modules/sockjs-client/lib/transport/sender/xdr.js"(exports, module) { + "use strict"; + var EventEmitter = require_emitter().EventEmitter; + var inherits = require_inherits_browser(); + var eventUtils = require_event(); + var browser = require_browser2(); + var urlUtils = require_url(); + var debug = function() { + }; + if (true) { + debug = require_browser()("sockjs-client:sender:xdr"); + } + function XDRObject(method, url, payload) { + debug(method, url); + var self2 = this; + EventEmitter.call(this); + setTimeout(function() { + self2._start(method, url, payload); + }, 0); + } + inherits(XDRObject, EventEmitter); + XDRObject.prototype._start = function(method, url, payload) { + debug("_start"); + var self2 = this; + var xdr = new window.XDomainRequest(); + url = urlUtils.addQuery(url, "t=" + +/* @__PURE__ */ new Date()); + xdr.onerror = function() { + debug("onerror"); + self2._error(); + }; + xdr.ontimeout = function() { + debug("ontimeout"); + self2._error(); + }; + xdr.onprogress = function() { + debug("progress", xdr.responseText); + self2.emit("chunk", 200, xdr.responseText); + }; + xdr.onload = function() { + debug("load"); + self2.emit("finish", 200, xdr.responseText); + self2._cleanup(false); + }; + this.xdr = xdr; + this.unloadRef = eventUtils.unloadAdd(function() { + self2._cleanup(true); + }); + try { + this.xdr.open(method, url); + if (this.timeout) { + this.xdr.timeout = this.timeout; + } + this.xdr.send(payload); + } catch (x) { + this._error(); + } + }; + XDRObject.prototype._error = function() { + this.emit("finish", 0, ""); + this._cleanup(false); + }; + XDRObject.prototype._cleanup = function(abort) { + debug("cleanup", abort); + if (!this.xdr) { + return; + } + this.removeAllListeners(); + eventUtils.unloadDel(this.unloadRef); + this.xdr.ontimeout = this.xdr.onerror = this.xdr.onprogress = this.xdr.onload = null; + if (abort) { + try { + this.xdr.abort(); + } catch (x) { + } + } + this.unloadRef = this.xdr = null; + }; + XDRObject.prototype.close = function() { + debug("close"); + this._cleanup(true); + }; + XDRObject.enabled = !!(window.XDomainRequest && browser.hasDomain()); + module.exports = XDRObject; + } + }); + + // node_modules/sockjs-client/lib/transport/xdr-streaming.js + var require_xdr_streaming = __commonJS({ + "node_modules/sockjs-client/lib/transport/xdr-streaming.js"(exports, module) { + "use strict"; + var inherits = require_inherits_browser(); + var AjaxBasedTransport = require_ajax_based(); + var XhrReceiver = require_xhr(); + var XDRObject = require_xdr(); + function XdrStreamingTransport(transUrl) { + if (!XDRObject.enabled) { + throw new Error("Transport created when disabled"); + } + AjaxBasedTransport.call(this, transUrl, "/xhr_streaming", XhrReceiver, XDRObject); + } + inherits(XdrStreamingTransport, AjaxBasedTransport); + XdrStreamingTransport.enabled = function(info) { + if (info.cookie_needed || info.nullOrigin) { + return false; + } + return XDRObject.enabled && info.sameScheme; + }; + XdrStreamingTransport.transportName = "xdr-streaming"; + XdrStreamingTransport.roundTrips = 2; + module.exports = XdrStreamingTransport; + } + }); + + // node_modules/sockjs-client/lib/transport/browser/eventsource.js + var require_eventsource = __commonJS({ + "node_modules/sockjs-client/lib/transport/browser/eventsource.js"(exports, module) { + module.exports = window.EventSource; + } + }); + + // node_modules/sockjs-client/lib/transport/receiver/eventsource.js + var require_eventsource2 = __commonJS({ + "node_modules/sockjs-client/lib/transport/receiver/eventsource.js"(exports, module) { + "use strict"; + var inherits = require_inherits_browser(); + var EventEmitter = require_emitter().EventEmitter; + var EventSourceDriver = require_eventsource(); + var debug = function() { + }; + if (true) { + debug = require_browser()("sockjs-client:receiver:eventsource"); + } + function EventSourceReceiver(url) { + debug(url); + EventEmitter.call(this); + var self2 = this; + var es = this.es = new EventSourceDriver(url); + es.onmessage = function(e) { + debug("message", e.data); + self2.emit("message", decodeURI(e.data)); + }; + es.onerror = function(e) { + debug("error", es.readyState, e); + var reason = es.readyState !== 2 ? "network" : "permanent"; + self2._cleanup(); + self2._close(reason); + }; + } + inherits(EventSourceReceiver, EventEmitter); + EventSourceReceiver.prototype.abort = function() { + debug("abort"); + this._cleanup(); + this._close("user"); + }; + EventSourceReceiver.prototype._cleanup = function() { + debug("cleanup"); + var es = this.es; + if (es) { + es.onmessage = es.onerror = null; + es.close(); + this.es = null; + } + }; + EventSourceReceiver.prototype._close = function(reason) { + debug("close", reason); + var self2 = this; + setTimeout(function() { + self2.emit("close", null, reason); + self2.removeAllListeners(); + }, 200); + }; + module.exports = EventSourceReceiver; + } + }); + + // node_modules/sockjs-client/lib/transport/eventsource.js + var require_eventsource3 = __commonJS({ + "node_modules/sockjs-client/lib/transport/eventsource.js"(exports, module) { + "use strict"; + var inherits = require_inherits_browser(); + var AjaxBasedTransport = require_ajax_based(); + var EventSourceReceiver = require_eventsource2(); + var XHRCorsObject = require_xhr_cors(); + var EventSourceDriver = require_eventsource(); + function EventSourceTransport(transUrl) { + if (!EventSourceTransport.enabled()) { + throw new Error("Transport created when disabled"); + } + AjaxBasedTransport.call(this, transUrl, "/eventsource", EventSourceReceiver, XHRCorsObject); + } + inherits(EventSourceTransport, AjaxBasedTransport); + EventSourceTransport.enabled = function() { + return !!EventSourceDriver; + }; + EventSourceTransport.transportName = "eventsource"; + EventSourceTransport.roundTrips = 2; + module.exports = EventSourceTransport; + } + }); + + // node_modules/sockjs-client/lib/version.js + var require_version = __commonJS({ + "node_modules/sockjs-client/lib/version.js"(exports, module) { + module.exports = "1.6.1"; + } + }); + + // node_modules/sockjs-client/lib/utils/iframe.js + var require_iframe = __commonJS({ + "node_modules/sockjs-client/lib/utils/iframe.js"(exports, module) { + "use strict"; + var eventUtils = require_event(); + var browser = require_browser2(); + var debug = function() { + }; + if (true) { + debug = require_browser()("sockjs-client:utils:iframe"); + } + module.exports = { + WPrefix: "_jp", + currentWindowId: null, + polluteGlobalNamespace: function() { + if (!(module.exports.WPrefix in window)) { + window[module.exports.WPrefix] = {}; + } + }, + postMessage: function(type, data) { + if (window.parent !== window) { + window.parent.postMessage(JSON.stringify({ + windowId: module.exports.currentWindowId, + type, + data: data || "" + }), "*"); + } else { + debug("Cannot postMessage, no parent window.", type, data); + } + }, + createIframe: function(iframeUrl, errorCallback) { + var iframe = window.document.createElement("iframe"); + var tref, unloadRef; + var unattach = function() { + debug("unattach"); + clearTimeout(tref); + try { + iframe.onload = null; + } catch (x) { + } + iframe.onerror = null; + }; + var cleanup = function() { + debug("cleanup"); + if (iframe) { + unattach(); + setTimeout(function() { + if (iframe) { + iframe.parentNode.removeChild(iframe); + } + iframe = null; + }, 0); + eventUtils.unloadDel(unloadRef); + } + }; + var onerror = function(err) { + debug("onerror", err); + if (iframe) { + cleanup(); + errorCallback(err); + } + }; + var post = function(msg, origin) { + debug("post", msg, origin); + setTimeout(function() { + try { + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage(msg, origin); + } + } catch (x) { + } + }, 0); + }; + iframe.src = iframeUrl; + iframe.style.display = "none"; + iframe.style.position = "absolute"; + iframe.onerror = function() { + onerror("onerror"); + }; + iframe.onload = function() { + debug("onload"); + clearTimeout(tref); + tref = setTimeout(function() { + onerror("onload timeout"); + }, 2e3); + }; + window.document.body.appendChild(iframe); + tref = setTimeout(function() { + onerror("timeout"); + }, 15e3); + unloadRef = eventUtils.unloadAdd(cleanup); + return { + post, + cleanup, + loaded: unattach + }; + }, + createHtmlfile: function(iframeUrl, errorCallback) { + var axo = ["Active"].concat("Object").join("X"); + var doc = new window[axo]("htmlfile"); + var tref, unloadRef; + var iframe; + var unattach = function() { + clearTimeout(tref); + iframe.onerror = null; + }; + var cleanup = function() { + if (doc) { + unattach(); + eventUtils.unloadDel(unloadRef); + iframe.parentNode.removeChild(iframe); + iframe = doc = null; + CollectGarbage(); + } + }; + var onerror = function(r) { + debug("onerror", r); + if (doc) { + cleanup(); + errorCallback(r); + } + }; + var post = function(msg, origin) { + try { + setTimeout(function() { + if (iframe && iframe.contentWindow) { + iframe.contentWindow.postMessage(msg, origin); + } + }, 0); + } catch (x) { + } + }; + doc.open(); + doc.write('