[V3] Plugins implemenations (#3570)

* plugin handler and lifecycle

* rebase

* remove reflect

s

* remove Config and NewPlugin from plugin template

* Remove plugin manager, generation of plugin interface

* implement http handlers for services

remove log

trim path

prefix wails/services

* update plugine example

* Misc updates

* Ported plugins to services, rewritten example

* Added fileserver

* Update OnStartup and use a context for the application

* Rename PathPrefix to Route. Create docs.

* Use service config copy. Add Name to Service Options. Improve service generation.

* Use service config copy. Add Name to Service Options. Improve service generation. Update README

* Remove rogue db

* Update changelog.md

---------

Co-authored-by: Lea O'Anthony <lea.anthony@gmail.com>
This commit is contained in:
Atterpac 2024-09-01 00:26:22 -07:00 committed by GitHub
commit e316cd0719
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
78 changed files with 1362 additions and 1813 deletions

View file

@ -19,6 +19,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- [windows] Window class name option by [windom](https://github.com/windom/) in [#3682](https://github.com/wailsapp/wails/pull/3682)
- Services have been expanded to provide plugin functionality. By [atterpac](https://github.com/atterpac) and [leaanthony](https://github.com/leaanthony) in [#3570](https://github.com/wailsapp/wails/pull/3570)
### Fixed
- [windows] Fixed syso icon file generation bug by [atterpac](https://github.com/atterpac) in [#3675](https://github.com/wailsapp/wails/pull/3675)

View file

@ -0,0 +1,145 @@
# Services
Services in Wails v3 provide a powerful way to extend the functionality of your application. They allow you to create
modular, reusable components that can be easily integrated into your Wails application.
## Overview
Services are designed to encapsulate specific functionality and can be registered with the application at startup.
They can handle various tasks such as file serving, database operations, logging, and more.
Services can also interact with the application lifecycle and respond to HTTP requests.
## Creating a Service
To create a service, you simply define a struct. Here's a basic structure of a service:
```go
type MyService struct {
// Your service fields
}
func NewMyService() *MyService {
// Initialize and return your service
}
func (s *MyService) Greet(name string) string {
return fmt.Sprintf("Hello, %s!", name)
}
```
This service has a single method, `Greet`, which accepts a name and returns a greeting.
## Registering a Service
To register a service with the application, you need to provide an instance of the service to the `Services` field of
the `application.Options` struct (All services need to be wrapped by an `application.NewService` call. Here's an example:
```go
app := application.New(application.Options{
Services: []application.Service{
application.NewService(NewMyService()),
},
})
```
## Optional Methods
Services can implement optional methods to hook into the application lifecycle:
### Name
```go
func (s *Service) Name() string
```
This method returns the name of the service. It is used for logging purposes only.
### OnStartup
```go
func (s *Service) OnStartup(ctx context.Context, options application.ServiceOptions) error
```
This method is called when the application is starting up. You can use it to initialize resources, set up connections,
or perform any necessary setup tasks. The context is the application context, and the `options` parameter provides
additional information about the service.
### OnShutdown
```go
func (s *Service) OnShutdown() error
```
This method is called when the application is shutting down. Use it to clean up resources, close connections, or
perform any necessary cleanup tasks.
### ServeHTTP
```go
func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request)
```
If your service needs to handle HTTP requests, implement this method. It allows your service to act as an HTTP handler.
The route of the handler is defined in the service options:
```go
application.NewService(fileserver.New(&fileserver.Config{
RootPath: rootPath,
}), application.ServiceOptions{
Route: "/files",
}),
```
## Example: File Server Service
Let's look at a simplified version of the `fileserver` service as an example:
```go
type Service struct {
config *Config
fs http.Handler
}
func New(config *Config) *Service {
return &Service{
config: config,
fs: http.FileServer(http.Dir(config.RootPath)),
}
}
func (s *Service) Name() string {
return "github.com/wailsapp/wails/v3/services/fileserver"
}
func (s *Service) OnStartup(ctx context.Context, options application.ServiceOptions) error {
// Any initialization code here
return nil
}
func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
s.fs.ServeHTTP(w, r)
}
```
We can now use this service in our application:
```go
app := application.New(application.Options{
Services: []application.Service{
application.NewService(fileserver.New(&fileserver.Config{
RootPath: rootPath,
}), application.ServiceOptions{
Route: "/files",
}),
```
All requests to `/files` will be handled by the `fileserver` service.
## Application Lifecycle and Services
1. During application initialization, services are registered with the application.
2. When the application starts (`app.Run()`), the `OnStartup` method of each service is called with the application
context and service options.
3. Throughout the application's lifetime, services can perform their specific tasks.
4. If a service implements `ServeHTTP`, it can handle HTTP requests at the specified path.
5. When the application is shutting down, the `OnShutdown` method of each service is called as well as the context being cancelled.

View file

@ -143,6 +143,7 @@ nav:
- Your First Application: getting-started/your-first-app.md
- Next Steps: getting-started/next-steps.md
- Learn More:
- Services: learn/services.md
- Runtime: learn/runtime.md
- Plugins: learn/plugins.md
- Guides:

View file

@ -57,10 +57,8 @@ func main() {
generate.NewSubCommandFunction(".desktop", "Generate .desktop file", commands.GenerateDotDesktop)
generate.NewSubCommandFunction("appimage", "Generate Linux AppImage", commands.GenerateAppImage)
plugin := app.NewSubCommand("plugin", "Plugin tools")
//plugin.NewSubCommandFunction("list", "List plugins", commands.PluginList)
plugin.NewSubCommandFunction("init", "Initialise a new plugin", commands.PluginInit)
//plugin.NewSubCommandFunction("add", "Add a plugin", commands.PluginAdd)
plugin := app.NewSubCommand("service", "Service tools")
plugin.NewSubCommandFunction("init", "Initialise a new service", commands.ServiceInit)
tool := app.NewSubCommand("tool", "Various tools")
tool.NewSubCommandFunction("checkport", "Checks if a port is open. Useful for testing if vite is running.", commands.ToolCheckPort)
tool.NewSubCommandFunction("watcher", "Watches files and runs a command when they change", commands.Watcher)

View file

@ -1,3 +0,0 @@
# Plugins Example
This example is not ready for testing yet.

View file

@ -1,42 +0,0 @@
version: '3'
tasks:
pre-build:
summary: Pre-build hooks
post-build:
summary: Post-build hooks
build:
summary: Builds the application
cmds:
- task: pre-build
- go build -gcflags=all="-N -l" -o bin/testapp main.go
- task: post-build
env:
CGO_CFLAGS: "-mmacosx-version-min=10.13"
CGO_LDFLAGS: "-mmacosx-version-min=10.13"
MACOSX_DEPLOYMENT_TARGET: "10.13"
generate-icons:
summary: Generates Windows `.ico` and Mac `.icns` files from an image
cmds:
# Generates both .ico and .icns files
- wails generate icons -input build/appicon.png
build-prod:
summary: Creates a production build of the application
cmds:
- go build -tags production -ldflags="-w -s" -o bin/testapp
package-darwin:
summary: Packages a production build of the application into a `.app` bundle
deps:
- build-prod
- generate-icons
cmds:
- mkdir -p buildtest.app/Contents/{MacOS,Resources}
- cp build/icons.icns buildtest.app/Contents/Resources
- cp bin/testapp buildtest.app/Contents/MacOS
- cp build/Info.plist buildtest.app/Contents

View file

@ -1,35 +0,0 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>My App</string>
<key>CFBundleExecutable</key>
<string>app</string>
<key>CFBundleIdentifier</key>
<string>com.wails.app</string>
<key>CFBundleVersion</key>
<string>v1.0.0</string>
<key>CFBundleGetInfoString</key>
<string>The ultimate thing</string>
<key>CFBundleShortVersionString</key>
<string>v1</string>
<key>CFBundleIconFile</key>
<string>icons</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>(c) Me</string>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsLocalNetworking</key>
<true/>
</dict>
</dict>
</plist>
</dict>
</plist>

View file

@ -1,27 +0,0 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleName</key>
<string>My App</string>
<key>CFBundleExecutable</key>
<string>testapp</string>
<key>CFBundleIdentifier</key>
<string>com.wails.app</string>
<key>CFBundleVersion</key>
<string>v1.0.0</string>
<key>CFBundleGetInfoString</key>
<string>The ultimate thing</string>
<key>CFBundleShortVersionString</key>
<string>v1</string>
<key>CFBundleIconFile</key>
<string>icons</string>
<key>LSMinimumSystemVersion</key>
<string>10.13.0</string>
<key>NSHighResolutionCapable</key>
<string>true</string>
<key>NSHumanReadableCopyright</key>
<string>(c) Me</string>
</dict>
</plist>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 130 KiB

View file

@ -1,15 +0,0 @@
{
"fixed": {
"file_version": "v1.0.0"
},
"info": {
"0000": {
"ProductVersion": "v1.0.0",
"CompanyName": "My Company Name",
"FileDescription": "A thing that does a thing",
"LegalCopyright": "(c) 2023 My Company Name",
"ProductName": "My Product Name",
"Comments": "This is a comment"
}
}
}

View file

@ -1,15 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1" xmlns:asmv3="urn:schemas-microsoft-com:asm.v3">
<assemblyIdentity type="win32" name="com.wails.myproductname" version="v1.0.0.0" processorArchitecture="*"/>
<dependency>
<dependentAssembly>
<assemblyIdentity type="win32" name="Microsoft.Windows.Common-Controls" version="6.0.0.0" processorArchitecture="*" publicKeyToken="6595b64144ccf1df" language="*"/>
</dependentAssembly>
</dependency>
<asmv3:application>
<asmv3:windowsSettings>
<dpiAware xmlns="http://schemas.microsoft.com/SMI/2005/WindowsSettings">true/pm</dpiAware> <!-- fallback for Windows 7 and 8 -->
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">permonitorv2,permonitor</dpiAwareness> <!-- falls back to per-monitor if per-monitor v2 is not supported -->
</asmv3:windowsSettings>
</asmv3:application>
</assembly>

View file

@ -1,65 +0,0 @@
module plugin_demo
go 1.22
toolchain go1.22.0
require github.com/wailsapp/wails/v3 v3.0.0-alpha.0
require (
dario.cat/mergo v1.0.0 // indirect
github.com/Microsoft/go-winio v0.6.1 // indirect
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 // indirect
github.com/bep/debounce v1.2.1 // indirect
github.com/cloudflare/circl v1.3.7 // indirect
github.com/cyphar/filepath-securejoin v0.2.4 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/ebitengine/purego v0.4.0-alpha.4 // indirect
github.com/emirpasic/gods v1.18.1 // indirect
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 // indirect
github.com/go-git/go-billy/v5 v5.5.0 // indirect
github.com/go-git/go-git/v5 v5.11.0 // indirect
github.com/go-ole/go-ole v1.2.6 // indirect
github.com/godbus/dbus/v5 v5.1.0 // indirect
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect
github.com/google/uuid v1.3.0 // indirect
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 // indirect
github.com/jchv/go-winloader v0.0.0-20210711035445-715c2860da7e // indirect
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect
github.com/kevinburke/ssh_config v1.2.0 // indirect
github.com/leaanthony/go-ansi-parser v1.6.1 // indirect
github.com/leaanthony/u v1.1.0 // indirect
github.com/lmittmann/tint v1.0.3 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.19 // indirect
github.com/pjbgf/sha1cd v0.3.0 // indirect
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec // indirect
github.com/rivo/uniseg v0.4.4 // indirect
github.com/samber/lo v1.38.1 // indirect
github.com/sergi/go-diff v1.2.0 // indirect
github.com/skeema/knownhosts v1.2.1 // indirect
github.com/wailsapp/go-webview2 v1.0.9 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect
github.com/xanzy/ssh-agent v0.3.3 // indirect
golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df // indirect
golang.org/x/mod v0.12.0 // indirect
golang.org/x/net v0.21.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/tools v0.13.0 // indirect
gopkg.in/warnings.v0 v0.1.2 // indirect
lukechampine.com/uint128 v1.2.0 // indirect
modernc.org/cc/v3 v3.40.0 // indirect
modernc.org/ccgo/v3 v3.16.13 // indirect
modernc.org/libc v1.22.3 // indirect
modernc.org/mathutil v1.5.0 // indirect
modernc.org/memory v1.5.0 // indirect
modernc.org/opt v0.1.3 // indirect
modernc.org/sqlite v1.21.0 // indirect
modernc.org/strutil v1.1.3 // indirect
modernc.org/token v1.0.1 // indirect
)
replace github.com/wailsapp/wails/v3 => ../..

View file

@ -1,227 +0,0 @@
dario.cat/mergo v1.0.0 h1:AGCNq9Evsj31mOgNPcLyXc+4PNABt905YmuqPYYpBWk=
dario.cat/mergo v1.0.0/go.mod h1:uNxQE+84aUszobStD9th8a29P2fMDhsBdgRYvZOxGmk=
github.com/Microsoft/go-winio v0.5.2/go.mod h1:WpS1mjBmmwHBEWmogvA2mj8546UReBk4v8QkMxJ6pZY=
github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migciow=
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371 h1:kkhsdkhsCvIsutKu5zLMgWtgh9YxGCNAw8Ad8hjwfYg=
github.com/ProtonMail/go-crypto v0.0.0-20230828082145-3c4c8a2d2371/go.mod h1:EjAoLdwvbIOoOQr3ihjnSoLZRtE8azugULFRteWMNc0=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be h1:9AeTilPcZAjCFIImctFaOjnTIavg87rW78vTPkQqLI8=
github.com/anmitsu/go-shlex v0.0.0-20200514113438-38f4b401e2be/go.mod h1:ySMOLuWl6zY27l47sB3qLNK6tF2fkHG55UZxx8oIVo4=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5 h1:0CwZNZbxp69SHPdPJAN/hZIm0C4OItdklCFmMRWYpio=
github.com/armon/go-socks5 v0.0.0-20160902184237-e75332964ef5/go.mod h1:wHh0iHkYZB8zMSxRWpUBQtwG5a7fFgvEO+odwuTv2gs=
github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY=
github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0=
github.com/bwesterb/go-ristretto v1.2.3/go.mod h1:fUIoIZaG73pV5biE2Blr2xEzDoMj7NFEuV9ekS419A0=
github.com/cloudflare/circl v1.3.3/go.mod h1:5XYMA4rFBvNIrhs50XuiBJ15vF2pZn4nnUKZrLbUZFA=
github.com/cloudflare/circl v1.3.7 h1:qlCDlTPz2n9fu58M0Nh1J/JzcFpfgkFHHX3O35r5vcU=
github.com/cloudflare/circl v1.3.7/go.mod h1:sRTcRWXGLrKw6yIGJ+l7amYJFfAXbZG0kBSc8r4zxgA=
github.com/cyphar/filepath-securejoin v0.2.4 h1:Ugdm7cg7i6ZK6x3xDF1oEu1nfkyfH53EtKeQYTC3kyg=
github.com/cyphar/filepath-securejoin v0.2.4/go.mod h1:aPGpWjXOXUn2NCNjFvBE6aRxGGx79pTxQpKOJNYHHl4=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dustin/go-humanize v1.0.0 h1:VSnTsYCnlFHaM2/igO1h6X3HA71jcobQuxemgkq4zYo=
github.com/dustin/go-humanize v1.0.0/go.mod h1:HtrtbFcZ19U5GC7JDqmcUSB87Iq5E25KnS6fMYU6eOk=
github.com/ebitengine/purego v0.4.0-alpha.4 h1:Y7yIV06Yo5M2BAdD7EVPhfp6LZ0tEcQo5770OhYUVes=
github.com/ebitengine/purego v0.4.0-alpha.4/go.mod h1:ah1In8AOtksoNK6yk5z1HTJeUkC1Ez4Wk2idgGslMwQ=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a h1:mATvB/9r/3gvcejNsXKSkQ6lcIaNec2nyfOdlTBR2lU=
github.com/elazarl/goproxy v0.0.0-20230808193330-2592e75ae04a/go.mod h1:Ro8st/ElPeALwNFlcTpWmkr6IoMFfkjXAvTHpevnDsM=
github.com/emirpasic/gods v1.18.1 h1:FXtiHYKDGKCW2KzwZKx0iC0PQmdlorYgdFG9jPXJ1Bc=
github.com/emirpasic/gods v1.18.1/go.mod h1:8tpGGwCnJ5H4r6BWwaV6OrWmMoPhUl5jm/FMNAnJvWQ=
github.com/gliderlabs/ssh v0.3.5 h1:OcaySEmAQJgyYcArR+gGGTHCyE7nvhEMTlYY+Dp8CpY=
github.com/gliderlabs/ssh v0.3.5/go.mod h1:8XB4KraRrX39qHhT6yxPsHedjA08I/uBVwj4xC+/+z4=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376 h1:+zs/tPmkDkHx3U66DAb0lQFJrpS6731Oaa12ikc+DiI=
github.com/go-git/gcfg v1.5.1-0.20230307220236-3a3c6141e376/go.mod h1:an3vInlBmSxCcxctByoQdvwPiA7DTK7jaaFDBTtu0ic=
github.com/go-git/go-billy/v5 v5.5.0 h1:yEY4yhzCDuMGSv83oGxiBotRzhwhNr8VZyphhiu+mTU=
github.com/go-git/go-billy/v5 v5.5.0/go.mod h1:hmexnoNsr2SJU1Ju67OaNz5ASJY3+sHgFRpCtpDCKow=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399 h1:eMje31YglSBqCdIqdhKBW8lokaMrL3uTkpGYlE2OOT4=
github.com/go-git/go-git-fixtures/v4 v4.3.2-0.20231010084843-55a94097c399/go.mod h1:1OCfN199q1Jm3HZlxleg+Dw/mwps2Wbk9frAWm+4FII=
github.com/go-git/go-git/v5 v5.11.0 h1:XIZc1p+8YzypNr34itUfSvYJcv+eYdTnTvOZ2vD3cA4=
github.com/go-git/go-git/v5 v5.11.0/go.mod h1:6GFcX2P3NM7FPBfpePbpLd21XxsgdAt+lKqXmCUiUCY=
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/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk=
github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da h1:oI5xCqsCo564l8iNU+DwB5epxmsaqB+rhGL0m5jtYqE=
github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da/go.mod h1:cIg4eruTrX1D+g88fzRXU5OdNfaM+9IcxsU14FzY7Hc=
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26 h1:Xim43kblpZXfIBQsbuBVKCudVG457BR2GZFIz3uw3hQ=
github.com/google/pprof v0.0.0-20221118152302-e6195bd50e26/go.mod h1:dDKJzRmX4S37WGHujM7tX//fmj1uioxKzKxz3lo4HJo=
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99 h1:BQSFePA1RWJOlocH6Fxy8MmwDt+yVQYULKfN0RoTN8A=
github.com/jbenet/go-context v0.0.0-20150711004518-d14ea06fba99/go.mod h1:1lJo3i6rXxKeerYnT8Nvf0QmHCRC1n8sfWVwXF2Frvo=
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/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 h1:Z9n2FFNUXsshfwJMBgNA0RU6/i7WVaAegv3PtuIHPMs=
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
github.com/kevinburke/ssh_config v1.2.0 h1:x584FjTGwHzMwvHx18PXxbBVzfnxogHaAReU4gf13a4=
github.com/kevinburke/ssh_config v1.2.0/go.mod h1:CT57kijsi8u/K/BOFA39wgDQJ9CxiF4nAY/ojJ6r6mM=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
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/leaanthony/go-ansi-parser v1.6.1 h1:xd8bzARK3dErqkPFtoF9F3/HgN8UQk0ed1YDKpEz01A=
github.com/leaanthony/go-ansi-parser v1.6.1/go.mod h1:+vva/2y4alzVmmIEpk9QDhA7vLC5zKDTRwfZGOp3IWU=
github.com/leaanthony/u v1.1.0 h1:2n0d2BwPVXSUq5yhe8lJPHdxevE2qK5G99PMStMZMaI=
github.com/leaanthony/u v1.1.0/go.mod h1:9+o6hejoRljvZ3BzdYlVL0JYCwtnAsVuN9pVTQcaRfI=
github.com/lmittmann/tint v1.0.3 h1:W5PHeA2D8bBJVvabNfQD/XW9HPLZK1XoPZH0cq8NouQ=
github.com/lmittmann/tint v1.0.3/go.mod h1:HIS3gSy7qNwGCj+5oRjAutErFBl4BzdQP6cJZ0NfMwE=
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-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattn/go-sqlite3 v1.14.16 h1:yOQRA0RpS5PFz/oikGwBEqvAWhWg5ufRz4ETLjwpU1Y=
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
github.com/onsi/gomega v1.27.10 h1:naR28SdDFlqrG6kScpT8VWpu1xWY5nJRCF3XaYyBjhI=
github.com/onsi/gomega v1.27.10/go.mod h1:RsS8tutOdbdgzbPtzzATp12yT7kM5I5aElG3evPbQ0M=
github.com/pjbgf/sha1cd v0.3.0 h1:4D5XXmUUBUl/xQ6IjCkEAbqXskkq/4O7LmGn0AqMDs4=
github.com/pjbgf/sha1cd v0.3.0/go.mod h1:nZ1rrWOcGJ5uZgEEVL1VUM9iRQiZvWdbZjkKyFzPPsI=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU=
github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI=
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/remyoudompheng/bigfft v0.0.0-20200410134404-eec4a21b6bb0/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec/go.mod h1:qqbHyh8v60DhA7CoWK5oRCqLrMHRGoxYCSS9EjAz6Eo=
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/rogpeppe/go-internal v1.11.0 h1:cWPaGQEPrBb5/AsnsZesgZZ9yb1OQ+GOISoDNXVBh4M=
github.com/rogpeppe/go-internal v1.11.0/go.mod h1:ddIwULY96R17DhadqLgMfk9H9tvdUzkipdSkR5nkCZA=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/sergi/go-diff v1.2.0 h1:XU+rvMAioB0UC3q1MFrIQy4Vo5/4VsRDQQXHsEya6xQ=
github.com/sergi/go-diff v1.2.0/go.mod h1:STckp+ISIX8hZLjrqAeVduY0gWCT9IjLuqbuNXdaHfM=
github.com/sirupsen/logrus v1.7.0/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/skeema/knownhosts v1.2.1 h1:SHWdIUa82uGZz+F+47k8SY4QhhI291cXCpopT1lK2AQ=
github.com/skeema/knownhosts v1.2.1/go.mod h1:xYbVRSPxqBZFrdmDyMmsOs+uX1UZC3nTN3ThzgDxUwo=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
github.com/wailsapp/go-webview2 v1.0.9 h1:lrU+q0cf1wgLdR69rN+ZnRtMJNaJRrcQ4ELxoO7/xjs=
github.com/wailsapp/go-webview2 v1.0.9/go.mod h1:Uk2BePfCRzttBBjFrBmqKGJd41P6QIHeV9kTgIeOZNo=
github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhwHs=
github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o=
github.com/xanzy/ssh-agent v0.3.3 h1:+/15pJfg/RsTxqYcX6fHqOXZwwMP+2VyYWJeWM2qQFM=
github.com/xanzy/ssh-agent v0.3.3/go.mod h1:6dzNDKs0J9rVPHPhaGCukekBHKqfl+L3KghI1Bc68Uw=
github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4=
golang.org/x/crypto v0.3.1-0.20221117191849-2c476679df9a/go.mod h1:hebNnKkNXi2UzZN1eVRvBB7co0a+JxK6XbPiWVs/3J4=
golang.org/x/crypto v0.7.0/go.mod h1:pYwdfH91IfpZVANVyUOhSIPZaFoJGxTFbZhFTx+dXZU=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df h1:UA2aFVmmsIlefxMk29Dp2juaUSth8Pyn3Tq5Y5mJGME=
golang.org/x/exp v0.0.0-20230626212559-97b1e661b5df/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4=
golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/mod v0.12.0 h1:rmsUpXtvNzj340zd98LZ4KntptpfRHwpFOHG188oHXc=
golang.org/x/mod v0.12.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs=
golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c=
golang.org/x/net v0.2.0/go.mod h1:KqCZLdyyvdV855qA2rE3GC2aiw5xGR5TEjj8smXukLY=
golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs=
golang.org/x/net v0.8.0/go.mod h1:QVkue5JL9kW//ek3r6jTKnTFis1tRmNAW2P1shuFdJc=
golang.org/x/net v0.21.0 h1:AQyQV4dYCvJ7vGmJyKki9+PBdyvhkSd8EIx/qb0AYv4=
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
golang.org/x/sync v0.4.0 h1:zxkM55ReGkDlKSM+Fu41A+zmbZuaPVbGMzvvdUPznYQ=
golang.org/x/sync v0.4.0/go.mod h1:FU7BRWz2tNW+3quACPkgCx/L+uEAv1htQ0V83Z9Rj+Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210124154548-22da62e12c0c/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
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-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.2.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.3.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.2.0/go.mod h1:TVmDHMZPmdnySmBfhjOoOdhjzdE1h4u1VwSiw2l1Nuc=
golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k=
golang.org/x/term v0.6.0/go.mod h1:m6U89DPEgQRMq3DNkDClhWw02AUbt2daBVO4cn4Hv9U=
golang.org/x/term v0.18.0 h1:FcHjZXDMxI8mM3nwhX9HlKop4C0YQvCVCdwYl2wOtE8=
golang.org/x/term v0.18.0/go.mod h1:ILwASektA3OnRv7amZ1xhE/KTR+u50pbXfZ03+6Nx58=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.4.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8=
golang.org/x/text v0.8.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.14.0 h1:ScX5w1eTa3QqT8oi6+ziP7dTV1S2+ALU0bI+0zXKWiQ=
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
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.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc=
golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU=
golang.org/x/tools v0.13.0 h1:Iey4qkscZuv0VvIt8E0neZjtPVQFSc870HQ448QgEmQ=
golang.org/x/tools v0.13.0/go.mod h1:HvlwmtVNQAhOuCjW7xxvovg8wbNq7LwfXh/k7wXUl58=
golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk=
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q=
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.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
lukechampine.com/uint128 v1.2.0 h1:mBi/5l91vocEN8otkC5bDLhi2KdCticRiwbdB0O+rjI=
lukechampine.com/uint128 v1.2.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
modernc.org/cc/v3 v3.40.0 h1:P3g79IUS/93SYhtoeaHW+kRCIrYaxJ27MFPv+7kaTOw=
modernc.org/cc/v3 v3.40.0/go.mod h1:/bTg4dnWkSXowUO6ssQKnOV0yMVxDYNIsIrzqTFDGH0=
modernc.org/ccgo/v3 v3.16.13 h1:Mkgdzl46i5F/CNR/Kj80Ri59hC8TKAhZrYSaqvkwzUw=
modernc.org/ccgo/v3 v3.16.13/go.mod h1:2Quk+5YgpImhPjv2Qsob1DnZ/4som1lJTodubIcoUkY=
modernc.org/ccorpus v1.11.6 h1:J16RXiiqiCgua6+ZvQot4yUuUy8zxgqbqEEUuGPlISk=
modernc.org/ccorpus v1.11.6/go.mod h1:2gEUTrWqdpH2pXsmTM1ZkjeSrUWDpjMu2T6m29L/ErQ=
modernc.org/httpfs v1.0.6 h1:AAgIpFZRXuYnkjftxTAZwMIiwEqAfk8aVB2/oA6nAeM=
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
modernc.org/libc v1.22.3 h1:D/g6O5ftAfavceqlLOFwaZuA5KYafKwmr30A6iSqoyY=
modernc.org/libc v1.22.3/go.mod h1:MQrloYP209xa2zHome2a8HLiLm6k0UT8CoHpV74tOFw=
modernc.org/mathutil v1.5.0 h1:rV0Ko/6SfM+8G+yKiyI830l3Wuz1zRutdslNoQ0kfiQ=
modernc.org/mathutil v1.5.0/go.mod h1:mZW8CKdRPY1v87qxC/wUdX5O1qDzXMP5TH3wjfpga6E=
modernc.org/memory v1.5.0 h1:N+/8c5rE6EqugZwHii4IFsaJ7MUhoWX07J5tC/iI5Ds=
modernc.org/memory v1.5.0/go.mod h1:PkUhL0Mugw21sHPeskwZW4D6VscE/GQJOnIpCnW6pSU=
modernc.org/opt v0.1.3 h1:3XOZf2yznlhC+ibLltsDGzABUGVx8J6pnFMS3E4dcq4=
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
modernc.org/sqlite v1.21.0 h1:4aP4MdUf15i3R3M2mx6Q90WHKz3nZLoz96zlB6tNdow=
modernc.org/sqlite v1.21.0/go.mod h1:XwQ0wZPIh1iKb5mkvCJ3szzbhk+tykC8ZWqTRTgYRwI=
modernc.org/strutil v1.1.3 h1:fNMm+oJklMGYfU9Ylcywl0CO5O6nTfaowNsh2wpPjzY=
modernc.org/strutil v1.1.3/go.mod h1:MEHNA7PdEnEwLvspRMtWTNnp2nnyvMfkimT1NKNAGbw=
modernc.org/tcl v1.15.1 h1:mOQwiEK4p7HruMZcwKTZPw/aqtGM4aY00uzWhlKKYws=
modernc.org/tcl v1.15.1/go.mod h1:aEjeGJX2gz1oWKOLDVZ2tnEWLUrIn8H+GFu+akoDhqs=
modernc.org/token v1.0.1 h1:A3qvTqOwexpfZZeyI0FeGPDlSWX5pjZu9hF4lU+EKWg=
modernc.org/token v1.0.1/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
modernc.org/z v1.7.0 h1:xkDw/KepgEjeizO2sNco+hqYkU12taxQFqPEmgm1GWE=
modernc.org/z v1.7.0/go.mod h1:hVdgNMh8ggTuRG1rGU8x+xGRFfiQUIAw0ZqlPy8+HyQ=

View file

@ -1,32 +0,0 @@
# Hashes Plugin
This example plugin provides a way to generate hashes of strings.
## Usage
Add the plugin to the `Plugins` option in the Applications options:
```go
Plugins: map[string]application.Plugin{
"hashes": hashes.NewPlugin(),
},
```
You can then call the Generate method from the frontend:
```js
import {Call} from "/wails/runtime.js";
Call.Plugin("hashes","Generate","hello world").then((result) => console.log(result))
```
This method returns a struct with the following fields:
```typescript
interface Hashes {
md5: string;
sha1: string;
sha256: string;
}
```
A TypeScript definition file is provided for this interface.

View file

@ -1,58 +0,0 @@
package hashes
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
"github.com/wailsapp/wails/v3/pkg/application"
"io/fs"
)
// ---------------- Plugin Setup ----------------
type Plugin struct{}
func NewPlugin() *Plugin {
return &Plugin{}
}
func (r *Plugin) Shutdown() error { return nil }
func (r *Plugin) Name() string {
return "Hashes Plugin"
}
func (r *Plugin) Init(api application.PluginAPI) error {
return nil
}
func (r *Plugin) CallableByJS() []string {
return []string{
"Generate",
}
}
func (r *Plugin) Assets() fs.FS {
return nil
}
// ---------------- Plugin Methods ----------------
type Hashes struct {
MD5 string `json:"md5"`
SHA1 string `json:"sha1"`
SHA256 string `json:"sha256"`
}
func (r *Plugin) Generate(s string) Hashes {
md5Hash := md5.Sum([]byte(s))
sha1Hash := sha1.Sum([]byte(s))
sha256Hash := sha256.Sum256([]byte(s))
return Hashes{
MD5: hex.EncodeToString(md5Hash[:]),
SHA1: hex.EncodeToString(sha1Hash[:]),
SHA256: hex.EncodeToString(sha256Hash[:]),
}
}

View file

@ -1,10 +0,0 @@
# This is the plugin definition file for the "Hashes" plugin.
Name = "Hashes"
Description = "Provides a method to generate a number of hashes."
Author = "Lea Anthony"
Version = "v1.0.0"
Website = "https://wails.io"
License = "MIT"

Binary file not shown.

Before

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

View file

@ -1,61 +0,0 @@
package main
import (
"embed"
"log/slog"
"os"
"plugin_demo/hashes"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/plugins/kvstore"
"github.com/wailsapp/wails/v3/plugins/log"
"github.com/wailsapp/wails/v3/plugins/single_instance"
"github.com/wailsapp/wails/v3/plugins/sqlite"
"github.com/wailsapp/wails/v3/plugins/start_at_login"
)
//go:embed assets/*
var assets embed.FS
func main() {
app := application.New(application.Options{
Name: "Plugin Demo",
Description: "A demo of the plugins API",
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
LogLevel: slog.LevelDebug,
Plugins: map[string]application.Plugin{
"hashes": hashes.NewPlugin(),
"log": log.NewPlugin(),
"sqlite": sqlite.NewPlugin(&sqlite.Config{
DBFile: "test.db",
}),
"kvstore": kvstore.NewPlugin(&kvstore.Config{
Filename: "store.json",
AutoSave: true,
}),
"single_instance": single_instance.NewPlugin(&single_instance.Config{
// When true, the original app will be activated when a second instance is launched
ActivateAppOnSubsequentLaunch: true,
}),
"start_at_login": start_at_login.NewPlugin(start_at_login.Config{}),
},
Assets: application.AssetOptions{
Handler: application.BundledAssetFileServer(assets),
},
})
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Width: 1024,
Height: 768,
})
err := app.Run()
if err != nil {
println(err.Error())
os.Exit(1)
}
}

View file

@ -0,0 +1,35 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as $models from "./models.js";
/**
* @param {string} s
* @returns {Promise<$models.Hashes> & { cancel(): void }}
*/
export function Generate(s) {
let $resultPromise = /** @type {any} */($Call.ByID(1640814231, s));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType0($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* @returns {Promise<string> & { cancel(): void }}
*/
export function Name() {
let $resultPromise = /** @type {any} */($Call.ByID(2815914379));
return $resultPromise;
}
// Private type creation functions
const $$createType0 = $models.Hashes.createFrom;

View file

@ -0,0 +1,10 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as Hashes from "./hashes.js";
export {
Hashes
};
export * from "./models.js";

View file

@ -0,0 +1,49 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Create as $Create} from "/wails/runtime.js";
export class Hashes {
/**
* Creates a new Hashes instance.
* @param {Partial<Hashes>} [$$source = {}] - The source object to create the Hashes.
*/
constructor($$source = {}) {
if (!("md5" in $$source)) {
/**
* @member
* @type {string}
*/
this["md5"] = "";
}
if (!("sha1" in $$source)) {
/**
* @member
* @type {string}
*/
this["sha1"] = "";
}
if (!("sha256" in $$source)) {
/**
* @member
* @type {string}
*/
this["sha256"] = "";
}
Object.assign(this, $$source);
}
/**
* Creates a new Hashes instance from a string or object.
* @param {any} [$$source = {}]
* @returns {Hashes}
*/
static createFrom($$source = {}) {
let $$parsedSource = typeof $$source === 'string' ? JSON.parse($$source) : $$source;
return new Hashes(/** @type {Partial<Hashes>} */($$parsedSource));
}
}

View file

@ -0,0 +1,8 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as KeyValueStore from "./keyvaluestore.js";
export {
KeyValueStore
};

View file

@ -0,0 +1,56 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "/wails/runtime.js";
/**
* Delete deletes the key from the store. If AutoSave is true, the store is saved to disk.
* @param {string} key
* @returns {Promise<void> & { cancel(): void }}
*/
export function Delete(key) {
let $resultPromise = /** @type {any} */($Call.ByID(1029952841, key));
return $resultPromise;
}
/**
* Get returns the value for the given key. If key is empty, the entire store is returned.
* @param {string} key
* @returns {Promise<any> & { cancel(): void }}
*/
export function Get(key) {
let $resultPromise = /** @type {any} */($Call.ByID(3017738442, key));
return $resultPromise;
}
/**
* Name returns the name of the plugin.
* @returns {Promise<string> & { cancel(): void }}
*/
export function Name() {
let $resultPromise = /** @type {any} */($Call.ByID(2879709053));
return $resultPromise;
}
/**
* Save saves the store to disk
* @returns {Promise<void> & { cancel(): void }}
*/
export function Save() {
let $resultPromise = /** @type {any} */($Call.ByID(840897339));
return $resultPromise;
}
/**
* Set sets the value for the given key. If AutoSave is true, the store is saved to disk.
* @param {string} key
* @param {any} value
* @returns {Promise<void> & { cancel(): void }}
*/
export function Set(key, value) {
let $resultPromise = /** @type {any} */($Call.ByID(2329265830, key, value));
return $resultPromise;
}

View file

@ -0,0 +1,8 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as LoggerService from "./loggerservice.js";
export {
LoggerService
};

View file

@ -0,0 +1,70 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "/wails/runtime.js";
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import * as slog$0 from "../../../../../../../log/slog/models.js";
/**
* @param {string} message
* @param {any[]} args
* @returns {Promise<void> & { cancel(): void }}
*/
export function Debug(message, ...args) {
let $resultPromise = /** @type {any} */($Call.ByID(1384012895, message, args));
return $resultPromise;
}
/**
* @param {string} message
* @param {any[]} args
* @returns {Promise<void> & { cancel(): void }}
*/
export function Error(message, ...args) {
let $resultPromise = /** @type {any} */($Call.ByID(1324251502, message, args));
return $resultPromise;
}
/**
* @param {string} message
* @param {any[]} args
* @returns {Promise<void> & { cancel(): void }}
*/
export function Info(message, ...args) {
let $resultPromise = /** @type {any} */($Call.ByID(3712350036, message, args));
return $resultPromise;
}
/**
* Name returns the name of the plugin.
* You should use the go module format e.g. github.com/myuser/myplugin
* @returns {Promise<string> & { cancel(): void }}
*/
export function Name() {
let $resultPromise = /** @type {any} */($Call.ByID(3407342027));
return $resultPromise;
}
/**
* @param {slog$0.Level} level
* @returns {Promise<void> & { cancel(): void }}
*/
export function SetLogLevel(level) {
let $resultPromise = /** @type {any} */($Call.ByID(2521579448, level));
return $resultPromise;
}
/**
* @param {string} message
* @param {any[]} args
* @returns {Promise<void> & { cancel(): void }}
*/
export function Warning(message, ...args) {
let $resultPromise = /** @type {any} */($Call.ByID(2902024404, message, args));
return $resultPromise;
}

View file

@ -0,0 +1,8 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
import * as Service from "./service.js";
export {
Service
};

View file

@ -0,0 +1,72 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Call as $Call, Create as $Create} from "/wails/runtime.js";
/**
* @returns {Promise<string> & { cancel(): void }}
*/
export function Close() {
let $resultPromise = /** @type {any} */($Call.ByID(1888105376));
return $resultPromise;
}
/**
* @param {string} query
* @param {any[]} args
* @returns {Promise<void> & { cancel(): void }}
*/
export function Execute(query, ...args) {
let $resultPromise = /** @type {any} */($Call.ByID(3811930203, query, args));
return $resultPromise;
}
/**
* Name returns the name of the plugin.
* You should use the go module format e.g. github.com/myuser/myplugin
* @returns {Promise<string> & { cancel(): void }}
*/
export function Name() {
let $resultPromise = /** @type {any} */($Call.ByID(2075046103));
return $resultPromise;
}
/**
* @param {string} dbPath
* @returns {Promise<string> & { cancel(): void }}
*/
export function Open(dbPath) {
let $resultPromise = /** @type {any} */($Call.ByID(2012175612, dbPath));
return $resultPromise;
}
/**
* @param {string} query
* @param {any[]} args
* @returns {Promise<{ [_: string]: any }[]> & { cancel(): void }}
*/
export function Select(query, ...args) {
let $resultPromise = /** @type {any} */($Call.ByID(2472933124, query, args));
let $typingPromise = /** @type {any} */($resultPromise.then(($result) => {
return $$createType1($result);
}));
$typingPromise.cancel = $resultPromise.cancel.bind($resultPromise);
return $typingPromise;
}
/**
* Shutdown is called when the app is shutting down
* You can use this to clean up any resources you have allocated
* @returns {Promise<void> & { cancel(): void }}
*/
export function Shutdown() {
let $resultPromise = /** @type {any} */($Call.ByID(846401686));
return $resultPromise;
}
// Private type creation functions
const $$createType0 = $Create.Map($Create.Any, $Create.Any);
const $$createType1 = $Create.Array($$createType0);

View file

@ -0,0 +1,5 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
export * from "./models.js";

View file

@ -0,0 +1,13 @@
// @ts-check
// Cynhyrchwyd y ffeil hon yn awtomatig. PEIDIWCH Â MODIWL
// This file is automatically generated. DO NOT EDIT
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore: Unused imports
import {Create as $Create} from "/wails/runtime.js";
/**
* A Level is the importance or severity of a log event.
* The higher the level, the more important or severe the event.
* @typedef {any} Level
*/

View file

@ -6,15 +6,15 @@
<link rel="stylesheet" href="/style.css">
<script type="module" src="/wails/runtime.js"></script>
<script type="module">
import * as sqlite from '/wails/plugin/sqlite/sqlite.js';
import {Call} from "/wails/runtime.js";
import * as kvpairs from '/wails/plugin/kvstore/kvstore.js';
import {Debug, Info, Warning, Error} from '/wails/plugin/log/log.js';
import * as sqlite from './bindings/github.com/wailsapp/wails/v3/pkg/services/sqlite/service.js';
import * as kvstore from './bindings/github.com/wailsapp/wails/v3/pkg/services/kvstore/keyvaluestore.js';
import {Debug, Info, Warning, Error} from './bindings/github.com/wailsapp/wails/v3/pkg/services/log/loggerservice.js';
import * as hash from './bindings/github.com/wailsapp/wails/v3/examples/services/hashes/hashes.js';
function runHash() {
let hashstring = document.getElementById("hashstring").value;
// Plugin methods can be called by name using the Plugin method
Call.Plugin("hashes", "Generate", hashstring).then((result) => {
hash.Generate(hashstring).then((result) => {
// Result is an object with the hash and the algorithm used is the key
// Output a table with the field name in the left cell and the value in the right
let table = document.createElement("table");
@ -84,14 +84,14 @@
}
function deleteKVPair(key) {
kvpairs.Delete(key).then(() => {
kvstore.Delete(key).then(() => {
getKVStoreValues();
}).catch((err) => {
document.getElementById("kvstoreresults").innerHTML = err;
});
}
function getKVStoreValues() {
kvpairs.Get().then((result) => {
kvstore.Get("").then((result) => {
let kvstoreresults = document.getElementById('kvstoreresults');
kvstoreresults.innerHTML = '';
let table = document.createElement('table');
@ -175,7 +175,7 @@
// Validate the key and value
if (key) {
// Call the Set method
kvpairs.Set(key, value);
kvstore.Set(key, value);
// Get all values from the kvstore
getKVStoreValues();
} else {
@ -224,7 +224,7 @@
</script>
</head>
<body>
<h2>Plugins</h2>
<h2>Services</h2>
<div>
<div class="tab" style="--wails-draggable: drag">
<button class="tablinks defaultOpen" onclick="openTab('sqlite')">SQLite</button>
@ -232,14 +232,13 @@
<button class="tablinks" onclick="openTab('kvstore')">K/V Store</button>
<button class="tablinks" onclick="openTab('log')">Log</button>
</div>
<div id="sqlite" class="tabcontent">
<p>The sqlite plugin provides easy integration with sqlite dbs.</p>
<p>The sqlite service provides easy integration with sqlite dbs.</p>
<p>The demo DB has a single table: Users.</p>
<p>Enter a query below and hit the "Run" button.</p>
<div>
<div style="display: flex;justify-content: space-around;">
<input style="width:90%" type="text" id="query" value="select * from Users where age > 20"><br/>
<input style="width:90%" type="text" id="query" value="select * from users where age > 20"><br/>
<button type="button" id="runquery">Run</button>
</div>
<div id="sqlresults">
@ -247,7 +246,7 @@
</div>
</div>
<div id="hashes" class="tabcontent">
<p>The hashes plugin provides hashing functions.</p>
<p>The hashes service provides hashing functions.</p>
<div>
<div style="display: flex;justify-content: space-around;">
<input style="width:90%" type="text" id="hashstring" placeholder="Type any string here..."><br/>
@ -258,7 +257,7 @@
</div>
</div>
<div id="kvstore" class="tabcontent">
<p>The kvstore plugin provides a means for reading and writing to a json file.</p>
<p>The kvstore service provides a means for reading and writing to a json file.</p>
<p>Enter a key/value pair in the form below to add it to the file.</p>
<p>A blank value will remove the key.</p>
<div>

View file

@ -7,6 +7,8 @@ html {
-moz-user-select: none;
-ms-user-select: none;
-webkit-touch-callout: none;
height: 100vh;
width: 100%;
}
body {
@ -16,6 +18,15 @@ body {
"Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue",
sans-serif;
overscroll-behavior: none;
overflow-y: hidden;
background-image: url("/files/images/eryri1.png");
background-color: rgba(33, 37, 43, 0.85);
background-blend-mode: overlay;
background-size: cover;
background-position: center;
background-repeat: no-repeat;
height: 100vh;
width: 100%;
}
.logo {

Binary file not shown.

After

Width:  |  Height:  |  Size: 657 KiB

View file

@ -0,0 +1,40 @@
package hashes
import (
"crypto/md5"
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
)
type Hashes struct {
MD5 string `json:"md5"`
SHA1 string `json:"sha1"`
SHA256 string `json:"sha256"`
}
func (h *Hashes) Generate(s string) Hashes {
md5Hash := md5.Sum([]byte(s))
sha1Hash := sha1.Sum([]byte(s))
sha256Hash := sha256.Sum256([]byte(s))
return Hashes{
MD5: hex.EncodeToString(md5Hash[:]),
SHA1: hex.EncodeToString(sha1Hash[:]),
SHA256: hex.EncodeToString(sha256Hash[:]),
}
}
func New() *Hashes {
return &Hashes{}
}
func (h *Hashes) OnShutdown() error { return nil }
func (h *Hashes) Name() string {
return "Hashes Service"
}
func (h *Hashes) OnStartup() error {
return nil
}

View file

@ -0,0 +1,61 @@
package main
import (
"embed"
"github.com/wailsapp/wails/v3/examples/services/hashes"
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/pkg/services/fileserver"
"github.com/wailsapp/wails/v3/pkg/services/kvstore"
"github.com/wailsapp/wails/v3/pkg/services/log"
"github.com/wailsapp/wails/v3/pkg/services/sqlite"
"log/slog"
"os"
"path/filepath"
)
//go:embed assets/*
var assets embed.FS
func main() {
rootPath, _ := filepath.Abs("./files")
app := application.New(application.Options{
Name: "Services Demo",
Description: "A demo of the services API",
Mac: application.MacOptions{
ApplicationShouldTerminateAfterLastWindowClosed: true,
},
LogLevel: slog.LevelDebug,
Services: []application.Service{
application.NewService(hashes.New()),
application.NewService(sqlite.New(&sqlite.Config{
DBFile: "test.db",
})),
application.NewService(kvstore.New(&kvstore.Config{
Filename: "store.json",
AutoSave: true,
})),
application.NewService(log.New()),
application.NewService(fileserver.New(&fileserver.Config{
RootPath: rootPath,
}), application.ServiceOptions{
Route: "/files",
}),
},
Assets: application.AssetOptions{
Handler: application.BundledAssetFileServer(assets),
},
})
app.NewWebviewWindowWithOptions(application.WebviewWindowOptions{
Width: 1024,
Height: 768,
})
err := app.Run()
if err != nil {
println(err.Error())
os.Exit(1)
}
}

View file

@ -1,14 +1,11 @@
package assetserver
import (
"embed"
"fmt"
"io/fs"
"net"
"net/http"
"net/http/httptest"
"net/url"
"path"
"strings"
"time"
)
@ -16,7 +13,7 @@ import (
const (
webViewRequestHeaderWindowId = "x-wails-window-id"
webViewRequestHeaderWindowName = "x-wails-window-name"
pluginPrefix = "/wails/plugin"
servicePrefix = "wails/services"
)
type RuntimeHandler interface {
@ -28,16 +25,14 @@ type AssetServer struct {
handler http.Handler
//pluginScripts map[string]string
services map[string]http.Handler
assetServerWebView
pluginAssets map[string]fs.FS
}
func NewAssetServer(options *Options) (*AssetServer, error) {
result := &AssetServer{
options: options,
pluginAssets: make(map[string]fs.FS),
options: options,
}
userHandler := options.Handler
@ -113,39 +108,35 @@ func (a *AssetServer) serveHTTP(rw http.ResponseWriter, req *http.Request, userH
default:
// Check if this is a plugin asset
if !strings.HasPrefix(reqPath, pluginPrefix) {
userHandler.ServeHTTP(rw, req)
return
}
// Ensure there is 4 parts to the reqPath
parts := strings.SplitN(reqPath, "/", 5)
if len(parts) < 5 {
rw.WriteHeader(http.StatusNotFound)
return
}
// Get the first 3 parts of the reqPath
pluginPath := "/" + path.Join(parts[1], parts[2], parts[3])
// Get the remaining part of the reqPath
fileName := parts[4]
// Check if this is a registered plugin asset
if assetFS, ok := a.pluginAssets[pluginPath]; ok {
// Check if the file exists
file, err := fs.ReadFile(assetFS, fileName)
if err != nil {
a.serveError(rw, err, "Unable to read file %s", reqPath)
// Check if the path matches the keys in the services map
for route, handler := range a.services {
if strings.HasPrefix(reqPath, route) {
req.URL.Path = strings.TrimPrefix(reqPath, route)
// Strip leading slash
req.URL.Path = strings.TrimPrefix(req.URL.Path, "/")
handler.ServeHTTP(rw, req)
return
}
a.writeBlob(rw, reqPath, file)
} else {
userHandler.ServeHTTP(rw, req)
}
// Check if it can be served by the user-provided handler
if !strings.HasPrefix(reqPath, servicePrefix) {
userHandler.ServeHTTP(rw, req)
return
}
rw.WriteHeader(http.StatusNotFound)
return
}
}
func (a *AssetServer) AttachServiceHandler(prefix string, handler http.Handler) {
if a.services == nil {
a.services = make(map[string]http.Handler)
}
a.services[prefix] = handler
}
func (a *AssetServer) writeBlob(rw http.ResponseWriter, filename string, blob []byte) {
err := ServeFile(rw, filename, blob)
if err != nil {
@ -159,30 +150,6 @@ func (a *AssetServer) serveError(rw http.ResponseWriter, err error, msg string,
rw.WriteHeader(http.StatusInternalServerError)
}
//func (a *AssetServer) AddPluginScript(pluginName string, script string) {
// if a.pluginScripts == nil {
// a.pluginScripts = make(map[string]string)
// }
// pluginName = strings.ReplaceAll(pluginName, "/", "_")
// pluginName = html.EscapeString(pluginName)
// pluginScriptName := fmt.Sprintf("/wails/plugin/%s.js", pluginName)
// a.pluginScripts[pluginScriptName] = script
//}
func (a *AssetServer) AddPluginAssets(pluginPath string, vfs fs.FS) error {
pluginPath = path.Join(pluginPrefix, pluginPath)
_, exists := a.pluginAssets[pluginPath]
if exists {
return fmt.Errorf("plugin path already exists: %s", pluginPath)
}
if embedFs, isEmbedFs := vfs.(embed.FS); isEmbedFs {
rootFolder, _ := findEmbedRootPath(embedFs)
vfs, _ = fs.Sub(vfs, path.Clean(rootFolder))
}
a.pluginAssets[pluginPath] = vfs
return nil
}
func GetStartURL(userURL string) (string, error) {
devServerURL := GetDevServerURL()
startURL := baseURL.String()

View file

@ -2,7 +2,7 @@ package commands
import (
"github.com/wailsapp/wails/v3/internal/flags"
"github.com/wailsapp/wails/v3/internal/plugins"
"github.com/wailsapp/wails/v3/internal/service"
"strings"
"github.com/pterm/pterm"
@ -31,7 +31,7 @@ func toCamelCasePlugin(s string) string {
return camelCase + "Plugin"
}
func PluginInit(options *flags.PluginInit) error {
func ServiceInit(options *flags.ServiceInit) error {
if options.Quiet {
pterm.DisableOutput()
@ -41,5 +41,5 @@ func PluginInit(options *flags.PluginInit) error {
options.PackageName = toCamelCasePlugin(options.Name)
}
return plugins.Install(options)
return service.Install(options)
}

View file

@ -1,9 +1,14 @@
package flags
type PluginInit struct {
type ServiceInit struct {
Name string `name:"n" description:"Name of plugin" default:"example_plugin"`
Description string `name:"d" description:"Description of plugin" default:"Example plugin"`
PackageName string `name:"p" description:"Package name for plugin" default:""`
OutputDir string `name:"o" description:"Output directory" default:"."`
Quiet bool `name:"q" description:"Suppress output to console"`
Author string `name:"a" description:"Author of plugin" default:""`
Version string `name:"v" description:"Version of plugin" default:""`
Website string `name:"w" description:"Website of plugin" default:""`
Repository string `name:"r" description:"Repository of plugin" default:""`
License string `name:"l" description:"License of plugin" default:""`
}

View file

@ -120,7 +120,8 @@ func FindServices(pkgs []*packages.Package, systemPaths *config.SystemPaths, log
if fn.Name() == "NewService" && fn.Pkg().Path() == systemPaths.ApplicationPackage {
// Check signature.
signature := fn.Type().(*types.Signature)
if signature.Params().Len() != 1 || signature.Results().Len() != 1 || tp.Len() != 1 || tp.At(0).Obj() == nil {
if signature.Params().Len() > 2 || signature.Results().Len() != 1 || tp.Len() != 1 || tp.At(0).Obj() == nil {
logger.Warningf("Param Len: %d, Results Len: %d, tp.Len: %d, tp.At(0).Obj(): %v", signature.Params().Len(), signature.Results().Len(), tp.Len(), tp.At(0).Obj())
return ErrBadApplicationPackage
}

View file

@ -54,6 +54,22 @@ type (
}
)
func isInternalServiceMethod(method *types.Func) bool {
internalServiceMethods := []string{
"OnStartup",
"OnShutdown",
"ServeHTTP",
}
methodName := method.Name()
for _, name := range internalServiceMethods {
if name == methodName {
return true
}
}
return false
}
func newServiceInfo(collector *Collector, obj *types.TypeName) *ServiceInfo {
return &ServiceInfo{
TypeInfo: collector.Type(obj),
@ -171,6 +187,10 @@ var typeAny = types.Universe.Lookup("any").Type().Underlying()
// collectMethod collects and returns information about a service method.
// It is intended to be called only by ServiceInfo.Collect.
func (info *ServiceInfo) collectMethod(method *types.Func) *ServiceMethodInfo {
if isInternalServiceMethod(method) {
// Ignore internal methods.
return nil
}
collector := info.collector
obj := info.Object().(*types.TypeName)

View file

@ -1,82 +0,0 @@
# Next Steps
Congratulations on generating a plugin. This guide will help you author your plugin
and provide some tips on how to get started.
## Plugin Structure
The plugin is a standard Go module that adheres to the following interface:
```go
type Plugin interface {
Name() string
Init(app *App) error
Shutdown()
}
```
The `Name()` method returns the name of the plugin. It should follow the Go module naming convention
and have a prefix of `wails-plugin-`, e.g. `github.com/myuser/wails-plugin-example`.
The `Init()` method is called when the plugin is loaded. The `App` parameter is a pointer to the
main application struct. This may be used for showing dialogs, listening for events or even opening
new windows. The `Init()` method should return an error if it fails to initialise. This method is
called synchronously so the application will not start until it returns.
The `Shutdown()` method is called when the application is shutting down. This is a good place to
perform any cleanup. This method is called synchronously so the application will not exit completely until
it returns.
## Plugin Directory Structure
The plugin directory structure is as follows:
```
plugin-name
├── models.d.ts
├── plugin.js
├── plugin.go
├── README.md
├── go.mod
├── go.sum
└── plugin.toml
```
### `plugin.go`
This file contains the plugin code. It should contain a struct that implements the `Plugin` interface
and a `NewPlugin()` method that returns a pointer to the struct. Methods are exported by capitalising
the first letter of the method name. These methods may be called from the frontend. If methods
accept or return structs, these structs must be exported.
### `plugin.js`
This file should contain any JavaScript code that may help developers use the plugin.
In the example plugin, this file contains function wrappers for the plugin methods.
It's good to include JSDocs as that will help developers using your plugin.
### `models.d.ts`
This file should contain TypeScript definitions for any structs that are passed
or returned from the plugin.
`
### `plugin.toml`
This file contains the plugin metadata. It is important to fill this out correctly
as it will be used by the Wails CLI.
### `README.md`
This file should contain a description of the plugin and how to use it. It should
also contain a link to the plugin repository and how to report bugs.
### `go.mod` and `go.sum`
These are standard Go module files. The package name in `go.mod` should match the
name of the plugin, e.g. `github.com/myuser/wails-plugin-example`.
## Promoting your Plugin
Once you have created your plugin, you should promote it on the Wails Discord server
in the `#plugins` channel. You should also open a PR to promote your plugin on the Wails
website. Update the `website/content/plugins.md` file and add your plugin to the list.

View file

@ -1,38 +0,0 @@
# {{.Name}} Plugin
This example plugin provides a way to generate hashes of strings.
## Installation
Add the plugin to the `Plugins` option in the Applications options:
```go
Plugins: map[string]application.Plugin{
"{{.Name}}": {{.Name}}.NewPlugin(),
},
```
## Usage
You can then call the methods from the frontend:
```js
wails.Plugin("{{.Name}}","All","hello world").then((result) => console.log(result))
```
This method returns a struct with the following fields:
```typescript
interface Hashes {
MD5: string;
SHA1: string;
SHA256: string;
}
```
A TypeScript definition file is provided for this interface.
## Support
If you find a bug in this plugin, please raise a ticket [here](https://github.com/plugin/repository).
Please do not contact the Wails team for support.

View file

@ -1,10 +0,0 @@
// models.d.ts
// This file should contain any models that are used by the plugin.
export namespace {{.Name}}Plugin {
export interface Hashes {
MD5: string;
SHA1: string;
SHA256: string;
}
}

View file

@ -1,67 +0,0 @@
package {{.Name}}
import (
"github.com/wailsapp/wails/v3/pkg/application"
)
// ---------------- Plugin Setup ----------------
// This is the main plugin struct. It can be named anything you like.
// It must implement the application.Plugin interface.
// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops.
type Config struct {
// Add any configuration options here
}
type Plugin struct{
config *Config
app *application.App
}
func NewPlugin(config *Config) *Plugin {
return &Plugin{
config: config,
}
}
// Shutdown is called when the app is shutting down
// You can use this to clean up any resources you have allocated
func (p *Plugin) Shutdown() {}
// Name returns the name of the plugin.
// You should use the go module format e.g. github.com/myuser/myplugin
func (p *Plugin) Name() string {
return "github.com/myuser/{{.Name}}"
}
// Init is called when the app is starting up. You can use this to
// initialise any resources you need. You can also access the application
// instance via the app property.
func (p *Plugin) Init(app *application.App) error {
p.app = app
return nil
}
// Exported returns a list of exported methods that can be called from the frontend
func (p *Plugin) CallableByJS() []string {
return []string{
"Greet",
}
}
// InjectJS returns any JS that should be injected into the frontend
func (p *Plugin) InjectJS() string {
return ""
}
// ---------------- Plugin Methods ----------------
// Plugin methods are just normal Go methods. You can add as many as you like.
// The only requirement is that they are exported (start with a capital letter).
// You can also return any type that is JSON serializable.
// Any methods that you want to be callable from the frontend must be returned by the
// CallableByJS() method above.
// See https://golang.org/pkg/encoding/json/#Marshal for more information.
func (p *Plugin) Greet(name string) string {
return "Hello " + name
}

View file

@ -1,46 +0,0 @@
// plugin.js
// This file should contain helper functions for the that can be used by the frontend.
// Below are examples of how to use JSDoc to define the Hashes struct and the exported functions.
/**
* @typedef {Object} Hashes - A collection of hashes.
* @property {string} md5 - The MD5 hash of a string, represented as a hexadecimal string.
* @property {string} sha1 - The SHA-1 hash of a string, represented as a hexadecimal string.
* @property {string} sha256 - The SHA-256 hash of a string, represented as a hexadecimal string.
*/
/**
* Generate all hashes for a string.
* @param input {string} - The string to generate hashes for.
* @returns {Promise<Hashes>}
*/
export function All(input) {
return wails.Plugin("{{.Name}}", "All", input);
}
/**
* Generate the MD5 hash for a string.
* @param input {string} - The string to generate the hash for.
* @returns {Promise<string>}
*/
export function MD5(input) {
return wails.Plugin("{{.Name}}", "MD5", input);
}
/**
* Generate the SHA-1 hash for a string.
* @param input {string} - The string to generate the hash for.
* @returns {Promise<string>}
*/
export function SHA1(input) {
return wails.Plugin("{{.Name}}", "SHA1", input);
}
/**
* Generate the SHA-256 hash for a string.
* @param input {string} - The string to generate the hash for.
* @returns {Promise<string>}
*/
export function SHA256(input) {
return wails.Plugin("{{.Name}}", "SHA256", input);
}

View file

@ -1,11 +0,0 @@
# This is the plugin definition file for the "{{.Name}}" plugin.
Name = "{{.Name}}"
Description = "{{.Description}}"
Author = ""
Version = ""
Website = ""
Repository = ""
License = ""

View file

@ -1,4 +1,4 @@
package plugins
package service
import (
"embed"
@ -15,19 +15,19 @@ import (
)
//go:embed template
var pluginTemplate embed.FS
var serviceTemplate embed.FS
type TemplateOptions struct {
*flags.PluginInit
*flags.ServiceInit
}
func Install(options *flags.PluginInit) error {
func Install(options *flags.ServiceInit) error {
if options.OutputDir == "." || options.OutputDir == "" {
options.OutputDir = filepath.Join(lo.Must(os.Getwd()), options.Name)
}
fmt.Printf("Creating plugin '%s' into '%s'\n", options.Name, options.OutputDir)
tfs, err := fs.Sub(pluginTemplate, "template")
fmt.Printf("Generating service '%s' into '%s'\n", options.Name, options.OutputDir)
tfs, err := fs.Sub(serviceTemplate, "template")
if err != nil {
return err
}

View file

@ -0,0 +1,129 @@
# Wails v3 Service Template
This README provides an overview of the Wails v3 service template and explains how to adapt it to create your own custom service.
## Overview
The service template provides a basic structure for creating a Wails v3 service. A service in Wails v3 is a Go package that can be integrated into your Wails application to provide specific functionality, handle HTTP requests, and interact with the frontend.
## Template Structure
The template defines a `MyService` struct and several methods:
### MyService Struct
```go
type MyService struct {
ctx context.Context
options application.ServiceOptions
}
```
This is the main service struct. You can rename it to better reflect your service's purpose. The struct holds a context and service options, which are set during startup.
### Name Method
```go
func (p *MyService) Name() string
```
This method returns the name of the service. It's used to identify the service within the Wails application.
### OnStartup Method
```go
func (p *MyService) OnStartup(ctx context.Context, options application.ServiceOptions) error
```
This method is called when the app is starting up. Use it to initialize resources, set up connections, or perform any necessary setup tasks.
It receives a context and service options, which are stored in the service struct.
### OnShutdown Method
```go
func (p *MyService) OnShutdown() error
```
This method is called when the app is shutting down. Use it to clean up resources, close connections, or perform any necessary cleanup tasks.
### ServeHTTP Method
```go
func (p *MyService) ServeHTTP(w http.ResponseWriter, r *http.Request)
```
This method handles HTTP requests to the service. It's called when the frontend makes an HTTP request to the backend
at the path specified in the `Route` field of the service options.
### Service Methods
```go
func (p *MyService) Greet(name string) string
```
This is an example of a service method. You can add as many methods as you need. These methods can be called from the frontend.
## Adapting the Template
To create your own service:
1. Rename the `MyService` struct to reflect your service's purpose (e.g., `DatabaseService`, `AuthService`).
2. Update the `Name` method to return your service's unique identifier.
3. Implement the `OnStartup` method to initialize your service. This might include setting up database connections, loading configuration, etc.
4. If needed, implement the `OnShutdown` method to properly clean up resources when the application closes.
5. If your service needs to handle HTTP requests, implement the `ServeHTTP` method. Use this to create API endpoints, serve files, or handle any HTTP interactions.
6. Add your own methods to the service. These can include database operations, business logic, or any functionality your service needs to provide.
7. If your service requires configuration, consider adding a `Config` struct and a `New` function to create and configure your service.
## Example: Database Service
Here's how you might adapt the template for a database service:
```go
type DatabaseService struct {
ctx context.Context
options application.ServiceOptions
db *sql.DB
}
func (s *DatabaseService) Name() string {
return "github.com/myname/DatabaseService"
}
func (s *DatabaseService) OnStartup(ctx context.Context, options application.ServiceOptions) error {
s.ctx = ctx
s.options = options
// Initialize database connection
var err error
s.db, err = sql.Open("mysql", "user:password@/dbname")
return err
}
func (s *DatabaseService) OnShutdown() error {
return s.db.Close()
}
func (s *DatabaseService) GetUser(id int) (User, error) {
// Implement database query
}
// Add more methods as needed
```
## Long-running tasks
If your service needs to perform long-running tasks, consider using goroutines and channels to manage these tasks.
You can use the `context.Context` to listen for when the application shuts down:
```go
func (s *DatabaseService) longRunningTask() {
for {
select {
case <-s.ctx.Done():
// Cleanup and exit
return
// Perform long-running task
}
}
}
```

View file

@ -1,8 +1,8 @@
module {{.Name}}
go 1.20
go 1.23
require github.com/wailsapp/wails/v3 v3.0.0-alpha.0
require github.com/wailsapp/wails/v3 v3.0.0-alpha.4
require (
github.com/imdario/mergo v0.3.12 // indirect

View file

@ -0,0 +1,60 @@
package {{.Name}}
import (
"context"
"github.com/wailsapp/wails/v3/pkg/application"
)
// ---------------- Service Setup ----------------
// This is the main service struct. It can be named anything you like.
// Both the OnStartup() and OnShutdown() methods are called synchronously when the app starts and stops.
// Changing the name of this struct will change the name of the services class in the frontend
// Bound methods will exist inside frontend/bindings/github.com/user/{{.Name}} under the name of the struct
type MyService struct{
ctx context.Context
options application.ServiceOptions
}
// Name is the name of the service
func (p *MyService) Name() string {
return "{{.Name}}"
}
// OnStartup is called when the app is starting up. You can use this to
// initialise any resources you need. You can also access the application
// instance via the app property.
// OPTIONAL: This method is optional.
func (p *MyService) OnStartup(ctx context.Context, options application.ServiceOptions) error {
p.ctx = ctx
p.options = options
return nil
}
// OnShutdown is called when the app is shutting down via runtime.Quit() call
// You can use this to clean up any resources you have allocated
// OPTIONAL: This method is optional.
func (p *MyService) OnShutdown() error {
return nil
}
// ServeHTTP is called when the app is running and the frontend makes an HTTP request to the backend at the path
// specified in the `Route` field of the service Options.
// OPTIONAL: This method is optional.
func (p *MyService) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// You can use the request to get the path, query parameters, headers, etc.
// You can also use the response to set the status code, headers, body etc.
// Consult the net/http documentation for more information: https://pkg.go.dev/net/http
// Log the request to the console
log.Printf("Received request: %s %s", r.Method, r.URL.Path)
}
// ---------------- Service Methods ----------------
// Service methods are just normal Go methods. You can add as many as you like.
// The only requirement is that they are exported (start with a capital letter).
// You can also return any type that is JSON serializable.
// See https://golang.org/pkg/encoding/json/#Marshal for more information.
func (p *MyService) Greet(name string) string {
return "Hello " + name
}

View file

@ -0,0 +1,8 @@
# This is the plugin definition file for the "{{.Name}}" plugin.
Name: "{{.Name}}"
Description: "{{.Description}}"
Author: "{{.Author}}"
Version: "{{.Version}}"
Website: "{{.Website}}"
Repository: "{{.Repository}}"
License: "{{.License}}"

View file

@ -1,6 +1,7 @@
package application
import (
"context"
"embed"
"encoding/json"
"fmt"
@ -135,18 +136,18 @@ func New(appOptions Options) *App {
result.handleFatalError(fmt.Errorf("Fatal error in application initialisation: " + err.Error()))
}
result.plugins = NewPluginManager(appOptions.Plugins, srv)
errors := result.plugins.Init()
if len(errors) > 0 {
for _, err := range errors {
result.handleError(fmt.Errorf("Error initialising plugin: " + err.Error()))
for _, service := range appOptions.Services {
if thisService, ok := service.instance.(ServiceStartup); ok {
err := thisService.OnStartup(result.ctx, service.options)
if err != nil {
name := service.options.Name
if name == "" {
name = getServiceName(service)
}
globalApplication.error("OnStartup() failed:", "service", name, "error", err.Error())
continue
}
}
result.handleFatalError(fmt.Errorf("fatal error in plugins initialisation"))
}
err = result.bindings.AddPlugins(appOptions.Plugins)
if err != nil {
result.handleFatalError(fmt.Errorf("Fatal error in application initialisation: " + err.Error()))
}
// Process keybindings
@ -267,6 +268,8 @@ type eventHook struct {
}
type App struct {
ctx context.Context
cancel context.CancelFunc
options Options
applicationEventListeners map[uint][]*EventListener
applicationEventListenersLock sync.RWMutex
@ -293,7 +296,6 @@ type App struct {
pendingRun []runnable
bindings *Bindings
plugins *PluginManager
// platform app
impl platformApp
@ -361,6 +363,7 @@ func (a *App) handleFatalError(err error) {
}
func (a *App) init() {
a.ctx, a.cancel = context.WithCancel(context.Background())
a.applicationEventHooks = make(map[uint][]*eventHook)
a.applicationEventListeners = make(map[uint][]*EventListener)
a.windows = make(map[uint]Window)
@ -440,6 +443,10 @@ func (a *App) RegisterListener(listener WailsEventListener) {
a.wailsEventListenerLock.Unlock()
}
func (a *App) RegisterServiceHandler(prefix string, handler http.Handler) {
a.assets.AttachServiceHandler(prefix, handler)
}
func (a *App) NewWebviewWindow() *WebviewWindow {
return a.NewWebviewWindowWithOptions(WebviewWindowOptions{})
}
@ -579,10 +586,16 @@ func (a *App) Run() error {
return err
}
errors := a.plugins.Shutdown()
if len(errors) > 0 {
for _, err := range errors {
a.error("Error shutting down plugin: " + err.Error())
// Cancel the context
a.cancel()
for _, service := range a.options.Services {
// If it conforms to the ServiceShutdown interface, call the Shutdown method
if thisService, ok := service.instance.(ServiceShutdown); ok {
err := thisService.OnShutdown()
if err != nil {
a.error("Error shutting down service: " + err.Error())
}
}
}

View file

@ -13,12 +13,29 @@ import (
// Valid values may only be obtained by calling [NewService].
type Service struct {
instance any
options ServiceOptions
}
type ServiceOptions struct {
// Name can be set to override the name of the service
// This is useful for logging and debugging purposes
Name string
// Route is the path to the assets
Route string
}
var DefaultServiceOptions = ServiceOptions{
Route: "",
}
// NewService returns a Service value wrapping the given pointer.
// If T is not a named type, the returned value is invalid.
func NewService[T any](instance *T) Service {
return Service{instance}
// The prefix is used if Service implements a http.Handler only one allowed
func NewService[T any](instance *T, options ...ServiceOptions) Service {
if len(options) == 1 {
return Service{instance, options[0]}
}
return Service{instance, DefaultServiceOptions}
}
func (s Service) Instance() any {
@ -62,9 +79,6 @@ type Options struct {
// Assets are the application assets to be used.
Assets AssetOptions
// Plugins is a map of plugins used by the application
Plugins map[string]Plugin
// Flags are key value pairs that are available to the frontend.
// This is also used by Wails to provide information to the frontend.
Flags map[string]any

View file

@ -5,6 +5,7 @@ import (
"encoding/json"
"errors"
"fmt"
"net/http"
"reflect"
"runtime"
"strings"
@ -79,12 +80,17 @@ type Bindings struct {
}
func NewBindings(instances []Service, aliases map[uint32]uint32) (*Bindings, error) {
app := Get()
b := &Bindings{
boundMethods: make(map[string]*BoundMethod),
boundByID: make(map[uint32]*BoundMethod),
methodAliases: aliases,
}
for _, binding := range instances {
handler, ok := binding.Instance().(http.Handler)
if ok && binding.options.Route != "" {
app.assets.AttachServiceHandler(binding.options.Route, handler)
}
err := b.Add(binding.Instance())
if err != nil {
return nil, err
@ -108,34 +114,6 @@ func (b *Bindings) Add(namedPtr interface{}) error {
return nil
}
func (b *Bindings) AddPlugins(plugins map[string]Plugin) error {
for pluginID, plugin := range plugins {
methods, err := b.getMethods(plugin, true)
if err != nil {
return fmt.Errorf("cannot add plugin '%s' to app: %s", pluginID, err.Error())
}
exportedMethods := plugin.CallableByJS()
for _, method := range methods {
// Do not expose reserved methods
if lo.Contains(reservedPluginMethods, method.Name) {
continue
}
// Do not expose methods that are not in the exported list
if !lo.Contains(exportedMethods, method.Name) {
continue
}
// Add it as a regular method
b.boundMethods[fmt.Sprintf("wails-plugins.%s.%s", pluginID, method.Name)] = method
b.boundByID[method.ID] = method
globalApplication.debug("Added plugin method: "+pluginID+"."+method.Name, "id", method.ID)
}
}
return nil
}
// Get returns the bound method with the given name
func (b *Bindings) Get(options *CallOptions) *BoundMethod {
method, ok := b.boundMethods[options.MethodName]

View file

@ -28,7 +28,6 @@ const (
)
type MessageProcessor struct {
pluginManager *PluginManager
logger *slog.Logger
runningCalls map[string]context.CancelFunc

View file

@ -1,67 +0,0 @@
package application
import (
"github.com/pkg/errors"
"github.com/wailsapp/wails/v3/internal/assetserver"
"io/fs"
)
type PluginAPI interface {
}
type Plugin interface {
Name() string
Init(api PluginAPI) error
Shutdown() error
CallableByJS() []string
Assets() fs.FS
}
type PluginManager struct {
plugins map[string]Plugin
assetServer *assetserver.AssetServer
initialisedPlugins []Plugin
}
func NewPluginManager(plugins map[string]Plugin, assetServer *assetserver.AssetServer) *PluginManager {
result := &PluginManager{
plugins: plugins,
assetServer: assetServer,
}
return result
}
func (p *PluginManager) Init() []error {
api := newPluginAPI()
for id, plugin := range p.plugins {
err := plugin.Init(api)
if err != nil {
globalApplication.error("Plugin '%s' failed to initialise: %s", plugin.Name(), err.Error())
return p.Shutdown()
}
p.initialisedPlugins = append(p.initialisedPlugins, plugin)
assets := plugin.Assets()
if assets != nil {
err = p.assetServer.AddPluginAssets(id, assets)
if err != nil {
return []error{errors.Wrap(err, "Failed to add plugin assets: "+plugin.Name())}
}
}
globalApplication.debug("Plugin initialised: " + plugin.Name())
}
return nil
}
func (p *PluginManager) Shutdown() []error {
var errs []error
for _, plugin := range p.initialisedPlugins {
err := plugin.Shutdown()
globalApplication.debug("Plugin shutdown: " + plugin.Name())
if err != nil {
err = errors.Wrap(err, "Plugin failed to shutdown: "+plugin.Name())
errs = append(errs, err)
}
}
return errs
}

View file

@ -1,7 +0,0 @@
package application
type pluginAPI struct{}
func newPluginAPI() *pluginAPI {
return &pluginAPI{}
}

View file

@ -0,0 +1,27 @@
package application
import (
"context"
"reflect"
)
type ServiceName interface {
Name() string
}
type ServiceStartup interface {
OnStartup(ctx context.Context, options ServiceOptions) error
}
type ServiceShutdown interface {
OnShutdown() error
}
func getServiceName(service any) string {
// First check it conforms to ServiceName interface
if serviceName, ok := service.(ServiceName); ok {
return serviceName.Name()
}
// Next, get the name from the type
return reflect.TypeOf(service).String()
}

View file

@ -1,29 +1,29 @@
package events
type ApplicationEventType uint
type WindowEventType uint
type WindowEventType uint
var Common = newCommonEvents()
type commonEvents struct {
ApplicationStarted ApplicationEventType
WindowMaximise WindowEventType
WindowUnMaximise WindowEventType
WindowFullscreen WindowEventType
WindowMaximise WindowEventType
WindowUnMaximise WindowEventType
WindowFullscreen WindowEventType
WindowUnFullscreen WindowEventType
WindowRestore WindowEventType
WindowMinimise WindowEventType
WindowUnMinimise WindowEventType
WindowClosing WindowEventType
WindowZoom WindowEventType
WindowZoomIn WindowEventType
WindowZoomOut WindowEventType
WindowZoomReset WindowEventType
WindowFocus WindowEventType
WindowLostFocus WindowEventType
WindowShow WindowEventType
WindowHide WindowEventType
WindowDPIChanged WindowEventType
WindowRestore WindowEventType
WindowMinimise WindowEventType
WindowUnMinimise WindowEventType
WindowClosing WindowEventType
WindowZoom WindowEventType
WindowZoomIn WindowEventType
WindowZoomOut WindowEventType
WindowZoomReset WindowEventType
WindowFocus WindowEventType
WindowLostFocus WindowEventType
WindowShow WindowEventType
WindowHide WindowEventType
WindowDPIChanged WindowEventType
WindowFilesDropped WindowEventType
WindowRuntimeReady WindowEventType
ThemeChanged ApplicationEventType
@ -88,130 +88,130 @@ func newLinuxEvents() linuxEvents {
var Mac = newMacEvents()
type macEvents struct {
ApplicationDidBecomeActive ApplicationEventType
ApplicationDidChangeBackingProperties ApplicationEventType
ApplicationDidChangeEffectiveAppearance ApplicationEventType
ApplicationDidChangeIcon ApplicationEventType
ApplicationDidChangeOcclusionState ApplicationEventType
ApplicationDidChangeScreenParameters ApplicationEventType
ApplicationDidChangeStatusBarFrame ApplicationEventType
ApplicationDidChangeStatusBarOrientation ApplicationEventType
ApplicationDidFinishLaunching ApplicationEventType
ApplicationDidHide ApplicationEventType
ApplicationDidResignActiveNotification ApplicationEventType
ApplicationDidUnhide ApplicationEventType
ApplicationDidUpdate ApplicationEventType
ApplicationWillBecomeActive ApplicationEventType
ApplicationWillFinishLaunching ApplicationEventType
ApplicationWillHide ApplicationEventType
ApplicationWillResignActive ApplicationEventType
ApplicationWillTerminate ApplicationEventType
ApplicationWillUnhide ApplicationEventType
ApplicationWillUpdate ApplicationEventType
ApplicationDidChangeTheme ApplicationEventType
ApplicationShouldHandleReopen ApplicationEventType
WindowDidBecomeKey WindowEventType
WindowDidBecomeMain WindowEventType
WindowDidBeginSheet WindowEventType
WindowDidChangeAlpha WindowEventType
WindowDidChangeBackingLocation WindowEventType
WindowDidChangeBackingProperties WindowEventType
WindowDidChangeCollectionBehavior WindowEventType
WindowDidChangeEffectiveAppearance WindowEventType
WindowDidChangeOcclusionState WindowEventType
WindowDidChangeOrderingMode WindowEventType
WindowDidChangeScreen WindowEventType
WindowDidChangeScreenParameters WindowEventType
WindowDidChangeScreenProfile WindowEventType
WindowDidChangeScreenSpace WindowEventType
WindowDidChangeScreenSpaceProperties WindowEventType
WindowDidChangeSharingType WindowEventType
WindowDidChangeSpace WindowEventType
WindowDidChangeSpaceOrderingMode WindowEventType
WindowDidChangeTitle WindowEventType
WindowDidChangeToolbar WindowEventType
WindowDidChangeVisibility WindowEventType
WindowDidDeminiaturize WindowEventType
WindowDidEndSheet WindowEventType
WindowDidEnterFullScreen WindowEventType
WindowDidEnterVersionBrowser WindowEventType
WindowDidExitFullScreen WindowEventType
WindowDidExitVersionBrowser WindowEventType
WindowDidExpose WindowEventType
WindowDidFocus WindowEventType
WindowDidMiniaturize WindowEventType
WindowDidMove WindowEventType
WindowDidOrderOffScreen WindowEventType
WindowDidOrderOnScreen WindowEventType
WindowDidResignKey WindowEventType
WindowDidResignMain WindowEventType
WindowDidResize WindowEventType
WindowDidUpdate WindowEventType
WindowDidUpdateAlpha WindowEventType
WindowDidUpdateCollectionBehavior WindowEventType
WindowDidUpdateCollectionProperties WindowEventType
WindowDidUpdateShadow WindowEventType
WindowDidUpdateTitle WindowEventType
WindowDidUpdateToolbar WindowEventType
WindowDidUpdateVisibility WindowEventType
WindowShouldClose WindowEventType
WindowWillBecomeKey WindowEventType
WindowWillBecomeMain WindowEventType
WindowWillBeginSheet WindowEventType
WindowWillChangeOrderingMode WindowEventType
WindowWillClose WindowEventType
WindowWillDeminiaturize WindowEventType
WindowWillEnterFullScreen WindowEventType
WindowWillEnterVersionBrowser WindowEventType
WindowWillExitFullScreen WindowEventType
WindowWillExitVersionBrowser WindowEventType
WindowWillFocus WindowEventType
WindowWillMiniaturize WindowEventType
WindowWillMove WindowEventType
WindowWillOrderOffScreen WindowEventType
WindowWillOrderOnScreen WindowEventType
WindowWillResignMain WindowEventType
WindowWillResize WindowEventType
WindowWillUnfocus WindowEventType
WindowWillUpdate WindowEventType
WindowWillUpdateAlpha WindowEventType
WindowWillUpdateCollectionBehavior WindowEventType
WindowWillUpdateCollectionProperties WindowEventType
WindowWillUpdateShadow WindowEventType
WindowWillUpdateTitle WindowEventType
WindowWillUpdateToolbar WindowEventType
WindowWillUpdateVisibility WindowEventType
WindowWillUseStandardFrame WindowEventType
MenuWillOpen ApplicationEventType
MenuDidOpen ApplicationEventType
MenuDidClose ApplicationEventType
MenuWillSendAction ApplicationEventType
MenuDidSendAction ApplicationEventType
MenuWillHighlightItem ApplicationEventType
MenuDidHighlightItem ApplicationEventType
MenuWillDisplayItem ApplicationEventType
MenuDidDisplayItem ApplicationEventType
MenuWillAddItem ApplicationEventType
MenuDidAddItem ApplicationEventType
MenuWillRemoveItem ApplicationEventType
MenuDidRemoveItem ApplicationEventType
MenuWillBeginTracking ApplicationEventType
MenuDidBeginTracking ApplicationEventType
MenuWillEndTracking ApplicationEventType
MenuDidEndTracking ApplicationEventType
MenuWillUpdate ApplicationEventType
MenuDidUpdate ApplicationEventType
MenuWillPopUp ApplicationEventType
MenuDidPopUp ApplicationEventType
MenuWillSendActionToItem ApplicationEventType
MenuDidSendActionToItem ApplicationEventType
WebViewDidStartProvisionalNavigation WindowEventType
ApplicationDidBecomeActive ApplicationEventType
ApplicationDidChangeBackingProperties ApplicationEventType
ApplicationDidChangeEffectiveAppearance ApplicationEventType
ApplicationDidChangeIcon ApplicationEventType
ApplicationDidChangeOcclusionState ApplicationEventType
ApplicationDidChangeScreenParameters ApplicationEventType
ApplicationDidChangeStatusBarFrame ApplicationEventType
ApplicationDidChangeStatusBarOrientation ApplicationEventType
ApplicationDidFinishLaunching ApplicationEventType
ApplicationDidHide ApplicationEventType
ApplicationDidResignActiveNotification ApplicationEventType
ApplicationDidUnhide ApplicationEventType
ApplicationDidUpdate ApplicationEventType
ApplicationWillBecomeActive ApplicationEventType
ApplicationWillFinishLaunching ApplicationEventType
ApplicationWillHide ApplicationEventType
ApplicationWillResignActive ApplicationEventType
ApplicationWillTerminate ApplicationEventType
ApplicationWillUnhide ApplicationEventType
ApplicationWillUpdate ApplicationEventType
ApplicationDidChangeTheme ApplicationEventType
ApplicationShouldHandleReopen ApplicationEventType
WindowDidBecomeKey WindowEventType
WindowDidBecomeMain WindowEventType
WindowDidBeginSheet WindowEventType
WindowDidChangeAlpha WindowEventType
WindowDidChangeBackingLocation WindowEventType
WindowDidChangeBackingProperties WindowEventType
WindowDidChangeCollectionBehavior WindowEventType
WindowDidChangeEffectiveAppearance WindowEventType
WindowDidChangeOcclusionState WindowEventType
WindowDidChangeOrderingMode WindowEventType
WindowDidChangeScreen WindowEventType
WindowDidChangeScreenParameters WindowEventType
WindowDidChangeScreenProfile WindowEventType
WindowDidChangeScreenSpace WindowEventType
WindowDidChangeScreenSpaceProperties WindowEventType
WindowDidChangeSharingType WindowEventType
WindowDidChangeSpace WindowEventType
WindowDidChangeSpaceOrderingMode WindowEventType
WindowDidChangeTitle WindowEventType
WindowDidChangeToolbar WindowEventType
WindowDidChangeVisibility WindowEventType
WindowDidDeminiaturize WindowEventType
WindowDidEndSheet WindowEventType
WindowDidEnterFullScreen WindowEventType
WindowDidEnterVersionBrowser WindowEventType
WindowDidExitFullScreen WindowEventType
WindowDidExitVersionBrowser WindowEventType
WindowDidExpose WindowEventType
WindowDidFocus WindowEventType
WindowDidMiniaturize WindowEventType
WindowDidMove WindowEventType
WindowDidOrderOffScreen WindowEventType
WindowDidOrderOnScreen WindowEventType
WindowDidResignKey WindowEventType
WindowDidResignMain WindowEventType
WindowDidResize WindowEventType
WindowDidUpdate WindowEventType
WindowDidUpdateAlpha WindowEventType
WindowDidUpdateCollectionBehavior WindowEventType
WindowDidUpdateCollectionProperties WindowEventType
WindowDidUpdateShadow WindowEventType
WindowDidUpdateTitle WindowEventType
WindowDidUpdateToolbar WindowEventType
WindowDidUpdateVisibility WindowEventType
WindowShouldClose WindowEventType
WindowWillBecomeKey WindowEventType
WindowWillBecomeMain WindowEventType
WindowWillBeginSheet WindowEventType
WindowWillChangeOrderingMode WindowEventType
WindowWillClose WindowEventType
WindowWillDeminiaturize WindowEventType
WindowWillEnterFullScreen WindowEventType
WindowWillEnterVersionBrowser WindowEventType
WindowWillExitFullScreen WindowEventType
WindowWillExitVersionBrowser WindowEventType
WindowWillFocus WindowEventType
WindowWillMiniaturize WindowEventType
WindowWillMove WindowEventType
WindowWillOrderOffScreen WindowEventType
WindowWillOrderOnScreen WindowEventType
WindowWillResignMain WindowEventType
WindowWillResize WindowEventType
WindowWillUnfocus WindowEventType
WindowWillUpdate WindowEventType
WindowWillUpdateAlpha WindowEventType
WindowWillUpdateCollectionBehavior WindowEventType
WindowWillUpdateCollectionProperties WindowEventType
WindowWillUpdateShadow WindowEventType
WindowWillUpdateTitle WindowEventType
WindowWillUpdateToolbar WindowEventType
WindowWillUpdateVisibility WindowEventType
WindowWillUseStandardFrame WindowEventType
MenuWillOpen ApplicationEventType
MenuDidOpen ApplicationEventType
MenuDidClose ApplicationEventType
MenuWillSendAction ApplicationEventType
MenuDidSendAction ApplicationEventType
MenuWillHighlightItem ApplicationEventType
MenuDidHighlightItem ApplicationEventType
MenuWillDisplayItem ApplicationEventType
MenuDidDisplayItem ApplicationEventType
MenuWillAddItem ApplicationEventType
MenuDidAddItem ApplicationEventType
MenuWillRemoveItem ApplicationEventType
MenuDidRemoveItem ApplicationEventType
MenuWillBeginTracking ApplicationEventType
MenuDidBeginTracking ApplicationEventType
MenuWillEndTracking ApplicationEventType
MenuDidEndTracking ApplicationEventType
MenuWillUpdate ApplicationEventType
MenuDidUpdate ApplicationEventType
MenuWillPopUp ApplicationEventType
MenuDidPopUp ApplicationEventType
MenuWillSendActionToItem ApplicationEventType
MenuDidSendActionToItem ApplicationEventType
WebViewDidStartProvisionalNavigation WindowEventType
WebViewDidReceiveServerRedirectForProvisionalNavigation WindowEventType
WebViewDidFinishNavigation WindowEventType
WebViewDidCommitNavigation WindowEventType
WindowFileDraggingEntered WindowEventType
WindowFileDraggingPerformed WindowEventType
WindowFileDraggingExited WindowEventType
WebViewDidFinishNavigation WindowEventType
WebViewDidCommitNavigation WindowEventType
WindowFileDraggingEntered WindowEventType
WindowFileDraggingPerformed WindowEventType
WindowFileDraggingExited WindowEventType
}
func newMacEvents() macEvents {
@ -346,13 +346,13 @@ func newMacEvents() macEvents {
var Windows = newWindowsEvents()
type windowsEvents struct {
SystemThemeChanged ApplicationEventType
APMPowerStatusChange ApplicationEventType
APMSuspend ApplicationEventType
APMResumeAutomatic ApplicationEventType
APMResumeSuspend ApplicationEventType
APMPowerSettingChange ApplicationEventType
ApplicationStarted ApplicationEventType
SystemThemeChanged ApplicationEventType
APMPowerStatusChange ApplicationEventType
APMSuspend ApplicationEventType
APMResumeAutomatic ApplicationEventType
APMResumeSuspend ApplicationEventType
APMPowerSettingChange ApplicationEventType
ApplicationStarted ApplicationEventType
WebViewNavigationCompleted WindowEventType
WindowInactive WindowEventType
WindowActive WindowEventType
@ -595,4 +595,3 @@ var eventToJS = map[uint]string{
1204: "common:WindowDidMove",
1205: "common:WindowDidResize",
}

View file

@ -0,0 +1,52 @@
package fileserver
import (
"context"
"net/http"
"github.com/wailsapp/wails/v3/pkg/application"
)
// ---------------- Service Setup ----------------
// This is the main Service struct. It can be named anything you like.
type Config struct {
RootPath string
}
type Service struct {
config *Config
fs http.Handler
}
func New(config *Config) *Service {
return &Service{
config: config,
fs: http.FileServer(http.Dir(config.RootPath)),
}
}
// OnShutdown is called when the app is shutting down
// You can use this to clean up any resources you have allocated
func (s *Service) OnShutdown() error {
return nil
}
// Name returns the name of the plugin.
// You should use the go module format e.g. github.com/myuser/myplugin
func (s *Service) Name() string {
return "github.com/wailsapp/wails/v3/services/fileserver"
}
// OnStartup is called when the app is starting up. You can use this to
// initialise any resources you need.
func (s *Service) OnStartup(ctx context.Context, options application.ServiceOptions) error {
// Any initialization code here
return nil
}
func (s *Service) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// Create a new file server rooted at the given path
// Strip the base path out of the request path
s.fs.ServeHTTP(w, r)
}

View file

@ -1,18 +1,15 @@
package kvstore
import (
"embed"
"context"
"encoding/json"
"github.com/pkg/errors"
"github.com/wailsapp/wails/v3/pkg/application"
"io"
"io/fs"
"os"
"sync"
)
//go:embed assets/*
var assets embed.FS
"github.com/pkg/errors"
"github.com/wailsapp/wails/v3/pkg/application"
)
type KeyValueStore struct {
config *Config
@ -27,17 +24,17 @@ type Config struct {
AutoSave bool
}
type Plugin struct{}
type Service struct{}
func NewPlugin(config *Config) *KeyValueStore {
func New(config *Config) *KeyValueStore {
return &KeyValueStore{
config: config,
data: make(map[string]any),
}
}
// Shutdown will save the store to disk if there are unsaved changes.
func (kvs *KeyValueStore) Shutdown() error {
// OnShutdown will save the store to disk if there are unsaved changes.
func (kvs *KeyValueStore) OnShutdown() error {
if kvs.unsaved {
err := kvs.Save()
if err != nil {
@ -52,9 +49,8 @@ func (kvs *KeyValueStore) Name() string {
return "github.com/wailsapp/wails/v3/plugins/kvstore"
}
// Init is called when the plugin is loaded. It is passed the application.App
// instance. This is where you should do any setup.
func (kvs *KeyValueStore) Init(api application.PluginAPI) error {
// OnStartup is called when the plugin is loaded. This is where you should do any setup.
func (kvs *KeyValueStore) OnStartup(ctx context.Context, options application.ServiceOptions) error {
err := kvs.open(kvs.config.Filename)
if err != nil {
return err
@ -63,21 +59,6 @@ func (kvs *KeyValueStore) Init(api application.PluginAPI) error {
return nil
}
func (kvs *KeyValueStore) CallableByJS() []string {
return []string{
"Set",
"Get",
"Delete",
"Save",
}
}
func (kvs *KeyValueStore) Assets() fs.FS {
return assets
}
// ---------------- Plugin Methods ----------------
func (kvs *KeyValueStore) open(filename string) (err error) {
kvs.filename = filename
kvs.data = make(map[string]any)

View file

@ -0,0 +1,77 @@
package log
import (
"context"
_ "embed"
"log/slog"
"github.com/wailsapp/wails/v3/pkg/application"
)
type Config struct {
// Logger is the logger to use. If not set, a default logger will be used.
Logger *slog.Logger
// LogLevel defines the log level of the logger.
LogLevel slog.Level
// Handles errors that occur when writing to the log
ErrorHandler func(err error)
}
type LoggerService struct {
config *Config
app *application.App
level slog.LevelVar
}
func NewLoggerService(config *Config) *LoggerService {
if config.Logger == nil {
config.Logger = application.DefaultLogger(config.LogLevel)
}
result := &LoggerService{
config: config,
}
result.level.Set(config.LogLevel)
return result
}
func New() *LoggerService {
return NewLoggerService(&Config{})
}
// OnShutdown is called when the app is shutting down
// You can use this to clean up any resources you have allocated
func (l *LoggerService) OnShutdown() error { return nil }
// Name returns the name of the plugin.
// You should use the go module format e.g. github.com/myuser/myplugin
func (l *LoggerService) Name() string {
return "github.com/wailsapp/wails/v3/plugins/log"
}
func (l *LoggerService) OnStartup(ctx context.Context, options application.ServiceOptions) error {
// Any initialization code here
return nil
}
func (l *LoggerService) Debug(message string, args ...any) {
l.config.Logger.Debug(message, args...)
}
func (l *LoggerService) Info(message string, args ...any) {
l.config.Logger.Info(message, args...)
}
func (l *LoggerService) Warning(message string, args ...any) {
l.config.Logger.Warn(message, args...)
}
func (l *LoggerService) Error(message string, args ...any) {
l.config.Logger.Error(message, args...)
}
func (l *LoggerService) SetLogLevel(level slog.Level) {
l.level.Set(level)
}

View file

@ -0,0 +1,127 @@
package sqlite
import (
"context"
"database/sql"
"errors"
"github.com/wailsapp/wails/v3/pkg/application"
_ "modernc.org/sqlite"
)
// ---------------- Service Setup ----------------
// This is the main Service struct. It can be named anything you like.
type Config struct {
DBFile string
}
type Service struct {
config *Config
conn *sql.DB
}
func New(config *Config) *Service {
return &Service{
config: config,
}
}
// OnShutdown is called when the app is shutting down
// You can use this to clean up any resources you have allocated
func (s *Service) OnShutdown() error {
if s.conn != nil {
return s.conn.Close()
}
return nil
}
// Name returns the name of the plugin.
// You should use the go module format e.g. github.com/myuser/myplugin
func (s *Service) Name() string {
return "github.com/wailsapp/wails/v3/plugins/sqlite"
}
// OnStartup is called when the app is starting up. You can use this to
// initialise any resources you need.
func (s *Service) OnStartup(ctx context.Context, options application.ServiceOptions) error {
if s.config.DBFile == "" {
return errors.New(`no database file specified. Please set DBFile in the config to either a filename or use ":memory:" to use an in-memory database`)
}
db, err := s.Open(s.config.DBFile)
if err != nil {
return err
}
_ = db
return nil
}
func (s *Service) Open(dbPath string) (string, error) {
var err error
s.conn, err = sql.Open("sqlite", dbPath)
if err != nil {
return "", err
}
return "Database connection opened", nil
}
func (s *Service) Execute(query string, args ...any) error {
if s.conn == nil {
return errors.New("no open database connection")
}
_, err := s.conn.Exec(query, args...)
if err != nil {
return err
}
return nil
}
func (s *Service) Select(query string, args ...any) ([]map[string]any, error) {
if s.conn == nil {
return nil, errors.New("no open database connection")
}
rows, err := s.conn.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
columns, err := rows.Columns()
var results []map[string]any
for rows.Next() {
values := make([]any, len(columns))
pointers := make([]any, len(columns))
for i := range values {
pointers[i] = &values[i]
}
if err := rows.Scan(pointers...); err != nil {
return nil, err
}
row := make(map[string]any, len(columns))
for i, column := range columns {
row[column] = values[i]
}
results = append(results, row)
}
return results, nil
}
func (s *Service) Close() (string, error) {
if s.conn == nil {
return "", errors.New("no open database connection")
}
err := s.conn.Close()
if err != nil {
return "", err
}
s.conn = nil
return "Database connection closed", nil
}

View file

@ -1,89 +0,0 @@
# KVStore Plugin
This plugin provides a simple key/value store for your Wails applications.
## Installation
Add the plugin to the `Plugins` option in the Applications options:
```go
package main
import (
"github.com/wailsapp/wails/v3/pkg/application"
"github.com/wailsapp/wails/v3/plugins/kvstore"
)
func main() {
kvstorePlugin := kvstore.NewPlugin(&kvstore.Config{
Filename: "myapp.db",
})
app := application.New(application.Options{
// ...
Plugins: map[string]application.Plugin{
"kvstore": kvstorePlugin,
},
})
```
### Options
```go
type Config struct {
Filename string
AutoSave bool
}
```
- `Filename` - The name of the file to store the key/value pairs in. This file will be created in the application's data directory.
- `AutoSave` - If true, the store will be saved to disk after every change. If false, you will need to call `Save()` to persist the changes.
## Usage
### Go
You can call the methods exported by the plugin directly:
```go
// Set a key
err := kvstore.Set("url", "https://www.google.com")
if err != nil {
// handle error
}
// Get a key
url := kvstore.Get("url").(string)
// Delete a key
err = kvstore.Delete("url")
if err != nil {
// handle error
}
// If you have not enables AutoSave, you will need to call Save() to persist the changes
err = kvstore.Save()
if err != nil {
// handle error
}
```
### Javascript
You can call the methods from the frontend using the Plugin method:
```js
wails.Plugin("kvstore","Set", "url", "https://www.google.com")
wails.Plugin("kvstore","Get", "url").then((url) => {
})
wails.Plugin("kvstore","Delete", "url").then((url) => {
})
// or
wails.Plugin("browser","OpenFile","/path/to/file")
```
## Support
If you find a bug in this plugin, please raise a ticket on the Wails [Issue Tracker](https://github.com/wailsapp/wails/issues).

View file

@ -1,42 +0,0 @@
// plugin.js
// This file should contain helper functions for the that can be used by the frontend.
// Below are examples of how to use JSDoc to define the Hashes struct and the exported functions.
import { Call } from '/wails/runtime.js';
/**
* Get the value of a key.
* @param key {string} - The store key.
* @returns {Promise<any>} - The value of the key.
*/
export function Get(key) {
return Call.ByID(3322496224, key);
}
/**
* Set the value of a key.
@param key {string} - The store key.
@param value {any} - The value to set.
* @returns {Promise<void>}
*/
export function Set(key, value) {
return Call.ByID(1207638860, key, value);
}
/**
* Save the database to disk.
* @returns {Promise<void|Error>}
*/
export function Save() {
return Call.ByID(1377075201);
}
/**
* Delete a key from the store.
* @param key {string} - The key to delete.
* @returns {Promise<void>}
*/
export function Delete(key) {
return Call.ByID(737249231, key);
}

View file

@ -1,11 +0,0 @@
# This is the plugin definition file for the "kvstore" plugin.
---
Name: kvstore
Description: A Simple Key/Value Store for Wails Applications
Author: Lea Anthony
Version: v1.0.0
Website: https://wails.io
Repository: https://github.com/wailsapp/wails/v3/plugins/kvstore
License: MIT

View file

@ -1,51 +0,0 @@
# log Plugin
This example plugin provides a way to generate hashes of strings.
## Installation
Add the plugin to the `Plugins` option in the Applications options:
```go
Plugins: map[string]application.Plugin{
"log": log.NewPlugin(),
},
```
## Usage
You can then call the methods from the frontend:
```js
wails.Plugin("log","Debug","hello world")
```
### Methods
- Trace
- Debug
- Info
- Warning
- Error
- Fatal
- SetLevel
SetLevel takes an integer value from JS:
```js
wails.Plugin("log","SetLevel",1)
```
Levels are:
- Trace: 1
- Debug: 2
- Info: 3
- Warning: 4
- Error: 5
- Fatal: 6
## Support
If you find a bug in this plugin, please raise a ticket [here](https://github.com/plugin/repository).
Please do not contact the Wails team for support.

View file

@ -1,61 +0,0 @@
// plugin.js
// This file should contain helper functions for the that can be used by the frontend.
// Below are examples of how to use JSDoc to define the Hashes struct and the exported functions.
import {Call} from '/wails/runtime.js';
/**
* Log at the Debug level.
* @param input {string} - The message in printf format.
* @param args {...any} - The arguments for the log message.
* @returns {Promise<void|Error>}
*/
export function Debug(input, ...args) {
return Call.ByID(4111675027, input, ...args);
}
/**
* Log at the Info level.
* @param input {string} - The message in printf format.
* @param args {...any} - The arguments for the log message.
* @returns {Promise<void|Error>}
*/
export function Info(input, ...args) {
return Call.ByID(2391172776, input, ...args);
}
/**
* Log at the Warning level.
* @param input {string} - The message in printf format.
* @param args {...any} - The arguments for the log message.
* @returns {Promise<void|Error>}
*/
export function Warning(input, ...args) {
return Call.ByID(2762394760, input, ...args);
}
/**
* Log at the Error level.
* @param input {string} - The message in printf format.
* @param args {...any} - The arguments for the log message.
* @returns {Promise<void|Error>}
*/
export function Error(input, ...args) {
return Call.ByID(878590242, input, ...args);
}
export const LevelDebug = -4
export const LevelInfo = 0
export const LevelWarn = 4
export const LevelError = 8
/**
* Set Log level
* @param level {LogLevel} - The log level to set.
* @returns {Promise<void|Error>}
*/
export function SetLogLevel(level) {
return Call.ByID(2758810652, level);
}

View file

@ -1,105 +0,0 @@
package log
import (
"embed"
_ "embed"
"github.com/wailsapp/wails/v3/pkg/application"
"io/fs"
"log/slog"
)
//go:embed assets/*
var assets embed.FS
// ---------------- Plugin Setup ----------------
// This is the main plugin struct. It can be named anything you like.
// It must implement the application.Plugin interface.
// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops.
type Config struct {
// Logger is the logger to use. If not set, a default logger will be used.
Logger *slog.Logger
// LogLevel defines the log level of the logger.
LogLevel slog.Level
// Handles errors that occur when writing to the log
ErrorHandler func(err error)
}
type Plugin struct {
config *Config
app *application.App
level slog.LevelVar
}
func NewPluginWithConfig(config *Config) *Plugin {
if config.Logger == nil {
config.Logger = application.DefaultLogger(config.LogLevel)
}
result := &Plugin{
config: config,
}
result.level.Set(config.LogLevel)
return result
}
func NewPlugin() *Plugin {
return NewPluginWithConfig(&Config{})
}
// Shutdown is called when the app is shutting down
// You can use this to clean up any resources you have allocated
func (p *Plugin) Shutdown() error { return nil }
// Name returns the name of the plugin.
// You should use the go module format e.g. github.com/myuser/myplugin
func (p *Plugin) Name() string {
return "github.com/wailsapp/wails/v3/plugins/log"
}
func (p *Plugin) Init(api application.PluginAPI) error {
return nil
}
// CallableByJS returns a list of methods that can be called from the frontend
func (p *Plugin) CallableByJS() []string {
return []string{
"Debug",
"Info",
"Warning",
"Error",
"SetLogLevel",
}
}
func (p *Plugin) Assets() fs.FS {
return assets
}
// ---------------- Plugin Methods ----------------
// Plugin methods are just normal Go methods. You can add as many as you like.
// The only requirement is that they are exported (start with a capital letter).
// You can also return any type that is JSON serializable.
// See https://golang.org/pkg/encoding/json/#Marshal for more information.
func (p *Plugin) Debug(message string, args ...any) {
p.config.Logger.Debug(message, args...)
}
func (p *Plugin) Info(message string, args ...any) {
p.config.Logger.Info(message, args...)
}
func (p *Plugin) Warning(message string, args ...any) {
p.config.Logger.Warn(message, args...)
}
func (p *Plugin) Error(message string, args ...any) {
p.config.Logger.Error(message, args...)
}
func (p *Plugin) SetLogLevel(level slog.Level) {
p.level.Set(level)
}

View file

@ -1,11 +0,0 @@
# This is the plugin definition file for the "log" plugin.
---
Name: log
Description: A basic logger
Author: Lea Anthony
Version: v1.0.0
Website: https://wails.io
Repository: https://github.com/wailsapp/wails/v3/plugins/log
License: MIT

View file

@ -1,39 +0,0 @@
import {Call} from '/wails/runtime.js';
/**
* Open a sqlite DB.
* @param filename {string} - file to open.
* @returns {Promise<void>}
*/
export function Open(filename) {
return Call.ByID(147348976, filename);
}
/**
* Close a sqlite DB.
* @returns {Promise<void>}
*/
export function Close() {
return Call.ByID(3998329564);
}
/**
* Execute a SQL statement.
* @param statement {string} - SQL statement to execute.
* @param args {...any} - Arguments to pass to the statement.
* @returns {Promise<void>}
*/
export function Execute(statement, ...args) {
return Call.ByID(2804887383, statement, ...args);
}
/**
* Perform a select query.
* @param statement {string} - Select SQL statement.
* @param args {...any} - Arguments to pass to the statement.
* @returns {Promise<any>}
*/
export function Select(statement, ...args) {
return Call.ByID(2209315040, statement, ...args);
}

View file

@ -1,160 +0,0 @@
package sqlite
import (
"database/sql"
"embed"
_ "embed"
"errors"
"github.com/wailsapp/wails/v3/pkg/application"
"io/fs"
_ "modernc.org/sqlite"
)
//go:embed assets/*
var assets embed.FS
// ---------------- Plugin Setup ----------------
// This is the main plugin struct. It can be named anything you like.
// It must implement the application.Plugin interface.
// Both the Init() and Shutdown() methods are called synchronously when the app starts and stops.
type Config struct {
DBFile string
CanCallOpen bool
CanCallClose bool
}
type Plugin struct {
config *Config
conn *sql.DB
callableMethods []string
js string
}
func NewPlugin(config *Config) *Plugin {
return &Plugin{
config: config,
}
}
// Shutdown is called when the app is shutting down
// You can use this to clean up any resources you have allocated
func (p *Plugin) Shutdown() error {
if p.conn != nil {
return p.conn.Close()
}
return nil
}
// Name returns the name of the plugin.
// You should use the go module format e.g. github.com/myuser/myplugin
func (p *Plugin) Name() string {
return "github.com/wailsapp/wails/v3/plugins/sqlite"
}
// Init is called when the app is starting up. You can use this to
// initialise any resources you need.
func (p *Plugin) Init(api application.PluginAPI) error {
p.callableMethods = []string{"Execute", "Select"}
if p.config.CanCallOpen {
p.callableMethods = append(p.callableMethods, "Open")
}
if p.config.CanCallClose {
p.callableMethods = append(p.callableMethods, "Close")
}
if p.config.DBFile == "" {
return errors.New(`no database file specified. Please set DBFile in the config to either a filename or use ":memory:" to use an in-memory database`)
}
_, err := p.Open(p.config.DBFile)
if err != nil {
return err
}
return nil
}
// CallableByJS returns a list of exported methods that can be called from the frontend
func (p *Plugin) CallableByJS() []string {
return p.callableMethods
}
func (p *Plugin) Assets() fs.FS {
return assets
}
// ---------------- Plugin Methods ----------------
// Plugin methods are just normal Go methods. You can add as many as you like.
// The only requirement is that they are exported (start with a capital letter).
// You can also return any type that is JSON serializable.
// Any methods that you want to be callable from the frontend must be returned by the
// Exported() method above.
// See https://golang.org/pkg/encoding/json/#Marshal for more information.
func (p *Plugin) Open(dbPath string) (string, error) {
var err error
p.conn, err = sql.Open("sqlite", dbPath)
if err != nil {
return "", err
}
return "Database connection opened", nil
}
func (p *Plugin) Execute(query string, args ...any) error {
if p.conn == nil {
return errors.New("no open database connection")
}
_, err := p.conn.Exec(query, args...)
if err != nil {
return err
}
return nil
}
func (p *Plugin) Select(query string, args ...any) ([]map[string]any, error) {
if p.conn == nil {
return nil, errors.New("no open database connection")
}
rows, err := p.conn.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
columns, err := rows.Columns()
var results []map[string]any
for rows.Next() {
values := make([]any, len(columns))
pointers := make([]any, len(columns))
for i := range values {
pointers[i] = &values[i]
}
if err := rows.Scan(pointers...); err != nil {
return nil, err
}
row := make(map[string]any, len(columns))
for i, column := range columns {
row[column] = values[i]
}
results = append(results, row)
}
return results, nil
}
func (p *Plugin) Close() (string, error) {
if p.conn == nil {
return "", errors.New("no open database connection")
}
err := p.conn.Close()
if err != nil {
return "", err
}
p.conn = nil
return "Database connection closed", nil
}

View file

@ -1,11 +0,0 @@
# This is the plugin definition file for the "sqlite" plugin.
---
Name: sqlite
Description: Provides easy access to SQLite DBs
Author: Lea Anthony
Version: v1.0.0
Website: https://wails.io/plugins/sqlite
Repository: https://github.com/wailsapp/wails/v3/plugins/sqlite
License: MIT