From 392b175abfa19fe9566dd7908fec69cd45144b77 Mon Sep 17 00:00:00 2001 From: Chan Wen Xu Date: Sat, 20 Mar 2021 18:56:02 +0800 Subject: [PATCH 1/5] feat: Implement a type-safe Object struct --- wasm/go.mod | 3 ++ wasm/object.go | 142 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) create mode 100644 wasm/go.mod create mode 100644 wasm/object.go diff --git a/wasm/go.mod b/wasm/go.mod new file mode 100644 index 0000000..31e956b --- /dev/null +++ b/wasm/go.mod @@ -0,0 +1,3 @@ +module gitea.teamortix.com/Team-Ortix/go-mod-wasm/wasm + +go 1.16 diff --git a/wasm/object.go b/wasm/object.go new file mode 100644 index 0000000..f1c3fe5 --- /dev/null +++ b/wasm/object.go @@ -0,0 +1,142 @@ +package wasm + +import ( + "fmt" + "syscall/js" +) + +// TypeMismatchError is returned when a function is called with a js.Value that has the incorrect type. +type TypeMismatchError struct { + Expected js.Type + Actual js.Type +} + +func (e TypeMismatchError) Error() string { + return fmt.Sprintf("expected %v type, got %v type instead", e.Expected, e.Actual) +} + +// Global returns the global object as a Object. +// If the global object is not an object, it panics. +func Global() Object { + global, err := NewObject(js.Global()) + if err != nil { + panic(err) + } + return global +} + +// Object is a statically typed Object instance of js.Value. +// It should be instantiated with NewObject where it is checked for type instead of directly. +// Calling methods on a zero Object is undefined behaviour. +type Object struct { + value js.Value +} + +// NewObject instantiates a new Object with the provided js.Value. +// If the js.Value is not an Object, it returns a TypeMismatchError. +func NewObject(raw js.Value) (Object, error) { + if raw.Type() != js.TypeObject { + return Object{}, TypeMismatchError{ + Expected: js.TypeObject, + Actual: raw.Type(), + } + } + + return Object{raw}, nil +} + +// Get recursively gets the Object's properties, returning a TypeMismatchError if it encounters a non-object while +// descending through the object. +func (o Object) Get(path ...string) (js.Value, error) { + current := o.value + for _, v := range path { + if current.Type() != js.TypeObject { + return js.Value{}, TypeMismatchError{ + Expected: js.TypeObject, + Actual: current.Type(), + } + } + + current = current.Get(v) + } + return current, nil +} + +// Expect is a helper function that calls Get and checks the type of the final result. +// It returns a TypeMismatchError if a non-object is encountered while descending the path or the final type does not +// match with the provided expected type. +func (o Object) Expect(expectedType js.Type, path ...string) (js.Value, error) { + value, err := o.Get(path...) + if err != nil { + return js.Value{}, err + } + + if value.Type() != expectedType { + return js.Value{}, TypeMismatchError{ + Expected: expectedType, + Actual: value.Type(), + } + } + + return value, nil +} + +// Delete removes property p from the object. +func (o Object) Delete(p string) { + o.value.Delete(p) +} + +// Equal checks if the object is equal to another value. +// It is equivalent to JS's === operator. +func (o Object) Equal(v js.Value) bool { + return o.value.Equal(v) +} + +// Index indexes into the object. +func (o Object) Index(i int) js.Value { + return o.value.Index(i) +} + +// InstanceOf implements the instanceof operator in JavaScript. +// If t is not a constructor, this function returns false. +func (o Object) InstanceOf(t js.Value) bool { + if t.Type() != js.TypeFunction { + return false + } + return o.value.InstanceOf(t) +} + +// JSValue implements the js.Wrapper interface. +func (o Object) JSValue() js.Value { + return o.value +} + +// Length returns the "length" property of the object. +func (o Object) Length() int { + return o.value.Length() +} + +// Set sets the property p to the value of js.ValueOf(x). +func (o Object) Set(p string, x interface{}) { + o.value.Set(p, x) +} + +// SetIndex sets the index i to the value of js.ValueOf(x). +func (o Object) SetIndex(i int, x interface{}) { + o.value.SetIndex(i, x) +} + +// String returns the object marshalled as a JSON string for debugging purposes. +func (o Object) String() string { + stringify, err := Global().Expect(js.TypeFunction, "JSON", "stringify") + if err != nil { + panic(err) + } + + jsonStr := stringify.Invoke(o) + if jsonStr.Type() != js.TypeString { + panic("JSON.stringify returned a " + jsonStr.Type().String()) + } + + return jsonStr.String() +} -- 2.38.4 From f3864a59ca6e22fc8c44f359abeeeac30493cb98 Mon Sep 17 00:00:00 2001 From: Chan Wen Xu Date: Sat, 20 Mar 2021 20:44:04 +0800 Subject: [PATCH 2/5] feat: Implement Promise --- wasm/error.go | 13 +++++ wasm/promise.go | 145 ++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 wasm/error.go create mode 100644 wasm/promise.go diff --git a/wasm/error.go b/wasm/error.go new file mode 100644 index 0000000..063043a --- /dev/null +++ b/wasm/error.go @@ -0,0 +1,13 @@ +package wasm + +import "syscall/js" + +// NewError returns a JS Error with the provided Go error's error message. +func NewError(goErr error) js.Value { + errConstructor, err := Global().Expect(js.TypeFunction, "Error") + if err != nil { + panic("Error constructor not found") + } + + return errConstructor.New(goErr.Error()) +} diff --git a/wasm/promise.go b/wasm/promise.go new file mode 100644 index 0000000..7c3d8d2 --- /dev/null +++ b/wasm/promise.go @@ -0,0 +1,145 @@ +package wasm + +import "syscall/js" + +// Promise is an instance of a JS promise. +// The zero value of this struct is not a valid Promise. +type Promise struct { + Object +} + +// FromJSValue turns a JS value to a Promise. +func (p *Promise) FromJSValue(value js.Value) error { + var err error + p.Object, err = NewObject(value) + return err +} + +// NewPromise returns a promise that is fulfilled or rejected when the provided handler returns. +// The handler is spawned in its own goroutine. +func NewPromise(handler func() (interface{}, error)) Promise { + resultChan := make(chan interface{}) + errChan := make(chan error) + + // Invoke the handler in a new goroutine. + go func() { + result, err := handler() + if err != nil { + errChan <- err + return + } + resultChan <- result + }() + + // Create a JS promise handler. + var jsHandler js.Func + jsHandler = js.FuncOf(func(this js.Value, args []js.Value) interface{} { + if len(args) < 2 { + panic("not enough arguments are passed to the Promise constructor handler") + } + + resolve := args[0] + reject := args[1] + + if resolve.Type() != js.TypeFunction || reject.Type() != js.TypeFunction { + panic("invalid type passed to Promise constructor handler") + } + + go func() { + select { + case r := <-resultChan: + resolve.Invoke(ToJSValue(r)) + case err := <-errChan: + reject.Invoke(NewError(err)) + } + + // Free up resources now that we are done. + jsHandler.Release() + }() + + return nil + }) + + promise, err := Global().Expect(js.TypeFunction, "Promise") + if err != nil { + panic("Promise constructor not found") + } + + return mustJSValueToPromise(promise.New(jsHandler)) +} + +// PromiseAll creates a promise that is fulfilled when all the provided promises have been fulfilled. +// The promise is rejected when any of the promises provided rejects. +// It is implemented by calling Promise.all on JS. +func PromiseAll(promise ...Promise) Promise { + promiseAll, err := Global().Expect(js.TypeFunction, "Promise", "all") + if err != nil { + panic("Promise.all not found") + } + + pInterface := make([]interface{}, 0, len(promise)) + for _, v := range promise { + pInterface = append(pInterface, v) + } + + return mustJSValueToPromise(promiseAll.Invoke(pInterface)) +} + +// PromiseAllSettled creates a promise that is fulfilled when all the provided promises have been fulfilled or rejected. +// It is implemented by calling Promise.allSettled on JS. +func PromiseAllSettled(promise ...Promise) Promise { + promiseAllSettled, err := Global().Expect(js.TypeFunction, "Promise", "allSettled") + if err != nil { + panic("Promise.allSettled not found") + } + + pInterface := make([]interface{}, 0, len(promise)) + for _, v := range promise { + pInterface = append(pInterface, v) + } + + return mustJSValueToPromise(promiseAllSettled.Invoke(pInterface)) +} + +// PromiseAny creates a promise that is fulfilled when any of the provided promises have been fulfilled. +// The promise is rejected when all of the provided promises gets rejected. +// It is implemented by calling Promise.any on JS. +func PromiseAny(promise ...Promise) Promise { + promiseAny, err := Global().Expect(js.TypeFunction, "Promise", "any") + if err != nil { + panic("Promise.any not found") + } + + pInterface := make([]interface{}, 0, len(promise)) + for _, v := range promise { + pInterface = append(pInterface, v) + } + + return mustJSValueToPromise(promiseAny.Invoke(pInterface)) +} + +// PromiseRace creates a promise that is fulfilled or rejected when one of the provided promises fulfill or reject. +// It is implemented by calling Promise.race on JS. +func PromiseRace(promise ...Promise) Promise { + promiseRace, err := Global().Expect(js.TypeFunction, "Promise", "race") + if err != nil { + panic("Promise.race not found") + } + + pInterface := make([]interface{}, 0, len(promise)) + for _, v := range promise { + pInterface = append(pInterface, v) + } + + return mustJSValueToPromise(promiseRace.Invoke(pInterface)) +} + +func mustJSValueToPromise(v js.Value) Promise { + var p Promise + err := p.FromJSValue(v) + if err != nil { + panic("Expected a Promise from JS standard library") + } + + return p +} -- 2.38.4 From ad5b341b1e043b90752693615447a02d8472037a Mon Sep 17 00:00:00 2001 From: Chan Wen Xu Date: Sat, 20 Mar 2021 23:45:44 +0800 Subject: [PATCH 3/5] feat: Add reflection to create JS value from Go value --- wasm/function.go | 105 ++++++++++++++++++++++++++++ wasm/object.go | 8 +-- wasm/reflect_from.go | 11 +++ wasm/reflect_to.go | 161 +++++++++++++++++++++++++++++++++++++++++++ wasm/wasm.go | 32 +++++++++ 5 files changed, 313 insertions(+), 4 deletions(-) create mode 100644 wasm/function.go create mode 100644 wasm/reflect_from.go create mode 100644 wasm/reflect_to.go create mode 100644 wasm/wasm.go diff --git a/wasm/function.go b/wasm/function.go new file mode 100644 index 0000000..5256c3b --- /dev/null +++ b/wasm/function.go @@ -0,0 +1,105 @@ +package wasm + +import ( + "errors" + "reflect" + "syscall/js" +) + +// ErrInvalidArgumentType is returned when a generated Go function wrapper receives invalid argument types from JS. +var ErrInvalidArgumentType = errors.New("invalid argument passed into Go function") + +var errorType = reflect.TypeOf((*error)(nil)).Elem() + +type goThrowable struct { + Result js.Value `wasm:"result"` + Error js.Value `wasm:"error"` +} + +func toJSFunc(x reflect.Value) js.Value { + funcType := x.Type() + var hasError bool + if funcType.NumOut() != 0 { + hasError = funcType.Out(funcType.NumOut()-1) == errorType + } + + return js.FuncOf(func(this js.Value, args []js.Value) interface{} { + in, err := conformJSValueToType(funcType, this, args) + if err != nil { + return ToJSValue(goThrowable{ + Error: NewError(err), + }) + } + + out := x.Call(in) + + if !hasError { + return ToJSValue(goThrowable{ + Result: returnValue(out), + }) + } + + lastParam := out[len(out)-1] + if !lastParam.IsNil() { + return ToJSValue(goThrowable{ + Error: NewError(lastParam.Interface().(error)), + }) + } + return ToJSValue(goThrowable{ + Result: returnValue(out[:len(out)-1]), + }) + }).JSValue() +} + +var jsValueType = reflect.TypeOf(js.Value{}) + +func conformJSValueToType(funcType reflect.Type, this js.Value, values []js.Value) ([]reflect.Value, error) { + if funcType.NumIn() == 0 { + if len(values) != 0 { + return nil, ErrInvalidArgumentType + } + return []reflect.Value{}, nil + } + + if funcType.In(0) == jsValueType { + values = append([]js.Value{this}, values...) + } + + if funcType.IsVariadic() && funcType.NumIn()-1 > len(values) { + return nil, ErrInvalidArgumentType + } + + if !funcType.IsVariadic() && funcType.NumIn() != len(values) { + return nil, ErrInvalidArgumentType + } + + in := make([]reflect.Value, 0, len(values)) + for i, v := range values { + paramType := funcType.In(i) + x := reflect.Zero(paramType).Interface() + err := FromJSValue(v, &x) + if err != nil { + return nil, err + } + + in = append(in, reflect.ValueOf(x)) + } + + return in, nil +} + +func returnValue(x []reflect.Value) js.Value { + switch len(x) { + case 0: + return js.Undefined() + case 1: + return ToJSValue(x[0].Interface()) + } + + xInterface := make([]interface{}, 0, len(x)) + for _, v := range x { + xInterface = append(xInterface, v.Interface()) + } + + return ToJSValue(xInterface) +} diff --git a/wasm/object.go b/wasm/object.go index f1c3fe5..b26b45d 100644 --- a/wasm/object.go +++ b/wasm/object.go @@ -116,14 +116,14 @@ func (o Object) Length() int { return o.value.Length() } -// Set sets the property p to the value of js.ValueOf(x). +// Set sets the property p to the value of ToJSValue(x). func (o Object) Set(p string, x interface{}) { - o.value.Set(p, x) + o.value.Set(p, ToJSValue(x)) } -// SetIndex sets the index i to the value of js.ValueOf(x). +// SetIndex sets the index i to the value of ToJSValue(x). func (o Object) SetIndex(i int, x interface{}) { - o.value.SetIndex(i, x) + o.value.SetIndex(i, ToJSValue(x)) } // String returns the object marshalled as a JSON string for debugging purposes. diff --git a/wasm/reflect_from.go b/wasm/reflect_from.go new file mode 100644 index 0000000..6fe4039 --- /dev/null +++ b/wasm/reflect_from.go @@ -0,0 +1,11 @@ +package wasm + +import ( + "fmt" + "syscall/js" +) + +func FromJSValue(x js.Value, out interface{}) error { + // TODO + return fmt.Errorf("unimplemented") +} diff --git a/wasm/reflect_to.go b/wasm/reflect_to.go new file mode 100644 index 0000000..fa94c4f --- /dev/null +++ b/wasm/reflect_to.go @@ -0,0 +1,161 @@ +package wasm + +import ( + "fmt" + "reflect" + "syscall/js" + "unsafe" +) + +// ToJSValue converts a given Go value into its equivalent JS form. +// +// One special case is that complex numbers (complex64 and complex128) are converted into objects with a real and imag +// property holding a number each. +// +// A function is converted into a JS function where the function returns an error if the provided arguments do not conform +// to the Go equivalent but otherwise calls the Go function. +// +// The "this" argument of a function is always passed to the Go function if its first parameter is of type js.Value. +// Otherwise, it is simply ignored. +// +// If the last return value of a function is an error, it will be thrown in JS if it's non-nil. +// If the function returns multiple non-error values, it is converted to an array when returning to JS. +// +// It panics when a channel or a map with keys other than string and integers are passed in. +func ToJSValue(x interface{}) js.Value { + if x == nil { + return js.Null() + } + + // Fast path for basic types that do not require reflection. + switch x := x.(type) { + case js.Value: + return x + case js.Wrapper: + return x.JSValue() + case bool, int, int8, int16, int32, int64, uint, uint8, uint16, uint32, uint64, uintptr, + unsafe.Pointer, float32, float64, string: + return js.ValueOf(x) + case complex64: + return js.ValueOf(map[string]interface{}{ + "real": real(x), + "imag": imag(x), + }) + case complex128: + return js.ValueOf(map[string]interface{}{ + "real": real(x), + "imag": imag(x), + }) + } + + value := reflect.ValueOf(x) + + if value.Kind() == reflect.Ptr { + value = reflect.Indirect(value) + if !value.IsValid() { + return js.Undefined() + } + } + + switch value.Kind() { + case reflect.Array, reflect.Slice: + return toJSArray(value) + case reflect.Func: + return toJSFunc(value) + case reflect.Map: + return mapToJSObject(value) + case reflect.Struct: + return structToJSObject(value) + default: + panic(fmt.Sprintf("cannot convert %T to a JS value", x)) + } +} + +func toJSArray(x reflect.Value) js.Value { + arrayConstructor, err := Global().Get("Array") + if err != nil { + panic("Array constructor not found") + } + + array := arrayConstructor.New() + for i := 0; i < x.Len(); i++ { + array.SetIndex(i, ToJSValue(x.Index(i).Interface())) + } + + return array +} + +func mapToJSObject(x reflect.Value) js.Value { + objectConstructor, err := Global().Get("Object") + if err != nil { + panic("Object constructor not found") + } + + obj := objectConstructor.New() + iter := x.MapRange() + for { + if !iter.Next() { + break + } + + key := iter.Key() + value := iter.Value().Interface() + switch key := key.Interface().(type) { + case int: + obj.SetIndex(key, ToJSValue(value)) + case int8: + obj.SetIndex(int(key), ToJSValue(value)) + case int16: + obj.SetIndex(int(key), ToJSValue(value)) + case int32: + obj.SetIndex(int(key), ToJSValue(value)) + case int64: + obj.SetIndex(int(key), ToJSValue(value)) + case uint: + obj.SetIndex(int(key), ToJSValue(value)) + case uint8: + obj.SetIndex(int(key), ToJSValue(value)) + case uint16: + obj.SetIndex(int(key), ToJSValue(value)) + case uint32: + obj.SetIndex(int(key), ToJSValue(value)) + case uint64: + obj.SetIndex(int(key), ToJSValue(value)) + case uintptr: + obj.SetIndex(int(key), ToJSValue(value)) + case string: + obj.Set(key, ToJSValue(value)) + default: + panic(fmt.Sprintf("cannot convert %T into a JS value as its key is not a string or an integer", + x.Interface())) + } + } + + return obj +} + +func structToJSObject(x reflect.Value) js.Value { + objectConstructor, err := Global().Get("Object") + if err != nil { + panic("Object constructor not found") + } + + obj := objectConstructor.New() + + structType := x.Type() + for i := 0; i < structType.NumField(); i++ { + field := structType.Field(i) + if field.PkgPath != "" { + continue + } + + name := field.Name + if tagName, ok := field.Tag.Lookup("wasm"); ok { + name = tagName + } + + obj.Set(name, ToJSValue(x.Field(i).Interface())) + } + + return obj +} diff --git a/wasm/wasm.go b/wasm/wasm.go new file mode 100644 index 0000000..56d94c2 --- /dev/null +++ b/wasm/wasm.go @@ -0,0 +1,32 @@ +package wasm + +// Magic values to communicate with the JS library. +const ( + globalIdent = "__go_wasm__" + readyHint = "__ready__" +) + +var bridge Object + +func init() { + bridgeJS, err := Global().Get(globalIdent) + if err != nil { + panic("JS wrapper " + globalIdent + " not found") + } + + bridge, err = NewObject(bridgeJS) + if err != nil { + panic("JS wrapper " + globalIdent + " is not an object") + } +} + +// Ready notifies the JS bridge that the WASM is ready. +// It should be called when every value and function is exposed. +func Ready() { + Expose(readyHint, true) +} + +// Expose exposes a copy of the provided value in JS. +func Expose(property string, x interface{}) { + bridge.Set(property, x) +} -- 2.38.4 From 37088bf3821eb7cb2bf119e20210d9a41909746a Mon Sep 17 00:00:00 2001 From: Chan Wen Xu Date: Sun, 21 Mar 2021 20:29:31 +0800 Subject: [PATCH 4/5] docs: Document unexported functions Some unexported functions are non-trivial and may be confusing. This commit clarifies their use. --- wasm/function.go | 11 +++++++++++ wasm/reflect_from.go | 2 ++ wasm/reflect_to.go | 3 +++ 3 files changed, 16 insertions(+) diff --git a/wasm/function.go b/wasm/function.go index 5256c3b..a509c85 100644 --- a/wasm/function.go +++ b/wasm/function.go @@ -16,6 +16,10 @@ type goThrowable struct { Error js.Value `wasm:"error"` } +// toJSFunc takes a reflect.Value of a Go function and converts it to a JS function that: +// Errors if the parameter types do not conform to the Go function signature, +// Throws an error if the last returned value is an error and is non-nil, +// Return an array if there's multiple non-error return values. func toJSFunc(x reflect.Value) js.Value { funcType := x.Type() var hasError bool @@ -53,6 +57,8 @@ func toJSFunc(x reflect.Value) js.Value { var jsValueType = reflect.TypeOf(js.Value{}) +// conformJSValueToType attempts to convert the provided JS values to reflect.Values that match the +// types expected for the parameters of funcType. func conformJSValueToType(funcType reflect.Type, this js.Value, values []js.Value) ([]reflect.Value, error) { if funcType.NumIn() == 0 { if len(values) != 0 { @@ -62,6 +68,7 @@ func conformJSValueToType(funcType reflect.Type, this js.Value, values []js.Valu } if funcType.In(0) == jsValueType { + // If the first parameter is a js.Value, it is assumed to be the value of `this`. values = append([]js.Value{this}, values...) } @@ -88,6 +95,10 @@ func conformJSValueToType(funcType reflect.Type, this js.Value, values []js.Valu return in, nil } +// returnValue wraps returned values by Go in a JS-friendly way. +// If there are no returned values, it returns undefined. +// If there is exactly one, it returns the JS equivalent. +// If there is more than one, it returns an array containing the JS equivalent of every returned value. func returnValue(x []reflect.Value) js.Value { switch len(x) { case 0: diff --git a/wasm/reflect_from.go b/wasm/reflect_from.go index 6fe4039..8b119b7 100644 --- a/wasm/reflect_from.go +++ b/wasm/reflect_from.go @@ -5,6 +5,8 @@ import ( "syscall/js" ) +// FromJSValue converts a given js.Value to the Go equivalent. +// The new value of 'out' is undefined if FromJSValue returns an error. func FromJSValue(x js.Value, out interface{}) error { // TODO return fmt.Errorf("unimplemented") diff --git a/wasm/reflect_to.go b/wasm/reflect_to.go index fa94c4f..bf6a271 100644 --- a/wasm/reflect_to.go +++ b/wasm/reflect_to.go @@ -71,6 +71,7 @@ func ToJSValue(x interface{}) js.Value { } } +// toJSArray converts the provided array or slice to a JS array. func toJSArray(x reflect.Value) js.Value { arrayConstructor, err := Global().Get("Array") if err != nil { @@ -85,6 +86,7 @@ func toJSArray(x reflect.Value) js.Value { return array } +// mapToJSObject converts the provided map to a JS object. func mapToJSObject(x reflect.Value) js.Value { objectConstructor, err := Global().Get("Object") if err != nil { @@ -134,6 +136,7 @@ func mapToJSObject(x reflect.Value) js.Value { return obj } +// structToJSObject converts a struct to a JS object. func structToJSObject(x reflect.Value) js.Value { objectConstructor, err := Global().Get("Object") if err != nil { -- 2.38.4 From 9faa609930fdff016d0ca5ce8dafed9198531c30 Mon Sep 17 00:00:00 2001 From: Chan Wen Xu Date: Sun, 21 Mar 2021 21:38:39 +0800 Subject: [PATCH 5/5] fix: Use function wrapper provided by JS --- wasm/function.go | 4 ++-- wasm/wasm.go | 17 ++++++++++++++--- 2 files changed, 16 insertions(+), 5 deletions(-) diff --git a/wasm/function.go b/wasm/function.go index a509c85..5b29f35 100644 --- a/wasm/function.go +++ b/wasm/function.go @@ -27,7 +27,7 @@ func toJSFunc(x reflect.Value) js.Value { hasError = funcType.Out(funcType.NumOut()-1) == errorType } - return js.FuncOf(func(this js.Value, args []js.Value) interface{} { + return funcWrapper.Invoke(js.FuncOf(func(this js.Value, args []js.Value) interface{} { in, err := conformJSValueToType(funcType, this, args) if err != nil { return ToJSValue(goThrowable{ @@ -52,7 +52,7 @@ func toJSFunc(x reflect.Value) js.Value { return ToJSValue(goThrowable{ Result: returnValue(out[:len(out)-1]), }) - }).JSValue() + })) } var jsValueType = reflect.TypeOf(js.Value{}) diff --git a/wasm/wasm.go b/wasm/wasm.go index 56d94c2..3e9f8e4 100644 --- a/wasm/wasm.go +++ b/wasm/wasm.go @@ -1,12 +1,18 @@ package wasm +import "syscall/js" + // Magic values to communicate with the JS library. const ( - globalIdent = "__go_wasm__" - readyHint = "__ready__" + globalIdent = "__go_wasm__" + readyHint = "__ready__" + funcWrapperName = "__wrapper__" ) -var bridge Object +var ( + bridge Object + funcWrapper js.Value +) func init() { bridgeJS, err := Global().Get(globalIdent) @@ -18,6 +24,11 @@ func init() { if err != nil { panic("JS wrapper " + globalIdent + " is not an object") } + + funcWrapper, err = bridge.Get(funcWrapperName) + if err != nil { + panic("JS wrapper " + globalIdent + "." + funcWrapperName + " not found") + } } // Ready notifies the JS bridge that the WASM is ready. -- 2.38.4