diff --git a/v2/cmd/wails/internal/commands/dev/dev.go b/v2/cmd/wails/internal/commands/dev/dev.go index 7cc39114e..ee34d35e1 100644 --- a/v2/cmd/wails/internal/commands/dev/dev.go +++ b/v2/cmd/wails/internal/commands/dev/dev.go @@ -5,9 +5,11 @@ import ( "fmt" "io" "net/http" + "net/url" "os" "os/exec" "os/signal" + "path" "path/filepath" "runtime" "strings" @@ -32,8 +34,6 @@ import ( "github.com/wailsapp/wails/v2/pkg/commands/build" ) -const defaultDevServerURL = "http://localhost:34115" - func LogGreen(message string, args ...interface{}) { text := fmt.Sprintf(message, args...) println(colour.Green(text)) @@ -71,9 +71,11 @@ type devFlags struct { loglevel string forceBuild bool debounceMS int - devServerURL string + devServer string appargs string saveConfig bool + + frontendDevServerURL string } // AddSubcommand adds the `dev` command for the Wails application @@ -95,7 +97,8 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error { command.StringFlag("loglevel", "Loglevel to use - Trace, Debug, Info, Warning, Error", &flags.loglevel) command.BoolFlag("f", "Force build application", &flags.forceBuild) command.IntFlag("debounce", "The amount of time to wait to trigger a reload on change", &flags.debounceMS) - command.StringFlag("devserverurl", "The url of the dev server to use", &flags.devServerURL) + command.StringFlag("devserver", "The address of the wails dev server", &flags.devServer) + command.StringFlag("frontenddevserverurl", "The url of the external frontend dev server to use", &flags.frontendDevServerURL) command.StringFlag("appargs", "arguments to pass to the underlying app (quoted and space searated)", &flags.appargs) command.BoolFlag("save", "Save given flags as defaults", &flags.saveConfig) @@ -172,13 +175,14 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error { defer closer(&devCommandWaitGroup) } + devServerURL, err := url.Parse("http://" + flags.devServer) + if err != nil { + return err + } + // open browser if flags.openBrowser { - url := defaultDevServerURL - if flags.devServerURL != "" { - url = flags.devServerURL - } - err = browser.OpenURL(url) + err = browser.OpenURL(devServerURL.String()) if err != nil { return err } @@ -186,6 +190,10 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error { // create the project files watcher watcher, err := initialiseWatcher(cwd, logger.Fatal) + if err != nil { + return err + } + defer func(watcher *fsnotify.Watcher) { err := watcher.Close() if err != nil { @@ -194,11 +202,14 @@ func AddSubcommand(app *clir.Cli, w io.Writer) error { }(watcher) LogGreen("Watching (sub)/directory: %s", cwd) - LogGreen("Using Dev Server URL: %s", flags.devServerURL) + LogGreen("Using DevServer URL: %s", devServerURL) + if flags.frontendDevServerURL != "" { + LogGreen("Using Frontend DevServer URL: %s", flags.frontendDevServerURL) + } LogGreen("Using reload debounce setting of %d milliseconds", flags.debounceMS) // Watch for changes and trigger restartApp() - doWatcherLoop(buildOptions, debugBinaryProcess, flags, watcher, exitCodeChannel, quitChannel) + doWatcherLoop(buildOptions, debugBinaryProcess, flags, watcher, exitCodeChannel, quitChannel, devServerURL) // Kill the current program if running if debugBinaryProcess != nil { @@ -261,7 +272,6 @@ func runCommand(dir string, exitOnError bool, command string, args ...string) er // defaultDevFlags generates devFlags with default options func defaultDevFlags() devFlags { return devFlags{ - devServerURL: defaultDevServerURL, compilerCommand: "go", verbosity: 1, extensions: "go", @@ -319,12 +329,12 @@ func loadAndMergeProjectConfig(cwd string, flags *devFlags) (*project.Project, e projectConfig.ReloadDirectories = filepath.ToSlash(flags.reloadDirs) } - if flags.devServerURL == defaultDevServerURL && projectConfig.DevServerURL != defaultDevServerURL && projectConfig.DevServerURL != "" { - flags.devServerURL = projectConfig.DevServerURL + if flags.devServer == "" && projectConfig.DevServer != "" { + flags.devServer = projectConfig.DevServer } - if flags.devServerURL != projectConfig.DevServerURL { - projectConfig.DevServerURL = flags.devServerURL + if flags.frontendDevServerURL == "" && projectConfig.FrontendDevServerURL != "" { + flags.frontendDevServerURL = projectConfig.FrontendDevServerURL } if flags.wailsjsdir == "" && projectConfig.WailsJSDir != "" { @@ -466,7 +476,8 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process // Set environment variables accordingly os.Setenv("loglevel", flags.loglevel) os.Setenv("assetdir", flags.assetDir) - os.Setenv("devserverurl", flags.devServerURL) + os.Setenv("devserver", flags.devServer) + os.Setenv("frontenddevserverurl", flags.frontendDevServerURL) // Start up new binary with correct args newProcess := process.NewProcess(appBinary, args...) @@ -486,7 +497,7 @@ func restartApp(buildOptions *build.Options, debugBinaryProcess *process.Process } // doWatcherLoop is the main watch loop that runs while dev is active -func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, flags devFlags, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal) { +func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Process, flags devFlags, watcher *fsnotify.Watcher, exitCodeChannel chan int, quitChannel chan os.Signal, devServerURL *url.URL) { // Main Loop var ( err error @@ -513,6 +524,9 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc reload := false assetDir := "" changedPaths := map[string]struct{}{} + + assetDirURL := joinPath(devServerURL, "/wails/assetdir") + reloadURL := joinPath(devServerURL, "/wails/reload") for quit == false { //reload := false select { @@ -584,9 +598,14 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc debugBinaryProcess = newBinaryProcess } } + + if flags.frontendDevServerURL != "" { + // If we are using an external dev server all the reload of the frontend part can be skipped + continue + } if len(changedPaths) != 0 { if assetDir == "" { - resp, err := http.Get("http://localhost:34115/wails/assetdir") + resp, err := http.Get(assetDirURL) if err != nil { LogRed("Error during retrieving assetdir: %s", err.Error()) } else { @@ -615,7 +634,7 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc } if reload { reload = false - _, err = http.Get("http://localhost:34115/wails/reload") + _, err = http.Get(reloadURL) if err != nil { LogRed("Error during refresh: %s", err.Error()) } @@ -626,3 +645,9 @@ func doWatcherLoop(buildOptions *build.Options, debugBinaryProcess *process.Proc } } } + +func joinPath(url *url.URL, subPath string) string { + u := *url + u.Path = path.Join(u.Path, subPath) + return u.String() +} diff --git a/v2/cmd/wails/internal/commands/initialise/templates/templates/svelte/wails.tmpl.json b/v2/cmd/wails/internal/commands/initialise/templates/templates/svelte/wails.tmpl.json index 283077796..f3dd5cd6c 100644 --- a/v2/cmd/wails/internal/commands/initialise/templates/templates/svelte/wails.tmpl.json +++ b/v2/cmd/wails/internal/commands/initialise/templates/templates/svelte/wails.tmpl.json @@ -3,9 +3,10 @@ "outputfilename": "{{.BinaryName}}", "frontend:install": "npm install", "frontend:build": "npm run build", - "frontend:dev:watcher": "npm run build:watch", + "frontend:dev:watcher": "npm run dev", + "frontend:dev:serverUrl": "http://localhost:3000", "wailsjsdir": "./frontend", - "debounceMS": 2000, + "debounceMS": 1000, "author": { "name": "{{.AuthorName}}", "email": "{{.AuthorEmail}}" diff --git a/v2/cmd/wails/internal/version.go b/v2/cmd/wails/internal/version.go index 913742774..505eb7048 100644 --- a/v2/cmd/wails/internal/version.go +++ b/v2/cmd/wails/internal/version.go @@ -1,3 +1,3 @@ package internal -var Version = "v2.0.0-beta.34" +var Version = "v2.0.0-beta.34.frontenddevserver" // Just for testing purposes do not merge diff --git a/v2/go.mod b/v2/go.mod index ce475f5b9..19c743729 100644 --- a/v2/go.mod +++ b/v2/go.mod @@ -4,21 +4,17 @@ go 1.17 require ( github.com/Masterminds/semver v1.5.0 - github.com/fatih/structtag v1.2.0 github.com/flytam/filenamify v1.0.0 github.com/fsnotify/fsnotify v1.4.9 github.com/gabriel-vasile/mimetype v1.3.1 github.com/go-git/go-billy/v5 v5.2.0 // indirect github.com/go-git/go-git/v5 v5.3.0 - github.com/gofiber/fiber/v2 v2.17.0 - github.com/gofiber/websocket/v2 v2.0.8 - github.com/golang/protobuf v1.5.2 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 github.com/google/uuid v1.1.2 // indirect - github.com/gorilla/websocket v1.4.1 github.com/imdario/mergo v0.3.12 github.com/jackmordaunt/icns v1.0.0 github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e + github.com/labstack/echo/v4 v4.7.2 github.com/leaanthony/clir v1.0.4 github.com/leaanthony/debme v1.2.1 github.com/leaanthony/go-ansi-parser v1.0.1 @@ -33,48 +29,43 @@ require ( github.com/pkg/browser v0.0.0-20210706143420-7d21f8c997e2 github.com/pkg/errors v0.9.1 github.com/tc-hib/winres v0.1.5 - github.com/tdewolff/minify v2.3.6+incompatible - github.com/tdewolff/parse v2.3.4+incompatible // indirect - github.com/tdewolff/test v1.0.6 // indirect github.com/tidwall/sjson v1.1.7 github.com/wzshiming/ctc v1.2.3 github.com/ztrue/tracerr v0.3.0 golang.org/x/mod v0.4.1 - golang.org/x/net v0.0.0-20210510120150-4163338589ed + golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 golang.org/x/tools v0.1.0 - nhooyr.io/websocket v1.8.6 ) require ( github.com/Microsoft/go-winio v0.4.16 // indirect - github.com/andybalholm/brotli v1.0.2 // indirect github.com/emirpasic/gods v1.12.0 // indirect - github.com/fasthttp/websocket v0.0.0-20200320073529-1554a54587ab // indirect github.com/go-git/gcfg v1.5.0 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/google/go-cmp v0.5.5 // indirect github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect - github.com/klauspost/compress v1.12.2 // indirect github.com/kr/pretty v0.3.0 // indirect + github.com/labstack/gommon v0.3.1 // indirect github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e // indirect + github.com/mattn/go-colorable v0.1.11 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect github.com/mattn/go-runewidth v0.0.7 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect - github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f // indirect github.com/sergi/go-diff v1.1.0 // indirect github.com/tidwall/gjson v1.8.0 // indirect github.com/tidwall/match v1.0.3 // indirect github.com/tidwall/pretty v1.1.0 // indirect github.com/tkrajina/go-reflector v0.5.5 // indirect github.com/valyala/bytebufferpool v1.0.0 // indirect - github.com/valyala/fasthttp v1.28.0 // indirect - github.com/valyala/tcplisten v1.0.0 // indirect + github.com/valyala/fasttemplate v1.2.1 // indirect github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect - golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a // indirect + golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 // indirect golang.org/x/image v0.0.0-20201208152932-35266b937fa6 // indirect + golang.org/x/text v0.3.7 // indirect golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect gopkg.in/warnings.v0 v0.1.2 // indirect ) diff --git a/v2/go.sum b/v2/go.sum index f408167df..7fa7b327c 100644 --- a/v2/go.sum +++ b/v2/go.sum @@ -5,8 +5,6 @@ github.com/Microsoft/go-winio v0.4.16 h1:FtSW/jqD+l4ba5iPBj9CODVtgfYAD8w2wS923g/ github.com/Microsoft/go-winio v0.4.16/go.mod h1:XB6nPKklQyQ7GC9LdcBEcBl8PF76WugXOPRXwdLnMv0= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7 h1:uSoVVbwJiQipAclBbw+8quDsfcvFjOpI5iCf4p/cqCs= github.com/alcortesm/tgz v0.0.0-20161220082320-9c5fe88206d7/go.mod h1:6zEj6s6u/ghQa61ZWa/C2Aw3RkjiTBOix7dkqa1VLIs= -github.com/andybalholm/brotli v1.0.2 h1:JKnhI/XQ75uFBTiuzXpzFrUriDPiZjlOSzh6wXogP0E= -github.com/andybalholm/brotli v1.0.2/go.mod h1:loMXtMfwqflxFJPmdbJO0a3KNoPuLBgiu3qAvBg8x/Y= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239 h1:kFOfPq6dUM1hTo4JG6LR5AXSUEsOjtdm0kw0FtQtMJA= github.com/anmitsu/go-shlex v0.0.0-20161002113705-648efa622239/go.mod h1:2FmKhYUyUczH0OGQWaF5ceTx0UBShxjsH6f8oGKYe2c= github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio= @@ -17,10 +15,6 @@ 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/emirpasic/gods v1.12.0 h1:QAUIPSaCu4G+POclxeqb3F+WPpdKqFGlw36+yOzGlrg= github.com/emirpasic/gods v1.12.0/go.mod h1:YfzfFFoVP/catgzJb4IKIqXjX78Ha8FMSDh3ymbK86o= -github.com/fasthttp/websocket v0.0.0-20200320073529-1554a54587ab h1:9e2joQGp642wHGFP5m86SDptAavrdGBe8/x9DGEEAaI= -github.com/fasthttp/websocket v0.0.0-20200320073529-1554a54587ab/go.mod h1:smsv/h4PBEBaU0XDTY5UwJTpZv69fQ0FfcLJr21mA6Y= -github.com/fatih/structtag v1.2.0 h1:/OdNE99OxoI/PqaW/SuSK9uxxT3f/tcSZgon/ssNSx4= -github.com/fatih/structtag v1.2.0/go.mod h1:mBJUNpUnHmRKrKlQQlmCrh5PuhftFbNv8Ys4/aAZl94= github.com/flynn/go-shlex v0.0.0-20150515145356-3f9db97f8568/go.mod h1:xEzjJPgXI435gkrCt3MPfRiAkVrwSbHsst4LCFVfpJc= github.com/flytam/filenamify v1.0.0 h1:ewx6BY2dj7U6h2zGPJmt33q/BjkSf/YsY/woQvnUNIs= github.com/flytam/filenamify v1.0.0/go.mod h1:Dzf9kVycwcsBlr2ATg6uxjqiFgKGH+5SKFuhdeP5zu8= @@ -28,10 +22,6 @@ github.com/fsnotify/fsnotify v1.4.9 h1:hsms1Qyu0jgnwNXIxa+/V/PDsU6CfLf6CNO8H7IWo github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= github.com/gabriel-vasile/mimetype v1.3.1 h1:qevA6c2MtE1RorlScnixeG0VA1H4xrXyhyX3oWBynNQ= github.com/gabriel-vasile/mimetype v1.3.1/go.mod h1:fA8fi6KUiG7MgQQ+mEWotXoEOvmxRtOJlERCzSmRvr8= -github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= -github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= -github.com/gin-gonic/gin v1.6.3 h1:ahKqKTFpO5KTPHxWZjEdPScmYaGtLo8Y4DMHoEsnp14= -github.com/gin-gonic/gin v1.6.3/go.mod h1:75u5sXoLsGZoRN5Sgbi1eraJ4GU3++wFwWzhwvtwp4M= github.com/gliderlabs/ssh v0.2.2 h1:6zsha5zo/TWhRhwqCD3+EarCAgZ2yN28ipRnGPnwkI0= github.com/gliderlabs/ssh v0.2.2/go.mod h1:U7qILu1NlMHj9FlMhZLlkCdDnU1DBEAqr0aevW3Awn0= github.com/go-git/gcfg v1.5.0 h1:Q5ViNfGF8zFgyJWPqYwA7qGFoMTEiBmdlkcfRmpIMa4= @@ -47,41 +37,15 @@ github.com/go-git/go-git/v5 v5.3.0/go.mod h1:xdX4bWJ48aOrdhnl2XqHYstHbbp6+LFS4r4 github.com/go-ole/go-ole v1.2.4/go.mod h1:XCwSNxSkXRo4vlyPy93sltvi/qJq0jqQhjqQNIwKuxM= github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= -github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= -github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q= -github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= -github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no= -github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= -github.com/go-playground/validator/v10 v10.2.0 h1:KgJ0snyC2R9VXYN2rneOtQcw5aHQB1Vv0sFl1UcHBOY= -github.com/go-playground/validator/v10 v10.2.0/go.mod h1:uOYAAleCW8F/7oMFd6aG0GOhaH6EGOAJShg8Id5JGkI= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee h1:s+21KNqlpePfkah2I+gwHF8xmJWRjooY+5248k6m4A0= -github.com/gobwas/httphead v0.0.0-20180130184737-2c6c146eadee/go.mod h1:L0fX3K22YWvt/FAX9NnzrNzcI4wNYi9Yku4O0LKYflo= -github.com/gobwas/pool v0.2.0 h1:QEmUOlnSjWtnpRGHF3SauEiOsy82Cup83Vf2LcMlnc8= -github.com/gobwas/pool v0.2.0/go.mod h1:q8bcK0KcYlCgd9e7WYLm9LpyS+YeLd8JVDW6WezmKEw= -github.com/gobwas/ws v1.0.2 h1:CoAavW/wd/kulfZmSIBt6p24n4j7tHgNVCjsfHVNUbo= -github.com/gobwas/ws v1.0.2/go.mod h1:szmBTxLgaFppYjEmNtny/v3w89xOydFnnZMcgRRu/EM= -github.com/gofiber/fiber/v2 v2.17.0 h1:qP3PkGUbBB0i9iQh5E057XI1yO5CZigUxZhyUFYAFoM= -github.com/gofiber/fiber/v2 v2.17.0/go.mod h1:iftruuHGkRYGEXVISmdD7HTYWyfS2Bh+Dkfq4n/1Owg= -github.com/gofiber/websocket/v2 v2.0.8 h1:Hb4y6IxYZVMO0segROODXJiXVgVD3a6i7wnfot8kM6k= -github.com/gofiber/websocket/v2 v2.0.8/go.mod h1:fv8HSGQX09sauNv9g5Xq8GeGAaahLFYQKKb4ZdT0x2w= -github.com/golang/protobuf v1.3.3/go.mod h1:vzj43D7+SQXF/4pzW/hwtAqwc6iTitCiVSaWz5lYuqw= -github.com/golang/protobuf v1.3.5/go.mod h1:6O5/vntMXwX2lRkT1hjjk0nAC1IDOTvTlVgjlRvqsdk= -github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= -github.com/golang/protobuf v1.5.2 h1:ROPKBNFfQgOUMifHyP+KYbvpjbdoFNs+aK7DXlji0Tw= -github.com/golang/protobuf v1.5.2/go.mod h1:XVQd3VNwM+JqD3oG2Ue2ip4fOMUkwXdXDdiuN0vRsmY= -github.com/golang/snappy v0.0.3/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I= github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= -github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= -github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.1/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.1.2 h1:EVhdT+1Kseyi1/pUmXKaFxYsDNy9RQYkMWRH68J/W7Y= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/gorilla/websocket v1.4.1 h1:q7AeDBpnBk8AogcD4DSag/Ukw/KV+YhzLj2bP5HvKCM= -github.com/gorilla/websocket v1.4.1/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/imdario/mergo v0.3.12 h1:b6R2BslTbIEToALKP7LxUvijTsNI9TAe80pLWN2g/HU= github.com/imdario/mergo v0.3.12/go.mod h1:jmQim1M+e3UYxmgPu/WyfjB3N3VflVyUjjjwH0dnCYA= github.com/jackmordaunt/icns v1.0.0 h1:RYSxplerf/l/DUd09AHtITwckkv/mqjVv4DjYdPmAMQ= @@ -91,15 +55,8 @@ github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e h1:Q3+PugElBCf4PFpxhErSzU3/PY5sFL5Z6rfv4AbGAck= github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e/go.mod h1:alcuEEnZsY1WQsagKhZDsoPCRoOijYqhZvPwLG0kzVs= github.com/jessevdk/go-flags v1.5.0/go.mod h1:Fw0T6WPc1dYxT4mKEZRfG5kJhaTDP9pj1c2EWnYs/m4= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 h1:DowS9hvgyYSX4TO5NpyC606/Z4SxnNYbT+WX27or6Ck= github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM= -github.com/klauspost/compress v1.8.2/go.mod h1:RyIbtBH6LamlWaDj8nUwkbUhJ87Yi3uG0guNDohfE1A= -github.com/klauspost/compress v1.10.3/go.mod h1:aoV0uJVorq1K+umq18yTdKaF57EivdYsUV+/s2qKfXs= -github.com/klauspost/compress v1.12.2 h1:2KCfW3I9M7nSc5wOqXAlW2v2U6v+w6cbjvbfp+OykW8= -github.com/klauspost/compress v1.12.2/go.mod h1:8dP1Hq4DHOhN9w426knH3Rhby4rFm6D8eO+e+Dq5Gzg= -github.com/klauspost/cpuid v1.2.1/go.mod h1:Pj4uuM528wm8OyEC2QMXAi2YiTZ96dNQPGgoMS4s3ek= github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= @@ -109,6 +66,10 @@ 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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI= +github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks= +github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o= +github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM= github.com/leaanthony/clir v1.0.4 h1:Dov2y9zWJmZr7CjaCe86lKa4b5CSxskGAt2yBkoDyiU= github.com/leaanthony/clir v1.0.4/go.mod h1:k/RBkdkFl18xkkACMCLt09bhiZnrGORoxmomeMvDpE0= github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oOc= @@ -127,22 +88,18 @@ github.com/leaanthony/typescriptify-golang-structs v0.1.7 h1:yoznzWzyxkO/iWdlpq+ github.com/leaanthony/typescriptify-golang-structs v0.1.7/go.mod h1:cWtOkiVhMF77e6phAXUcfNwYmMwCJ67Sij24lfvi9Js= github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ= github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU= -github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y= -github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e h1:9MlwzLdW7QSDrhDjFlsEYmxpFyIoXmYRon3dt0io31k= github.com/logrusorgru/aurora v0.0.0-20181002194514-a7b3b318ed4e/go.mod h1:7rIyQOR62GCctdiQpZ/zOJlFyk6y+94wXzv6RNZgaR4= github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/matryer/is v1.4.0/go.mod h1:8I/i5uYgLzgsgEloJE1U6xx5HkBQpAZvepWuujKwMRU= -github.com/mattn/go-isatty v0.0.12 h1:wuysRhFDzyxgEmMf5xjvJ2M9dZoWAXNNr5LSBS7uHXY= -github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs= +github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= github.com/mattn/go-runewidth v0.0.7 h1:Ei8KR0497xHyKJPAv59M1dkC+rOZCMBJ+t3fZ+twI54= github.com/mattn/go-runewidth v0.0.7/go.mod h1:H031xJmbD/WCDINGzjvQ9THkh0rPKHF+m2gUSrubnMI= github.com/mitchellh/go-homedir v1.1.0 h1:lukF9ziXFxDFPkA1vsr5zpc1XuPDn/wFntq5mG+4E0Y= github.com/mitchellh/go-homedir v1.1.0/go.mod h1:SfyaCUpYCn1Vlf4IUYiD9fPX4A5wJrkLzIz1N1q0pr0= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421 h1:ZqeYNhU3OHLH3mGKHDcjJRFFRrJa6eAM5H+CtDdOsPc= -github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742 h1:Esafd1046DLDQ0W1YjYsBW+p8U2u7vzgW2SQVmlNazg= -github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 h1:zYyBkD/k9seD2A7fsi6Oo2LfFZAehjjQMERAvZLEDnQ= github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646/go.mod h1:jpp1/29i3P1S/RLdc7JQKbRpFeM1dOBd8T9ki5s+AY8= github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= @@ -157,26 +114,17 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.6.1 h1:/FiVV8dS/e+YqF2JvO3yXRFbBLTIuSDkuC7aBOAvL+k= github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= -github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f h1:PgA+Olipyj258EIEYnpFFONrrCcAIWNUNoFhUfMqAGY= -github.com/savsgio/gotils v0.0.0-20200117113501-90175b0fbe3f/go.mod h1:lHhJedqxCoHN+zMtwGNTXWmF0u9Jt363FYRhV6g0CdY= github.com/sergi/go-diff v1.1.0 h1:we8PVUC3FE2uYfodKH/nBHMSetSfHDR6scGdBi+erh0= github.com/sergi/go-diff v1.1.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM= github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.1/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.7.0 h1:nwc3DEeHmmLAfoZucVR881uASk0Mfjw8xYJ99tb5CcY= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/tc-hib/winres v0.1.5 h1:2dA5yfjdoEA3UyRaOC92HNMt3jap66pLzoW4MjpC/0M= github.com/tc-hib/winres v0.1.5/go.mod h1:pe6dOR40VOrGz8PkzreVKNvEKnlE8t4yR8A8naL+t7A= -github.com/tdewolff/minify v2.3.6+incompatible h1:2hw5/9ZvxhWLvBUnHE06gElGYz+Jv9R4Eys0XUzItYo= -github.com/tdewolff/minify v2.3.6+incompatible/go.mod h1:9Ov578KJUmAWpS6NeZwRZyT56Uf6o3Mcz9CEsg8USYs= -github.com/tdewolff/parse v2.3.4+incompatible h1:x05/cnGwIMf4ceLuDMBOdQ1qGniMoxpP46ghf0Qzh38= -github.com/tdewolff/parse v2.3.4+incompatible/go.mod h1:8oBwCsVmUkgHO8M5iCzSIDtpzXOT0WXX9cWhz+bIzJQ= -github.com/tdewolff/test v1.0.6 h1:76mzYJQ83Op284kMT+63iCNCI7NEERsIN8dLM+RiKr4= -github.com/tdewolff/test v1.0.6/go.mod h1:6DAvZliBAAnD7rhVgwaM7DE5/d9NMOAJ09SqYqeK4QE= github.com/tidwall/gjson v1.8.0 h1:Qt+orfosKn0rbNTZqHYDqBrmm3UDA4KRkv70fDzG+PQ= github.com/tidwall/gjson v1.8.0/go.mod h1:5/xDoumyyDNerp2U36lyolv46b3uF/9Bu6OfyQ9GImk= github.com/tidwall/match v1.0.3 h1:FQUVvBImDutD8wJLN6c5eMzWtjgONK9MwIBCOrUJKeE= @@ -187,19 +135,10 @@ github.com/tidwall/sjson v1.1.7 h1:sgVPwu/yygHJ2m1pJDLgGM/h+1F5odx5Q9ljG3imRm8= github.com/tidwall/sjson v1.1.7/go.mod h1:w/yG+ezBeTdUxiKs5NcPicO9diP38nk96QBAbIIGeFs= github.com/tkrajina/go-reflector v0.5.5 h1:gwoQFNye30Kk7NrExj8zm3zFtrGPqOkzFMLuQZg1DtQ= github.com/tkrajina/go-reflector v0.5.5/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= -github.com/ugorji/go v1.1.7 h1:/68gy2h+1mWMrwZFeD1kQialdSzAb432dtpeJ42ovdo= -github.com/ugorji/go v1.1.7/go.mod h1:kZn38zHttfInRq0xu/PH0az30d+z6vm202qpg1oXVMw= -github.com/ugorji/go/codec v1.1.7 h1:2SvQaVZ1ouYrrKKwoSk2pzd4A9evlKJb9oTL+OaLUSs= -github.com/ugorji/go/codec v1.1.7/go.mod h1:Ax+UKWsSmolVDwsd+7N3ZtXu+yMGCf907BLYF3GoBXY= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= -github.com/valyala/fasthttp v1.9.0/go.mod h1:FstJa9V+Pj9vQ7OJie2qMHdwemEDaDiSdBnvPM1Su9w= -github.com/valyala/fasthttp v1.26.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= -github.com/valyala/fasthttp v1.28.0 h1:ruVmTmZaBR5i67NqnjvvH5gEv0zwHfWtbjoyW98iho4= -github.com/valyala/fasthttp v1.28.0/go.mod h1:cmWIqlu99AO/RKcp1HWaViTqc57FswJOfYYdPJBl8BA= -github.com/valyala/tcplisten v0.0.0-20161114210144-ceec8f93295a/go.mod h1:v3UYOV9WzVtRmSR+PDvWpU/qWl4Wa5LApYYX4ZtKbio= -github.com/valyala/tcplisten v1.0.0 h1:rBHj/Xf+E1tRGZyWIWwJDiRY0zc1Js+CV5DqwacVSA8= -github.com/valyala/tcplisten v1.0.0/go.mod h1:T0xQ8SeCZGxckz9qRXTfG43PvQ/mcWh7FwZEA7Ioqkc= +github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4= +github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/wzshiming/ctc v1.2.3 h1:q+hW3IQNsjIlOFBTGZZZeIXTElFM4grF4spW/errh/c= github.com/wzshiming/ctc v1.2.3/go.mod h1:2tVAtIY7SUyraSk0JxvwmONNPFL4ARavPuEsg5+KA28= github.com/wzshiming/winseq v0.0.0-20200112104235-db357dc107ae h1:tpXvBXC3hpQBDCc9OojJZCQMVRAbT3TTdUMP8WguXkY= @@ -214,8 +153,8 @@ golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACk golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20210322153248-0c34fe9e7dc2/go.mod h1:T9bdIzuCu7OtxOm1hfPfRQxPLYneinmdGuTeoZ9dtd4= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a h1:kr2P4QFmQr29mSLA43kwrOcgcReGTfbE9N577tCTuBc= -golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ= +golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/image v0.0.0-20200430140353-33d19683fad8/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= golang.org/x/image v0.0.0-20201208152932-35266b937fa6 h1:nfeHNc1nAqecKCy2FCy4HY+soOOe5sDLJ/gZLbx6GYI= golang.org/x/image v0.0.0-20201208152932-35266b937fa6/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= @@ -224,13 +163,12 @@ golang.org/x/mod v0.4.1 h1:Kvvh58BN8Y9/lBi7hTekvtMpm07eUZ0ck5pRHpsMWrY= golang.org/x/mod v0.4.1/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= -golang.org/x/net v0.0.0-20190827160401-ba9fcec4b297/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210326060303-6b1517762897/go.mod h1:uSPa2vr4CLtc/ILN5odXGNXS6mhrKVzTaCXzk9m6W3k= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= -golang.org/x/net v0.0.0-20210510120150-4163338589ed h1:p9UgmWI9wKpfYmgaV/IZKGdXc5qEK45tDwwwDyjS26I= -golang.org/x/net v0.0.0-20210510120150-4163338589ed/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY= +golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= @@ -240,7 +178,6 @@ golang.org/x/sys v0.0.0-20190507160741-ecd444e8653b/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200107162124-548cf772de50/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200302150141-5c8b2ff67527/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -249,18 +186,21 @@ golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20210320140829-1e4c9ba3b0c4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210324051608-47abb6519492/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= -golang.org/x/sys v0.0.0-20210514084401-e8d321eab015/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9 h1:XfKQ4OlFl8okEOr5UvAqFRVj8pY/4yfcXrddB8qAbU0= golang.org/x/sys v0.0.0-20220114195835-da31bd327af9/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/text v0.3.0/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.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/time v0.0.0-20191024005414-555d28b269f0/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= +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/time v0.0.0-20201208040808-7e3f01d25324/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-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.0 h1:po9/4sTYwZU9lPhi1tOrb4hCv3qrhiQ77LZfGa2OjwY= @@ -270,9 +210,6 @@ golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8T 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/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= -google.golang.org/protobuf v1.26.0 h1:bxAC2xTBsZGibn2RTntX0oH50xLsqy1OxA9tTL3p/lk= -google.golang.org/protobuf v1.26.0/go.mod h1:9q0QmTI4eRPtz6boOQmLYwt+qCgq0jsYwAQnmE0givc= 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-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -284,10 +221,8 @@ gopkg.in/warnings.v0 v0.1.2 h1:wFXVbFY8DY5/xOe1ECiWdKCzZlxgshcYVNkBHstARME= gopkg.in/warnings.v0 v0.1.2/go.mod h1:jksf8JmL6Qr/oQM2OXTHunEvvTAsrWBLb6OOjuVWRNI= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.3.0 h1:clyUAQHOM3G0M3f5vQj7LuJrETvjVot3Z5el9nffUtU= gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= -gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c h1:dUUwHk2QECo/6vqA44rthZ8ie2QXMNeKRTHCNY2nXvo= gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -nhooyr.io/websocket v1.8.6 h1:s+C3xAMLwGmlI31Nyn/eAehUlZPwfYZu2JXM621Q5/k= -nhooyr.io/websocket v1.8.6/go.mod h1:B70DZP8IakI65RVQ51MsWP/8jndNma26DVA/nFSCgW0= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b h1:h8qDotaEPuJATrMmW04NCwg7v22aHH28wwpauUhK9Oo= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/v2/internal/appng/app_dev.go b/v2/internal/appng/app_dev.go index f47b25bb6..3ee63c565 100644 --- a/v2/internal/appng/app_dev.go +++ b/v2/internal/appng/app_dev.go @@ -63,16 +63,21 @@ func CreateApp(appoptions *options.App) (*App, error) { // Check for CLI Flags var assetdirFlag *string - var devServerURLFlag *string + var devServerFlag *string + var frontendDevServerURLFlag *string var loglevelFlag *string assetdir := os.Getenv("assetdir") if assetdir == "" { assetdirFlag = flag.String("assetdir", "", "Directory to serve assets") } - devServerURL := os.Getenv("devserverurl") - if devServerURL == "" { - devServerURLFlag = flag.String("devserverurl", "", "URL of development server") + devServer := os.Getenv("devserver") + if devServer == "" { + devServerFlag = flag.String("devserver", "", "Address to bind the wails dev server to") + } + frontendDevServerURL := os.Getenv("frontenddevserverurl") + if frontendDevServerURL == "" { + frontendDevServerURLFlag = flag.String("frontenddevserverurl", "", "URL of the external frontend dev server") } loglevel := os.Getenv("loglevel") @@ -86,8 +91,11 @@ func CreateApp(appoptions *options.App) (*App, error) { if assetdirFlag != nil { assetdir = *assetdirFlag } - if devServerURLFlag != nil { - devServerURL = *devServerURLFlag + if devServerFlag != nil { + devServer = *devServerFlag + } + if frontendDevServerURLFlag != nil { + frontendDevServerURL = *frontendDevServerURLFlag } if loglevelFlag != nil { loglevel = *loglevelFlag @@ -102,7 +110,15 @@ func CreateApp(appoptions *options.App) (*App, error) { } } - if assetdir != "" { + if frontendDevServerURL != "" { + if devServer == "" { + return nil, fmt.Errorf("Unable to use FrontendDevServerUrl without a DevServer address") + } + ctx = context.WithValue(ctx, "starturl", "http://"+devServer) + ctx = context.WithValue(ctx, "frontenddevserverurl", frontendDevServerURL) + + myLogger.Info("Serving assets from frontend DevServer URL: %s", frontendDevServerURL) + } else if assetdir != "" { // Let's override the assets to serve from on disk, if needed absdir, err := filepath.Abs(assetdir) if err != nil { @@ -115,8 +131,8 @@ func CreateApp(appoptions *options.App) (*App, error) { ctx = context.WithValue(ctx, "assetdir", assetdir) } - if devServerURL != "" { - ctx = context.WithValue(ctx, "devserverurl", devServerURL) + if devServer != "" { + ctx = context.WithValue(ctx, "devserver", devServer) } if loglevel != "" { diff --git a/v2/internal/frontend/assetserver/assethandler.go b/v2/internal/frontend/assetserver/assethandler.go new file mode 100644 index 000000000..2594e4eb8 --- /dev/null +++ b/v2/internal/frontend/assetserver/assethandler.go @@ -0,0 +1,124 @@ +package assetserver + +import ( + "context" + _ "embed" + iofs "io/fs" + "net/http" + "os" + "path" + "strings" + "time" + + "github.com/wailsapp/wails/v2/internal/fs" + "github.com/wailsapp/wails/v2/internal/logger" + "github.com/wailsapp/wails/v2/pkg/options" +) + +//go:embed defaultindex.html +var defaultHTML []byte + +type assetHandler struct { + fs iofs.FS + + logger *logger.Logger + + servingFromDisk bool +} + +func NewAsssetHandler(ctx context.Context, options *options.App) (http.Handler, error) { + vfs := options.Assets + if vfs != nil { + if _, err := vfs.Open("."); err != nil { + return nil, err + } + + subDir, err := fs.FindPathToFile(vfs, "index.html") + if err != nil { + return nil, err + } + + vfs, err = iofs.Sub(vfs, path.Clean(subDir)) + if err != nil { + return nil, err + } + } + + result := &assetHandler{ + fs: vfs, + + // Check if we have been given a directory to serve assets from. + // If so, this means we are in dev mode and are serving assets off disk. + // We indicate this through the `servingFromDisk` flag to ensure requests + // aren't cached in dev mode. + servingFromDisk: ctx.Value("assetdir") != nil, + } + + if _logger := ctx.Value("logger"); _logger != nil { + result.logger = _logger.(*logger.Logger) + } + + return result, nil +} + +func (d *assetHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if d.fs == nil { + rw.WriteHeader(http.StatusNotFound) + return + } + + filename := strings.TrimPrefix(req.URL.Path, "/") + if d.logger != nil { + d.logger.Debug("[AssetHandler] Loading file '%s'", filename) + } + + var content []byte + var err error + switch filename { + case "", "index.html": + content, err = d.loadFile("index.html") + if err != nil { + err = nil + content = defaultHTML + } + + default: + content, err = d.loadFile(filename) + } + + if os.IsNotExist(err) { + rw.WriteHeader(http.StatusNotFound) + return + } + + if err == nil { + mimeType := GetMimetype(filename, content) + rw.Header().Set(HeaderContentType, mimeType) + rw.WriteHeader(http.StatusOK) + _, err = rw.Write(content) + } + + if err != nil { + rw.WriteHeader(http.StatusInternalServerError) + if d.logger != nil { + d.logger.Error("[AssetHandler] Unable to load file '%s': %s", filename, err) + } + } +} + +// loadFile will try to load the file from disk. If there is an error +// it will retry until eventually it will give up and error. +func (d *assetHandler) loadFile(filename string) ([]byte, error) { + if !d.servingFromDisk { + return iofs.ReadFile(d.fs, filename) + } + var result []byte + var err error + for tries := 0; tries < 50; tries++ { + result, err = iofs.ReadFile(d.fs, filename) + if err != nil { + time.Sleep(100 * time.Millisecond) + } + } + return result, err +} diff --git a/v2/internal/frontend/assetserver/assetserver.go b/v2/internal/frontend/assetserver/assetserver.go new file mode 100644 index 000000000..e6da09227 --- /dev/null +++ b/v2/internal/frontend/assetserver/assetserver.go @@ -0,0 +1,190 @@ +package assetserver + +import ( + "bytes" + "context" + "fmt" + "net/http" + "net/http/httptest" + "strings" + + "github.com/wailsapp/wails/v2/internal/frontend/runtime" + "github.com/wailsapp/wails/v2/internal/logger" + "github.com/wailsapp/wails/v2/pkg/options" + + "golang.org/x/net/html" +) + +const ( + runtimeJSPath = "/wails/runtime.js" + ipcJSPath = "/wails/ipc.js" +) + +type AssetServer struct { + handler http.Handler + runtimeJS []byte + ipcJS func(*http.Request) []byte + + logger *logger.Logger + + servingFromDisk bool + appendSpinnerToBody bool +} + +func NewAssetServer(ctx context.Context, options *options.App, bindingsJSON string) (*AssetServer, error) { + handler, err := NewAsssetHandler(ctx, options) + if err != nil { + return nil, err + } + + return NewAssetServerWithHandler(ctx, handler, bindingsJSON) +} + +func NewAssetServerWithHandler(ctx context.Context, handler http.Handler, bindingsJSON string) (*AssetServer, error) { + var buffer bytes.Buffer + buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n") + buffer.Write(runtime.RuntimeDesktopJS) + + result := &AssetServer{ + handler: handler, + runtimeJS: buffer.Bytes(), + + // Check if we have been given a directory to serve assets from. + // If so, this means we are in dev mode and are serving assets off disk. + // We indicate this through the `servingFromDisk` flag to ensure requests + // aren't cached in dev mode. + servingFromDisk: ctx.Value("assetdir") != nil, + } + + if _logger := ctx.Value("logger"); _logger != nil { + result.logger = _logger.(*logger.Logger) + } + + return result, nil +} + +func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + header := rw.Header() + if d.servingFromDisk { + header.Add(HeaderCacheControl, "no-cache") + } + + path := req.URL.Path + switch path { + case "", "/", "/index.html": + recorder := httptest.NewRecorder() + d.handler.ServeHTTP(recorder, req) + for k, v := range recorder.HeaderMap { + header[k] = v + } + + if recorder.Code != http.StatusOK { + rw.WriteHeader(recorder.Code) + return + } + + content, err := d.processIndexHTML(recorder.Body.Bytes()) + if err != nil { + d.serveError(rw, err, "Unable to processIndexHTML") + return + } + + d.writeBlob(rw, "/index.html", content) + + case runtimeJSPath: + d.writeBlob(rw, path, d.runtimeJS) + + case ipcJSPath: + content := runtime.DesktopIPC + if d.ipcJS != nil { + content = d.ipcJS(req) + } + d.writeBlob(rw, path, content) + + default: + d.handler.ServeHTTP(rw, req) + } +} + +func (d *AssetServer) Load(filename string) ([]byte, string, error) { + // This will be removed as soon as AssetsHandler have been fully introduced. + if !strings.HasPrefix(filename, "/") { + filename = "/" + filename + } + + req, err := http.NewRequest(http.MethodGet, "wails://wails"+filename, nil) + if err != nil { + return nil, "", err + } + + rw := httptest.NewRecorder() + d.ServeHTTP(rw, req) + + content := rw.Body.Bytes() + mimeType := rw.HeaderMap.Get(HeaderContentType) + if mimeType == "" { + mimeType = GetMimetype(filename, content) + } + return content, mimeType, nil +} + +func (d *AssetServer) processIndexHTML(indexHTML []byte) ([]byte, error) { + htmlNode, err := getHTMLNode(indexHTML) + if err != nil { + return nil, err + } + + if d.appendSpinnerToBody { + err = appendSpinnerToBody(htmlNode) + if err != nil { + return nil, err + } + } + + if err := insertScriptInHead(htmlNode, runtimeJSPath); err != nil { + return nil, err + } + + if err := insertScriptInHead(htmlNode, ipcJSPath); err != nil { + return nil, err + } + + var buffer bytes.Buffer + err = html.Render(&buffer, htmlNode) + if err != nil { + return nil, err + } + return buffer.Bytes(), nil +} + +func (d *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) { + header := rw.Header() + header.Set(HeaderContentLength, fmt.Sprintf("%d", len(blob))) + if mimeType := header.Get(HeaderContentType); mimeType == "" { + mimeType = GetMimetype(filename, blob) + header.Set(HeaderContentType, mimeType) + } + + rw.WriteHeader(http.StatusOK) + if _, err := rw.Write(blob); err != nil { + d.serveError(rw, err, "Unable to write content %s", filename) + } +} + +func (d *AssetServer) serveError(rw http.ResponseWriter, err error, msg string, args ...interface{}) { + args = append(args, err) + d.logError(msg+": %s", args...) + rw.WriteHeader(http.StatusInternalServerError) +} + +func (d *AssetServer) logDebug(message string, args ...interface{}) { + if d.logger != nil { + d.logger.Debug("[AssetServer] "+message, args...) + } +} + +func (d *AssetServer) logError(message string, args ...interface{}) { + if d.logger != nil { + d.logger.Error("[AssetServer] "+message, args...) + } +} diff --git a/v2/internal/frontend/assetserver/assetserver_browser_dev.go b/v2/internal/frontend/assetserver/assetserver_browser_dev.go index 93de7607a..6ced11787 100644 --- a/v2/internal/frontend/assetserver/assetserver_browser_dev.go +++ b/v2/internal/frontend/assetserver/assetserver_browser_dev.go @@ -4,114 +4,30 @@ package assetserver import ( - "bytes" "context" - "io/fs" + "net/http" "strings" "github.com/wailsapp/wails/v2/internal/frontend/runtime" - "github.com/wailsapp/wails/v2/internal/logger" - "golang.org/x/net/html" ) /* - The assetserver for dev serves assets from disk. It injects a websocket based IPC script into `index.html`. - */ - -type BrowserAssetServer struct { - assets fs.FS - runtimeJS []byte - logger *logger.Logger -} - -func NewBrowserAssetServer(ctx context.Context, assets fs.FS, bindingsJSON string) (*BrowserAssetServer, error) { - result := &BrowserAssetServer{} - _logger := ctx.Value("logger") - if _logger != nil { - result.logger = _logger.(*logger.Logger) - } - - var err error - result.assets, err = prepareAssetsForServing(assets) +func NewBrowserAssetServer(ctx context.Context, handler http.Handler, bindingsJSON string) (*AssetServer, error) { + result, err := NewAssetServerWithHandler(ctx, handler, bindingsJSON) if err != nil { return nil, err } - var buffer bytes.Buffer - buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n") - buffer.Write(runtime.RuntimeDesktopJS) - result.runtimeJS = buffer.Bytes() + result.appendSpinnerToBody = true + result.ipcJS = func(req *http.Request) []byte { + if strings.Contains(req.UserAgent(), WailsUserAgentValue) { + return runtime.DesktopIPC + } + return runtime.WebsocketIPC + } return result, nil } - -func (d *BrowserAssetServer) LogDebug(message string, args ...interface{}) { - if d.logger != nil { - d.logger.Debug("[BrowserAssetServer] "+message, args...) - } -} - -func (a *BrowserAssetServer) processIndexHTML() ([]byte, error) { - indexHTML, err := fs.ReadFile(a.assets, "index.html") - if err != nil { - return nil, err - } - htmlNode, err := getHTMLNode(indexHTML) - if err != nil { - return nil, err - } - err = appendSpinnerToBody(htmlNode) - if err != nil { - return nil, err - } - wailsOptions, err := extractOptions(indexHTML) - if err != nil { - return nil, err - } - - if wailsOptions.disableIPCInjection == false { - err := insertScriptInHead(htmlNode, "/wails/ipc.js") - if err != nil { - return nil, err - } - } - - if wailsOptions.disableRuntimeInjection == false { - err := insertScriptInHead(htmlNode, "/wails/runtime.js") - if err != nil { - return nil, err - } - } - - var buffer bytes.Buffer - err = html.Render(&buffer, htmlNode) - if err != nil { - return nil, err - } - return buffer.Bytes(), nil -} - -func (a *BrowserAssetServer) Load(filename string) ([]byte, string, error) { - var content []byte - var err error - switch filename { - case "/": - content, err = a.processIndexHTML() - case "/wails/runtime.js": - content = a.runtimeJS - case "/wails/ipc.js": - content = runtime.WebsocketIPC - default: - filename = strings.TrimPrefix(filename, "/") - a.LogDebug("Loading file: %s", filename) - content, err = fs.ReadFile(a.assets, filename) - } - if err != nil { - return nil, "", err - } - mimeType := GetMimetype(filename, content) - return content, mimeType, nil -} diff --git a/v2/internal/frontend/assetserver/assetserver_common.go b/v2/internal/frontend/assetserver/assetserver_common.go deleted file mode 100644 index d8ab4c9c6..000000000 --- a/v2/internal/frontend/assetserver/assetserver_common.go +++ /dev/null @@ -1,25 +0,0 @@ -package assetserver - -import ( - iofs "io/fs" - "path" - - "github.com/wailsapp/wails/v2/internal/fs" -) - -func prepareAssetsForServing(assets iofs.FS) (iofs.FS, error) { - if _, err := assets.Open("."); err != nil { - return nil, err - } - - subDir, err := fs.FindPathToFile(assets, "index.html") - if err != nil { - return nil, err - } - - assets, err = iofs.Sub(assets, path.Clean(subDir)) - if err != nil { - return nil, err - } - return assets, nil -} diff --git a/v2/internal/frontend/assetserver/assetserver_desktop.go b/v2/internal/frontend/assetserver/assetserver_desktop.go deleted file mode 100644 index c7d5fc7f2..000000000 --- a/v2/internal/frontend/assetserver/assetserver_desktop.go +++ /dev/null @@ -1,119 +0,0 @@ -package assetserver - -import ( - "bytes" - "context" - _ "embed" - "io/fs" - "log" - "strings" - "time" - - "github.com/wailsapp/wails/v2/internal/frontend/runtime" - "github.com/wailsapp/wails/v2/internal/logger" -) - -//go:embed defaultindex.html -var defaultHTML []byte - -type DesktopAssetServer struct { - assets fs.FS - runtimeJS []byte - logger *logger.Logger - servingFromDisk bool -} - -func NewDesktopAssetServer(ctx context.Context, assets fs.FS, bindingsJSON string, servingFromDisk bool) (*DesktopAssetServer, error) { - result := &DesktopAssetServer{ - servingFromDisk: servingFromDisk, - } - - _logger := ctx.Value("logger") - if _logger != nil { - result.logger = _logger.(*logger.Logger) - } - - var err error - result.assets, err = prepareAssetsForServing(assets) - if err != nil { - return nil, err - } - - var buffer bytes.Buffer - buffer.WriteString(`window.wailsbindings='` + bindingsJSON + `';` + "\n") - buffer.Write(runtime.RuntimeDesktopJS) - result.runtimeJS = buffer.Bytes() - - return result, nil -} - -func (d *DesktopAssetServer) LogDebug(message string, args ...interface{}) { - if d.logger != nil { - d.logger.Debug("[DesktopAssetServer] "+message, args...) - } -} - -// loadFile will try to load the file from disk. If there is an error -// it will retry until eventually it will give up and error. -func (d *DesktopAssetServer) loadFile(filename string) ([]byte, error) { - if !d.servingFromDisk { - return fs.ReadFile(d.assets, filename) - } - var result []byte - var err error - for tries := 0; tries < 50; tries++ { - result, err = fs.ReadFile(d.assets, filename) - if err != nil { - time.Sleep(100 * time.Millisecond) - } - } - return result, err -} - -func (d *DesktopAssetServer) processIndexHTML() ([]byte, error) { - indexHTML, err := d.loadFile("index.html") - if err != nil { - indexHTML = defaultHTML - } - wailsOptions, err := extractOptions(indexHTML) - if err != nil { - log.Fatal(err) - return nil, err - } - if wailsOptions.disableRuntimeInjection == false { - indexHTML, err = injectHTML(string(indexHTML), ``) - if err != nil { - return nil, err - } - } - if wailsOptions.disableIPCInjection == false { - indexHTML, err = injectHTML(string(indexHTML), ``) - if err != nil { - return nil, err - } - } - - return indexHTML, nil -} - -func (d *DesktopAssetServer) Load(filename string) ([]byte, string, error) { - var content []byte - var err error - switch filename { - case "/": - content, err = d.processIndexHTML() - case "/wails/runtime.js": - content = d.runtimeJS - case "/wails/ipc.js": - content = runtime.DesktopIPC - default: - filename = strings.TrimPrefix(filename, "/") - d.LogDebug("Loading file: %s", filename) - content, err = d.loadFile(filename) - } - if err != nil { - return nil, "", err - } - mimeType := GetMimetype(filename, content) - return content, mimeType, nil -} diff --git a/v2/internal/frontend/assetserver/common.go b/v2/internal/frontend/assetserver/common.go index 0695d7f27..c1e3a5a0d 100644 --- a/v2/internal/frontend/assetserver/common.go +++ b/v2/internal/frontend/assetserver/common.go @@ -3,91 +3,19 @@ package assetserver import ( "bytes" "errors" - "fmt" - "golang.org/x/net/html" - "strings" -) -type optionType string + "golang.org/x/net/html" +) const ( - noAutoInject optionType = "noautoinject" - noAutoInjectRuntime optionType = "noautoinjectruntime" - noAutoInjectIPC optionType = "noautoinjectipc" + HeaderContentType = "Content-Type" + HeaderContentLength = "Content-Length" + HeaderUserAgent = "User-Agent" + HeaderCacheControl = "Cache-Control" + + WailsUserAgentValue = "wails.io" ) -type Options struct { - disableRuntimeInjection bool - disableIPCInjection bool -} - -func newOptions(optionString string) *Options { - var result = &Options{} - optionString = strings.ToLower(optionString) - options := strings.Split(optionString, ",") - for _, option := range options { - switch optionType(strings.TrimSpace(option)) { - case noAutoInject: - result.disableRuntimeInjection = true - result.disableIPCInjection = true - case noAutoInjectIPC: - result.disableIPCInjection = true - case noAutoInjectRuntime: - result.disableRuntimeInjection = true - } - } - return result -} - -func injectHTML(input string, html string) ([]byte, error) { - splits := strings.Split(input, "") - if len(splits) != 2 { - return nil, fmt.Errorf("unable to locate a tag in your html") - } - - var result bytes.Buffer - result.WriteString(splits[0]) - result.WriteString(html) - result.WriteString("") - result.WriteString(splits[1]) - return result.Bytes(), nil -} - -func extractOptions(htmldata []byte) (*Options, error) { - doc, err := html.Parse(bytes.NewReader(htmldata)) - if err != nil { - return nil, err - } - var extractor func(*html.Node) *Options - extractor = func(node *html.Node) *Options { - if node.Type == html.ElementNode && node.Data == "meta" { - isWailsOptionsTag := false - wailsOptions := "" - for _, attr := range node.Attr { - if isWailsOptionsTag && attr.Key == "content" { - wailsOptions = attr.Val - } - if attr.Val == "wails-options" { - isWailsOptionsTag = true - } - } - return newOptions(wailsOptions) - } - for child := node.FirstChild; child != nil; child = child.NextSibling { - result := extractor(child) - if result != nil { - return result - } - } - return nil - } - result := extractor(doc) - if result == nil { - result = &Options{} - } - return result, nil -} - func createScriptNode(scriptName string) *html.Node { return &html.Node{ Type: html.ElementNode, diff --git a/v2/internal/frontend/assetserver/common_test.go b/v2/internal/frontend/assetserver/common_test.go deleted file mode 100644 index a60e1e12f..000000000 --- a/v2/internal/frontend/assetserver/common_test.go +++ /dev/null @@ -1,70 +0,0 @@ -package assetserver - -import ( - "reflect" - "testing" -) - -const realHTML = ` - - - test3 - - - - - - -
Please enter your name below �
-
- - -
- - - - - -` - -func genMeta(content string) []byte { - return []byte("") -} - -func genOptions(runtime bool, bindings bool) *Options { - return &Options{ - disableRuntimeInjection: runtime, - disableIPCInjection: bindings, - } -} - -func Test_extractOptions(t *testing.T) { - tests := []struct { - name string - htmldata []byte - want *Options - wantError bool - }{ - {"empty", []byte(""), &Options{}, false}, - {"bad data", []byte("<"), &Options{}, false}, - {"bad options", genMeta("noauto"), genOptions(false, false), false}, - {"realhtml", []byte(realHTML), genOptions(true, true), false}, - {"noautoinject", genMeta("noautoinject"), genOptions(true, true), false}, - {"noautoinjectipc", genMeta("noautoinjectipc"), genOptions(false, true), false}, - {"noautoinjectruntime", genMeta("noautoinjectruntime"), genOptions(true, false), false}, - {"spaces", genMeta(" noautoinjectruntime "), genOptions(true, false), false}, - {"multiple", genMeta("noautoinjectruntime,noautoinjectipc"), genOptions(true, true), false}, - {"multiple spaces", genMeta(" noautoinjectruntime, noautoinjectipc "), genOptions(true, true), false}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - got, err := extractOptions(tt.htmldata) - if !tt.wantError && err != nil { - t.Errorf("did not want error but got it") - } - if !reflect.DeepEqual(got, tt.want) { - t.Errorf("extractOptions() = %v, want %v", got, tt.want) - } - }) - } -} diff --git a/v2/internal/frontend/desktop/common/process_request.go b/v2/internal/frontend/desktop/common/process_request.go index d0a278571..4ea4519d3 100644 --- a/v2/internal/frontend/desktop/common/process_request.go +++ b/v2/internal/frontend/desktop/common/process_request.go @@ -23,7 +23,7 @@ func (r RequestRespone) String() string { return fmt.Sprintf("Body: '%s', StatusCode: %d", string(r.Body), r.StatusCode) } -func ProcessRequest(uri string, assets *assetserver.DesktopAssetServer, expectedScheme string, expectedHosts ...string) (RequestRespone, error) { +func ProcessRequest(uri string, assets *assetserver.AssetServer, expectedScheme string, expectedHosts ...string) (RequestRespone, error) { // Translate URI to file file, err := translateUriToFile(uri, expectedScheme, expectedHosts...) if err != nil { diff --git a/v2/internal/frontend/desktop/darwin/Application.h b/v2/internal/frontend/desktop/darwin/Application.h index a87275cdc..9389845a1 100644 --- a/v2/internal/frontend/desktop/darwin/Application.h +++ b/v2/internal/frontend/desktop/darwin/Application.h @@ -18,7 +18,7 @@ #define WindowStartsFullscreen 3 WailsContext* Create(const char* title, int width, int height, int frameless, int resizable, int fullscreen, int fullSizeContent, int hideTitleBar, int titlebarAppearsTransparent, int hideTitle, int useToolbar, int hideToolbarSeparator, int webviewIsTransparent, int alwaysOnTop, int hideWindowOnClose, const char *appearance, int windowIsTranslucent, int debug, int windowStartState, int startsHidden, int minWidth, int minHeight, int maxWidth, int maxHeight); -void Run(void*); +void Run(void*, const char* url); void SetTitle(void* ctx, const char *title); void Center(void* ctx); diff --git a/v2/internal/frontend/desktop/darwin/Application.m b/v2/internal/frontend/desktop/darwin/Application.m index 5fdc975a3..949fccaaf 100644 --- a/v2/internal/frontend/desktop/darwin/Application.m +++ b/v2/internal/frontend/desktop/darwin/Application.m @@ -330,7 +330,7 @@ void AppendSeparator(void* inMenu) { -void Run(void *inctx) { +void Run(void *inctx, const char* url) { WailsContext *ctx = (__bridge WailsContext*) inctx; NSApplication *app = [NSApplication sharedApplication]; AppDelegate* delegate = [AppDelegate new]; @@ -341,7 +341,10 @@ void Run(void *inctx) { delegate.startHidden = ctx.startHidden; delegate.startFullscreen = ctx.startFullscreen; - [ctx loadRequest:@"wails://wails/"]; + NSString *_url = safeInit(url); + [ctx loadRequest:_url]; + [_url release]; + [app setMainMenu:ctx.applicationMenu]; [app run]; [ctx release]; diff --git a/v2/internal/frontend/desktop/darwin/WailsContext.m b/v2/internal/frontend/desktop/darwin/WailsContext.m index 0dccdfc88..777ba7436 100644 --- a/v2/internal/frontend/desktop/darwin/WailsContext.m +++ b/v2/internal/frontend/desktop/darwin/WailsContext.m @@ -211,6 +211,7 @@ // Webview stuff here! WKWebViewConfiguration *config = [WKWebViewConfiguration new]; config.suppressesIncrementalRendering = true; + config.applicationNameForUserAgent = @"wails.io"; [config setURLSchemeHandler:self forURLScheme:@"wails"]; // [config.preferences setValue:[NSNumber numberWithBool:true] forKey:@"developerExtrasEnabled"]; diff --git a/v2/internal/frontend/desktop/darwin/frontend.go b/v2/internal/frontend/desktop/darwin/frontend.go index 26f2a888a..2ea6d8fda 100644 --- a/v2/internal/frontend/desktop/darwin/frontend.go +++ b/v2/internal/frontend/desktop/darwin/frontend.go @@ -48,7 +48,8 @@ type Frontend struct { debug bool // Assets - assets *assetserver.DesktopAssetServer + assets *assetserver.AssetServer + startURL string // main window handle mainWindow *Window @@ -65,29 +66,37 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. bindings: appBindings, dispatcher: dispatcher, ctx: ctx, + startURL: "wails://wails/", } - // Check if we have been given a directory to serve assets from. - // If so, this means we are in dev mode and are serving assets off disk. - // We indicate this through the `servingFromDisk` flag to ensure requests - // aren't cached by WebView2 in dev mode - _assetdir := ctx.Value("assetdir") - if _assetdir != nil { - result.servingFromDisk = true - } + _starturl, _ := ctx.Value("starturl").(string) + if _starturl != "" { + result.startURL = _starturl + } else { + // Check if we have been given a directory to serve assets from. + // If so, this means we are in dev mode and are serving assets off disk. + // We indicate this through the `servingFromDisk` flag to ensure requests + // aren't cached by WebView2 in dev mode + _assetdir := ctx.Value("assetdir") + if _assetdir != nil { + result.servingFromDisk = true + } - bindingsJSON, err := appBindings.ToJSON() - if err != nil { - log.Fatal(err) + bindingsJSON, err := appBindings.ToJSON() + if err != nil { + log.Fatal(err) + } + + assets, err := assetserver.NewAssetServer(ctx, appoptions, bindingsJSON) + if err != nil { + log.Fatal(err) + } + result.assets = assets + + go result.startRequestProcessor() } - assets, err := assetserver.NewDesktopAssetServer(ctx, appoptions.Assets, bindingsJSON, result.servingFromDisk) - if err != nil { - log.Fatal(err) - } - result.assets = assets go result.startMessageProcessor() - go result.startRequestProcessor() go result.startCallbackProcessor() return result @@ -134,7 +143,7 @@ func (f *Frontend) Run(ctx context.Context) error { f.frontendOptions.OnStartup(f.ctx) } }() - mainWindow.Run() + mainWindow.Run(f.startURL) return nil } diff --git a/v2/internal/frontend/desktop/darwin/window.go b/v2/internal/frontend/desktop/darwin/window.go index 98f012897..ec6e20d46 100644 --- a/v2/internal/frontend/desktop/darwin/window.go +++ b/v2/internal/frontend/desktop/darwin/window.go @@ -119,8 +119,10 @@ func (w *Window) Center() { C.Center(w.context) } -func (w *Window) Run() { - C.Run(w.context) +func (w *Window) Run(url string) { + _url := C.CString(url) + C.Run(w.context, _url) + C.free(unsafe.Pointer(_url)) } func (w *Window) Quit() { diff --git a/v2/internal/frontend/desktop/linux/frontend.go b/v2/internal/frontend/desktop/linux/frontend.go index 620d18c43..77451a3ec 100644 --- a/v2/internal/frontend/desktop/linux/frontend.go +++ b/v2/internal/frontend/desktop/linux/frontend.go @@ -39,7 +39,7 @@ type Frontend struct { debug bool // Assets - assets *assetserver.DesktopAssetServer + assets *assetserver.AssetServer startURL string // main window handle @@ -60,41 +60,38 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. bindings: appBindings, dispatcher: dispatcher, ctx: ctx, - startURL: "file://wails/", + startURL: "wails://wails/", } - bindingsJSON, err := appBindings.ToJSON() - if err != nil { - log.Fatal(err) - } + _starturl, _ := ctx.Value("starturl").(string) + if _starturl != "" { + result.startURL = _starturl + } else { + // Check if we have been given a directory to serve assets from. + // If so, this means we are in dev mode and are serving assets off disk. + // We indicate this through the `servingFromDisk` flag to ensure requests + // aren't cached by webkit. - _devServerURL := ctx.Value("devserverurl") - if _devServerURL != nil { - devServerURL := _devServerURL.(string) - if len(devServerURL) > 0 && devServerURL != "http://localhost:34115" { - result.startURL = devServerURL - return result + _assetdir := ctx.Value("assetdir") + if _assetdir != nil { + result.servingFromDisk = true } - } - // Check if we have been given a directory to serve assets from. - // If so, this means we are in dev mode and are serving assets off disk. - // We indicate this through the `servingFromDisk` flag to ensure requests - // aren't cached by webkit. + bindingsJSON, err := appBindings.ToJSON() + if err != nil { + log.Fatal(err) + } - _assetdir := ctx.Value("assetdir") - if _assetdir != nil { - result.servingFromDisk = true - } + assets, err := assetserver.NewAssetServer(ctx, appoptions, bindingsJSON) + if err != nil { + log.Fatal(err) + } + result.assets = assets - assets, err := assetserver.NewDesktopAssetServer(ctx, appoptions.Assets, bindingsJSON, result.servingFromDisk) - if err != nil { - log.Fatal(err) + go result.startRequestProcessor() } - result.assets = assets go result.startMessageProcessor() - go result.startRequestProcessor() C.gtk_init(nil, nil) @@ -127,7 +124,7 @@ func (f *Frontend) Run(ctx context.Context) error { } }() - f.mainWindow.Run() + f.mainWindow.Run(f.startURL) return nil } @@ -299,7 +296,7 @@ func (f *Frontend) processRequest(request unsafe.Pointer) { uri := C.webkit_uri_scheme_request_get_uri(req) goURI := C.GoString(uri) - res, err := common.ProcessRequest(goURI, f.assets, "wails", "", "null") + res, err := common.ProcessRequest(goURI, f.assets, "wails", "wails") if err != nil { f.logger.Error("Error processing request '%s': %s (HttpResponse=%s)", goURI, err, res) } diff --git a/v2/internal/frontend/desktop/linux/window.go b/v2/internal/frontend/desktop/linux/window.go index ccc6fcc18..7c6b6f363 100644 --- a/v2/internal/frontend/desktop/linux/window.go +++ b/v2/internal/frontend/desktop/linux/window.go @@ -200,6 +200,9 @@ GtkWidget* setupWebview(void* contentManager, GtkWindow* window, int hideWindowO } else { g_signal_connect(GTK_WIDGET(window), "delete-event", G_CALLBACK(close_button_pressed), NULL); } + + WebKitSettings *settings = webkit_web_view_get_settings(WEBKIT_WEB_VIEW(webview)); + webkit_settings_set_user_agent_with_application_details(settings, "wails.io", ""); return webview; } @@ -209,8 +212,8 @@ void devtoolsEnabled(void* webview, int enabled) { webkit_settings_set_enable_developer_extras(settings, genabled); } -void loadIndex(void* webview) { - webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), "wails:///"); +void loadIndex(void* webview, char* url) { + webkit_web_view_load_uri(WEBKIT_WEB_VIEW(webview), url); } typedef struct DragOptions { @@ -568,11 +571,12 @@ void SetWindowIcon(GtkWindow* window, const guchar* buf, gsize len) { */ import "C" import ( + "strings" + "unsafe" + "github.com/wailsapp/wails/v2/internal/frontend" "github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/options" - "strings" - "unsafe" ) func gtkBool(input bool) C.gboolean { @@ -788,12 +792,14 @@ func (w *Window) SetWindowIcon(icon []byte) { C.SetWindowIcon(w.asGTKWindow(), (*C.guchar)(&icon[0]), (C.gsize)(len(icon))) } -func (w *Window) Run() { +func (w *Window) Run(url string) { if w.menubar != nil { C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.vbox)), w.menubar, 0, 0, 0) } C.gtk_box_pack_start(C.GTKBOX(unsafe.Pointer(w.vbox)), C.GTKWIDGET(w.webview), 1, 1, 0) - C.loadIndex(w.webview) + _url := C.CString(url) + C.loadIndex(w.webview, _url) + defer C.free(unsafe.Pointer(_url)) C.gtk_widget_show_all(w.asGTKWidget()) w.Center() switch w.appoptions.WindowStartState { diff --git a/v2/internal/frontend/desktop/windows/frontend.go b/v2/internal/frontend/desktop/windows/frontend.go index d8d6a3adf..47ecbda00 100644 --- a/v2/internal/frontend/desktop/windows/frontend.go +++ b/v2/internal/frontend/desktop/windows/frontend.go @@ -7,7 +7,6 @@ import ( "context" "encoding/json" "fmt" - "github.com/wailsapp/wails/v2/internal/system/operatingsystem" "log" "runtime" "strconv" @@ -22,6 +21,7 @@ import ( "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc" "github.com/wailsapp/wails/v2/internal/frontend/desktop/windows/winc/w32" "github.com/wailsapp/wails/v2/internal/logger" + "github.com/wailsapp/wails/v2/internal/system/operatingsystem" "github.com/wailsapp/wails/v2/pkg/options" ) @@ -36,7 +36,7 @@ type Frontend struct { debug bool // Assets - assets *assetserver.DesktopAssetServer + assets *assetserver.AssetServer startURL string // main window handle @@ -66,18 +66,10 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. versionInfo: versionInfo, } - bindingsJSON, err := appBindings.ToJSON() - if err != nil { - log.Fatal(err) - } - - _devServerURL := ctx.Value("devserverurl") - if _devServerURL != nil { - devServerURL := _devServerURL.(string) - if len(devServerURL) > 0 && devServerURL != "http://localhost:34115" { - result.startURL = devServerURL - return result - } + _starturl, _ := ctx.Value("starturl").(string) + if _starturl != "" { + result.startURL = _starturl + return result } // Check if we have been given a directory to serve assets from. @@ -90,7 +82,12 @@ func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger. result.servingFromDisk = true } - assets, err := assetserver.NewDesktopAssetServer(ctx, appoptions.Assets, bindingsJSON, result.servingFromDisk) + bindingsJSON, err := appBindings.ToJSON() + if err != nil { + log.Fatal(err) + } + + assets, err := assetserver.NewAssetServer(ctx, appoptions, bindingsJSON) if err != nil { log.Fatal(err) } @@ -361,6 +358,15 @@ func (f *Frontend) Notify(name string, data ...interface{}) { } func (f *Frontend) processRequest(req *edge.ICoreWebView2WebResourceRequest, args *edge.ICoreWebView2WebResourceRequestedEventArgs) { + // Setting the UserAgent on the CoreWebView2Settings clears the whole default UserAgent of the Edge browser, but + // we want to just append our ApplicationIdentifier. So we adjust the UserAgent for every request. + if reqHeaders, err := req.GetHeaders(); err == nil { + useragent, _ := reqHeaders.GetHeader(assetserver.HeaderUserAgent) + useragent = strings.Join([]string{useragent, assetserver.WailsUserAgentValue}, " ") + reqHeaders.SetHeader(assetserver.HeaderUserAgent, useragent) + reqHeaders.Release() + } + //Get the request uri, _ := req.GetUri() diff --git a/v2/internal/frontend/devserver/devserver.go b/v2/internal/frontend/devserver/devserver.go index 0dc2cef7b..557a15e1e 100644 --- a/v2/internal/frontend/devserver/devserver.go +++ b/v2/internal/frontend/devserver/devserver.go @@ -9,14 +9,16 @@ import ( "context" "encoding/json" "fmt" - "io/fs" "log" + "net" + "net/http" + "net/http/httputil" + "net/url" "strings" "sync" "time" - "github.com/gofiber/fiber/v2" - "github.com/gofiber/websocket/v2" + "github.com/labstack/echo/v4" "github.com/wailsapp/wails/v2/internal/binding" "github.com/wailsapp/wails/v2/internal/frontend" "github.com/wailsapp/wails/v2/internal/frontend/assetserver" @@ -24,16 +26,16 @@ import ( "github.com/wailsapp/wails/v2/internal/menumanager" "github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v2/pkg/options" + "golang.org/x/net/websocket" ) type DevWebServer struct { - server *fiber.App + server *echo.Echo ctx context.Context appoptions *options.App logger *logger.Logger appBindings *binding.Bindings dispatcher frontend.Dispatcher - assetServer *assetserver.BrowserAssetServer socketMutex sync.Mutex websocketClients map[*websocket.Conn]*sync.Mutex menuManager *menumanager.Manager @@ -41,95 +43,73 @@ type DevWebServer struct { // Desktop frontend desktopFrontend frontend.Frontend -} -func (d *DevWebServer) WindowReload() { - d.broadcast("reload") + devServerAddr string } func (d *DevWebServer) Run(ctx context.Context) error { d.ctx = ctx - assetdir, _ := ctx.Value("assetdir").(string) - d.server.Get("/wails/assetdir", func(fctx *fiber.Ctx) error { - return fctx.SendString(assetdir) - }) + d.server.GET("/wails/reload", d.handleReload) + d.server.GET("/wails/ipc", d.handleIPCWebSocket) - d.server.Get("/wails/reload", func(fctx *fiber.Ctx) error { - d.WindowReload() - d.desktopFrontend.WindowReload() + var assetHandler http.Handler + _fronendDevServerURL, _ := ctx.Value("frontenddevserverurl").(string) + if _fronendDevServerURL == "" { + assetdir, _ := ctx.Value("assetdir").(string) + d.server.GET("/wails/assetdir", func(c echo.Context) error { + return c.String(http.StatusOK, assetdir) + }) + + var err error + assetHandler, err = assetserver.NewAsssetHandler(ctx, d.appoptions) + if err != nil { + log.Fatal(err) + } + } else { + externalURL, err := url.Parse(_fronendDevServerURL) + if err != nil { + return err + } + + waitCb := func() { d.LogDebug("Waiting for frontend DevServer '%s' to be ready", externalURL) } + if !checkPortIsOpen(externalURL.Host, time.Minute, waitCb) { + d.logger.Error("Timeout waiting for frontend DevServer") + } + + assetHandler = httputil.NewSingleHostReverseProxy(externalURL) + } + + // Setup internal dev server + bindingsJSON, err := d.appBindings.ToJSON() + if err != nil { + log.Fatal(err) + } + + assetServer, err := assetserver.NewBrowserAssetServer(ctx, assetHandler, bindingsJSON) + if err != nil { + log.Fatal(err) + } + + d.server.GET("/*", func(c echo.Context) error { + assetServer.ServeHTTP(c.Response(), c.Request()) return nil }) - d.server.Get("/wails/ipc", websocket.New(func(c *websocket.Conn) { - d.newWebsocketSession(c) - locker := d.websocketClients[c] - // websocket.Conn bindings https://pkg.go.dev/github.com/fasthttp/websocket?tab=doc#pkg-index - var ( - mt int - msg []byte - err error - ) - - for { - if mt, msg, err = c.ReadMessage(); err != nil { - break - } - // We do not support drag in browsers - if string(msg) == "drag" { - continue - } - - // Notify the other browsers of "EventEmit" - if len(msg) > 2 && strings.HasPrefix(string(msg), "EE") { - d.notifyExcludingSender(msg, c) - } - - // Send the message to dispatch to the frontend - result, err := d.dispatcher.ProcessMessage(string(msg), d) - if err != nil { - d.logger.Error(err.Error()) - } - if result != "" { - locker.Lock() - if err = c.WriteMessage(mt, []byte(result)); err != nil { - locker.Unlock() - break - } - locker.Unlock() - } - - } - })) - - _devServerURL := ctx.Value("devserverurl") - if _devServerURL == "http://localhost:34115" { - // Setup internal dev server - bindingsJSON, err := d.appBindings.ToJSON() - if err != nil { - log.Fatal(err) - } - - d.assetServer, err = assetserver.NewBrowserAssetServer(ctx, d.appoptions.Assets, bindingsJSON) - if err != nil { - log.Fatal(err) - } - - d.server.Get("*", d.loadAsset) - + if devServerAddr := d.devServerAddr; devServerAddr != "" { // Start server - go func(server *fiber.App, log *logger.Logger) { - err := server.Listen("localhost:34115") + go func(server *echo.Echo, log *logger.Logger) { + err := server.Start(devServerAddr) if err != nil { log.Error(err.Error()) } d.LogDebug("Shutdown completed") }(d.server, d.logger) - d.LogDebug("Serving application at http://localhost:34115") + d.LogDebug("Serving DevServer at http://%s", devServerAddr) defer func() { - err := d.server.Shutdown() + err := d.server.Shutdown(context.Background()) if err != nil { d.logger.Error(err.Error()) } @@ -137,7 +117,7 @@ func (d *DevWebServer) Run(ctx context.Context) error { } // Launch desktop app - err := d.desktopFrontend.Run(ctx) + err = d.desktopFrontend.Run(ctx) d.LogDebug("Starting shutdown") return err @@ -167,6 +147,11 @@ func (d *DevWebServer) MessageDialog(dialogOptions frontend.MessageDialogOptions return d.desktopFrontend.MessageDialog(dialogOptions) } +func (d *DevWebServer) WindowReload() { + d.broadcast("reload") + d.desktopFrontend.WindowReload() +} + func (d *DevWebServer) WindowSetTitle(title string) { d.desktopFrontend.WindowSetTitle(title) } @@ -256,28 +241,57 @@ func (d *DevWebServer) Notify(name string, data ...interface{}) { d.notify(name, data...) } -func (d *DevWebServer) loadAsset(ctx *fiber.Ctx) error { - data, mimetype, err := d.assetServer.Load(ctx.Path()) - if err != nil { - _, ok := err.(*fs.PathError) - if !ok { - return err +func (d *DevWebServer) handleReload(c echo.Context) error { + d.WindowReload() + return c.NoContent(http.StatusNoContent) +} + +func (d *DevWebServer) handleIPCWebSocket(c echo.Context) error { + websocket.Handler(func(c *websocket.Conn) { + d.LogDebug(fmt.Sprintf("Websocket client %p connected", c)) + d.socketMutex.Lock() + d.websocketClients[c] = &sync.Mutex{} + locker := d.websocketClients[c] + d.socketMutex.Unlock() + + defer func() { + d.socketMutex.Lock() + delete(d.websocketClients, c) + d.socketMutex.Unlock() + d.LogDebug(fmt.Sprintf("Websocket client %p disconnected", c)) + }() + + var msg string + defer c.Close() + for { + if err := websocket.Message.Receive(c, &msg); err != nil { + break + } + // We do not support drag in browsers + if msg == "drag" { + continue + } + + // Notify the other browsers of "EventEmit" + if len(msg) > 2 && strings.HasPrefix(string(msg), "EE") { + d.notifyExcludingSender([]byte(msg), c) + } + + // Send the message to dispatch to the frontend + result, err := d.dispatcher.ProcessMessage(string(msg), d) + if err != nil { + d.logger.Error(err.Error()) + } + if result != "" { + locker.Lock() + if err = websocket.Message.Send(c, result); err != nil { + locker.Unlock() + break + } + locker.Unlock() + } } - err := ctx.SendStatus(404) - if err != nil { - return err - } - return nil - } - err = ctx.SendStatus(200) - if err != nil { - return err - } - ctx.Set("Content-Type", mimetype) - err = ctx.Send(data) - if err != nil { - return err - } + }).ServeHTTP(c.Response(), c.Request()) return nil } @@ -285,20 +299,6 @@ func (d *DevWebServer) LogDebug(message string, args ...interface{}) { d.logger.Debug("[DevWebServer] "+message, args...) } -func (d *DevWebServer) newWebsocketSession(c *websocket.Conn) { - d.socketMutex.Lock() - defer d.socketMutex.Unlock() - c.SetCloseHandler(func(code int, text string) error { - d.socketMutex.Lock() - defer d.socketMutex.Unlock() - delete(d.websocketClients, c) - d.LogDebug(fmt.Sprintf("Websocket client %p disconnected", c)) - return nil - }) - d.websocketClients[c] = &sync.Mutex{} - d.LogDebug(fmt.Sprintf("Websocket client %p connected", c)) -} - type EventNotify struct { Name string `json:"name"` Data []interface{} `json:"data"` @@ -314,7 +314,7 @@ func (d *DevWebServer) broadcast(message string) { return } locker.Lock() - err := client.WriteMessage(websocket.TextMessage, []byte(message)) + err := websocket.Message.Send(client, message) if err != nil { locker.Unlock() d.logger.Error(err.Error()) @@ -348,7 +348,7 @@ func (d *DevWebServer) broadcastExcludingSender(message string, sender *websocke return } locker.Lock() - err := client.WriteMessage(websocket.TextMessage, []byte(message)) + err := websocket.Message.Send(client, message) if err != nil { locker.Unlock() d.logger.Error(err.Error()) @@ -374,19 +374,38 @@ func (d *DevWebServer) notifyExcludingSender(eventMessage []byte, sender *websoc func NewFrontend(ctx context.Context, appoptions *options.App, myLogger *logger.Logger, appBindings *binding.Bindings, dispatcher frontend.Dispatcher, menuManager *menumanager.Manager, desktopFrontend frontend.Frontend) *DevWebServer { result := &DevWebServer{ - ctx: ctx, - desktopFrontend: desktopFrontend, - appoptions: appoptions, - logger: myLogger, - appBindings: appBindings, - dispatcher: dispatcher, - server: fiber.New(fiber.Config{ - - ReadTimeout: time.Second * 5, - DisableStartupMessage: true, - }), + ctx: ctx, + desktopFrontend: desktopFrontend, + appoptions: appoptions, + logger: myLogger, + appBindings: appBindings, + dispatcher: dispatcher, + server: echo.New(), menuManager: menuManager, websocketClients: make(map[*websocket.Conn]*sync.Mutex), } + + result.devServerAddr, _ = ctx.Value("devserver").(string) + result.server.HideBanner = true + result.server.HidePort = true return result } + +func checkPortIsOpen(host string, timeout time.Duration, waitCB func()) (ret bool) { + if timeout == 0 { + timeout = time.Minute + } + + deadline := time.Now().Add(timeout) + for time.Now().Before(deadline) { + conn, _ := net.DialTimeout("tcp", host, 2*time.Second) + if conn != nil { + conn.Close() + return true + } + + waitCB() + time.Sleep(1 * time.Second) + } + return false +} diff --git a/v2/internal/frontend/runtime/dev/main.js b/v2/internal/frontend/runtime/dev/main.js index cdef6ad23..ed4f287a6 100644 --- a/v2/internal/frontend/runtime/dev/main.js +++ b/v2/internal/frontend/runtime/dev/main.js @@ -80,7 +80,7 @@ function handleDisconnect() { function _connect() { if (websocket == null) { - websocket = new WebSocket('ws://' + window.location.hostname + ':34115/wails/ipc'); + websocket = new WebSocket('ws://' + window.location.host + '/wails/ipc'); websocket.onopen = handleConnect; websocket.onerror = function (e) { e.stopImmediatePropagation(); diff --git a/v2/internal/frontend/runtime/ipc_websocket.js b/v2/internal/frontend/runtime/ipc_websocket.js index 9a00ed4af..96fba993b 100644 --- a/v2/internal/frontend/runtime/ipc_websocket.js +++ b/v2/internal/frontend/runtime/ipc_websocket.js @@ -4,7 +4,7 @@ }`,f=`__svelte_${qt(y)}_${l}`,u=P(t);N.add(u);let h=u.__svelte_stylesheet||(u.__svelte_stylesheet=Jt(t).sheet),_=u.__svelte_rules||(u.__svelte_rules={});_[f]||(_[f]=!0,h.insertRule(`@keyframes ${f} ${y}`,h.cssRules.length));let w=t.style.animation||"";return t.style.animation=`${w?`${w}, `:""}${f} ${i}ms linear ${r}ms 1 both`,B+=1,f}function Gt(t,e){let n=(t.style.animation||"").split(", "),i=n.filter(e?c=>c.indexOf(e)<0:c=>c.indexOf("__svelte")===-1),r=n.length-i.length;r&&(t.style.animation=i.join(", "),B-=r,B||Kt())}function Kt(){K(()=>{B||(N.forEach(t=>{let e=t.__svelte_stylesheet,n=e.cssRules.length;for(;n--;)e.deleteRule(n);t.__svelte_rules={}}),N.clear())})}var _t;function J(t){_t=t}var $=[];var pt=[],z=[],mt=[],Pt=Promise.resolve(),W=!1;function Rt(){W||(W=!0,Pt.then(yt))}function x(t){z.push(t)}var V=!1,U=new Set;function yt(){if(!V){V=!0;do{for(let t=0;t<$.length;t+=1){let e=$[t];J(e),Nt(e.$$)}for(J(null),$.length=0;pt.length;)pt.pop()();for(let t=0;t{k=null})),k}function X(t,e,n){t.dispatchEvent(Ht(`${e?"intro":"outro"}${n}`))}var T=new Set,m;function gt(){m={r:0,c:[],p:m}}function bt(){m.r||b(m.c),m=m.p}function C(t,e){t&&t.i&&(T.delete(t),t.i(e))}function Z(t,e,n,i){if(t&&t.o){if(T.has(t))return;T.add(t),m.c.push(()=>{T.delete(t),i&&(n&&t.d(1),i())}),t.o(e)}}var Vt={duration:0};function Q(t,e,n,i){let r=e(t,n),c=i?0:1,s=null,l=null,a=null;function o(){a&&Gt(t,a)}function y(u,h){let _=u.b-c;return h*=Math.abs(_),{a:c,b:u.b,d:_,duration:h,start:u.start,end:u.start+h,group:u.group}}function f(u){let{delay:h=0,duration:_=300,easing:w=O,tick:g=p,css:v}=r||Vt,q={start:Ot()+h,b:u};u||(q.group=m,m.r+=1),s||l?l=q:(v&&(o(),a=ht(t,c,u,_,h,w,v)),u&&g(0,1),s=y(q,_),x(()=>X(t,u,"start")),Dt(E=>{if(l&&E>l.start&&(s=y(l,_),l=null,X(t,s.b,"start"),v&&(o(),a=ht(t,c,s.b,s.duration,0,w,r.css))),s){if(E>=s.end)g(c=s.b,1-c),X(t,s.b,"end"),l||(s.b?o():--s.group.r||b(s.group.c)),s=null;else if(E>=s.start){let jt=E-s.start;c=s.a+s.d*w(jt/s.duration),g(c,1-c)}}return!!(s||l)}))}return{run(u){S(r)?Wt().then(()=>{r=r(),f(u)}):f(u)},end(){o(),s=l=null}}}var ce=typeof window!="undefined"?window:typeof globalThis!="undefined"?globalThis:global;var le=new Set(["allowfullscreen","allowpaymentrequest","async","autofocus","autoplay","checked","controls","default","defer","disabled","formnovalidate","hidden","ismap","loop","multiple","muted","nomodule","novalidate","open","playsinline","readonly","required","reversed","selected"]);function Ut(t,e,n,i){let{fragment:r,on_mount:c,on_destroy:s,after_update:l}=t.$$;r&&r.m(e,n),i||x(()=>{let a=c.map(G).filter(S);s?s.push(...a):b(a),t.$$.on_mount=[]}),l.forEach(x)}function wt(t,e){let n=t.$$;n.fragment!==null&&(b(n.on_destroy),n.fragment&&n.fragment.d(e),n.on_destroy=n.fragment=null,n.ctx=[])}function Xt(t,e){t.$$.dirty[0]===-1&&($.push(t),Rt(),t.$$.dirty.fill(0)),t.$$.dirty[e/31|0]|=1<{let _=h.length?h[0]:u;return o.ctx&&r(o.ctx[f],o.ctx[f]=_)&&(!o.skip_bound&&o.bound[f]&&o.bound[f](_),y&&Xt(t,f)),u}):[],o.update(),y=!0,b(o.before_update),o.fragment=i?i(o.ctx):!1,e.target){if(e.hydrate){At();let f=Tt(e.target);o.fragment&&o.fragment.l(f),f.forEach(A)}else o.fragment&&o.fragment.c();e.intro&&C(t.$$.fragment),Ut(t,e.target,e.anchor,e.customElement),Lt(),yt()}J(a)}var Zt;typeof HTMLElement=="function"&&(Zt=class extends HTMLElement{constructor(){super();this.attachShadow({mode:"open"})}connectedCallback(){let{on_mount:t}=this.$$;this.$$.on_disconnect=t.map(G).filter(S);for(let e in this.$$.slotted)this.appendChild(this.$$.slotted[e])}attributeChangedCallback(t,e,n){this[t]=n}disconnectedCallback(){b(this.$$.on_disconnect)}$destroy(){wt(this,1),this.$destroy=p}$on(t,e){let n=this.$$.callbacks[t]||(this.$$.callbacks[t]=[]);return n.push(e),()=>{let i=n.indexOf(e);i!==-1&&n.splice(i,1)}}$set(t){this.$$set&&!it(t)&&(this.$$.skip_bound=!0,this.$$set(t),this.$$.skip_bound=!1)}});var Y=class{$destroy(){wt(this,1),this.$destroy=p}$on(e,n){let i=this.$$.callbacks[e]||(this.$$.callbacks[e]=[]);return i.push(n),()=>{let r=i.indexOf(n);r!==-1&&i.splice(r,1)}}$set(e){this.$$set&&!it(e)&&(this.$$.skip_bound=!0,this.$$set(e),this.$$.skip_bound=!1)}};var M=[];function Ft(t,e=p){let n,i=new Set;function r(l){if(D(t,l)&&(t=l,n)){let a=!M.length;for(let o of i)o[1](),M.push(o,t);if(a){for(let o=0;o{i.delete(o),i.size===0&&(n(),n=null)}}return{set:r,update:c,subscribe:s}}var H=Ft(!1);function xt(){H.set(!0)}function Mt(){H.set(!1)}function tt(t,{delay:e=0,duration:n=400,easing:i=O}={}){let r=+getComputedStyle(t).opacity;return{delay:e,duration:n,easing:i,css:c=>`opacity: ${c*r}`}}function Qt(t){ut(t,"svelte-181h7z",`.wails-reconnect-overlay.svelte-181h7z{position:fixed;top:0;left:0;width:100%;height:100%;backdrop-filter:blur(2px) saturate(0%) contrast(50%) brightness(25%);z-index:999999 }.wails-reconnect-overlay-content.svelte-181h7z{position:relative;top:50%;transform:translateY(-50%);margin:0;background-image:url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAEsAAAA7CAMAAAAEsocZAAAC91BMVEUAAACzQ0PjMjLkMjLZLS7XLS+vJCjkMjKlEx6uGyHjMDGiFx7GJyrAISjUKy3mMzPlMjLjMzOsGyDKJirkMjK6HyXmMjLgMDC6IiLcMjLULC3MJyrRKSy+IibmMzPmMjK7ISXlMjLIJimzHSLkMjKtGiHZLC7BIifgMDCpGSDFIivcLy+yHSKoGR+eFBzNKCvlMjKxHSPkMTKxHSLmMjLKJyq5ICXDJCe6ISXdLzDkMjLmMzPFJSm2HyTlMTLhMDGyHSKUEBmhFx24HyTCJCjHJijjMzOiFh7mMjJ6BhDaLDCuGyOKABjnMzPGJinJJiquHCGEChSmGB/pMzOiFh7VKy3OKCu1HiSvHCLjMTLMKCrBIyeICxWxHCLDIyjSKizBIyh+CBO9ISa6ISWDChS9Iie1HyXVLC7FJSrLKCrlMjLiMTGPDhicFRywGyKXFBuhFx1/BxO7IiXkMTGeFBx8BxLkMTGnGR/GJCi4ICWsGyGJDxXSLS2yGiHSKi3CJCfnMzPQKiyECRTKJiq6ISWUERq/Iye0HiPDJCjGJSm6ICaPDxiTEBrdLy+3HyXSKiy0HyOQEBi4ICWhFh1+CBO9IieODhfSKyzWLC2LDhh8BxHKKCq7ISWaFBzkMzPqNDTTLC3EJSiHDBacExyvGyO1HyTPKCy+IieoGSC7ISaVEhrMKCvQKyusGyG0HiKACBPIJSq/JCaABxR5BRLEJCnkMzPJJinEJimPDRZ2BRKqHx/jMjLnMzPgMDHULC3NKSvQKSzsNDTWLS7SKyy3HyTKJyrDJSjbLzDYLC6mGB/GJSnVLC61HiPLKCrHJSm/Iye8Iia6ICWzHSKxHCLaLi/PKSupGR+7ICXpMzPbLi/IJinJJSmsGyGrGiCkFx6PDheJCxaFChXBIyfAIieSDxmBCBPlMjLeLzDdLzC5HySMDRe+ISWvGyGcFBzSKSzPJyvMJyrEJCjDIyefFRyWERriMDHUKiy/ISaZExv0NjbwNTXuNDTrMzMI0c+yAAAAu3RSTlMAA8HR/gwGgAj+MEpGCsC+hGpjQjYnIxgWBfzx7urizMrFqqB1bF83KhsR/fz8+/r5+fXv7unZ1tC+t6mmopqKdW1nYVpVRjUeHhIQBPr59/b28/Hx8ODg3NvUw8O/vKeim5aNioiDgn1vZWNjX1xUU1JPTUVFPT08Mi4qJyIh/Pv7+/n4+Pf39fT08/Du7efn5uXj4uHa19XNwsG/vrq2tbSuramlnpyYkpGNiIZ+enRraGVjVVBKOzghdjzRsAAABJVJREFUWMPtllVQG1EYhTc0ASpoobS0FCulUHd3oUjd3d3d3d3d3d2b7CYhnkBCCHGDEIK7Vh56d0NpOgwkYfLQzvA9ZrLfnPvfc+8uVEst/yheBJup3Nya2MjU6pa/jWLZtxjXpZFtVB4uVNI6m5gIruNkVFebqIb5Ug2ym4TIEM/gtUOGbg613oBzjAzZFrZ+lXu/3TIiMXXS5M6HTvrNHeLpZLEh6suGNW9fzZ9zd/qVi2eOHygqi5cDE5GUrJocONgzyqo0UXNSUlKSEhMztFqtXq9vNxImAmS3g7Y6QlbjdBWVGW36jt4wDGTUXjUsafh5zJWRkdFuZGtWGnCRmg+HasiGMUClTTzW0ZuVgLlGDIPM4Lhi0IrVq+tv2hS21fNrSONQgpM9DsJ4t3fM9PkvJuKj2ZjrZwvILKvaSTgciUSirjt6dOfOpyd169bDb9rMOwF9Hj4OD100gY0YXYb299bjzMrqj9doNByJWlVXFB9DT5dmJuvy+cq83JyuS6ayEYSHulKL8dmFnBkrCeZlHKMrC5XRhXGCZB2Ty1fkleRQaMCFT2DBsEafzRFJu7/2MicbKynPhQUDLiZwMWLJZKNLzoLbJBYVcurSmbmn+rcyJ8vCMgmlmaW6gnwun/+3C96VpAUuET1ZgRR36r2xWlnYSnf3oKABA14uXDDvydxHs6cpTV1p3hlJ2rJCiUjIZCByItXg8sHJijuvT64CuMTABUYvb6NN1Jdp1PH7D7f3bo2eS5KvW4RJr7atWT5w4MBBg9zdBw9+37BS7QIoFS5WnIaj12dr1DEXFgdvr4fh4eFl+u/wz8uf3jjHic8s4DL2Dal0IANyUBeCRCcwOBJV26JsjSpGwHVuSai69jvqD+jr56OgtKy0zAAK5mLTVBKVKL5tNthGAR9JneJQ/bFsHNzy+U7IlCYROxtMpIjR0ceoQVnowracLLpAQWETqV361bPoFo3cEbz2zYLZM7t3HWXcxmiBOgttS1ycWkTXMWh4mGigdug9DFdttqCFgTN6nD0q1XEVSoCxEjyFCi2eNC6Z69MRVIImJ6JQSf5gcFVCuF+aDhCa1F6MJFDaiNBQAh2TMfWBjhmLsAxUjG/fmjs0qjJck8D0GPBcuUuZW1LS/tIsPzqmQt17PvZQknlwnf4tHDBc+7t5VV3QQCkdc+Ur8/hdrz0but0RCumWiYbiKmLJ7EVbRomj4Q7+y5wsaXvfTGFpQcHB7n2WbG4MGdniw2Tm8xl5Yhr7MrSYHQ3uampz10aWyHyuzxvqaW/6W4MjXAUD3QV2aw97ZxhGjxCohYf5TpTHMXU1BbsAuoFnkRygVieIGAbqiF7rrH4rfWpKJouBCtyHJF8ctEyGubBa+C6NsMYEUonJFITHZqWBxXUA12Dv76Tf/PgOBmeNiiLG1pcKo1HAq8jLpY4JU1yWEixVNaOgoRJAKBSZHTZTU+wJOMtUDZvlVITC6FTlksyrEBoPHXpxxbzdaqzigUtVDkJVIOtVQ9UEOR4VGUh/kHWq0edJ6CxnZ+eePXva2bnY/cF/I1RLLf8vvwDANdMSMegxcAAAAABJRU5ErkJggg==);background-repeat:no-repeat;background-position:center }.wails-reconnect-overlay-loadingspinner.svelte-181h7z{pointer-events:none;width:2.5em;height:2.5em;border:.4em solid transparent;border-color:#f00 #eee0 #f00 #eee0;border-radius:50%;animation:svelte-181h7z-loadingspin 1s linear infinite;margin:auto;padding:2.5em - }@keyframes svelte-181h7z-loadingspin{100%{transform:rotate(360deg)}}`)}function St(t){let e,n,i;return{c(){e=L("div"),e.innerHTML='
',dt(e,"class","wails-reconnect-overlay svelte-181h7z")},m(r,c){R(r,e,c),i=!0},i(r){i||(x(()=>{n||(n=Q(e,tt,{duration:300},!0)),n.run(1)}),i=!0)},o(r){n||(n=Q(e,tt,{duration:300},!1)),n.run(0),i=!1},d(r){r&&A(e),r&&n&&n.end()}}}function Yt(t){let e,n,i=t[0]&&St(t);return{c(){i&&i.c(),e=ft()},m(r,c){i&&i.m(r,c),R(r,e,c),n=!0},p(r,[c]){r[0]?i?c&1&&C(i,1):(i=St(r),i.c(),C(i,1),i.m(e.parentNode,e)):i&&(gt(),Z(i,1,1,()=>{i=null}),bt())},i(r){n||(C(i),n=!0)},o(r){Z(i),n=!1},d(r){i&&i.d(r),r&&A(e)}}}function te(t,e,n){let i;return ot(t,H,r=>n(0,i=r)),[i]}var $t=class extends Y{constructor(e){super();vt(this,e,te,Yt,D,{},Qt)}},kt=$t;var ee={},et=null,I=[];window.WailsInvoke=t=>{if(!et){console.log("Queueing: "+t),I.push(t);return}et(t)};window.addEventListener("DOMContentLoaded",()=>{ee.overlay=new kt({target:document.body,anchor:document.querySelector("#wails-spinner")})});var d=null,Ct;window.onbeforeunload=function(){d&&(d.onclose=function(){},d.close(),d=null)};Et();function ne(){et=t=>{d.send(t)};for(let t=0;t
',dt(e,"class","wails-reconnect-overlay svelte-181h7z")},m(r,c){R(r,e,c),i=!0},i(r){i||(x(()=>{n||(n=Q(e,tt,{duration:300},!0)),n.run(1)}),i=!0)},o(r){n||(n=Q(e,tt,{duration:300},!1)),n.run(0),i=!1},d(r){r&&A(e),r&&n&&n.end()}}}function Yt(t){let e,n,i=t[0]&&St(t);return{c(){i&&i.c(),e=ft()},m(r,c){i&&i.m(r,c),R(r,e,c),n=!0},p(r,[c]){r[0]?i?c&1&&C(i,1):(i=St(r),i.c(),C(i,1),i.m(e.parentNode,e)):i&&(gt(),Z(i,1,1,()=>{i=null}),bt())},i(r){n||(C(i),n=!0)},o(r){Z(i),n=!1},d(r){i&&i.d(r),r&&A(e)}}}function te(t,e,n){let i;return ot(t,H,r=>n(0,i=r)),[i]}var $t=class extends Y{constructor(e){super();vt(this,e,te,Yt,D,{},Qt)}},kt=$t;var ee={},et=null,I=[];window.WailsInvoke=t=>{if(!et){console.log("Queueing: "+t),I.push(t);return}et(t)};window.addEventListener("DOMContentLoaded",()=>{ee.overlay=new kt({target:document.body,anchor:document.querySelector("#wails-spinner")})});var d=null,Ct;window.onbeforeunload=function(){d&&(d.onclose=function(){},d.close(),d=null)};Et();function ne(){et=t=>{d.send(t)};for(let t=0;t