diff --git a/v3/examples/binding/go.mod b/v3/examples/binding/go.mod index 7fe99e7db..47ffa7a4a 100644 --- a/v3/examples/binding/go.mod +++ b/v3/examples/binding/go.mod @@ -1,6 +1,8 @@ module binding -go 1.20 +go 1.21 + +toolchain go1.21.0 require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 @@ -17,7 +19,6 @@ require ( github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/go-webview2 v1.0.2 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.5.1 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/sys v0.9.0 // indirect @@ -25,6 +26,4 @@ require ( replace github.com/wailsapp/wails/v3 => ../.. -replace github.com/wailsapp/wails/v2 => ../../../v2 - replace github.com/ebitengine/purego v0.4.0-alpha.4 => github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01 diff --git a/v3/examples/frameless/go.mod b/v3/examples/frameless/go.mod index fc7e373b9..7265c79f2 100644 --- a/v3/examples/frameless/go.mod +++ b/v3/examples/frameless/go.mod @@ -1,6 +1,8 @@ module frameless -go 1.20 +go 1.21 + +toolchain go1.21.0 require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 @@ -17,12 +19,9 @@ require ( github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/go-webview2 v1.0.2 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.5.1 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/net v0.7.0 // indirect golang.org/x/sys v0.9.0 // indirect ) replace github.com/wailsapp/wails/v3 => ../.. - -replace github.com/wailsapp/wails/v2 => ../../../v2 diff --git a/v3/examples/plugins/go.mod b/v3/examples/plugins/go.mod index 6b15a58cc..1e82aa24f 100644 --- a/v3/examples/plugins/go.mod +++ b/v3/examples/plugins/go.mod @@ -1,6 +1,8 @@ module plugin_demo -go 1.20 +go 1.21 + +toolchain go1.21.0 require github.com/wailsapp/wails/v3 v3.0.0-alpha.0 @@ -23,7 +25,6 @@ require ( github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/go-webview2 v1.0.2 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.5.1 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/mod v0.11.0 // indirect golang.org/x/net v0.7.0 // indirect @@ -42,5 +43,3 @@ require ( ) replace github.com/wailsapp/wails/v3 => ../.. - -replace github.com/wailsapp/wails/v2 => ../../../v2 diff --git a/v3/go.mod b/v3/go.mod index fd395bbe5..b919e71a5 100644 --- a/v3/go.mod +++ b/v3/go.mod @@ -28,7 +28,8 @@ require ( github.com/samber/lo v1.37.0 github.com/tc-hib/winres v0.1.6 github.com/wailsapp/go-webview2 v1.0.2 - github.com/wailsapp/wails/v2 v2.5.1 + github.com/wailsapp/mimetype v1.4.1 + golang.org/x/net v0.7.0 golang.org/x/sys v0.9.0 modernc.org/sqlite v1.21.0 ) @@ -56,7 +57,6 @@ require ( github.com/joho/godotenv v1.5.1 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect github.com/kevinburke/ssh_config v0.0.0-20201106050909-4977a11b4351 // indirect - github.com/leaanthony/slicer v1.5.0 // indirect github.com/lithammer/fuzzysearch v1.1.5 // indirect github.com/mattn/go-runewidth v0.0.14 // indirect github.com/mattn/go-zglob v0.0.4 // indirect @@ -70,14 +70,12 @@ require ( github.com/rivo/uniseg v0.2.0 // indirect github.com/sajari/fuzzy v1.0.0 // indirect github.com/sergi/go-diff v1.2.0 // indirect - github.com/wailsapp/mimetype v1.4.1 // indirect github.com/xanzy/ssh-agent v0.3.0 // indirect github.com/xo/terminfo v0.0.0-20210125001918-ca9a967f8778 // indirect golang.org/x/crypto v0.1.0 // indirect golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect golang.org/x/image v0.5.0 // indirect golang.org/x/mod v0.11.0 // indirect - golang.org/x/net v0.7.0 // indirect golang.org/x/oauth2 v0.0.0-20200902213428-5d25da1a8d43 // indirect golang.org/x/sync v0.3.0 // indirect golang.org/x/term v0.9.0 // indirect @@ -99,6 +97,4 @@ require ( mvdan.cc/sh/v3 v3.7.0 // indirect ) -replace github.com/wailsapp/wails/v2 => ../v2 - replace github.com/ebitengine/purego v0.4.0-alpha.4 => github.com/tmclane/purego v0.0.0-20230601213035-1f25e70d7b01 diff --git a/v3/go.sum b/v3/go.sum index 72dedf374..7fcc0daa4 100644 --- a/v3/go.sum +++ b/v3/go.sum @@ -232,7 +232,6 @@ github.com/leaanthony/debme v1.2.1 h1:9Tgwf+kjcrbMQ4WnPcEIUcQuIZYqdWftzZkBr+i/oO github.com/leaanthony/debme v1.2.1/go.mod h1:3V+sCm5tYAgQymvSOfYQ5Xx2JCr+OXiD9Jkw3otUjiA= github.com/leaanthony/gosod v1.0.3 h1:Fnt+/B6NjQOVuCWOKYRREZnjGyvg+mEhd1nkkA04aTQ= github.com/leaanthony/gosod v1.0.3/go.mod h1:BJ2J+oHsQIyIQpnLPjnqFGTMnOZXDbvWtRCSG7jGxs4= -github.com/leaanthony/slicer v1.5.0 h1:aHYTN8xbCCLxJmkNKiLB6tgcMARl4eWmH9/F+S/0HtY= github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5AzRtwV7m4AY= github.com/leaanthony/winicon v1.0.0 h1:ZNt5U5dY71oEoKZ97UVwJRT4e+5xo5o/ieKuHuk8NqQ= github.com/leaanthony/winicon v1.0.0/go.mod h1:en5xhijl92aphrJdmRPlh4NI1L6wq3gEm0LpXAPghjU= diff --git a/v3/internal/debug/debug.go b/v3/internal/debug/debug.go index be3480a23..d0f0c9dc2 100644 --- a/v3/internal/debug/debug.go +++ b/v3/internal/debug/debug.go @@ -32,8 +32,6 @@ func isLocalBuild() bool { // RelativePath returns a qualified path created by joining the // directory of the calling file and the given relative path. -// -// Example: RelativePath("..") in *this* file would give you '/path/to/wails2/v2/internal` func RelativePath(relativepath string, optionalpaths ...string) string { _, thisFile, _, _ := runtime.Caller(1) localDir := filepath.Dir(thisFile) diff --git a/v3/internal/plugins/template/go.mod.tmpl b/v3/internal/plugins/template/go.mod.tmpl index 77249136f..84ff2e07b 100644 --- a/v3/internal/plugins/template/go.mod.tmpl +++ b/v3/internal/plugins/template/go.mod.tmpl @@ -8,6 +8,5 @@ require ( github.com/imdario/mergo v0.3.12 // indirect github.com/leaanthony/slicer v1.5.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect ) diff --git a/v3/internal/plugins/template/go.sum b/v3/internal/plugins/template/go.sum index 29c7b303e..2e2337936 100644 --- a/v3/internal/plugins/template/go.sum +++ b/v3/internal/plugins/template/go.sum @@ -5,8 +5,6 @@ github.com/leaanthony/slicer v1.5.0/go.mod h1:FwrApmf8gOrpzEWM2J/9Lh79tyq8KTX5Az github.com/matryer/is v1.4.0 h1:sosSmIWwkYITGrxZ25ULNDeKiMNzFSr4V/eqBQP0PeE= github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs= github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= -github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 h1:Wn+nhnS+VytzE0PegUzSh4T3hXJCtggKGD/4U5H9+wQ= -github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6/go.mod h1:zlNLI0E2c2qA6miiuAHtp0Bac8FaGH0tlhA19OssR/8= github.com/wailsapp/wails/v3 v3.0.0-alpha.0 h1:T5gqG98Xr8LBf69oxlPkhpsFD59w2SnqUZk6XHj8Zoc= github.com/wailsapp/wails/v3 v3.0.0-alpha.0/go.mod h1:OAfO5bP0TSUvCIHZYc6Dqfow/9RqxzHvYtmhWPpo1c0= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= diff --git a/v3/internal/templates/_base/default/Taskfile.tmpl.yml b/v3/internal/templates/_base/default/Taskfile.tmpl.yml index f21158a39..6fe474737 100644 --- a/v3/internal/templates/_base/default/Taskfile.tmpl.yml +++ b/v3/internal/templates/_base/default/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,7 +52,7 @@ tasks: - windows cmds: - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp.exe +- task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build build: diff --git a/v3/internal/templates/_base/default/go.mod.tmpl b/v3/internal/templates/_base/default/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/_base/default/go.mod.tmpl +++ b/v3/internal/templates/_base/default/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/lit-ts/Taskfile.tmpl.yml b/v3/internal/templates/lit-ts/Taskfile.tmpl.yml index f21158a39..6fe474737 100644 --- a/v3/internal/templates/lit-ts/Taskfile.tmpl.yml +++ b/v3/internal/templates/lit-ts/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,7 +52,7 @@ tasks: - windows cmds: - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp.exe +- task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build build: diff --git a/v3/internal/templates/lit-ts/go.mod.tmpl b/v3/internal/templates/lit-ts/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/lit-ts/go.mod.tmpl +++ b/v3/internal/templates/lit-ts/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/lit/Taskfile.tmpl.yml b/v3/internal/templates/lit/Taskfile.tmpl.yml index f21158a39..af4686530 100644 --- a/v3/internal/templates/lit/Taskfile.tmpl.yml +++ b/v3/internal/templates/lit/Taskfile.tmpl.yml @@ -39,8 +39,9 @@ tasks: - darwin cmds: - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,6 +52,7 @@ tasks: - windows cmds: - task: pre-build + - task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build diff --git a/v3/internal/templates/lit/go.mod.tmpl b/v3/internal/templates/lit/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/lit/go.mod.tmpl +++ b/v3/internal/templates/lit/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/preact-ts/Taskfile.tmpl.yml b/v3/internal/templates/preact-ts/Taskfile.tmpl.yml index f21158a39..68631d952 100644 --- a/v3/internal/templates/preact-ts/Taskfile.tmpl.yml +++ b/v3/internal/templates/preact-ts/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,6 +52,7 @@ tasks: - windows cmds: - task: pre-build + - task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build diff --git a/v3/internal/templates/preact-ts/go.mod.tmpl b/v3/internal/templates/preact-ts/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/preact-ts/go.mod.tmpl +++ b/v3/internal/templates/preact-ts/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/preact/Taskfile.tmpl.yml b/v3/internal/templates/preact/Taskfile.tmpl.yml index f21158a39..68631d952 100644 --- a/v3/internal/templates/preact/Taskfile.tmpl.yml +++ b/v3/internal/templates/preact/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,6 +52,7 @@ tasks: - windows cmds: - task: pre-build + - task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build diff --git a/v3/internal/templates/preact/go.mod.tmpl b/v3/internal/templates/preact/go.mod.tmpl index 3883674e9..5a7721646 100644 --- a/v3/internal/templates/preact/go.mod.tmpl +++ b/v3/internal/templates/preact/go.mod.tmpl @@ -8,10 +8,8 @@ require ( github.com/imdario/mergo v0.3.12 // indirect github.com/leaanthony/slicer v1.5.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/net v0.0.0-20220722155237-a158d28d115b // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/react-swc-ts/Taskfile.tmpl.yml b/v3/internal/templates/react-swc-ts/Taskfile.tmpl.yml index f21158a39..6fe474737 100644 --- a/v3/internal/templates/react-swc-ts/Taskfile.tmpl.yml +++ b/v3/internal/templates/react-swc-ts/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,7 +52,7 @@ tasks: - windows cmds: - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp.exe +- task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build build: diff --git a/v3/internal/templates/react-swc-ts/go.mod.tmpl b/v3/internal/templates/react-swc-ts/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/react-swc-ts/go.mod.tmpl +++ b/v3/internal/templates/react-swc-ts/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/react-swc/Taskfile.tmpl.yml b/v3/internal/templates/react-swc/Taskfile.tmpl.yml index f21158a39..68631d952 100644 --- a/v3/internal/templates/react-swc/Taskfile.tmpl.yml +++ b/v3/internal/templates/react-swc/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,6 +52,7 @@ tasks: - windows cmds: - task: pre-build + - task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build diff --git a/v3/internal/templates/react-swc/go.mod.tmpl b/v3/internal/templates/react-swc/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/react-swc/go.mod.tmpl +++ b/v3/internal/templates/react-swc/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/react-ts/Taskfile.tmpl.yml b/v3/internal/templates/react-ts/Taskfile.tmpl.yml index f21158a39..68631d952 100644 --- a/v3/internal/templates/react-ts/Taskfile.tmpl.yml +++ b/v3/internal/templates/react-ts/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,6 +52,7 @@ tasks: - windows cmds: - task: pre-build + - task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build diff --git a/v3/internal/templates/react-ts/go.mod.tmpl b/v3/internal/templates/react-ts/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/react-ts/go.mod.tmpl +++ b/v3/internal/templates/react-ts/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/react/Taskfile.tmpl.yml b/v3/internal/templates/react/Taskfile.tmpl.yml index f21158a39..68631d952 100644 --- a/v3/internal/templates/react/Taskfile.tmpl.yml +++ b/v3/internal/templates/react/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,6 +52,7 @@ tasks: - windows cmds: - task: pre-build + - task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build diff --git a/v3/internal/templates/react/go.mod.tmpl b/v3/internal/templates/react/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/react/go.mod.tmpl +++ b/v3/internal/templates/react/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/svelte-ts/Taskfile.tmpl.yml b/v3/internal/templates/svelte-ts/Taskfile.tmpl.yml index f21158a39..68631d952 100644 --- a/v3/internal/templates/svelte-ts/Taskfile.tmpl.yml +++ b/v3/internal/templates/svelte-ts/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,6 +52,7 @@ tasks: - windows cmds: - task: pre-build + - task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build diff --git a/v3/internal/templates/svelte-ts/go.mod.tmpl b/v3/internal/templates/svelte-ts/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/svelte-ts/go.mod.tmpl +++ b/v3/internal/templates/svelte-ts/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/svelte/Taskfile.tmpl.yml b/v3/internal/templates/svelte/Taskfile.tmpl.yml index f21158a39..68631d952 100644 --- a/v3/internal/templates/svelte/Taskfile.tmpl.yml +++ b/v3/internal/templates/svelte/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,6 +52,7 @@ tasks: - windows cmds: - task: pre-build + - task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build diff --git a/v3/internal/templates/svelte/go.mod.tmpl b/v3/internal/templates/svelte/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/svelte/go.mod.tmpl +++ b/v3/internal/templates/svelte/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/vanilla-ts/Taskfile.tmpl.yml b/v3/internal/templates/vanilla-ts/Taskfile.tmpl.yml index f21158a39..68631d952 100644 --- a/v3/internal/templates/vanilla-ts/Taskfile.tmpl.yml +++ b/v3/internal/templates/vanilla-ts/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,6 +52,7 @@ tasks: - windows cmds: - task: pre-build + - task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build diff --git a/v3/internal/templates/vanilla-ts/go.mod.tmpl b/v3/internal/templates/vanilla-ts/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/vanilla-ts/go.mod.tmpl +++ b/v3/internal/templates/vanilla-ts/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/vanilla/Taskfile.tmpl.yml b/v3/internal/templates/vanilla/Taskfile.tmpl.yml index f21158a39..68631d952 100644 --- a/v3/internal/templates/vanilla/Taskfile.tmpl.yml +++ b/v3/internal/templates/vanilla/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,6 +52,7 @@ tasks: - windows cmds: - task: pre-build + - task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build diff --git a/v3/internal/templates/vanilla/go.mod.tmpl b/v3/internal/templates/vanilla/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/vanilla/go.mod.tmpl +++ b/v3/internal/templates/vanilla/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/vue-ts/Taskfile.tmpl.yml b/v3/internal/templates/vue-ts/Taskfile.tmpl.yml index f21158a39..68631d952 100644 --- a/v3/internal/templates/vue-ts/Taskfile.tmpl.yml +++ b/v3/internal/templates/vue-ts/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,6 +52,7 @@ tasks: - windows cmds: - task: pre-build + - task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build diff --git a/v3/internal/templates/vue-ts/go.mod.tmpl b/v3/internal/templates/vue-ts/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/vue-ts/go.mod.tmpl +++ b/v3/internal/templates/vue-ts/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/internal/templates/vue/Taskfile.tmpl.yml b/v3/internal/templates/vue/Taskfile.tmpl.yml index f21158a39..68631d952 100644 --- a/v3/internal/templates/vue/Taskfile.tmpl.yml +++ b/v3/internal/templates/vue/Taskfile.tmpl.yml @@ -38,9 +38,10 @@ tasks: platforms: - darwin cmds: - - task: pre-build - - go build -gcflags=all="-N -l" -o bin/testapp - - task: post-build + - task: pre-build + - task: build-frontend + - go build -gcflags=all="-N -l" -o bin/testapp + - task: post-build env: CGO_CFLAGS: "-mmacosx-version-min=10.13" CGO_LDFLAGS: "-mmacosx-version-min=10.13" @@ -51,6 +52,7 @@ tasks: - windows cmds: - task: pre-build + - task: build-frontend - go build -gcflags=all="-N -l" -o bin/testapp.exe - task: post-build diff --git a/v3/internal/templates/vue/go.mod.tmpl b/v3/internal/templates/vue/go.mod.tmpl index 4fb0017bc..3dad9b6fa 100644 --- a/v3/internal/templates/vue/go.mod.tmpl +++ b/v3/internal/templates/vue/go.mod.tmpl @@ -11,11 +11,9 @@ require ( github.com/modern-go/reflect2 v1.0.2 // indirect github.com/samber/lo v1.37.0 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect - github.com/wailsapp/wails/v2 v2.3.2-0.20230117193915-45c3a501d9e6 // indirect golang.org/x/exp v0.0.0-20220930202632-ec3f01382ef9 // indirect golang.org/x/net v0.7.0 // indirect ) {{if gt (len .LocalModulePath) 0}} replace github.com/wailsapp/wails/v3 => {{.LocalModulePath}}v3 -replace github.com/wailsapp/wails/v2 => {{.LocalModulePath}}v2 {{end}} diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go index 1d1caee7a..e64fdcceb 100644 --- a/v3/pkg/application/application.go +++ b/v3/pkg/application/application.go @@ -16,9 +16,8 @@ import ( "github.com/samber/lo" - "github.com/wailsapp/wails/v2/pkg/assetserver" - "github.com/wailsapp/wails/v2/pkg/assetserver/webview" - assetserveroptions "github.com/wailsapp/wails/v2/pkg/options/assetserver" + "github.com/wailsapp/wails/v3/pkg/assetserver" + "github.com/wailsapp/wails/v3/pkg/assetserver/webview" wailsruntime "github.com/wailsapp/wails/v3/internal/runtime" "github.com/wailsapp/wails/v3/pkg/events" @@ -72,10 +71,10 @@ func New(appOptions Options) *App { result.Events = NewWailsEventProcessor(result.dispatchEventToWindows) - opts := assetserveroptions.Options{ + opts := &assetserver.Options{ Assets: appOptions.Assets.FS, Handler: appOptions.Assets.Handler, - Middleware: assetserveroptions.Middleware(appOptions.Assets.Middleware), + Middleware: assetserver.Middleware(appOptions.Assets.Middleware), } // TODO ServingFrom disk? diff --git a/v3/pkg/application/application_darwin.go b/v3/pkg/application/application_darwin.go index af0b1934a..1842f3c0a 100644 --- a/v3/pkg/application/application_darwin.go +++ b/v3/pkg/application/application_darwin.go @@ -128,7 +128,7 @@ import "C" import ( "unsafe" - "github.com/wailsapp/wails/v2/pkg/assetserver/webview" + "github.com/wailsapp/wails/v3/pkg/assetserver/webview" "github.com/wailsapp/wails/v3/pkg/events" ) diff --git a/v3/pkg/application/linux_cgo.go b/v3/pkg/application/linux_cgo.go index 132d85727..ca1727848 100644 --- a/v3/pkg/application/linux_cgo.go +++ b/v3/pkg/application/linux_cgo.go @@ -7,7 +7,7 @@ import ( "strings" "unsafe" - "github.com/wailsapp/wails/v2/pkg/assetserver/webview" + "github.com/wailsapp/wails/v3/pkg/assetserver/webview" "github.com/wailsapp/wails/v3/pkg/events" ) diff --git a/v3/pkg/application/linux_purego.go b/v3/pkg/application/linux_purego.go index ef44dd98b..ea859ffb6 100644 --- a/v3/pkg/application/linux_purego.go +++ b/v3/pkg/application/linux_purego.go @@ -9,7 +9,7 @@ import ( "unsafe" "github.com/ebitengine/purego" - "github.com/wailsapp/wails/v2/pkg/assetserver/webview" + "github.com/wailsapp/wails/v3/pkg/assetserver/webview" "github.com/wailsapp/wails/v3/pkg/events" ) diff --git a/v3/pkg/application/plugins.go b/v3/pkg/application/plugins.go index 90ade7358..69247272b 100644 --- a/v3/pkg/application/plugins.go +++ b/v3/pkg/application/plugins.go @@ -1,6 +1,6 @@ package application -import "github.com/wailsapp/wails/v2/pkg/assetserver" +import "github.com/wailsapp/wails/v3/pkg/assetserver" type Plugin interface { Name() string diff --git a/v3/pkg/application/webview_window_linux.go b/v3/pkg/application/webview_window_linux.go index fe6ddc808..182e758a8 100644 --- a/v3/pkg/application/webview_window_linux.go +++ b/v3/pkg/application/webview_window_linux.go @@ -8,7 +8,6 @@ import ( "sync" "unsafe" - "github.com/wailsapp/wails/v2/pkg/menu" "github.com/wailsapp/wails/v3/pkg/events" ) @@ -29,7 +28,7 @@ type linuxWebviewWindow struct { parent *WebviewWindow menubar pointer vbox pointer - menu *menu.Menu + menu *Menu accels pointer lastWidth int lastHeight int diff --git a/v3/pkg/application/webview_window_windows.go b/v3/pkg/application/webview_window_windows.go index 1af70097e..22343c38c 100644 --- a/v3/pkg/application/webview_window_windows.go +++ b/v3/pkg/application/webview_window_windows.go @@ -7,8 +7,8 @@ import ( "fmt" "github.com/bep/debounce" "github.com/wailsapp/go-webview2/webviewloader" - "github.com/wailsapp/wails/v2/pkg/assetserver" "github.com/wailsapp/wails/v3/internal/capabilities" + "github.com/wailsapp/wails/v3/pkg/assetserver" "io" "net/http" "net/http/httptest" diff --git a/v3/pkg/assetserver/assethandler.go b/v3/pkg/assetserver/assethandler.go new file mode 100644 index 000000000..1bca1bd50 --- /dev/null +++ b/v3/pkg/assetserver/assethandler.go @@ -0,0 +1,202 @@ +package assetserver + +import ( + "bytes" + "embed" + "errors" + "fmt" + "io" + iofs "io/fs" + "net/http" + "os" + "path" + "strings" +) + +type Logger interface { + Debug(message string, args ...interface{}) + Error(message string, args ...interface{}) +} + +//go:embed defaultindex.html +var defaultHTML []byte + +const ( + indexHTML = "index.html" +) + +type assetHandler struct { + fs iofs.FS + handler http.Handler + + logger Logger + + retryMissingFiles bool +} + +func NewAssetHandler(options *Options, log Logger) (http.Handler, error) { + + vfs := options.Assets + if vfs != nil { + if _, err := vfs.Open("."); err != nil { + return nil, err + } + + subDir, err := FindPathToFile(vfs, indexHTML) + if err != nil { + if errors.Is(err, os.ErrNotExist) { + msg := "no `index.html` could be found in your Assets fs.FS" + if embedFs, isEmbedFs := vfs.(embed.FS); isEmbedFs { + rootFolder, _ := FindEmbedRootPath(embedFs) + msg += fmt.Sprintf(", please make sure the embedded directory '%s' is correct and contains your assets", rootFolder) + } + + return nil, fmt.Errorf(msg) + } + + return nil, err + } + + vfs, err = iofs.Sub(vfs, path.Clean(subDir)) + if err != nil { + return nil, err + } + } + + var result http.Handler = &assetHandler{ + fs: vfs, + handler: options.Handler, + logger: log, + } + + if middleware := options.Middleware; middleware != nil { + result = middleware(result) + } + + return result, nil +} + +func (d *assetHandler) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + url := req.URL.Path + handler := d.handler + if strings.EqualFold(req.Method, http.MethodGet) { + filename := path.Clean(strings.TrimPrefix(url, "/")) + + d.logDebug("Handling request '%s' (file='%s')", url, filename) + if err := d.serveFSFile(rw, req, filename); err != nil { + if os.IsNotExist(err) { + if handler != nil { + d.logDebug("File '%s' not found, serving '%s' by AssetHandler", filename, url) + handler.ServeHTTP(rw, req) + err = nil + } else { + rw.WriteHeader(http.StatusNotFound) + err = nil + } + } + + if err != nil { + d.logError("Unable to handle request '%s': %s", url, err) + http.Error(rw, err.Error(), http.StatusInternalServerError) + } + } + } else if handler != nil { + d.logDebug("No GET request, serving '%s' by AssetHandler", url) + handler.ServeHTTP(rw, req) + } else { + rw.WriteHeader(http.StatusMethodNotAllowed) + } +} + +// serveFile will try to load the file from the fs.FS and write it to the response +func (d *assetHandler) serveFSFile(rw http.ResponseWriter, req *http.Request, filename string) error { + if d.fs == nil { + return os.ErrNotExist + } + + file, err := d.fs.Open(filename) + if err != nil { + return err + } + defer file.Close() + + statInfo, err := file.Stat() + if err != nil { + return err + } + + url := req.URL.Path + isDirectoryPath := url == "" || url[len(url)-1] == '/' + if statInfo.IsDir() { + if !isDirectoryPath { + // If the URL doesn't end in a slash normally a http.redirect should be done, but that currently doesn't work on + // WebKit WebViews (macOS/Linux). + // So we handle this as a specific error + return fmt.Errorf("a directory has been requested without a trailing slash, please add a trailing slash to your request") + } + + filename = path.Join(filename, indexHTML) + + file, err = d.fs.Open(filename) + if err != nil { + return err + } + defer file.Close() + + statInfo, err = file.Stat() + if err != nil { + return err + } + } else if isDirectoryPath { + return fmt.Errorf("a file has been requested with a trailing slash, please remove the trailing slash from your request") + } + + var buf [512]byte + var n int + if _, haveType := rw.Header()[HeaderContentType]; !haveType { + // Detect MimeType by sniffing the first 512 bytes + n, err = file.Read(buf[:]) + if err != nil && err != io.EOF { + return err + } + + // Do the custom MimeType sniffing even though http.ServeContent would do it in case + // of an io.ReadSeeker. We would like to have a consistent behaviour in both cases. + if contentType := GetMimetype(filename, buf[:n]); contentType != "" { + rw.Header().Set(HeaderContentType, contentType) + } + } + + if fileSeeker, _ := file.(io.ReadSeeker); fileSeeker != nil { + if _, err := fileSeeker.Seek(0, io.SeekStart); err != nil { + return fmt.Errorf("seeker can't seek") + } + + http.ServeContent(rw, req, statInfo.Name(), statInfo.ModTime(), fileSeeker) + return nil + } + + rw.Header().Set(HeaderContentLength, fmt.Sprintf("%d", statInfo.Size())) + + // Write the first 512 bytes used for MimeType sniffing + _, err = io.Copy(rw, bytes.NewReader(buf[:n])) + if err != nil { + return err + } + + // Copy the remaining content of the file + _, err = io.Copy(rw, file) + return err +} + +func (d *assetHandler) logDebug(message string, args ...interface{}) { + if d.logger != nil { + d.logger.Debug("[AssetHandler] "+message, args...) + } +} + +func (d *assetHandler) logError(message string, args ...interface{}) { + if d.logger != nil { + d.logger.Error("[AssetHandler] "+message, args...) + } +} diff --git a/v3/pkg/assetserver/assethandler_external.go b/v3/pkg/assetserver/assethandler_external.go new file mode 100644 index 000000000..56d286bcb --- /dev/null +++ b/v3/pkg/assetserver/assethandler_external.go @@ -0,0 +1,78 @@ +//go:build dev +// +build dev + +package assetserver + +import ( + "errors" + "fmt" + "net/http" + "net/http/httputil" + "net/url" +) + +func NewExternalAssetsHandler(logger Logger, options Options, url *url.URL) http.Handler { + baseHandler := options.Handler + + errSkipProxy := fmt.Errorf("skip proxying") + + proxy := httputil.NewSingleHostReverseProxy(url) + baseDirector := proxy.Director + proxy.Director = func(r *http.Request) { + baseDirector(r) + if logger != nil { + logger.Debug("[ExternalAssetHandler] Loading '%s'", r.URL) + } + } + + proxy.ModifyResponse = func(res *http.Response) error { + if baseHandler == nil { + return nil + } + + if res.StatusCode == http.StatusSwitchingProtocols { + return nil + } + + if res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusMethodNotAllowed { + return errSkipProxy + } + + return nil + } + + proxy.ErrorHandler = func(rw http.ResponseWriter, r *http.Request, err error) { + if baseHandler != nil && errors.Is(err, errSkipProxy) { + if logger != nil { + logger.Debug("[ExternalAssetHandler] Loading '%s' failed, using original AssetHandler", r.URL) + } + baseHandler.ServeHTTP(rw, r) + } else { + if logger != nil { + logger.Error("[ExternalAssetHandler] Proxy error: %v", err) + } + rw.WriteHeader(http.StatusBadGateway) + } + } + + var result http.Handler = http.HandlerFunc( + func(rw http.ResponseWriter, req *http.Request) { + if req.Method == http.MethodGet { + proxy.ServeHTTP(rw, req) + return + } + + if baseHandler != nil { + baseHandler.ServeHTTP(rw, req) + return + } + + rw.WriteHeader(http.StatusMethodNotAllowed) + }) + + if middleware := options.Middleware; middleware != nil { + result = middleware(result) + } + + return result +} diff --git a/v3/pkg/assetserver/assetserver.go b/v3/pkg/assetserver/assetserver.go new file mode 100644 index 000000000..de9faa57a --- /dev/null +++ b/v3/pkg/assetserver/assetserver.go @@ -0,0 +1,246 @@ +package assetserver + +import ( + "bytes" + "fmt" + "math/rand" + "net/http" + "net/http/httptest" + "strings" + + "golang.org/x/net/html" +) + +const ( + runtimeJSPath = "/wails/runtime.js" + ipcJSPath = "/wails/ipc.js" + runtimePath = "/wails/runtime" + capabilitiesPath = "/wails/capabilities" + flagsPath = "/wails/flags" +) + +type RuntimeAssets interface { + DesktopIPC() []byte + WebsocketIPC() []byte + RuntimeDesktopJS() []byte +} + +type RuntimeHandler interface { + HandleRuntimeCall(w http.ResponseWriter, r *http.Request) +} + +type AssetServer struct { + handler http.Handler + runtimeJS []byte + ipcJS func(*http.Request) []byte + + logger Logger + runtime RuntimeAssets + + servingFromDisk bool + appendSpinnerToBody bool + + // Use http based runtime + runtimeHandler RuntimeHandler + + // plugin scripts + pluginScripts map[string]string + + // GetCapabilities returns the capabilities of the runtime + GetCapabilities func() []byte + + // GetFlags returns the application flags + GetFlags func() []byte + + assetServerWebView +} + +func NewAssetServerMainPage(bindingsJSON string, options *Options, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) { + return NewAssetServer(bindingsJSON, options, servingFromDisk, logger, runtime) +} + +func NewAssetServer(bindingsJSON string, options *Options, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) { + handler, err := NewAssetHandler(options, logger) + if err != nil { + return nil, err + } + + return NewAssetServerWithHandler(handler, bindingsJSON, servingFromDisk, logger, runtime) +} + +func NewAssetServerWithHandler(handler http.Handler, bindingsJSON string, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) { + var buffer bytes.Buffer + if bindingsJSON != "" { + 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: servingFromDisk, + logger: logger, + runtime: runtime, + } + + return result, nil +} + +func (d *AssetServer) UseRuntimeHandler(handler RuntimeHandler) { + d.runtimeHandler = handler +} + +func (d *AssetServer) AddPluginScript(pluginName string, script string) { + if d.pluginScripts == nil { + d.pluginScripts = make(map[string]string) + } + pluginName = strings.ReplaceAll(pluginName, "/", "_") + pluginName = html.EscapeString(pluginName) + pluginScriptName := fmt.Sprintf("/plugin_%s_%d.js", pluginName, rand.Intn(100000)) + d.pluginScripts[pluginScriptName] = script +} + +func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) { + if isWebSocket(req) { + // WebSockets are not supported by the AssetServer + rw.WriteHeader(http.StatusNotImplemented) + return + } + + 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 + } + + switch recorder.Code { + case http.StatusOK: + content, err := d.processIndexHTML(recorder.Body.Bytes()) + if err != nil { + d.serveError(rw, err, "Unable to processIndexHTML") + return + } + d.writeBlob(rw, indexHTML, content) + + case http.StatusNotFound: + d.writeBlob(rw, indexHTML, defaultHTML) + + default: + rw.WriteHeader(recorder.Code) + + } + + case runtimeJSPath: + d.writeBlob(rw, path, d.runtimeJS) + + case capabilitiesPath: + var data = []byte("{}") + if d.GetCapabilities != nil { + data = d.GetCapabilities() + } + d.writeBlob(rw, path, data) + + case flagsPath: + var data = []byte("{}") + if d.GetFlags != nil { + data = d.GetFlags() + } + d.writeBlob(rw, path, data) + + case runtimePath: + if d.runtimeHandler != nil { + d.runtimeHandler.HandleRuntimeCall(rw, req) + } else { + d.handler.ServeHTTP(rw, req) + } + + case ipcJSPath: + content := d.runtime.DesktopIPC() + if d.ipcJS != nil { + content = d.ipcJS(req) + } + d.writeBlob(rw, path, content) + + default: + // Check if this is a plugin script + if script, ok := d.pluginScripts[path]; ok { + d.writeBlob(rw, path, []byte(script)) + return + } + d.handler.ServeHTTP(rw, req) + } +} + +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 + } + + // Inject plugins + for scriptName := range d.pluginScripts { + if err := insertScriptInHead(htmlNode, scriptName); 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) { + err := serveFile(rw, filename, blob) + if 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/v3/pkg/assetserver/assetserver_dev.go b/v3/pkg/assetserver/assetserver_dev.go new file mode 100644 index 000000000..f6a2a0d2f --- /dev/null +++ b/v3/pkg/assetserver/assetserver_dev.go @@ -0,0 +1,31 @@ +//go:build dev +// +build dev + +package assetserver + +import ( + "net/http" + "strings" +) + +/* +The assetserver for the dev mode. +Depending on the UserAgent it injects a websocket based IPC script into `index.html` or the default desktop IPC. The +default desktop IPC is injected when the webview accesses the devserver. +*/ +func NewDevAssetServer(handler http.Handler, bindingsJSON string, servingFromDisk bool, logger Logger, runtime RuntimeAssets) (*AssetServer, error) { + result, err := NewAssetServerWithHandler(handler, bindingsJSON, servingFromDisk, logger, runtime) + if err != nil { + return nil, err + } + + 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 +} diff --git a/v3/pkg/assetserver/assetserver_legacy.go b/v3/pkg/assetserver/assetserver_legacy.go new file mode 100644 index 000000000..44f50a5f9 --- /dev/null +++ b/v3/pkg/assetserver/assetserver_legacy.go @@ -0,0 +1,78 @@ +package assetserver + +import ( + "io" + "net/http" + + "github.com/wailsapp/wails/v3/pkg/assetserver/webview" +) + +// ProcessHTTPRequest processes the HTTP Request by faking a golang HTTP Server. +// The request will be finished with a StatusNotImplemented code if no handler has written to the response. +func (d *AssetServer) ProcessHTTPRequestLegacy(rw http.ResponseWriter, reqGetter func() (*http.Request, error)) { + d.processWebViewRequest(&legacyRequest{reqGetter: reqGetter, rw: rw}) +} + +type legacyRequest struct { + req *http.Request + rw http.ResponseWriter + + reqGetter func() (*http.Request, error) +} + +func (r *legacyRequest) URL() (string, error) { + req, err := r.request() + if err != nil { + return "", err + } + return req.URL.String(), nil +} + +func (r *legacyRequest) Method() (string, error) { + req, err := r.request() + if err != nil { + return "", err + } + return req.Method, nil +} + +func (r *legacyRequest) Header() (http.Header, error) { + req, err := r.request() + if err != nil { + return nil, err + } + return req.Header, nil +} + +func (r *legacyRequest) Body() (io.ReadCloser, error) { + req, err := r.request() + if err != nil { + return nil, err + } + return req.Body, nil +} + +func (r legacyRequest) Response() webview.ResponseWriter { + return &legacyRequestNoOpCloserResponseWriter{r.rw} +} + +func (r legacyRequest) Close() error { return nil } + +func (r *legacyRequest) request() (*http.Request, error) { + if r.req != nil { + return r.req, nil + } + + req, err := r.reqGetter() + if err != nil { + return nil, err + } + r.req = req + return req, nil +} + +type legacyRequestNoOpCloserResponseWriter struct { + http.ResponseWriter +} + +func (*legacyRequestNoOpCloserResponseWriter) Finish() {} diff --git a/v3/pkg/assetserver/assetserver_webview.go b/v3/pkg/assetserver/assetserver_webview.go new file mode 100644 index 000000000..698db5d4f --- /dev/null +++ b/v3/pkg/assetserver/assetserver_webview.go @@ -0,0 +1,161 @@ +package assetserver + +import ( + "fmt" + "net/http" + "net/url" + "strconv" + "strings" + "sync" + + "github.com/wailsapp/wails/v3/pkg/assetserver/webview" +) + +type assetServerWebView struct { + // ExpectedWebViewHost is checked against the Request Host of every WebViewRequest, other hosts won't be processed. + ExpectedWebViewHost string + + dispatchInit sync.Once + dispatchReqC chan<- webview.Request + dispatchWorkers int +} + +// ServeWebViewRequest processes the HTTP Request asynchronously by faking a golang HTTP Server. +// The request will be finished with a StatusNotImplemented code if no handler has written to the response. +// The AssetServer takes ownership of the request and the caller mustn't close it or access it in any other way. +func (d *AssetServer) ServeWebViewRequest(req webview.Request) { + d.dispatchInit.Do(func() { + workers := d.dispatchWorkers + if workers == 0 { + workers = 10 + } + + workerC := make(chan webview.Request, workers*2) + for i := 0; i < workers; i++ { + go func() { + for req := range workerC { + uri, _ := req.URL() + d.processWebViewRequest(req) + if err := req.Close(); err != nil { + d.logError("Unable to call close for request for uri '%s'", uri) + } + } + }() + } + + dispatchC := make(chan webview.Request) + go queueingDispatcher(50, dispatchC, workerC) + + d.dispatchReqC = dispatchC + }) + + d.dispatchReqC <- req +} + +// processHTTPRequest processes the HTTP Request by faking a golang HTTP Server. +// The request will be finished with a StatusNotImplemented code if no handler has written to the response. +func (d *AssetServer) processWebViewRequest(r webview.Request) { + wrw := r.Response() + defer wrw.Finish() + + var rw http.ResponseWriter = &contentTypeSniffer{rw: wrw} // Make sure we have a Content-Type sniffer + defer rw.WriteHeader(http.StatusNotImplemented) // This is a NOP when a handler has already written and set the status + + uri, err := r.URL() + if err != nil { + d.logError("Error processing request, unable to get URL: %s (HttpResponse=500)", err) + http.Error(rw, err.Error(), http.StatusInternalServerError) + return + } + + method, err := r.Method() + if err != nil { + d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Method: %w", err)) + return + } + + header, err := r.Header() + if err != nil { + d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Header: %w", err)) + return + } + + body, err := r.Body() + if err != nil { + d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Body: %w", err)) + return + } + + if body == nil { + body = http.NoBody + } + defer body.Close() + + req, err := http.NewRequest(method, uri, body) + if err != nil { + d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("HTTP-Request: %w", err)) + return + } + req.Header = header + + if req.RemoteAddr == "" { + // 192.0.2.0/24 is "TEST-NET" in RFC 5737 + req.RemoteAddr = "192.0.2.1:1234" + } + + if req.RequestURI == "" && req.URL != nil { + req.RequestURI = req.URL.String() + } + + if req.ContentLength == 0 { + req.ContentLength, _ = strconv.ParseInt(req.Header.Get(HeaderContentLength), 10, 64) + } else { + req.Header.Set(HeaderContentLength, fmt.Sprintf("%d", req.ContentLength)) + } + + if host := req.Header.Get(HeaderHost); host != "" { + req.Host = host + } + + if expectedHost := d.ExpectedWebViewHost; expectedHost != "" && expectedHost != req.Host { + d.webviewRequestErrorHandler(uri, rw, fmt.Errorf("expected host '%s' in request, but was '%s'", expectedHost, req.Host)) + return + } + + d.ServeHTTP(rw, req) +} + +func (d *AssetServer) webviewRequestErrorHandler(uri string, rw http.ResponseWriter, err error) { + logInfo := uri + if uri, err := url.ParseRequestURI(uri); err == nil { + logInfo = strings.Replace(logInfo, fmt.Sprintf("%s://%s", uri.Scheme, uri.Host), "", 1) + } + + d.logError("Error processing request '%s': %s (HttpResponse=500)", logInfo, err) + http.Error(rw, err.Error(), http.StatusInternalServerError) +} + +func queueingDispatcher[T any](minQueueSize uint, inC <-chan T, outC chan<- T) { + q := newRingqueue[T](minQueueSize) + for { + in, ok := <-inC + if !ok { + return + } + + q.Add(in) + for q.Len() != 0 { + out, _ := q.Peek() + select { + case outC <- out: + q.Remove() + case in, ok := <-inC: + if !ok { + return + } + + q.Add(in) + } + } + } +} diff --git a/v3/pkg/assetserver/common.go b/v3/pkg/assetserver/common.go new file mode 100644 index 000000000..b787cc6bb --- /dev/null +++ b/v3/pkg/assetserver/common.go @@ -0,0 +1,115 @@ +package assetserver + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + "strings" + + "golang.org/x/net/html" +) + +const ( + HeaderHost = "Host" + HeaderContentType = "Content-Type" + HeaderContentLength = "Content-Length" + HeaderUserAgent = "User-Agent" + HeaderCacheControl = "Cache-Control" + HeaderUpgrade = "Upgrade" + + WailsUserAgentValue = "wails.io" +) + +func serveFile(rw http.ResponseWriter, filename string, blob []byte) error { + 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) + _, err := io.Copy(rw, bytes.NewReader(blob)) + return err +} + +func createScriptNode(scriptName string) *html.Node { + return &html.Node{ + Type: html.ElementNode, + Data: "script", + Attr: []html.Attribute{ + { + Key: "src", + Val: scriptName, + }, + }, + } +} + +func createDivNode(id string) *html.Node { + return &html.Node{ + Type: html.ElementNode, + Data: "div", + Attr: []html.Attribute{ + { + Namespace: "", + Key: "id", + Val: id, + }, + }, + } +} + +func insertScriptInHead(htmlNode *html.Node, scriptName string) error { + headNode := findFirstTag(htmlNode, "head") + if headNode == nil { + return errors.New("cannot find head in HTML") + } + scriptNode := createScriptNode(scriptName) + if headNode.FirstChild != nil { + headNode.InsertBefore(scriptNode, headNode.FirstChild) + } else { + headNode.AppendChild(scriptNode) + } + return nil +} + +func appendSpinnerToBody(htmlNode *html.Node) error { + bodyNode := findFirstTag(htmlNode, "body") + if bodyNode == nil { + return errors.New("cannot find body in HTML") + } + scriptNode := createDivNode("wails-spinner") + bodyNode.AppendChild(scriptNode) + return nil +} + +func getHTMLNode(htmldata []byte) (*html.Node, error) { + return html.Parse(bytes.NewReader(htmldata)) +} + +func findFirstTag(htmlnode *html.Node, tagName string) *html.Node { + var extractor func(*html.Node) *html.Node + var result *html.Node + extractor = func(node *html.Node) *html.Node { + if node.Type == html.ElementNode && node.Data == tagName { + return node + } + for child := node.FirstChild; child != nil; child = child.NextSibling { + result := extractor(child) + if result != nil { + return result + } + } + return nil + } + result = extractor(htmlnode) + return result +} + +func isWebSocket(req *http.Request) bool { + upgrade := req.Header.Get(HeaderUpgrade) + return strings.EqualFold(upgrade, "websocket") +} diff --git a/v3/pkg/assetserver/content_type_sniffer.go b/v3/pkg/assetserver/content_type_sniffer.go new file mode 100644 index 000000000..475428ae5 --- /dev/null +++ b/v3/pkg/assetserver/content_type_sniffer.go @@ -0,0 +1,42 @@ +package assetserver + +import ( + "net/http" +) + +type contentTypeSniffer struct { + rw http.ResponseWriter + + wroteHeader bool +} + +func (rw *contentTypeSniffer) Header() http.Header { + return rw.rw.Header() +} + +func (rw *contentTypeSniffer) Write(buf []byte) (int, error) { + rw.writeHeader(buf) + return rw.rw.Write(buf) +} + +func (rw *contentTypeSniffer) WriteHeader(code int) { + if rw.wroteHeader { + return + } + + rw.rw.WriteHeader(code) + rw.wroteHeader = true +} + +func (rw *contentTypeSniffer) writeHeader(b []byte) { + if rw.wroteHeader { + return + } + + m := rw.rw.Header() + if _, hasType := m[HeaderContentType]; !hasType { + m.Set(HeaderContentType, http.DetectContentType(b)) + } + + rw.WriteHeader(http.StatusOK) +} diff --git a/v3/pkg/assetserver/defaultindex.html b/v3/pkg/assetserver/defaultindex.html new file mode 100644 index 000000000..1ea97c405 --- /dev/null +++ b/v3/pkg/assetserver/defaultindex.html @@ -0,0 +1,39 @@ + + +
+ +Please try reloading the page
+ + \ No newline at end of file diff --git a/v3/pkg/assetserver/fs.go b/v3/pkg/assetserver/fs.go new file mode 100644 index 000000000..7ecc9cec8 --- /dev/null +++ b/v3/pkg/assetserver/fs.go @@ -0,0 +1,75 @@ +package assetserver + +import ( + "embed" + "fmt" + "io/fs" + "os" + "path/filepath" + "strings" +) + +// FindEmbedRootPath finds the root path in the embed FS. It's the directory which contains all the files. +func FindEmbedRootPath(fsys embed.FS) (string, error) { + stopErr := fmt.Errorf("files or multiple dirs found") + + fPath := "" + err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + + if d.IsDir() { + fPath = path + if entries, dErr := fs.ReadDir(fsys, path); dErr != nil { + return dErr + } else if len(entries) <= 1 { + return nil + } + } + + return stopErr + }) + + if err != nil && err != stopErr { + return "", err + } + + return fPath, nil +} + +func FindPathToFile(fsys fs.FS, file string) (string, error) { + stat, _ := fs.Stat(fsys, file) + if stat != nil { + return ".", nil + } + var indexFiles []string + err := fs.WalkDir(fsys, ".", func(path string, d fs.DirEntry, err error) error { + if err != nil { + return err + } + if strings.HasSuffix(path, file) { + indexFiles = append(indexFiles, path) + } + return nil + }) + if err != nil { + return "", err + } + + if len(indexFiles) > 1 { + selected := indexFiles[0] + for _, f := range indexFiles { + if len(f) < len(selected) { + selected = f + } + } + path, _ := filepath.Split(selected) + return path, nil + } + if len(indexFiles) > 0 { + path, _ := filepath.Split(indexFiles[0]) + return path, nil + } + return "", fmt.Errorf("%s: %w", file, os.ErrNotExist) +} diff --git a/v3/pkg/assetserver/middleware.go b/v3/pkg/assetserver/middleware.go new file mode 100644 index 000000000..b3826ab7d --- /dev/null +++ b/v3/pkg/assetserver/middleware.go @@ -0,0 +1,20 @@ +package assetserver + +import ( + "net/http" +) + +// Middleware defines a HTTP middleware that can be applied to the AssetServer. +// The handler passed as next is the next handler in the chain. One can decide to call the next handler +// or implement a specialized handling. +type Middleware func(next http.Handler) http.Handler + +// ChainMiddleware allows chaining multiple middlewares to one middleware. +func ChainMiddleware(middleware ...Middleware) Middleware { + return func(h http.Handler) http.Handler { + for i := len(middleware) - 1; i >= 0; i-- { + h = middleware[i](h) + } + return h + } +} diff --git a/v3/pkg/assetserver/mimecache.go b/v3/pkg/assetserver/mimecache.go new file mode 100644 index 000000000..9d97e8f5a --- /dev/null +++ b/v3/pkg/assetserver/mimecache.go @@ -0,0 +1,67 @@ +package assetserver + +import ( + "net/http" + "path/filepath" + "sync" + + "github.com/wailsapp/mimetype" +) + +var ( + mimeCache = map[string]string{} + mimeMutex sync.Mutex + + // The list of builtin mime-types by extension as defined by + // the golang standard lib package "mime" + // The standard lib also takes into account mime type definitions from + // etc files like '/etc/apache2/mime.types' but we want to have the + // same behavivour on all platforms and not depend on some external file. + mimeTypesByExt = map[string]string{ + ".avif": "image/avif", + ".css": "text/css; charset=utf-8", + ".gif": "image/gif", + ".htm": "text/html; charset=utf-8", + ".html": "text/html; charset=utf-8", + ".jpeg": "image/jpeg", + ".jpg": "image/jpeg", + ".js": "text/javascript; charset=utf-8", + ".json": "application/json", + ".mjs": "text/javascript; charset=utf-8", + ".pdf": "application/pdf", + ".png": "image/png", + ".svg": "image/svg+xml", + ".wasm": "application/wasm", + ".webp": "image/webp", + ".xml": "text/xml; charset=utf-8", + } +) + +func GetMimetype(filename string, data []byte) string { + mimeMutex.Lock() + defer mimeMutex.Unlock() + + result := mimeTypesByExt[filepath.Ext(filename)] + if result != "" { + return result + } + + result = mimeCache[filename] + if result != "" { + return result + } + + detect := mimetype.Detect(data) + if detect == nil { + result = http.DetectContentType(data) + } else { + result = detect.String() + } + + if result == "" { + result = "application/octet-stream" + } + + mimeCache[filename] = result + return result +} diff --git a/v3/pkg/assetserver/mimecache_test.go b/v3/pkg/assetserver/mimecache_test.go new file mode 100644 index 000000000..48e5943fa --- /dev/null +++ b/v3/pkg/assetserver/mimecache_test.go @@ -0,0 +1,46 @@ +package assetserver + +import ( + "testing" +) + +func TestGetMimetype(t *testing.T) { + type args struct { + filename string + data []byte + } + bomUTF8 := []byte{0xef, 0xbb, 0xbf} + var emptyMsg []byte + css := []byte("body{margin:0;padding:0;background-color:#d579b2}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;text-align:center;color:#2c3e50;background-color:#ededed}#nav{padding:30px}#nav a{font-weight:700;color:#2c\n3e50}#nav a.router-link-exact-active{color:#42b983}.hello[data-v-4e26ad49]{margin:10px 0}") + html := []byte("title") + bomHtml := append(bomUTF8, html...) + svg := []byte("") + svgWithComment := append([]byte(""), svg...) + svgWithCommentAndControlChars := append([]byte(" \r\n "), svgWithComment...) + svgWithBomCommentAndControlChars := append(bomUTF8, append([]byte(" \r\n "), svgWithComment...)...) + + tests := []struct { + name string + args args + want string + }{ + {"nil data", args{"nil.svg", nil}, "image/svg+xml"}, + {"empty data", args{"empty.html", emptyMsg}, "text/html; charset=utf-8"}, + {"css", args{"test.css", css}, "text/css; charset=utf-8"}, + {"js", args{"test.js", []byte("let foo = 'bar'; console.log(foo);")}, "text/javascript; charset=utf-8"}, + {"mjs", args{"test.mjs", []byte("let foo = 'bar'; console.log(foo);")}, "text/javascript; charset=utf-8"}, + {"html-utf8", args{"test_utf8.html", html}, "text/html; charset=utf-8"}, + {"html-bom-utf8", args{"test_bom_utf8.html", bomHtml}, "text/html; charset=utf-8"}, + {"svg", args{"test.svg", svg}, "image/svg+xml"}, + {"svg-w-comment", args{"test_comment.svg", svgWithComment}, "image/svg+xml"}, + {"svg-w-control-comment", args{"test_control_comment.svg", svgWithCommentAndControlChars}, "image/svg+xml"}, + {"svg-w-bom-control-comment", args{"test_bom_control_comment.svg", svgWithBomCommentAndControlChars}, "image/svg+xml"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + if got := GetMimetype(tt.args.filename, tt.args.data); got != tt.want { + t.Errorf("GetMimetype() = '%v', want '%v'", got, tt.want) + } + }) + } +} diff --git a/v3/pkg/assetserver/options.go b/v3/pkg/assetserver/options.go new file mode 100644 index 000000000..674451a0c --- /dev/null +++ b/v3/pkg/assetserver/options.go @@ -0,0 +1,45 @@ +package assetserver + +import ( + "fmt" + "io/fs" + "net/http" +) + +// Options defines the configuration of the AssetServer. +type Options struct { + // Assets defines the static assets to be used. A GET request is first tried to be served from this Assets. If the Assets returns + // `os.ErrNotExist` for that file, the request handling will fallback to the Handler and tries to serve the GET + // request from it. + // + // If set to nil, all GET requests will be forwarded to Handler. + Assets fs.FS + + // Handler will be called for every GET request that can't be served from Assets, due to `os.ErrNotExist`. Furthermore all + // non GET requests will always be served from this Handler. + // + // If not defined, the result is the following in cases where the Handler would have been called: + // GET request: `http.StatusNotFound` + // Other request: `http.StatusMethodNotAllowed` + Handler http.Handler + + // Middleware is a HTTP Middleware which allows to hook into the AssetServer request chain. It allows to skip the default + // request handler dynamically, e.g. implement specialized Routing etc. + // The Middleware is called to build a new `http.Handler` used by the AssetSever and it also receives the default + // handler used by the AssetServer as an argument. + // + // If not defined, the default AssetServer request chain is executed. + // + // Multiple Middlewares can be chained together with: + // ChainMiddleware(middleware ...Middleware) Middleware + Middleware Middleware +} + +// Validate the options +func (o Options) Validate() error { + if o.Assets == nil && o.Handler == nil && o.Middleware == nil { + return fmt.Errorf("AssetServer options invalid: either Assets, Handler or Middleware must be set") + } + + return nil +} diff --git a/v3/pkg/assetserver/ringqueue.go b/v3/pkg/assetserver/ringqueue.go new file mode 100644 index 000000000..b94e7cd5c --- /dev/null +++ b/v3/pkg/assetserver/ringqueue.go @@ -0,0 +1,101 @@ +// Code from https://github.com/erikdubbelboer/ringqueue +/* +The MIT License (MIT) + +Copyright (c) 2015 Erik Dubbelboer + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. +*/ +package assetserver + +type ringqueue[T any] struct { + nodes []T + head int + tail int + cnt int + + minSize int +} + +func newRingqueue[T any](minSize uint) *ringqueue[T] { + if minSize < 2 { + minSize = 2 + } + return &ringqueue[T]{ + nodes: make([]T, minSize), + minSize: int(minSize), + } +} + +func (q *ringqueue[T]) resize(n int) { + nodes := make([]T, n) + if q.head < q.tail { + copy(nodes, q.nodes[q.head:q.tail]) + } else { + copy(nodes, q.nodes[q.head:]) + copy(nodes[len(q.nodes)-q.head:], q.nodes[:q.tail]) + } + + q.tail = q.cnt % n + q.head = 0 + q.nodes = nodes +} + +func (q *ringqueue[T]) Add(i T) { + if q.cnt == len(q.nodes) { + // Also tested a grow rate of 1.5, see: http://stackoverflow.com/questions/2269063/buffer-growth-strategy + // In Go this resulted in a higher memory usage. + q.resize(q.cnt * 2) + } + q.nodes[q.tail] = i + q.tail = (q.tail + 1) % len(q.nodes) + q.cnt++ +} + +func (q *ringqueue[T]) Peek() (T, bool) { + if q.cnt == 0 { + var none T + return none, false + } + return q.nodes[q.head], true +} + +func (q *ringqueue[T]) Remove() (T, bool) { + if q.cnt == 0 { + var none T + return none, false + } + i := q.nodes[q.head] + q.head = (q.head + 1) % len(q.nodes) + q.cnt-- + + if n := len(q.nodes) / 2; n > q.minSize && q.cnt <= n { + q.resize(n) + } + + return i, true +} + +func (q *ringqueue[T]) Cap() int { + return cap(q.nodes) +} + +func (q *ringqueue[T]) Len() int { + return q.cnt +} diff --git a/v3/pkg/assetserver/testdata/index.html b/v3/pkg/assetserver/testdata/index.html new file mode 100644 index 000000000..76da518f4 --- /dev/null +++ b/v3/pkg/assetserver/testdata/index.html @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/v3/pkg/assetserver/testdata/main.css b/v3/pkg/assetserver/testdata/main.css new file mode 100644 index 000000000..57b00e6c6 --- /dev/null +++ b/v3/pkg/assetserver/testdata/main.css @@ -0,0 +1,39 @@ + +html { + text-align: center; + color: white; + background-color: rgba(1, 1, 1, 0.1); +} + +body { + color: white; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + margin: 0; +} + +#result { + margin-top: 1rem; +} + +button { + -webkit-appearance: default-button; + padding: 6px; +} + +#name { + border-radius: 3px; + outline: none; + height: 20px; + -webkit-font-smoothing: antialiased; +} + +#logo { + width: 40%; + height: 40%; + padding-top: 20%; + margin: auto; + display: block; + background-position: center; + background-repeat: no-repeat; + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTUxIDQzNiIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMiIgeG1sbnM6dj0iaHR0cHM6Ly92ZWN0YS5pby9uYW5vIj48ZyBmaWxsLXJ1bGU9Im5vbnplcm8iPjxwYXRoIGQ9Ik0xMDQuMDEgMzQ0LjM4OGgxOC40MDFsLTEuNzY4IDM5LjE2MSAxMi4xNDctMzkuMTYxaDE0Ljg2N2wtLjE4MSAzOS4xNjEgMTAuNTYxLTM5LjE2MWgxOC40MDFsLTIzLjI5NyA2Ni4xNzVoLTE2Ljk5N2wuMTgxLTQxLjM4My0xMi45MTcgNDEuMzgzaC0xNi45NTFsLTIuNDQ3LTY2LjE3NXptMTIwLjk3NSA0My4wNTloNy4zODhsLjIyNy0yNC41MjEtNy42MTUgMjQuNTIxem0tMjUuNzQ0IDIzLjExNmwyNC44ODMtNjYuMTc1aDIxLjgwMWw0LjY2NyA2Ni4xNzVoLTE4LjY3NGwuMDkyLTkuNzQ2aC0xMC45MjRsLTIuOTAxIDkuNzQ2aC0xOC45NDR6bTg4LjE4MyAwbDEwLjQ3LTY2LjE3NmgxOC40OTNsLTEwLjUxNiA2Ni4xNzUtMTguNDQ2LjAwMXptNjUuNzkzIDBsMTAuNTE2LTY2LjE3NWgxOC41MzZsLTcuODg2IDQ5Ljc2NmgxMy41NTJsLTIuNTgyIDE2LjQwOWgtMzIuMTM2em03NC43MjItMjAuMzUyYzIuMDU0IDEuNzIzIDQuMjE1IDMuMDUzIDYuNDgyIDMuOTlzNC40NCAxLjQwNCA2LjUyNiAxLjQwNGMxLjg0MyAwIDMuMzA4LS41MDYgNC4zOTYtMS41MThzMS42MzItMi4zOTUgMS42MzItNC4xNDhjMC0xLjUwOS0uNDU0LTMuMDEzLTEuMzU5LTQuNTA5cy0yLjY2LTMuNDgxLTUuMjU4LTUuOTU5Yy0zLjE0NC0zLjA1Mi01LjMwMy01Ljc0MS02LjQ4Mi04LjA2OXMtMS43NjYtNC44OTQtMS43NjYtNy43MDRjMC02LjMxNSAyLjAwMS0xMS4zMzIgNi4wMDUtMTUuMDQ4czkuNDM0LTUuNTc1IDE2LjI5NC01LjU3NWMyLjc4IDAgNS40MjIuMzEgNy45MzEuOTNzNS4wNiAxLjU3OSA3LjY2MSAyLjg3OGwtMi42MyAxNi4xMzZjLTEuOTk1LTEuMzktMy45MzUtMi40NDctNS44MjMtMy4xNzNzLTMuNjk0LTEuMDg5LTUuNDE3LTEuMDg5Yy0xLjU0MSAwLTIuNzU4LjQtMy42NDkgMS4ycy0xLjMzOCAxLjg5OC0xLjMzOCAzLjI4OGMwIDEuODc1IDEuNzA4IDQuNTAzIDUuMTIzIDcuODg2bC45OTcuOTk2YzMuNDQ1IDMuMzg2IDUuNzExIDYuMjg2IDYuNzk4IDguNzA1czEuNjMxIDUuMjA5IDEuNjMxIDguMzg0YzAgNy4wNzEtMi4xODMgMTIuNjQ2LTYuNTUgMTYuNzI0cy0xMC4zNDEgNi4xMi0xNy45MjUgNi4xMmMtMy4yMzQgMC02LjI5Mi0uMzg1LTkuMTc4LTEuMTU1cy01LjMxLTEuODM4LTcuMjc0LTMuMTk3bDMuMTczLTE3LjQ5NXoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNLjg4My0uMDgxTC4xMjEuMDgxLjI1Ni0uMDYzLjg4My0uMDgxeiIgZmlsbD0idXJsKCNBKSIgdHJhbnNmb3JtPSJtYXRyaXgoLTE2Ni41OTkgNC41NzEzMiA0LjU3MTMyIDE2Ni41OTkgMTQ3LjQwMyAxNjcuNjQ4KSIvPjxwYXRoIGQ9Ik0uODc4LS4yODVMLS4wNzMuNzEtMS4xODYuNTQyLjAxNS4yMDctLjg0Ni4wNzcuMzU1LS4yNThsLS44Ni0uMTNMLjY0OS0uNzFsLjIyOS40MjV6IiBmaWxsPSJ1cmwoI0IpIiB0cmFuc2Zvcm09Im1hdHJpeCgtMTA2LjQ0MyAtMTYuMDY2OSAtMTYuMDY2OSAxMDYuNDQzIDQyOC4xOSAxODguMDMzKSIvPjxwYXRoIGQ9Ik0uNDQtLjA0aDAgMEwuMjY1LS4wNTYuMTc3LjQzNy0uMzExLS4yNTUuMjYyLS40MzdoLjMwNkwuNDQtLjA0eiIgZmlsbD0idXJsKCNDKSIgdHJhbnNmb3JtPSJtYXRyaXgoLTExNC40ODQgLTE2Mi40MDggLTE2Mi40MDggMTE0LjQ4NCAzMzMuMjkxIDI4NS44MDQpIi8+PHBhdGggZD0iTS41IDBoMCAwIDB6IiBmaWxsPSJ1cmwoI0QpIiB0cmFuc2Zvcm09Im1hdHJpeCg2MS42OTE5IDU4LjgwOTEgNTguODA5MSAtNjEuNjkxOSAyNTguNjMxIDE4MC40MTMpIi8+PHBhdGggZD0iTS42MjItLjExNWguMTM5bC4wNDUuMTAyLjAyLjE5NS0uMjA0LS4yOTd6IiBmaWxsPSJ1cmwoI0UpIiB0cmFuc2Zvcm09Im1hdHJpeCgyMzguMTI2IDI5OC44OTMgMjk4Ljg5MyAtMjM4LjEyNiAxMTMuNTE2IC0xNTAuNTM2KSIvPjxwYXRoIGQ9Ik0uNDY3LjAwNUwuNDkuMDYyLjI3MS0uMDYyLjQ2Ny4wMDV6IiBmaWxsPSJ1cmwoI0YpIiB0cmFuc2Zvcm09Im1hdHJpeCgtMzY5LjUyOSAtOTcuNDExOCAtOTcuNDExOCAzNjkuNTI5IDU4Mi4zOCA5NC4wMjcpIi8+PGcgZmlsbD0idXJsKCNCKSI+PHBhdGggZD0iTS4yLjAwMWwuMDE5LS4wMTkuMzk1LjAzLS4wOTUuMDc3TC4yODIuMDY4LjIuMTM1LjQ2My4xOTQuMzc0LjI2Ni4xMzguMTg2aDAgMEwuMDQ3LjAzMy0uMTMxLS4yNjYuMi4wMDF6IiB0cmFuc2Zvcm09Im1hdHJpeCgtNDk2LjE1NiAtNTMuOTc1MSAtNTMuOTc1MSA0OTYuMTU2IDM2Ny44ODggMTI1LjA4NSkiLz48cGF0aCBkPSJNLjczNSAwaDAgMCAweiIgdHJhbnNmb3JtPSJtYXRyaXgoMTg1LjA3NiAxNzYuNDI3IDE3Ni40MjcgLTE4NS4wNzYgMTUzLjQ0NiA4MC4xNDg4KSIvPjwvZz48L2c+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJBIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLC0zLjQ2OTQ1ZS0xOCwtMy40Njk0NWUtMTgsLTEsMCwtMy4wNTc2MWUtMDYpIiB4bGluazpocmVmPSIjRyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZTMzMjMyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNmIwMDBkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9IkIiIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIwIiB4bGluazpocmVmPSIjRyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZTMzMjMyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNmIwMDBkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9IkMiIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIwIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEsLTEuMTEwMjJlLTE2LC0xLjExMDIyZS0xNiwtMSwwLC0yLjYxODYxZS0wNikiIHhsaW5rOmhyZWY9IiNHIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNlMzMyMzIiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM2YjAwMGQiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iRCIgeDE9IjAiIHkxPSIwIiB4Mj0iMSIgeTI9IjAiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMSwtNS41NTExMmUtMTcsLTUuNTUxMTJlLTE3LC0xLDAsLTEuNTc1NjJlLTA2KSIgeGxpbms6aHJlZj0iI0ciPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2UzMzIzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzZiMDAwZCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJFIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgtMC44MDE4OTksLTAuNTk3NDYsLTAuNTk3NDYsMC44MDE4OTksMS4zNDk1LDAuNDQ3NDU3KSIgeGxpbms6aHJlZj0iI0ciPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2UzMzIzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzZiMDAwZCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJGIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLC0yLjc3NTU2ZS0xNywtMi43NzU1NmUtMTcsLTEsMCwtMS45MjgyNmUtMDYpIiB4bGluazpocmVmPSIjRyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZTMzMjMyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNmIwMDBkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9IkciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIi8+PC9kZWZzPjwvc3ZnPg=="); +} diff --git a/v3/pkg/assetserver/testdata/main.js b/v3/pkg/assetserver/testdata/main.js new file mode 100644 index 000000000..274b4667c --- /dev/null +++ b/v3/pkg/assetserver/testdata/main.js @@ -0,0 +1,20 @@ +import {ready} from '@wails/runtime'; + +ready(() => { + // Get input + focus + let nameElement = document.getElementById("name"); + nameElement.focus(); + + // Setup the greet function + window.greet = function () { + + // Get name + let name = nameElement.value; + + // Call App.Greet(name) + window.backend.main.App.Greet(name).then((result) => { + // Update result with data back from App.Greet() + document.getElementById("result").innerText = result; + }); + }; +}); \ No newline at end of file diff --git a/v3/pkg/assetserver/testdata/subdir/index.html b/v3/pkg/assetserver/testdata/subdir/index.html new file mode 100644 index 000000000..76da518f4 --- /dev/null +++ b/v3/pkg/assetserver/testdata/subdir/index.html @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/v3/pkg/assetserver/testdata/subdir/main.css b/v3/pkg/assetserver/testdata/subdir/main.css new file mode 100644 index 000000000..57b00e6c6 --- /dev/null +++ b/v3/pkg/assetserver/testdata/subdir/main.css @@ -0,0 +1,39 @@ + +html { + text-align: center; + color: white; + background-color: rgba(1, 1, 1, 0.1); +} + +body { + color: white; + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif; + margin: 0; +} + +#result { + margin-top: 1rem; +} + +button { + -webkit-appearance: default-button; + padding: 6px; +} + +#name { + border-radius: 3px; + outline: none; + height: 20px; + -webkit-font-smoothing: antialiased; +} + +#logo { + width: 40%; + height: 40%; + padding-top: 20%; + margin: auto; + display: block; + background-position: center; + background-repeat: no-repeat; + background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHhtbG5zOnhsaW5rPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5L3hsaW5rIiB2aWV3Qm94PSIwIDAgNTUxIDQzNiIgZmlsbC1ydWxlPSJldmVub2RkIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2UtbWl0ZXJsaW1pdD0iMiIgeG1sbnM6dj0iaHR0cHM6Ly92ZWN0YS5pby9uYW5vIj48ZyBmaWxsLXJ1bGU9Im5vbnplcm8iPjxwYXRoIGQ9Ik0xMDQuMDEgMzQ0LjM4OGgxOC40MDFsLTEuNzY4IDM5LjE2MSAxMi4xNDctMzkuMTYxaDE0Ljg2N2wtLjE4MSAzOS4xNjEgMTAuNTYxLTM5LjE2MWgxOC40MDFsLTIzLjI5NyA2Ni4xNzVoLTE2Ljk5N2wuMTgxLTQxLjM4My0xMi45MTcgNDEuMzgzaC0xNi45NTFsLTIuNDQ3LTY2LjE3NXptMTIwLjk3NSA0My4wNTloNy4zODhsLjIyNy0yNC41MjEtNy42MTUgMjQuNTIxem0tMjUuNzQ0IDIzLjExNmwyNC44ODMtNjYuMTc1aDIxLjgwMWw0LjY2NyA2Ni4xNzVoLTE4LjY3NGwuMDkyLTkuNzQ2aC0xMC45MjRsLTIuOTAxIDkuNzQ2aC0xOC45NDR6bTg4LjE4MyAwbDEwLjQ3LTY2LjE3NmgxOC40OTNsLTEwLjUxNiA2Ni4xNzUtMTguNDQ2LjAwMXptNjUuNzkzIDBsMTAuNTE2LTY2LjE3NWgxOC41MzZsLTcuODg2IDQ5Ljc2NmgxMy41NTJsLTIuNTgyIDE2LjQwOWgtMzIuMTM2em03NC43MjItMjAuMzUyYzIuMDU0IDEuNzIzIDQuMjE1IDMuMDUzIDYuNDgyIDMuOTlzNC40NCAxLjQwNCA2LjUyNiAxLjQwNGMxLjg0MyAwIDMuMzA4LS41MDYgNC4zOTYtMS41MThzMS42MzItMi4zOTUgMS42MzItNC4xNDhjMC0xLjUwOS0uNDU0LTMuMDEzLTEuMzU5LTQuNTA5cy0yLjY2LTMuNDgxLTUuMjU4LTUuOTU5Yy0zLjE0NC0zLjA1Mi01LjMwMy01Ljc0MS02LjQ4Mi04LjA2OXMtMS43NjYtNC44OTQtMS43NjYtNy43MDRjMC02LjMxNSAyLjAwMS0xMS4zMzIgNi4wMDUtMTUuMDQ4czkuNDM0LTUuNTc1IDE2LjI5NC01LjU3NWMyLjc4IDAgNS40MjIuMzEgNy45MzEuOTNzNS4wNiAxLjU3OSA3LjY2MSAyLjg3OGwtMi42MyAxNi4xMzZjLTEuOTk1LTEuMzktMy45MzUtMi40NDctNS44MjMtMy4xNzNzLTMuNjk0LTEuMDg5LTUuNDE3LTEuMDg5Yy0xLjU0MSAwLTIuNzU4LjQtMy42NDkgMS4ycy0xLjMzOCAxLjg5OC0xLjMzOCAzLjI4OGMwIDEuODc1IDEuNzA4IDQuNTAzIDUuMTIzIDcuODg2bC45OTcuOTk2YzMuNDQ1IDMuMzg2IDUuNzExIDYuMjg2IDYuNzk4IDguNzA1czEuNjMxIDUuMjA5IDEuNjMxIDguMzg0YzAgNy4wNzEtMi4xODMgMTIuNjQ2LTYuNTUgMTYuNzI0cy0xMC4zNDEgNi4xMi0xNy45MjUgNi4xMmMtMy4yMzQgMC02LjI5Mi0uMzg1LTkuMTc4LTEuMTU1cy01LjMxLTEuODM4LTcuMjc0LTMuMTk3bDMuMTczLTE3LjQ5NXoiIGZpbGw9IiNmZmYiLz48cGF0aCBkPSJNLjg4My0uMDgxTC4xMjEuMDgxLjI1Ni0uMDYzLjg4My0uMDgxeiIgZmlsbD0idXJsKCNBKSIgdHJhbnNmb3JtPSJtYXRyaXgoLTE2Ni41OTkgNC41NzEzMiA0LjU3MTMyIDE2Ni41OTkgMTQ3LjQwMyAxNjcuNjQ4KSIvPjxwYXRoIGQ9Ik0uODc4LS4yODVMLS4wNzMuNzEtMS4xODYuNTQyLjAxNS4yMDctLjg0Ni4wNzcuMzU1LS4yNThsLS44Ni0uMTNMLjY0OS0uNzFsLjIyOS40MjV6IiBmaWxsPSJ1cmwoI0IpIiB0cmFuc2Zvcm09Im1hdHJpeCgtMTA2LjQ0MyAtMTYuMDY2OSAtMTYuMDY2OSAxMDYuNDQzIDQyOC4xOSAxODguMDMzKSIvPjxwYXRoIGQ9Ik0uNDQtLjA0aDAgMEwuMjY1LS4wNTYuMTc3LjQzNy0uMzExLS4yNTUuMjYyLS40MzdoLjMwNkwuNDQtLjA0eiIgZmlsbD0idXJsKCNDKSIgdHJhbnNmb3JtPSJtYXRyaXgoLTExNC40ODQgLTE2Mi40MDggLTE2Mi40MDggMTE0LjQ4NCAzMzMuMjkxIDI4NS44MDQpIi8+PHBhdGggZD0iTS41IDBoMCAwIDB6IiBmaWxsPSJ1cmwoI0QpIiB0cmFuc2Zvcm09Im1hdHJpeCg2MS42OTE5IDU4LjgwOTEgNTguODA5MSAtNjEuNjkxOSAyNTguNjMxIDE4MC40MTMpIi8+PHBhdGggZD0iTS42MjItLjExNWguMTM5bC4wNDUuMTAyLjAyLjE5NS0uMjA0LS4yOTd6IiBmaWxsPSJ1cmwoI0UpIiB0cmFuc2Zvcm09Im1hdHJpeCgyMzguMTI2IDI5OC44OTMgMjk4Ljg5MyAtMjM4LjEyNiAxMTMuNTE2IC0xNTAuNTM2KSIvPjxwYXRoIGQ9Ik0uNDY3LjAwNUwuNDkuMDYyLjI3MS0uMDYyLjQ2Ny4wMDV6IiBmaWxsPSJ1cmwoI0YpIiB0cmFuc2Zvcm09Im1hdHJpeCgtMzY5LjUyOSAtOTcuNDExOCAtOTcuNDExOCAzNjkuNTI5IDU4Mi4zOCA5NC4wMjcpIi8+PGcgZmlsbD0idXJsKCNCKSI+PHBhdGggZD0iTS4yLjAwMWwuMDE5LS4wMTkuMzk1LjAzLS4wOTUuMDc3TC4yODIuMDY4LjIuMTM1LjQ2My4xOTQuMzc0LjI2Ni4xMzguMTg2aDAgMEwuMDQ3LjAzMy0uMTMxLS4yNjYuMi4wMDF6IiB0cmFuc2Zvcm09Im1hdHJpeCgtNDk2LjE1NiAtNTMuOTc1MSAtNTMuOTc1MSA0OTYuMTU2IDM2Ny44ODggMTI1LjA4NSkiLz48cGF0aCBkPSJNLjczNSAwaDAgMCAweiIgdHJhbnNmb3JtPSJtYXRyaXgoMTg1LjA3NiAxNzYuNDI3IDE3Ni40MjcgLTE4NS4wNzYgMTUzLjQ0NiA4MC4xNDg4KSIvPjwvZz48L2c+PGRlZnM+PGxpbmVhckdyYWRpZW50IGlkPSJBIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLC0zLjQ2OTQ1ZS0xOCwtMy40Njk0NWUtMTgsLTEsMCwtMy4wNTc2MWUtMDYpIiB4bGluazpocmVmPSIjRyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZTMzMjMyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNmIwMDBkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9IkIiIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIwIiB4bGluazpocmVmPSIjRyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZTMzMjMyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNmIwMDBkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9IkMiIHgxPSIwIiB5MT0iMCIgeDI9IjEiIHkyPSIwIiBncmFkaWVudFRyYW5zZm9ybT0ibWF0cml4KDEsLTEuMTEwMjJlLTE2LC0xLjExMDIyZS0xNiwtMSwwLC0yLjYxODYxZS0wNikiIHhsaW5rOmhyZWY9IiNHIj48c3RvcCBvZmZzZXQ9IjAiIHN0b3AtY29sb3I9IiNlMzMyMzIiLz48c3RvcCBvZmZzZXQ9IjEiIHN0b3AtY29sb3I9IiM2YjAwMGQiLz48L2xpbmVhckdyYWRpZW50PjxsaW5lYXJHcmFkaWVudCBpZD0iRCIgeDE9IjAiIHkxPSIwIiB4Mj0iMSIgeTI9IjAiIGdyYWRpZW50VHJhbnNmb3JtPSJtYXRyaXgoMSwtNS41NTExMmUtMTcsLTUuNTUxMTJlLTE3LC0xLDAsLTEuNTc1NjJlLTA2KSIgeGxpbms6aHJlZj0iI0ciPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2UzMzIzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzZiMDAwZCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJFIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgtMC44MDE4OTksLTAuNTk3NDYsLTAuNTk3NDYsMC44MDE4OTksMS4zNDk1LDAuNDQ3NDU3KSIgeGxpbms6aHJlZj0iI0ciPjxzdG9wIG9mZnNldD0iMCIgc3RvcC1jb2xvcj0iI2UzMzIzMiIvPjxzdG9wIG9mZnNldD0iMSIgc3RvcC1jb2xvcj0iIzZiMDAwZCIvPjwvbGluZWFyR3JhZGllbnQ+PGxpbmVhckdyYWRpZW50IGlkPSJGIiB4MT0iMCIgeTE9IjAiIHgyPSIxIiB5Mj0iMCIgZ3JhZGllbnRUcmFuc2Zvcm09Im1hdHJpeCgxLC0yLjc3NTU2ZS0xNywtMi43NzU1NmUtMTcsLTEsMCwtMS45MjgyNmUtMDYpIiB4bGluazpocmVmPSIjRyI+PHN0b3Agb2Zmc2V0PSIwIiBzdG9wLWNvbG9yPSIjZTMzMjMyIi8+PHN0b3Agb2Zmc2V0PSIxIiBzdG9wLWNvbG9yPSIjNmIwMDBkIi8+PC9saW5lYXJHcmFkaWVudD48bGluZWFyR3JhZGllbnQgaWQ9IkciIGdyYWRpZW50VW5pdHM9InVzZXJTcGFjZU9uVXNlIi8+PC9kZWZzPjwvc3ZnPg=="); +} diff --git a/v3/pkg/assetserver/testdata/subdir/main.js b/v3/pkg/assetserver/testdata/subdir/main.js new file mode 100644 index 000000000..274b4667c --- /dev/null +++ b/v3/pkg/assetserver/testdata/subdir/main.js @@ -0,0 +1,20 @@ +import {ready} from '@wails/runtime'; + +ready(() => { + // Get input + focus + let nameElement = document.getElementById("name"); + nameElement.focus(); + + // Setup the greet function + window.greet = function () { + + // Get name + let name = nameElement.value; + + // Call App.Greet(name) + window.backend.main.App.Greet(name).then((result) => { + // Update result with data back from App.Greet() + document.getElementById("result").innerText = result; + }); + }; +}); \ No newline at end of file diff --git a/v3/pkg/assetserver/testdata/testdata.go b/v3/pkg/assetserver/testdata/testdata.go new file mode 100644 index 000000000..5387070ec --- /dev/null +++ b/v3/pkg/assetserver/testdata/testdata.go @@ -0,0 +1,6 @@ +package testdata + +import "embed" + +//go:embed index.html main.css main.js +var TopLevelFS embed.FS diff --git a/v3/pkg/assetserver/webview/request.go b/v3/pkg/assetserver/webview/request.go new file mode 100644 index 000000000..18ff29890 --- /dev/null +++ b/v3/pkg/assetserver/webview/request.go @@ -0,0 +1,17 @@ +package webview + +import ( + "io" + "net/http" +) + +type Request interface { + URL() (string, error) + Method() (string, error) + Header() (http.Header, error) + Body() (io.ReadCloser, error) + + Response() ResponseWriter + + Close() error +} diff --git a/v3/pkg/assetserver/webview/request_darwin.go b/v3/pkg/assetserver/webview/request_darwin.go new file mode 100644 index 000000000..f0e85780b --- /dev/null +++ b/v3/pkg/assetserver/webview/request_darwin.go @@ -0,0 +1,248 @@ +//go:build darwin + +package webview + +/* +#cgo CFLAGS: -x objective-c +#cgo LDFLAGS: -framework Foundation -framework WebKit + +#import