5
0
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:
Lea Anthony 2023-08-27 20:39:35 +10:00
parent 125d8a6f78
commit e5571defb7
No known key found for this signature in database
GPG Key ID: 33DAF7BB90A58405
9 changed files with 91 additions and 25 deletions

View File

@ -72,7 +72,31 @@ The clipboard API has been simplified. There is now a single `Clipboard` object
## Bindings ## 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 ## Dialogs

View File

@ -14,7 +14,7 @@ window.go.main = {
* @param name {string} * @param name {string}
* @returns {Promise<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 * GreetService.GreetPerson
@ -22,7 +22,7 @@ window.go.main = {
* @param person {main.Person} * @param person {main.Person}
* @returns {Promise<string>} * @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)); },
}, },
}; };

View File

@ -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-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 h1:p104kn46Q8WdvHunIJ9dAyjPVtrBPhSr3KT2yUst43I=
github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= 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.29.1 h1:q4mqGSR40qTOf9XZp2ySY3cM6enb2d+AqaxI/pEBiLk=
github.com/go-task/task/v3 v3.27.1/go.mod h1:SJBNIm6TFMCcFAMohmcqbJ0o9slGoZmzcydspFX5BLk=
github.com/go-task/task/v3 v3.29.1/go.mod h1:7AYcvV29++Yp64pejTjvnJgz/MjNMYdcPuUJgawDoyI= 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.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU=
github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38= 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-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.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.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.9.0 h1:KS/R3tvhPqvJvwcKfnBHJwwthS11LRhmM5D59eEXa0s= golang.org/x/sys v0.11.0 h1:eG7RXZHdqOJ1i+0lgLgCpSXAp6M3LYlAo6osgSi0xOM=
golang.org/x/sys v0.9.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.11.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= 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-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-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-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.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8=
golang.org/x/term v0.9.0 h1:GRRCnKYhdQrD8kfRAdQ6Zcw1P0OcELxGLKJvtjVMZ28= golang.org/x/term v0.11.0 h1:F9tnn/DA/Im8nCwm+fX+1/eBwi4qFjRT++MhtVC4ZX0=
golang.org/x/term v0.9.0/go.mod h1:M6DEAAIenWoTxdKrOltXcmDY3rSplQUkrvaDU5FcQyo=
golang.org/x/term v0.11.0/go.mod h1:zC9APTIj3jG3FdV/Ons+XE1riIZXG4aZ4GTHiPZJPIU= 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.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

View File

@ -13,9 +13,13 @@ var assets embed.FS
func main() { func main() {
app := application.New(application.Options{ app := application.New(application.Options{
Bind: []interface{}{ Bind: []any{
&GreetService{}, &GreetService{},
}, },
BindAliases: map[uint32]uint32{
1: 1411160069,
2: 4021313248,
},
Assets: application.AssetOptions{ Assets: application.AssetOptions{
FS: assets, FS: assets,
}, },

View File

@ -22,7 +22,7 @@ const bindingTemplate = `
* @param name {string} * @param name {string}
* @returns {Promise<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{ var reservedWords = []string{

View File

@ -329,8 +329,16 @@ func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err e
} }
// Check array type is of type "interface{}" // Check array type is of type "interface{}"
if _, ok := arrayType.Elt.(*ast.InterfaceType); !ok { _, isInterfaceType := arrayType.Elt.(*ast.InterfaceType)
continue if !isInterfaceType {
// Check it's an "any" type
ident, isAnyType := arrayType.Elt.(*ast.Ident)
if !isAnyType {
continue
}
if ident.Name != "any" {
continue
}
} }
callFound = true callFound = true
// Iterate through the slice elements // Iterate through the slice elements

View File

@ -86,7 +86,7 @@ func New(appOptions Options) *App {
result.assets.LogDetails() result.assets.LogDetails()
result.bindings, err = NewBindings(appOptions.Bind) result.bindings, err = NewBindings(appOptions.Bind, appOptions.BindAliases)
if err != nil { if err != nil {
println("Fatal error in application initialisation: ", err.Error()) println("Fatal error in application initialisation: ", err.Error())
os.Exit(1) os.Exit(1)

View File

@ -74,16 +74,18 @@ type BoundMethod struct {
} }
type Bindings struct { type Bindings struct {
boundMethods map[string]map[string]map[string]*BoundMethod boundMethods map[string]map[string]map[string]*BoundMethod
boundByID map[uint32]*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{ b := &Bindings{
boundMethods: make(map[string]map[string]map[string]*BoundMethod), boundMethods: make(map[string]map[string]map[string]*BoundMethod),
boundByID: make(map[uint32]*BoundMethod), boundByID: make(map[uint32]*BoundMethod),
methodAliases: aliases,
} }
for _, binding := range bindings { for _, binding := range structs {
err := b.Add(binding) err := b.Add(binding)
if err != nil { if err != nil {
return nil, err return nil, err
@ -174,6 +176,12 @@ func (b *Bindings) Get(options *CallOptions) *BoundMethod {
// GetByID returns the bound method with the given ID // GetByID returns the bound method with the given ID
func (b *Bindings) GetByID(id uint32) *BoundMethod { 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] result := b.boundByID[id]
return result return result
} }
@ -249,7 +257,14 @@ func (b *Bindings) getMethods(value interface{}, isPlugin bool) ([]*BoundMethod,
} }
if !isPlugin { 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 // Iterate inputs
methodType := method.Type() 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 // 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 // Check inputs
expectedInputLength := len(b.Inputs) expectedInputLength := len(b.Inputs)
actualInputLength := len(args) actualInputLength := len(args)
@ -315,9 +350,6 @@ func (b *BoundMethod) Call(args []interface{}) (interface{}, error) {
callResults := b.Method.Call(callArgs) callResults := b.Method.Call(callArgs)
//** Check results **// //** Check results **//
var returnValue interface{}
var err error
switch len(b.Outputs) { switch len(b.Outputs) {
case 1: case 1:
// Loop over results and determine if the result // Loop over results and determine if the result

View File

@ -13,6 +13,7 @@ type Options struct {
Mac MacOptions Mac MacOptions
Windows WindowsApplicationOptions Windows WindowsApplicationOptions
Bind []any Bind []any
BindAliases map[uint32]uint32
Logger *slog.Logger Logger *slog.Logger
Assets AssetOptions Assets AssetOptions
Plugins map[string]Plugin Plugins map[string]Plugin