5
0
mirror of https://github.com/wailsapp/wails.git synced 2025-05-04 16:40:41 +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
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

View File

@ -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)); },
},
};

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-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=

View File

@ -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,
},

View File

@ -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{

View File

@ -329,8 +329,16 @@ func (p *Project) findApplicationNewCalls(pkgs map[string]*ParsedPackage) (err e
}
// Check array type is of type "interface{}"
if _, ok := arrayType.Elt.(*ast.InterfaceType); !ok {
continue
_, 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

View File

@ -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)

View File

@ -74,16 +74,18 @@ type BoundMethod struct {
}
type Bindings struct {
boundMethods map[string]map[string]map[string]*BoundMethod
boundByID map[uint32]*BoundMethod
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),
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

View File

@ -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