diff --git a/v2/pkg/assetserver/assetserver.go b/v2/pkg/assetserver/assetserver.go
index e2e0372eb..78647b0c9 100644
--- a/v2/pkg/assetserver/assetserver.go
+++ b/v2/pkg/assetserver/assetserver.go
@@ -2,8 +2,11 @@ package assetserver
import (
"bytes"
+ "fmt"
+ "math/rand"
"net/http"
"net/http/httptest"
+ "strings"
"golang.org/x/net/html"
@@ -42,6 +45,9 @@ type AssetServer struct {
// Use http based runtime
runtimeHandler RuntimeHandler
+ // plugin scripts
+ pluginScripts map[string]string
+
assetServerWebView
}
@@ -89,6 +95,16 @@ func (d *AssetServer) UseRuntimeHandler(handler RuntimeHandler) {
d.runtimeHandler = handler
}
+func (d *AssetServer) AddPluginScript(pluginName string, script string) {
+ if d.pluginScripts == nil {
+ d.pluginScripts = make(map[string]string)
+ }
+ pluginName = strings.ReplaceAll(pluginName, "/", "_")
+ pluginName = html.EscapeString(pluginName)
+ pluginScriptName := fmt.Sprintf("/plugin_%s_%d.js", pluginName, rand.Intn(100000))
+ d.pluginScripts[pluginScriptName] = script
+}
+
func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
if isWebSocket(req) {
// Forward WebSockets to the distinct websocket handler if it exists
@@ -149,6 +165,11 @@ func (d *AssetServer) ServeHTTP(rw http.ResponseWriter, req *http.Request) {
d.writeBlob(rw, path, content)
default:
+ // Check if this is a plugin script
+ if script, ok := d.pluginScripts[path]; ok {
+ d.writeBlob(rw, path, []byte(script))
+ return
+ }
d.handler.ServeHTTP(rw, req)
}
}
@@ -174,6 +195,13 @@ func (d *AssetServer) processIndexHTML(indexHTML []byte) ([]byte, error) {
return nil, err
}
+ // Inject plugins
+ for scriptName := range d.pluginScripts {
+ if err := insertScriptInHead(htmlNode, scriptName); err != nil {
+ return nil, err
+ }
+ }
+
var buffer bytes.Buffer
err = html.Render(&buffer, htmlNode)
if err != nil {
diff --git a/v3/V3 Changes.md b/v3/V3 Changes.md
index 367a2cf5b..e2f8da7e9 100644
--- a/v3/V3 Changes.md
+++ b/v3/V3 Changes.md
@@ -108,3 +108,75 @@ This attribute specifies which javascript event should trigger the action. The d
```
+## Plugins
+
+Plugins are a way to extend the functionality of your Wails application.
+
+### Creating a plugin
+
+Plugins are standard Go structure that adhere to the following interface:
+
+```go
+type Plugin interface {
+ Name() string
+ Init(*application.App) error
+ Shutdown()
+ CallableByJS() []string
+ InjectJS() string
+}
+```
+
+The `Name()` method returns the name of the plugin. This is used for logging purposes.
+
+The `Init(*application.App) error` method is called when the plugin is loaded. The `*application.App`
+parameter is the application that the plugin is being loaded into. Any errors will prevent
+the application from starting.
+
+The `Shutdown()` method is called when the application is shutting down.
+
+The `CallableByJS()` method returns a list of exported functions that can be called from
+the frontend. These method names must exactly match the names of the methods exported
+by the plugin.
+
+The `InjectJS()` method returns JavaScript that should be injected into all windows as they are created. This is useful for adding custom JavaScript functions that complement the plugin.
+
+### Tips
+
+#### Enums
+
+In Go, enums are often defined as a type and a set of constants. For example:
+
+```go
+type MyEnum int
+
+const (
+ MyEnumOne MyEnum = iota
+ MyEnumTwo
+ MyEnumThree
+)
+```
+
+Due to incompatibility between Go and JavaScript, custom types cannot be used in this way. The best strategy is to use a type alias for float64:
+
+```go
+type MyEnum = float64
+
+const (
+ MyEnumOne MyEnum = iota
+ MyEnumTwo
+ MyEnumThree
+)
+```
+
+In Javascript, you can then use the following:
+
+```js
+const MyEnum = {
+ MyEnumOne: 0,
+ MyEnumTwo: 1,
+ MyEnumThree: 2
+}
+```
+
+- Why use `float64`? Can't we use `int`?
+ - Because JavaScript doesn't have a concept of `int`. Everything is a `number`, which translates to `float64` in Go. There are also restrictions on casting types in Go's reflection package, which means using `int` doesn't work.
diff --git a/v3/examples/plugins/plugins/hashes/plugin.go b/v3/examples/plugins/plugins/hashes/plugin.go
index b2bbaef4d..afdcec84a 100644
--- a/v3/examples/plugins/plugins/hashes/plugin.go
+++ b/v3/examples/plugins/plugins/hashes/plugin.go
@@ -5,6 +5,7 @@ import (
"crypto/sha1"
"crypto/sha256"
"encoding/hex"
+
"github.com/wailsapp/wails/v3/pkg/application"
)
@@ -32,6 +33,10 @@ func (r *Plugin) CallableByJS() []string {
}
}
+func (r *Plugin) InjectJS() string {
+ return ""
+}
+
// ---------------- Plugin Methods ----------------
type Hashes struct {
diff --git a/v3/pkg/application/application.go b/v3/pkg/application/application.go
index 6fe6a9090..e16a3e324 100644
--- a/v3/pkg/application/application.go
+++ b/v3/pkg/application/application.go
@@ -38,6 +38,7 @@ func New(appOptions Options) *App {
log: logger.New(appOptions.Logger.CustomLoggers...),
contextMenus: make(map[string]*Menu),
}
+ globalApplication = result
if !appOptions.Logger.Silent {
result.log.AddOutput(&logger.Console{})
@@ -60,7 +61,24 @@ func New(appOptions Options) *App {
srv.UseRuntimeHandler(NewMessageProcessor())
result.assets = srv
- globalApplication = result
+ result.bindings, err = NewBindings(appOptions.Bind)
+ if err != nil {
+ println("Fatal error in application initialisation: ", err.Error())
+ os.Exit(1)
+ }
+ err = result.bindings.AddPlugins(appOptions.Plugins)
+ if err != nil {
+ println("Fatal error in application initialisation: ", err.Error())
+ os.Exit(1)
+ }
+
+ result.plugins = NewPluginManager(appOptions.Plugins, srv)
+ err = result.plugins.Init()
+ if err != nil {
+ println("Fatal error in application initialisation: ", err.Error())
+ os.Exit(1)
+ }
+
return result
}
@@ -318,20 +336,6 @@ func (a *App) Run() error {
}
}()
- var err error
- a.bindings, err = NewBindings(a.options.Bind)
- if err != nil {
- return err
- }
-
- a.plugins = NewPluginManager(a.options.Plugins)
- err = a.plugins.Init()
- if err != nil {
- return err
- }
-
- a.bindings.AddPlugins(a.options.Plugins)
-
// run windows
for _, window := range a.windows {
go window.run()
@@ -348,7 +352,7 @@ func (a *App) Run() error {
// set the application Icon
a.impl.setIcon(a.options.Icon)
- err = a.impl.run()
+ err := a.impl.run()
if err != nil {
return err
}
diff --git a/v3/pkg/application/bindings.go b/v3/pkg/application/bindings.go
index 362ec9a96..9fe604c70 100644
--- a/v3/pkg/application/bindings.go
+++ b/v3/pkg/application/bindings.go
@@ -2,10 +2,11 @@ package application
import (
"fmt"
- "github.com/samber/lo"
"reflect"
"runtime"
"strings"
+
+ "github.com/samber/lo"
)
type CallOptions struct {
@@ -174,7 +175,7 @@ func (b *Bindings) getMethods(value interface{}) ([]*BoundMethod, error) {
if isFunction(value) {
name := runtime.FuncForPC(reflect.ValueOf(value).Pointer()).Name()
- return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct.", name)
+ return nil, fmt.Errorf("%s is a function, not a pointer to a struct. Wails v2 has deprecated the binding of functions. Please wrap your functions up in a struct and bind a pointer to that struct", name)
}
return nil, fmt.Errorf("not a pointer to a struct")
diff --git a/v3/pkg/application/plugins.go b/v3/pkg/application/plugins.go
index c12ef0c8e..f60dcd44a 100644
--- a/v3/pkg/application/plugins.go
+++ b/v3/pkg/application/plugins.go
@@ -1,5 +1,7 @@
package application
+import "github.com/wailsapp/wails/v2/pkg/assetserver"
+
type Plugin interface {
Name() string
Init(app *App) error
@@ -9,14 +11,15 @@ type Plugin interface {
}
type PluginManager struct {
- plugins map[string]Plugin
+ plugins map[string]Plugin
+ assetServer *assetserver.AssetServer
}
-func NewPluginManager(plugins map[string]Plugin) *PluginManager {
+func NewPluginManager(plugins map[string]Plugin, assetServer *assetserver.AssetServer) *PluginManager {
result := &PluginManager{
- plugins: plugins,
+ plugins: plugins,
+ assetServer: assetServer,
}
- globalApplication.OnWindowCreation(result.onWindowCreation)
return result
}
@@ -28,7 +31,7 @@ func (p *PluginManager) Init() error {
}
injectJS := plugin.InjectJS()
if injectJS != "" {
-
+ p.assetServer.AddPluginScript(plugin.Name(), injectJS)
}
globalApplication.info("Plugin '%s' initialised", plugin.Name())
}
@@ -41,12 +44,3 @@ func (p *PluginManager) Shutdown() {
globalApplication.info("Plugin '%s' shutdown", plugin.Name())
}
}
-
-func (p *PluginManager) onWindowCreation(window *WebviewWindow) {
- for _, plugin := range p.plugins {
- injectJS := plugin.InjectJS()
- if injectJS != "" {
- window.ExecJS(injectJS)
- }
- }
-}
diff --git a/v3/plugins/README.md b/v3/plugins/README.md
deleted file mode 100644
index 4edb0f81e..000000000
--- a/v3/plugins/README.md
+++ /dev/null
@@ -1,29 +0,0 @@
-# Plugins
-
-Plugins are a way to extend the functionality of your Wails application.
-
-## Creating a plugin
-
-Plugins are standard Go structure that adhere to the following interface:
-
-```go
-type Plugin interface {
- Name() string
- Init(*application.App) error
- Shutdown()
- CallableByJS() []string
-}
-```
-
-The `Name()` method returns the name of the plugin. This is used for logging purposes.
-
-The `Init(*application.App) error` method is called when the plugin is loaded. The `*application.App`
-parameter is the application that the plugin is being loaded into. Any errors will prevent
-the application from starting.
-
-The `Shutdown()` method is called when the application is shutting down.
-
-The `CallableByJS()` method returns a list of exported functions that can be called from
-the frontend. These method names must exactly match the names of the methods exported
-by the plugin.
-
diff --git a/v3/plugins/TODO.md b/v3/plugins/TODO.md
deleted file mode 100644
index 690966caf..000000000
--- a/v3/plugins/TODO.md
+++ /dev/null
@@ -1,3 +0,0 @@
-# TODO
-
-- [ ] Add InjectCSS() to the plugin API?
\ No newline at end of file
diff --git a/v3/plugins/browser/plugin.go b/v3/plugins/browser/plugin.go
index 918f8bf33..85925cd70 100644
--- a/v3/plugins/browser/plugin.go
+++ b/v3/plugins/browser/plugin.go
@@ -33,6 +33,10 @@ func (p *Plugin) CallableByJS() []string {
}
}
+func (p *Plugin) InjectJS() string {
+ return ""
+}
+
// ---------------- Plugin Methods ----------------
func (p *Plugin) OpenURL(url string) error {
diff --git a/v3/plugins/kvstore/kvstore.go b/v3/plugins/kvstore/kvstore.go
index 116fb4eb0..2fb9d49f2 100644
--- a/v3/plugins/kvstore/kvstore.go
+++ b/v3/plugins/kvstore/kvstore.go
@@ -2,11 +2,12 @@ package kvstore
import (
"encoding/json"
- "github.com/wailsapp/wails/v3/pkg/application"
- "github.com/wailsapp/wails/v3/pkg/logger"
"io"
"os"
"sync"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
+ "github.com/wailsapp/wails/v3/pkg/logger"
)
type KeyValueStore struct {
@@ -18,6 +19,10 @@ type KeyValueStore struct {
app *application.App
}
+func (kvs *KeyValueStore) InjectJS() string {
+ return ""
+}
+
type Config struct {
Filename string
AutoSave bool
@@ -67,6 +72,10 @@ func (kvs *KeyValueStore) CallableByJS() []string {
}
}
+func (p *Plugin) InjectJS() string {
+ return ""
+}
+
// ---------------- Plugin Methods ----------------
func (kvs *KeyValueStore) open(filename string) (err error) {
diff --git a/v3/plugins/log/plugin.go b/v3/plugins/log/plugin.go
index 794456d02..704c842f9 100644
--- a/v3/plugins/log/plugin.go
+++ b/v3/plugins/log/plugin.go
@@ -1,18 +1,23 @@
package log
import (
+ _ "embed"
"fmt"
- "github.com/wailsapp/wails/v3/pkg/application"
"io"
"os"
+
+ "github.com/wailsapp/wails/v3/pkg/application"
)
+//go:embed plugin.js
+var pluginJS string
+
// ---------------- 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 LogLevel int
+type LogLevel = float64
const (
Trace LogLevel = iota + 1
@@ -55,6 +60,7 @@ func NewPluginWithConfig(config *Config) *Plugin {
}
return &Plugin{
config: config,
+ level: config.Level,
}
}
@@ -88,9 +94,14 @@ func (p *Plugin) CallableByJS() []string {
"Warning",
"Error",
"Fatal",
+ "SetLevel",
}
}
+func (p *Plugin) InjectJS() string {
+ return pluginJS
+}
+
// ---------------- 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).
@@ -98,7 +109,7 @@ func (p *Plugin) CallableByJS() []string {
// See https://golang.org/pkg/encoding/json/#Marshal for more information.
func (p *Plugin) write(prefix string, level LogLevel, message string, args ...any) {
- if level >= p.config.Level {
+ if level >= p.level {
if !p.config.DisablePrefix {
message = prefix + " " + message
}
@@ -134,5 +145,8 @@ func (p *Plugin) Fatal(message string, args ...any) {
}
func (p *Plugin) SetLevel(newLevel LogLevel) {
+ if newLevel == 0 {
+ newLevel = Debug
+ }
p.level = newLevel
}
diff --git a/v3/plugins/log/plugin.js b/v3/plugins/log/plugin.js
index a5e952549..16811d5d1 100644
--- a/v3/plugins/log/plugin.js
+++ b/v3/plugins/log/plugin.js
@@ -8,7 +8,7 @@
* @param args {...any} - The arguments for the log message.
* @returns {Promise}
*/
-export function Trace(input, ...args) {
+function Trace(input, ...args) {
return wails.Plugin("log", "Trace", input, ...args);
}
@@ -19,7 +19,7 @@ export function Trace(input, ...args) {
* @returns {Promise}
*/
-export function Debug(input, ...args) {
+function Debug(input, ...args) {
return wails.Plugin("log", "Debug", input, ...args);
}
@@ -29,7 +29,7 @@ export function Debug(input, ...args) {
* @param args {...any} - The arguments for the log message.
* @returns {Promise}
*/
-export function Info(input, ...args) {
+function Info(input, ...args) {
return wails.Plugin("log", "Info", input, ...args);
}
@@ -39,7 +39,7 @@ export function Info(input, ...args) {
* @param args {...any} - The arguments for the log message.
* @returns {Promise}
*/
-export function Warning(input, ...args) {
+function Warning(input, ...args) {
return wails.Plugin("log", "Warning", input, ...args);
}
@@ -49,7 +49,7 @@ export function Warning(input, ...args) {
* @param args {...any} - The arguments for the log message.
* @returns {Promise}
*/
-export function Error(input, ...args) {
+function Error(input, ...args) {
return wails.Plugin("log", "Error", input, ...args);
}
@@ -59,6 +59,40 @@ export function Error(input, ...args) {
* @param args {...any} - The arguments for the log message.
* @returns {Promise}
*/
-export function Fatal(input, ...args) {
+function Fatal(input, ...args) {
return wails.Plugin("log", "Fatal", input, ...args);
-}
\ No newline at end of file
+}
+
+/**
+ * SetLevel sets the logging level
+ * @param level {Level} The log level to set
+ * @returns {Promise}
+ */
+function SetLevel(level) {
+ return wails.Plugin("log", "SetLevel", level);
+}
+
+/**
+ * Log Level.
+ * @readonly
+ * @enum {number}
+ */
+let Level = {
+ Trace: 1,
+ Debug: 2,
+ Info: 3,
+ Warning: 4,
+ Error: 5,
+ Fatal: 6,
+};
+
+window.Logger = {
+ Trace,
+ Debug,
+ Info,
+ Warning,
+ Error,
+ Fatal,
+ SetLevel,
+ Level,
+}