[v2] Add support for external frontend dev servers (#1290)

* [v2] Consolidate AssetServers

* [v2] Support starturl for webview on linux and darwin

* [v2] Add support for frontend DevServer

* [v2] Activate frontend DevServer in svelte template

* [website] Add bleeding edge guide for PRs

* DoNotMerge: Bump Version for testing

Co-authored-by: Lea Anthony <lea.anthony@gmail.com>
This commit is contained in:
stffabi 2022-03-30 15:40:44 +02:00 committed by GitHub
commit 65fb42d9e7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
29 changed files with 705 additions and 729 deletions

View file

@ -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()
}

View file

@ -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}}"

View file

@ -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

View file

@ -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
)

113
v2/go.sum
View file

@ -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=

View file

@ -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 != "" {

View file

@ -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
}

View file

@ -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...)
}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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), `<script src="/wails/runtime.js"></script>`)
if err != nil {
return nil, err
}
}
if wailsOptions.disableIPCInjection == false {
indexHTML, err = injectHTML(string(indexHTML), `<script src="/wails/ipc.js"></script>`)
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
}

View file

@ -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, "</head>")
if len(splits) != 2 {
return nil, fmt.Errorf("unable to locate a </head> tag in your html")
}
var result bytes.Buffer
result.WriteString(splits[0])
result.WriteString(html)
result.WriteString("</head>")
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,

View file

@ -1,70 +0,0 @@
package assetserver
import (
"reflect"
"testing"
)
const realHTML = `<html>
<head>
<title>test3</title>
<meta name="wails-options" content="noautoinject">
<link rel="stylesheet" href="/main.css">
</head>
<body data-wails-drag>
<div class="logo"></div>
<div class="result" id="result">Please enter your name below <EFBFBD></div>
<div class="input-box" id="input" data-wails-no-drag>
<input class="input" id="name" type="text" autocomplete="off">
<button class="btn" onclick="greet()">Greet</button>
</div>
<script src="/main.js"></script>
</body>
</html>
`
func genMeta(content string) []byte {
return []byte("<html><head><meta name=\"wails-options\" content=\"" + content + "\"></head><body></body></html>")
}
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)
}
})
}
}

View file

@ -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 {

View file

@ -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);

View file

@ -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];

View file

@ -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"];

View file

@ -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
}

View file

@ -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() {

View file

@ -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)
}

View file

@ -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 {

View file

@ -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()

View file

@ -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
}

View file

@ -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();

File diff suppressed because one or more lines are too long

View file

@ -23,6 +23,8 @@ type Project struct {
// Commands used in `wails dev`
DevCommand string `json:"frontend:dev"`
DevWatcherCommand string `json:"frontend:dev:watcher"`
// The url of the external wails dev server. If this is set, this server is used for the frontend. Default ""
FrontendDevServerURL string `json:"frontend:dev:serverUrl"`
// Directory to generate the API Module
WailsJSDir string `json:"wailsjsdir"`
@ -71,8 +73,8 @@ type Project struct {
// The debounce time for hot-reload of the built-in dev server. Default 100
DebounceMS int `json:"debounceMS"`
// The url to use to server assets. Default "https://localhost:34115"
DevServerURL string `json:"devserverurl"`
// The address to bind the wails dev server to. Default "localhost:34115"
DevServer string `json:"devServer"`
// Arguments that are forwared to the application in dev mode
AppArgs string `json:"appargs"`
@ -162,6 +164,10 @@ func Load(projectPath string) (*Project, error) {
result.Info.Comments = &v
}
if result.DevServer == "" {
result.DevServer = "localhost:34115"
}
// Return our project data
return &result, nil
}

View file

@ -35,7 +35,20 @@ To revert back to a stable version, run:
If you want to test a branch, follow the instructions above, but ensure you switch the branch you want to test before installing:
- `git clone https://github.com/wailsapp/wails`
- `cd wails`
- `git checkout -b branch-to-test --track origin/branch-to-test`
- `cd wails/v2/cmd/wails`
- `cd v2/cmd/wails`
- `go install`
## Testing a PR
If you want to test a PR, follow the instructions above, but ensure you fetch the PR and switch the branch before installing.
Please replace `[IDofThePR]` with the ID of the PR shown on github.com:
- `git clone https://github.com/wailsapp/wails`
- `cd wails`
- `git fetch -u origin pull/[IDofThePR]/head:test/pr-[IDofThePR]`
- `git checkout test/pr-[IDofThePR]`
- `git reset --hard HEAD`
- `cd v2/cmd/wails`
- `go install`

View file

@ -169,11 +169,12 @@ Your system is ready for Wails development!
| -noreload | Disable automatic reload when assets change | |
| -v | Verbosity level (0 - silent, 1 - standard, 2 - verbose) | 1 |
| -wailsjsdir | The directory to generate the generated Wails JS modules | Value in `wails.json` |
| -debounce | The time to wait for reload after an asset change is detected | 100 (milliseconds) |
| -devserverurl "url" | Use 3rd party dev server url, EG Vite | "http://localhost:34115" |
| -debounce | The time to wait for reload after an asset change is detected | 100 (milliseconds) |
| -devserver "host:port" | The address to bind the wails dev server to | "localhost:34115" |
| -frontenddevserverurl "url" | Use 3rd party dev server url to serve assets, EG Vite | "" |
| -appargs "args" | Arguments passed to the application in shell style | |
| -platform "platform" | Platform/Arch to target | `runtime.GOOS` |
| -save | Saves the given `assetdir`, `reloaddirs`, `wailsjsdir`, `debounce` and `devserverurl` flags in
| -save | Saves the given `assetdir`, `reloaddirs`, `wailsjsdir`, `debounce`, `devserver` and `frontenddevserverurl` flags in
`wails.json` to become the defaults for subsequent invocations. | |
Example:

View file

@ -14,12 +14,13 @@ The project config resides in the `wails.json` file in the project directory. Th
"frontend:install": "[The command to install node dependencies, run in the frontend directory - often `npm install`]",
"frontend:build": "[The command to build the assets, run in the frontend directory - often `npm run build`]",
"frontend:dev": "[This command is the dev equivalent of frontend:build. If not specified falls back to frontend:build]",
"frontend:dev:watcher": "[This command is run in a separate process on `wails dev`. Useful for 3rd party watchers]",
"frontend:dev:watcher": "[This command is run in a separate process on `wails dev`. Useful for 3rd party watchers or starting 3d party dev servers]",
"frontend:dev:serverUrl": "[URL to a 3rd party dev server to be used to serve assets, EG Vite",
"wailsjsdir": "[Relative path to the directory that the auto-generated JS modules will be created]",
"version": "[Project config version]",
"outputfilename": "[The name of the binary]",
"debounceMS": 100, // The default time the dev server waits to reload when it detects a vhange in assets
"devserverurl": "[URL to the dev server serving local assets. Default: http://localhost:34115]",
"devServer": "[Address to bind the wails dev sever to. Default: http://localhost:34115]",
"appargs": "[Arguments passed to the application in shell style when in dev mode]",
"runNonNativeBuildHooks": false, // Defines if build hooks should be run though they are defined for an OS other than the host OS.
"postBuildHooks": {
@ -40,5 +41,5 @@ The project config resides in the `wails.json` file in the project directory. Th
This file is read by the Wails CLI when running `wails build` or `wails dev`.
The `assetdir`, `reloaddirs`, `wailsjsdir`, `debounceMS` and `devserverurl` flags in `wails build/dev` will update the project config
The `assetdir`, `reloaddirs`, `wailsjsdir`, `debounceMS`, `devserver` and `frontenddevserverurl` flags in `wails build/dev` will update the project config
and thus become defaults for subsequent runs.