The kvstore plugin provides a means for reading and writing to a json file.
The kvstore service provides a means for reading and writing to a json file.
Enter a key/value pair in the form below to add it to the file.
A blank value will remove the key.
diff --git a/v3/examples/plugins/assets/style.css b/v3/examples/services/assets/style.css
similarity index 93%
rename from v3/examples/plugins/assets/style.css
rename to v3/examples/services/assets/style.css
index a32ce2879..f128a1aa1 100644
--- a/v3/examples/plugins/assets/style.css
+++ b/v3/examples/services/assets/style.css
@@ -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 {
diff --git a/v3/examples/services/files/images/eryri1.png b/v3/examples/services/files/images/eryri1.png
new file mode 100644
index 000000000..224d3b4ac
Binary files /dev/null and b/v3/examples/services/files/images/eryri1.png differ
diff --git a/v3/examples/services/hashes/hashes.go b/v3/examples/services/hashes/hashes.go
new file mode 100644
index 000000000..f7bbed7eb
--- /dev/null
+++ b/v3/examples/services/hashes/hashes.go
@@ -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
+}
diff --git a/v3/examples/services/main.go b/v3/examples/services/main.go
new file mode 100644
index 000000000..d0f8ea582
--- /dev/null
+++ b/v3/examples/services/main.go
@@ -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)
+ }
+}
diff --git a/v3/examples/plugins/store.json b/v3/examples/services/store.json
similarity index 100%
rename from v3/examples/plugins/store.json
rename to v3/examples/services/store.json
diff --git a/v3/examples/plugins/test.db b/v3/examples/services/test.db
similarity index 100%
rename from v3/examples/plugins/test.db
rename to v3/examples/services/test.db
diff --git a/v3/internal/assetserver/assetserver.go b/v3/internal/assetserver/assetserver.go
index b931d3d92..a52fe3c93 100644
--- a/v3/internal/assetserver/assetserver.go
+++ b/v3/internal/assetserver/assetserver.go
@@ -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()
diff --git a/v3/internal/commands/plugins.go b/v3/internal/commands/service.go
similarity index 83%
rename from v3/internal/commands/plugins.go
rename to v3/internal/commands/service.go
index a42790ab3..8babf87f3 100644
--- a/v3/internal/commands/plugins.go
+++ b/v3/internal/commands/service.go
@@ -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)
}
diff --git a/v3/internal/flags/plugin.go b/v3/internal/flags/service.go
similarity index 51%
rename from v3/internal/flags/plugin.go
rename to v3/internal/flags/service.go
index 62c0dd968..c52accd87 100644
--- a/v3/internal/flags/plugin.go
+++ b/v3/internal/flags/service.go
@@ -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:""`
}
diff --git a/v3/internal/generator/analyse.go b/v3/internal/generator/analyse.go
index a0a56bca1..6aca7b899 100644
--- a/v3/internal/generator/analyse.go
+++ b/v3/internal/generator/analyse.go
@@ -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
}
diff --git a/v3/internal/generator/collect/service.go b/v3/internal/generator/collect/service.go
index fc0d30a8c..007b92ba5 100644
--- a/v3/internal/generator/collect/service.go
+++ b/v3/internal/generator/collect/service.go
@@ -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)
diff --git a/v3/internal/plugins/template/NEXT STEPS.md b/v3/internal/plugins/template/NEXT STEPS.md
deleted file mode 100644
index e58c6b119..000000000
--- a/v3/internal/plugins/template/NEXT STEPS.md
+++ /dev/null
@@ -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.
\ No newline at end of file
diff --git a/v3/internal/plugins/template/README.tmpl.md b/v3/internal/plugins/template/README.tmpl.md
deleted file mode 100644
index 2f4944a3d..000000000
--- a/v3/internal/plugins/template/README.tmpl.md
+++ /dev/null
@@ -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.
\ No newline at end of file
diff --git a/v3/internal/plugins/template/models.d.ts.tmpl b/v3/internal/plugins/template/models.d.ts.tmpl
deleted file mode 100644
index 567c91d70..000000000
--- a/v3/internal/plugins/template/models.d.ts.tmpl
+++ /dev/null
@@ -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;
- }
-}
\ No newline at end of file
diff --git a/v3/internal/plugins/template/plugin.go.tmpl b/v3/internal/plugins/template/plugin.go.tmpl
deleted file mode 100644
index e8104f2bd..000000000
--- a/v3/internal/plugins/template/plugin.go.tmpl
+++ /dev/null
@@ -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
-}
diff --git a/v3/internal/plugins/template/plugin.tmpl.js b/v3/internal/plugins/template/plugin.tmpl.js
deleted file mode 100644
index c62bbb6e7..000000000
--- a/v3/internal/plugins/template/plugin.tmpl.js
+++ /dev/null
@@ -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
}
- */
-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}
- */
-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}
- */
-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}
- */
-export function SHA256(input) {
- return wails.Plugin("{{.Name}}", "SHA256", input);
-}
\ No newline at end of file
diff --git a/v3/internal/plugins/template/plugin.tmpl.toml b/v3/internal/plugins/template/plugin.tmpl.toml
deleted file mode 100644
index 76e7aa384..000000000
--- a/v3/internal/plugins/template/plugin.tmpl.toml
+++ /dev/null
@@ -1,11 +0,0 @@
-# This is the plugin definition file for the "{{.Name}}" plugin.
-
-Name = "{{.Name}}"
-Description = "{{.Description}}"
-Author = ""
-Version = ""
-Website = ""
-Repository = ""
-License = ""
-
-
diff --git a/v3/internal/plugins/plugins.go b/v3/internal/service/service.go
similarity index 64%
rename from v3/internal/plugins/plugins.go
rename to v3/internal/service/service.go
index 05de5f908..76c1c2560 100644
--- a/v3/internal/plugins/plugins.go
+++ b/v3/internal/service/service.go
@@ -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
}
diff --git a/v3/internal/service/template/README.tmpl.md b/v3/internal/service/template/README.tmpl.md
new file mode 100644
index 000000000..bffd2a697
--- /dev/null
+++ b/v3/internal/service/template/README.tmpl.md
@@ -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
+ }
+ }
+}
+```
diff --git a/v3/internal/plugins/template/go.mod.tmpl b/v3/internal/service/template/go.mod.tmpl
similarity index 80%
rename from v3/internal/plugins/template/go.mod.tmpl
rename to v3/internal/service/template/go.mod.tmpl
index 84ff2e07b..1b99c5892 100644
--- a/v3/internal/plugins/template/go.mod.tmpl
+++ b/v3/internal/service/template/go.mod.tmpl
@@ -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
diff --git a/v3/internal/plugins/template/go.sum b/v3/internal/service/template/go.sum
similarity index 100%
rename from v3/internal/plugins/template/go.sum
rename to v3/internal/service/template/go.sum
diff --git a/v3/internal/service/template/service.go.tmpl b/v3/internal/service/template/service.go.tmpl
new file mode 100644
index 000000000..9ba3990d9
--- /dev/null
+++ b/v3/internal/service/template/service.go.tmpl
@@ -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
+}
diff --git a/v3/internal/service/template/service.tmpl.yml b/v3/internal/service/template/service.tmpl.yml
new file mode 100644
index 000000000..bd018461e
--- /dev/null
+++ b/v3/internal/service/template/service.tmpl.yml
@@ -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}}"
diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go
index 540b749a9..de848f652 100644
--- a/v3/pkg/application/application.go
+++ b/v3/pkg/application/application.go
@@ -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())
+ }
}
}
diff --git a/v3/pkg/application/application_options.go b/v3/pkg/application/application_options.go
index cfda959b2..b9a412567 100644
--- a/v3/pkg/application/application_options.go
+++ b/v3/pkg/application/application_options.go
@@ -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
diff --git a/v3/pkg/application/bindings.go b/v3/pkg/application/bindings.go
index 69ef3ed76..a7b1eb6c1 100644
--- a/v3/pkg/application/bindings.go
+++ b/v3/pkg/application/bindings.go
@@ -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]
diff --git a/v3/pkg/application/messageprocessor.go b/v3/pkg/application/messageprocessor.go
index 3abc63d2a..62d7950dc 100644
--- a/v3/pkg/application/messageprocessor.go
+++ b/v3/pkg/application/messageprocessor.go
@@ -28,7 +28,6 @@ const (
)
type MessageProcessor struct {
- pluginManager *PluginManager
logger *slog.Logger
runningCalls map[string]context.CancelFunc
diff --git a/v3/pkg/application/plugins.go b/v3/pkg/application/plugins.go
deleted file mode 100644
index 88bcbcb4f..000000000
--- a/v3/pkg/application/plugins.go
+++ /dev/null
@@ -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
-}
diff --git a/v3/pkg/application/plugins_api.go b/v3/pkg/application/plugins_api.go
deleted file mode 100644
index 36d235cb1..000000000
--- a/v3/pkg/application/plugins_api.go
+++ /dev/null
@@ -1,7 +0,0 @@
-package application
-
-type pluginAPI struct{}
-
-func newPluginAPI() *pluginAPI {
- return &pluginAPI{}
-}
diff --git a/v3/pkg/application/services.go b/v3/pkg/application/services.go
new file mode 100644
index 000000000..0df8cb055
--- /dev/null
+++ b/v3/pkg/application/services.go
@@ -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()
+}
diff --git a/v3/pkg/events/events.go b/v3/pkg/events/events.go
index 3ef015872..bfa94d46a 100644
--- a/v3/pkg/events/events.go
+++ b/v3/pkg/events/events.go
@@ -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",
}
-
diff --git a/v3/pkg/services/fileserver/fileserver.go b/v3/pkg/services/fileserver/fileserver.go
new file mode 100644
index 000000000..ad9c11d60
--- /dev/null
+++ b/v3/pkg/services/fileserver/fileserver.go
@@ -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)
+}
diff --git a/v3/plugins/kvstore/kvstore.go b/v3/pkg/services/kvstore/kvstore.go
similarity index 79%
rename from v3/plugins/kvstore/kvstore.go
rename to v3/pkg/services/kvstore/kvstore.go
index dd87bc720..33f396a62 100644
--- a/v3/plugins/kvstore/kvstore.go
+++ b/v3/pkg/services/kvstore/kvstore.go
@@ -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)
diff --git a/v3/pkg/services/log/log.go b/v3/pkg/services/log/log.go
new file mode 100644
index 000000000..64a2bcee1
--- /dev/null
+++ b/v3/pkg/services/log/log.go
@@ -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)
+}
diff --git a/v3/pkg/services/sqlite/sqlite.go b/v3/pkg/services/sqlite/sqlite.go
new file mode 100644
index 000000000..f96b345cd
--- /dev/null
+++ b/v3/pkg/services/sqlite/sqlite.go
@@ -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
+}
diff --git a/v3/plugins/kvstore/README.md b/v3/plugins/kvstore/README.md
deleted file mode 100644
index c75c63435..000000000
--- a/v3/plugins/kvstore/README.md
+++ /dev/null
@@ -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).
diff --git a/v3/plugins/kvstore/assets/kvstore.js b/v3/plugins/kvstore/assets/kvstore.js
deleted file mode 100644
index 13265677a..000000000
--- a/v3/plugins/kvstore/assets/kvstore.js
+++ /dev/null
@@ -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} - 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}
- */
-export function Set(key, value) {
- return Call.ByID(1207638860, key, value);
-}
-
-
-/**
- * Save the database to disk.
- * @returns {Promise}
- */
-export function Save() {
- return Call.ByID(1377075201);
-}
-
-/**
- * Delete a key from the store.
- * @param key {string} - The key to delete.
- * @returns {Promise}
- */
-export function Delete(key) {
- return Call.ByID(737249231, key);
-}
\ No newline at end of file
diff --git a/v3/plugins/kvstore/plugin.yaml b/v3/plugins/kvstore/plugin.yaml
deleted file mode 100644
index 2c2a80cc2..000000000
--- a/v3/plugins/kvstore/plugin.yaml
+++ /dev/null
@@ -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
-
-
diff --git a/v3/plugins/log/README.md b/v3/plugins/log/README.md
deleted file mode 100644
index cd5f511e1..000000000
--- a/v3/plugins/log/README.md
+++ /dev/null
@@ -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.
\ No newline at end of file
diff --git a/v3/plugins/log/assets/log.js b/v3/plugins/log/assets/log.js
deleted file mode 100644
index 31afdc5ed..000000000
--- a/v3/plugins/log/assets/log.js
+++ /dev/null
@@ -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}
- */
-
-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}
- */
-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}
- */
-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}
- */
-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}
- */
-export function SetLogLevel(level) {
- return Call.ByID(2758810652, level);
-}
diff --git a/v3/plugins/log/plugin.go b/v3/plugins/log/plugin.go
deleted file mode 100644
index b4cc9f64f..000000000
--- a/v3/plugins/log/plugin.go
+++ /dev/null
@@ -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)
-}
diff --git a/v3/plugins/log/plugin.yaml b/v3/plugins/log/plugin.yaml
deleted file mode 100644
index ea0cf5f67..000000000
--- a/v3/plugins/log/plugin.yaml
+++ /dev/null
@@ -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
-
-
diff --git a/v3/plugins/sqlite/assets/sqlite.js b/v3/plugins/sqlite/assets/sqlite.js
deleted file mode 100644
index 137a7591a..000000000
--- a/v3/plugins/sqlite/assets/sqlite.js
+++ /dev/null
@@ -1,39 +0,0 @@
-
-import {Call} from '/wails/runtime.js';
-
-/**
- * Open a sqlite DB.
- * @param filename {string} - file to open.
- * @returns {Promise}
- */
-export function Open(filename) {
- return Call.ByID(147348976, filename);
-}
-
-/**
- * Close a sqlite DB.
- * @returns {Promise}
- */
-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}
- */
-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}
- */
-export function Select(statement, ...args) {
- return Call.ByID(2209315040, statement, ...args);
-}
diff --git a/v3/plugins/sqlite/plugin.go b/v3/plugins/sqlite/plugin.go
deleted file mode 100644
index 94c3b682b..000000000
--- a/v3/plugins/sqlite/plugin.go
+++ /dev/null
@@ -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
-}
diff --git a/v3/plugins/sqlite/plugin.yaml b/v3/plugins/sqlite/plugin.yaml
deleted file mode 100644
index 08a3d6926..000000000
--- a/v3/plugins/sqlite/plugin.yaml
+++ /dev/null
@@ -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
-
-