mirror of
https://github.com/wailsapp/wails.git
synced 2025-05-04 20:11:27 +08:00
[v3] Support bound methodID aliases. Support []any
for bindings generation. Use CallByID
in bindings.
This commit is contained in:
parent
125d8a6f78
commit
e5571defb7
@ -72,7 +72,31 @@ The clipboard API has been simplified. There is now a single `Clipboard` object
|
||||
|
||||
## Bindings
|
||||
|
||||
TBD
|
||||
Bindings work in a similar way to v2, by providing a means to bind struct methods to the frontend. These can be called in the frontend using the binding wrappers generated by the `wails3 generate bindings` command.
|
||||
Bound methods are identified as uint32 IDs, calculated using the [FNV hashing algorithm](https://en.wikipedia.org/wiki/Fowler%E2%80%93Noll%E2%80%93Vo_hash_function). This is to prevent the method name from being exposed in production builds.
|
||||
In debug mode, the method IDs are logged along with the calculated ID of the method to aid in debugging. If you wish to add an extra layer of obfuscation, you can use the `BindAliases` option. This allows you to specify a map of alias IDs to method IDs. When the frontend calls a method using an ID, the method ID will be looked up in the alias map first for a match. If it does not find it, it assumes it's a standard method ID and tries to find the method in the usual way.
|
||||
|
||||
Example:
|
||||
|
||||
```go
|
||||
app := application.New(application.Options{
|
||||
Bind: []any{
|
||||
&GreetService{},
|
||||
},
|
||||
BindAliases: map[uint32]uint32{
|
||||
1: 1411160069,
|
||||
2: 4021313248,
|
||||
},
|
||||
Assets: application.AssetOptions{
|
||||
FS: assets,
|
||||
},
|
||||
Mac: application.MacOptions{
|
||||
ApplicationShouldTerminateAfterLastWindowClosed: true,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
We can now call using this alias in the frontend: `wails.Call(1, "world!")`.
|
||||
|
||||
## Dialogs
|
||||
|
||||
|
@ -14,7 +14,7 @@ window.go.main = {
|
||||
* @param name {string}
|
||||
* @returns {Promise<string>}
|
||||
**/
|
||||
Greet: function(name) { wails.Call({"wails-method-id":1411160069, args: Array.prototype.slice.call(arguments, 0)}); },
|
||||
Greet: function(name) { wails.CallByID(1411160069, ...Array.prototype.slice.call(arguments, 0)); },
|
||||
|
||||
/**
|
||||
* GreetService.GreetPerson
|
||||
@ -22,7 +22,7 @@ window.go.main = {
|
||||
* @param person {main.Person}
|
||||
* @returns {Promise<string>}
|
||||
**/
|
||||
GreetPerson: function(person) { wails.Call({"wails-method-id":4021313248, args: Array.prototype.slice.call(arguments, 0)}); },
|
||||
GreetPerson: function(person) { wails.CallByID(4021313248, ...Array.prototype.slice.call(arguments, 0)); },
|
||||
},
|
||||
};
|
||||
|
||||
|
@ -58,8 +58,7 @@ github.com/go-ole/go-ole v1.2.6 h1:/Fpf6oFPoeFik9ty7siob0G6Ke8QvQEuVcuChpwXzpY=
|
||||
github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
|
||||
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE=
|
||||
github.com/go-task/task/v3 v3.27.1 h1:cftsoOqUo7/pCdtO7fDa4HreXKDvbrRhfhhha8bH9xc=
|
||||
github.com/go-task/task/v3 v3.27.1/go.mod h1:SJBNIm6TFMCcFAMohmcqbJ0o9slGoZmzcydspFX5BLk=
|
||||
github.com/go-task/task/v3 v3.29.1 h1:q4mqGSR40qTOf9XZp2ySY3cM6enb2d+AqaxI/pEBiLk=
|
||||
github.com/go-task/task/v3 v3.29.1/go.mod h1:7AYcvV29++Yp64pejTjvnJgz/MjNMYdcPuUJgawDoyI=
|
||||
github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
|
||||
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
|
||||
@ -243,15 +242,13 @@ golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBc
|
||||
golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s=
|
||||
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
|
||||
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210220032956-6a3ed077a48d/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
|
||||
golang.org/x/term v0.0.0-20210615171337-6886f2dfbf5b/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
|
||||
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28=
|
||||
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
|
||||
golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
|
||||
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU=
|
||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
|
||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
|
||||
|
@ -13,9 +13,13 @@ var assets embed.FS
|
||||
|
||||
func main() {
|
||||
app := application.New(application.Options{
|
||||
Bind: []interface{}{
|
||||
Bind: []any{
|
||||
&GreetService{},
|
||||
},
|
||||
BindAliases: map[uint32]uint32{
|
||||
1: 1411160069,
|
||||
2: 4021313248,
|
||||
},
|
||||
Assets: application.AssetOptions{
|
||||
FS: assets,
|
||||
},
|
||||
|
@ -22,7 +22,7 @@ const bindingTemplate = `
|
||||
* @param name {string}
|
||||
* @returns {Promise<string>}
|
||||
**/
|
||||
{{methodName}}: function({{inputs}}) { wails.Call({"wails-method-id":{{ID}}, args: Array.prototype.slice.call(arguments, 0)}); },
|
||||
{{methodName}}: function({{inputs}}) { wails.CallByID({{ID}}, ...Array.prototype.slice.call(arguments, 0)); },
|
||||
`
|
||||
|
||||
var reservedWords = []string{
|
||||
|
@ -329,9 +329,17 @@ func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err e
|
||||
}
|
||||
|
||||
// Check array type is of type "interface{}"
|
||||
if _, ok := arrayType.Elt.(*ast.InterfaceType); !ok {
|
||||
_, isInterfaceType := arrayType.Elt.(*ast.InterfaceType)
|
||||
if !isInterfaceType {
|
||||
// Check it's an "any" type
|
||||
ident, isAnyType := arrayType.Elt.(*ast.Ident)
|
||||
if !isAnyType {
|
||||
continue
|
||||
}
|
||||
if ident.Name != "any" {
|
||||
continue
|
||||
}
|
||||
}
|
||||
callFound = true
|
||||
// Iterate through the slice elements
|
||||
for _, elt := range sliceExpr.Elts {
|
||||
|
@ -86,7 +86,7 @@ func New(appOptions Options) *App {
|
||||
|
||||
result.assets.LogDetails()
|
||||
|
||||
result.bindings, err = NewBindings(appOptions.Bind)
|
||||
result.bindings, err = NewBindings(appOptions.Bind, appOptions.BindAliases)
|
||||
if err != nil {
|
||||
println("Fatal error in application initialisation: ", err.Error())
|
||||
os.Exit(1)
|
||||
|
@ -76,14 +76,16 @@ type BoundMethod struct {
|
||||
type Bindings struct {
|
||||
boundMethods map[string]map[string]map[string]*BoundMethod
|
||||
boundByID map[uint32]*BoundMethod
|
||||
methodAliases map[uint32]uint32
|
||||
}
|
||||
|
||||
func NewBindings(bindings []any) (*Bindings, error) {
|
||||
func NewBindings(structs []any, aliases map[uint32]uint32) (*Bindings, error) {
|
||||
b := &Bindings{
|
||||
boundMethods: make(map[string]map[string]map[string]*BoundMethod),
|
||||
boundByID: make(map[uint32]*BoundMethod),
|
||||
methodAliases: aliases,
|
||||
}
|
||||
for _, binding := range bindings {
|
||||
for _, binding := range structs {
|
||||
err := b.Add(binding)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
@ -174,6 +176,12 @@ func (b *Bindings) Get(options *CallOptions) *BoundMethod {
|
||||
|
||||
// GetByID returns the bound method with the given ID
|
||||
func (b *Bindings) GetByID(id uint32) *BoundMethod {
|
||||
// Check method aliases
|
||||
if b.methodAliases != nil {
|
||||
if alias, ok := b.methodAliases[id]; ok {
|
||||
id = alias
|
||||
}
|
||||
}
|
||||
result := b.boundByID[id]
|
||||
return result
|
||||
}
|
||||
@ -249,7 +257,14 @@ func (b *Bindings) getMethods(value interface{}, isPlugin bool) ([]*BoundMethod,
|
||||
}
|
||||
|
||||
if !isPlugin {
|
||||
globalApplication.Logger.Info("Adding method", "name", boundMethod, "id", boundMethod.ID)
|
||||
args := []any{"name", boundMethod, "id", boundMethod.ID}
|
||||
if b.methodAliases != nil {
|
||||
alias, found := lo.FindKey(b.methodAliases, boundMethod.ID)
|
||||
if found {
|
||||
args = append(args, "alias", alias)
|
||||
}
|
||||
}
|
||||
globalApplication.info("Adding method:", args...)
|
||||
}
|
||||
// Iterate inputs
|
||||
methodType := method.Type()
|
||||
@ -280,7 +295,27 @@ func (b *Bindings) getMethods(value interface{}, isPlugin bool) ([]*BoundMethod,
|
||||
}
|
||||
|
||||
// Call will attempt to call this bound method with the given args
|
||||
func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
|
||||
func (b *BoundMethod) Call(args []interface{}) (returnValue interface{}, err error) {
|
||||
|
||||
// Use a defer statement to capture panics
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
if str, ok := r.(string); ok {
|
||||
if strings.HasPrefix(str, "reflect: Call using") {
|
||||
// Remove prefix
|
||||
str = strings.Replace(str, "reflect: Call using ", "", 1)
|
||||
// Split on "as"
|
||||
parts := strings.Split(str, " as type ")
|
||||
if len(parts) == 2 {
|
||||
err = fmt.Errorf("invalid argument type: got '%s', expected '%s'", parts[0], parts[1])
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
err = fmt.Errorf("%v", r)
|
||||
}
|
||||
}()
|
||||
|
||||
// Check inputs
|
||||
expectedInputLength := len(b.Inputs)
|
||||
actualInputLength := len(args)
|
||||
@ -315,9 +350,6 @@ func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
|
||||
callResults := b.Method.Call(callArgs)
|
||||
|
||||
//** Check results **//
|
||||
var returnValue interface{}
|
||||
var err error
|
||||
|
||||
switch len(b.Outputs) {
|
||||
case 1:
|
||||
// Loop over results and determine if the result
|
||||
|
@ -13,6 +13,7 @@ type Options struct {
|
||||
Mac MacOptions
|
||||
Windows WindowsApplicationOptions
|
||||
Bind []any
|
||||
BindAliases map[uint32]uint32
|
||||
Logger *slog.Logger
|
||||
Assets AssetOptions
|
||||
Plugins map[string]Plugin
|
||||
|
Loading…
Reference in New Issue
Block a user