forked from Team-Ortix/golang-wasm
Compare commits
16 Commits
Author | SHA1 | Date |
---|---|---|
ALI Hamza | be4b312d49 | |
Chan Wen Xu | 421c331333 | |
Chan Wen Xu | 8eadf27091 | |
Chan Wen Xu | 9faa609930 | |
Chan Wen Xu | 37088bf382 | |
Chan Wen Xu | ad5b341b1e | |
Chan Wen Xu | f3864a59ca | |
Chan Wen Xu | 392b175abf | |
ALI Hamza | da1769920a | |
ALI Hamza | 3b119bd81a | |
ALI Hamza | 92296f6a56 | |
ALI Hamza | caaad47c74 | |
ALI Hamza | f6150013d5 | |
ALI Hamza | f6d264ea40 | |
ALI Hamza | ef20dff682 | |
ALI Hamza | f19165c53d |
@ -0,0 +1,4 @@
|
||||
node_modules
|
||||
.gocache
|
||||
wasm_exec.js
|
||||
*.wasm
|
@ -0,0 +1,3 @@
|
||||
example
|
||||
wasm
|
||||
.gocache
|
@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2021 Hamza Ali and Chan Wen Xu
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@ -0,0 +1 @@
|
||||
dist/main.js
|
@ -0,0 +1,13 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Golang-WASM</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
@ -0,0 +1,3 @@
|
||||
module example
|
||||
|
||||
go 1.16
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "example",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "GOROOT=`go env GOROOT` webpack serve"
|
||||
},
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"webpack": "^5.27.0",
|
||||
"webpack-dev-server": "^3.11.2"
|
||||
}
|
||||
}
|
@ -0,0 +1,20 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
func main() {
|
||||
fmt.Println("Hello from go-mod-wasm!")
|
||||
setup()
|
||||
|
||||
c := make(chan bool, 0) // To use anything from Go WASM, the program may not exit.
|
||||
<-c
|
||||
}
|
||||
|
||||
func setup() {
|
||||
fmt.Println("golang-wasm initialized")
|
||||
|
||||
js.Global()
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
import wasm from './api/main.go';
|
||||
|
||||
const { hello, helloName } = wasm;
|
||||
|
||||
(async () => {
|
||||
console.log(await hello());
|
||||
console.log(await helloName("world"));
|
||||
})()
|
@ -0,0 +1,53 @@
|
||||
const path = require('path');
|
||||
|
||||
module.exports = {
|
||||
entry: './src/index.js',
|
||||
devServer: {
|
||||
contentBase: path.resolve(__dirname, 'dist'),
|
||||
compress: true,
|
||||
port: 3000,
|
||||
},
|
||||
mode: "development",
|
||||
output: {
|
||||
filename: 'main.js',
|
||||
path: path.resolve(__dirname, 'dist'),
|
||||
},
|
||||
resolve: {
|
||||
extensions: [".js", ".go"],
|
||||
fallback: {
|
||||
"fs": false,
|
||||
"os": false,
|
||||
"util": false,
|
||||
"tls": false,
|
||||
"net": false,
|
||||
"path": false,
|
||||
"zlib": false,
|
||||
"http": false,
|
||||
"https": false,
|
||||
"stream": false,
|
||||
"crypto": false,
|
||||
}
|
||||
},
|
||||
module: {
|
||||
rules: [
|
||||
{
|
||||
test: /\.go$/,
|
||||
use: [
|
||||
{
|
||||
loader: path.resolve(__dirname, '../../src/index.js')
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
performance: {
|
||||
assetFilter: (file) => {
|
||||
return !/(\.wasm|.map)$/.test(file)
|
||||
}
|
||||
},
|
||||
ignoreWarnings: [
|
||||
{
|
||||
module: /wasm_exec.js$/
|
||||
}
|
||||
]
|
||||
};
|
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,79 @@
|
||||
const g = global || window || self;
|
||||
if (!g.__go_wasm__) {
|
||||
g.__go_wasm__ = {};
|
||||
}
|
||||
|
||||
const maxTime = 3 * 1000;
|
||||
|
||||
const bridge = g.__go_wasm__;
|
||||
|
||||
|
||||
/**
|
||||
* Wrapper is used by Go to run all Go functions in JS.
|
||||
* Go functions always return an object of the following spec:
|
||||
* {
|
||||
* result: undefined | any // undefined when error is returned, or function returns undefined
|
||||
* error: Error | undefined // undefined when no error is present
|
||||
* }
|
||||
*/
|
||||
function wrapper(goFunc) {
|
||||
return (...args) => {
|
||||
const result = goFunc.apply(undefined, args);
|
||||
if (result.error instanceof Error) {
|
||||
throw result.error;
|
||||
}
|
||||
return result.result;
|
||||
}
|
||||
}
|
||||
bridge.__wrapper__ = wrapper
|
||||
|
||||
function sleep() {
|
||||
return new Promise(requestAnimationFrame);
|
||||
}
|
||||
export default function (getBytes) {
|
||||
let proxy;
|
||||
|
||||
async function init() {
|
||||
const go = new g.Go();
|
||||
let bytes = await getBytes;
|
||||
let result = await WebAssembly.instantiate(bytes, go.importObject);
|
||||
go.run(result.instance);
|
||||
bridge.__proxy__ = proxy
|
||||
setTimeout(() => {
|
||||
if (bridge.__ready__ !== true) {
|
||||
console.warn("Golang Wasm Bridge (__go_wasm__.__ready__) still not true after max time");
|
||||
}
|
||||
}, maxTime);
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
|
||||
proxy = new Proxy(
|
||||
{},
|
||||
{
|
||||
get: (_, key) => {
|
||||
return (...args) => {
|
||||
return new Promise(async (res, rej) => {
|
||||
while (bridge.__ready__ !== true) {
|
||||
await sleep();
|
||||
}
|
||||
|
||||
if (typeof bridge[key] !== 'function') {
|
||||
res(bridge[key]);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
res(bridge[key].apply(undefined, args));
|
||||
} catch (e) {
|
||||
rej(e)
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return proxy;
|
||||
}
|
@ -0,0 +1,85 @@
|
||||
const fs = require("fs/promises");
|
||||
const util = require("util");
|
||||
const execFile = util.promisify(require("child_process").execFile);
|
||||
const path = require("path");
|
||||
const { lookpath } = require("lookpath");
|
||||
|
||||
module.exports = function (source) {
|
||||
const cb = this.async();
|
||||
|
||||
const goBin = lookpath("go");
|
||||
if (!goBin) {
|
||||
return cb(new Error("go bin not found in path."));
|
||||
}
|
||||
|
||||
if (!process.env.GOROOT) {
|
||||
return cb(new Error("Could not find GOROOT in environment.\n" +
|
||||
"Please try adding this to your script:\n" +
|
||||
"GOROOT=`go env GOROOT` npm run ..."));
|
||||
}
|
||||
|
||||
const parent = path.dirname(this.resourcePath);
|
||||
const outFile = this.resourcePath.slice(0, -2) + "wasm";
|
||||
let modDir = parent;
|
||||
|
||||
const opts = {
|
||||
cwd: parent,
|
||||
env: {
|
||||
GOPATH: process.env.GOPATH || path.join(process.env.HOME, "go"),
|
||||
GOROOT: process.env.GOROOT,
|
||||
GOCACHE: path.join(__dirname, ".gocache"),
|
||||
GOOS: "js",
|
||||
GOARCH: "wasm",
|
||||
},
|
||||
};
|
||||
|
||||
(async () => {
|
||||
let found = false;
|
||||
const root = path.resolve(path.sep);
|
||||
while (path.resolve(modDir) != root) {
|
||||
found = await fs.access(path.join(modDir, 'go.mod')).then(() => true).catch(() => false);
|
||||
if (found) {
|
||||
break;
|
||||
}
|
||||
modDir = path.join(modDir, "..");
|
||||
}
|
||||
|
||||
if (!found) {
|
||||
return cb(new Error("Could not find go.mod in any parent directory of " + this.resourcePath));
|
||||
}
|
||||
|
||||
const wasmOrigPath = path.join(process.env.GOROOT, "misc", "wasm", "wasm_exec.js");
|
||||
const wasmSavePath = path.join(__dirname, 'wasm_exec.js');
|
||||
const errorPaths = ["\t" + wasmOrigPath, "\t" + wasmSavePath];
|
||||
if (!(await fs.access(wasmOrigPath).then(() => true).catch(() => false)) &&
|
||||
!(await fs.access(wasmSavePath).then(() => true).catch(() => false))) {
|
||||
return cb(new Error("Could not find wasm_exec.js file. Invalid GOROOT? Searched paths:\n" +
|
||||
errorPaths.join(",\n") + "\n"));
|
||||
}
|
||||
|
||||
|
||||
const result = await execFile("go", ["build", "-o", outFile, parent], opts)
|
||||
.then(() => true)
|
||||
.catch(e => e);
|
||||
if (result instanceof Error) {
|
||||
return cb(result);
|
||||
}
|
||||
|
||||
found = await fs.access(wasmSavePath).then(() => true).catch(() => false);
|
||||
if (!found) fs.copyFile(wasmOrigPath, wasmSavePath);
|
||||
|
||||
const contents = await fs.readFile(outFile);
|
||||
fs.unlink(outFile);
|
||||
|
||||
const emitPath = path.basename(outFile);
|
||||
this.emitFile(emitPath, contents);
|
||||
this.addContextDependency(modDir);
|
||||
|
||||
cb(null,
|
||||
`require('!${wasmSavePath}');
|
||||
import goWasm from '${path.join(__dirname, 'bridge.js')}';
|
||||
|
||||
const wasm = fetch('${emitPath}').then(response => response.arrayBuffer());
|
||||
export default goWasm(wasm);`);
|
||||
})();
|
||||
}
|
@ -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())
|
||||
}
|
@ -0,0 +1,116 @@
|
||||
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"`
|
||||
}
|
||||
|
||||
// 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
|
||||
if funcType.NumOut() != 0 {
|
||||
hasError = funcType.Out(funcType.NumOut()-1) == errorType
|
||||
}
|
||||
|
||||
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{
|
||||
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]),
|
||||
})
|
||||
}))
|
||||
}
|
||||
|
||||
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 {
|
||||
return nil, ErrInvalidArgumentType
|
||||
}
|
||||
return []reflect.Value{}, nil
|
||||
}
|
||||
|
||||
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...)
|
||||
}
|
||||
|
||||
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)
|
||||
ptrX := reflect.New(paramType).Interface()
|
||||
err := FromJSValue(v, ptrX)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
in = append(in, reflect.ValueOf(ptrX).Elem())
|
||||
}
|
||||
|
||||
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:
|
||||
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)
|
||||
}
|
@ -0,0 +1,3 @@
|
||||
module gitea.teamortix.com/Team-Ortix/go-mod-wasm/wasm
|
||||
|
||||
go 1.16
|
@ -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 ToJSValue(x).
|
||||
func (o Object) Set(p string, x interface{}) {
|
||||
o.value.Set(p, ToJSValue(x))
|
||||
}
|
||||
|
||||
// SetIndex sets the index i to the value of ToJSValue(x).
|
||||
func (o Object) SetIndex(i int, x interface{}) {
|
||||
o.value.SetIndex(i, ToJSValue(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()
|
||||
}
|
@ -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
|
||||
}
|
@ -0,0 +1,411 @@
|
||||
package wasm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"syscall/js"
|
||||
)
|
||||
|
||||
// ErrMultipleReturnValue is an error where a JS function is attempted to be unmarshalled into a Go function with
|
||||
// multiple return values.
|
||||
var ErrMultipleReturnValue = errors.New("a JS function can only return one value")
|
||||
|
||||
// InvalidFromJSValueError is an error where an invalid argument is passed to FromJSValue.
|
||||
// The argument to Unmarshal must be a non-nil pointer.
|
||||
type InvalidFromJSValueError struct {
|
||||
Type reflect.Type
|
||||
}
|
||||
|
||||
// Error implements error.
|
||||
func (e InvalidFromJSValueError) Error() string {
|
||||
return "invalid argument passed to FromJSValue. Got type " + e.Type.String()
|
||||
}
|
||||
|
||||
// InvalidTypeError is an error where the JS value cannot be unmarshalled into the provided Go type.
|
||||
type InvalidTypeError struct {
|
||||
JSType js.Type
|
||||
GoType reflect.Type
|
||||
}
|
||||
|
||||
// Error implements error.
|
||||
func (e InvalidTypeError) Error() string {
|
||||
return "invalid unmarshalling: cannot unmarshal " + e.JSType.String() + " into " + e.GoType.String()
|
||||
}
|
||||
|
||||
// InvalidArrayError is an error where the JS's array length do not match Go's array length.
|
||||
type InvalidArrayError struct {
|
||||
Expected int
|
||||
Actual int
|
||||
}
|
||||
|
||||
// Error implements error.
|
||||
func (e InvalidArrayError) Error() string {
|
||||
return fmt.Sprintf(
|
||||
"invalid unmarshalling: expected array of length %d to match Go array but got JS array of length %d",
|
||||
e.Expected, e.Actual,
|
||||
)
|
||||
}
|
||||
|
||||
// Decoder is an interface which manually decodes js.Value on its own.
|
||||
// It overrides in FromJSValue.
|
||||
type Decoder interface {
|
||||
FromJSValue(js.Value) error
|
||||
}
|
||||
|
||||
// FromJSValue converts a given js.Value to the Go equivalent.
|
||||
// The new value of 'out' is undefined if FromJSValue returns an error.
|
||||
//
|
||||
// When a JS function is unmarshalled into a Go function with only one return value, the returned JS value is casted
|
||||
// into the type of the return value. If the conversion fails, the function call panics.
|
||||
//
|
||||
// When a JS function is unmarshalled into a Go function with two return values, the second one being error, the
|
||||
// conversion error is returned instead.
|
||||
func FromJSValue(x js.Value, out interface{}) error {
|
||||
v := reflect.ValueOf(out)
|
||||
if v.Kind() != reflect.Ptr || v.IsNil() {
|
||||
return &InvalidFromJSValueError{reflect.TypeOf(v)}
|
||||
}
|
||||
|
||||
return decodeValue(x, v.Elem())
|
||||
}
|
||||
|
||||
// decodeValue decodes the provided js.Value into the provided reflect.Value.
|
||||
func decodeValue(x js.Value, v reflect.Value) error {
|
||||
// If we have undefined or null, we need to be able to set to the pointer itself.
|
||||
// All code beyond this point are pointer-unaware so we handle undefined or null first.
|
||||
if x.Type() == js.TypeUndefined || x.Type() == js.TypeNull {
|
||||
return decodeNothing(v)
|
||||
}
|
||||
|
||||
// Implementations of Decoder are probably on pointer so do it before pointer code.
|
||||
if d, ok := v.Addr().Interface().(Decoder); ok {
|
||||
return d.FromJSValue(x)
|
||||
}
|
||||
|
||||
// Make sure everything is initialized and indirect it.
|
||||
// This prevents other decode functions from having to handle pointers.
|
||||
if v.Kind() == reflect.Ptr {
|
||||
initializePointerIfNil(v)
|
||||
v = reflect.Indirect(v)
|
||||
}
|
||||
|
||||
if v.Kind() == reflect.Interface && v.NumMethod() == 0 {
|
||||
// It's a interface{} so we just create the easiest Go representation we can in createInterface.
|
||||
res := createInterface(x)
|
||||
if res != nil {
|
||||
v.Set(reflect.ValueOf(res))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Directly set v if it's a js.Value.
|
||||
if _, ok := v.Interface().(js.Value); ok {
|
||||
v.Set(reflect.ValueOf(x))
|
||||
return nil
|
||||
}
|
||||
|
||||
// Go the reflection route.
|
||||
switch x.Type() {
|
||||
case js.TypeBoolean:
|
||||
return decodeBoolean(x, v)
|
||||
case js.TypeNumber:
|
||||
return decodeNumber(x, v)
|
||||
case js.TypeString:
|
||||
return decodeString(x, v)
|
||||
case js.TypeSymbol:
|
||||
return decodeSymbol(x, v)
|
||||
case js.TypeObject:
|
||||
if isArray(x) {
|
||||
return decodeArray(x, v)
|
||||
}
|
||||
return decodeObject(x, v)
|
||||
case js.TypeFunction:
|
||||
return decodeFunction(x, v)
|
||||
default:
|
||||
panic("unknown JS type: " + x.Type().String())
|
||||
}
|
||||
}
|
||||
|
||||
// decodeNothing decodes an undefined or a null into the provided reflect.Value.
|
||||
func decodeNothing(v reflect.Value) error {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return InvalidTypeError{js.TypeNull, v.Type()}
|
||||
}
|
||||
v.Set(reflect.ValueOf(nil))
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeBoolean decodes a bool into the provided reflect.Value.
|
||||
func decodeBoolean(x js.Value, v reflect.Value) error {
|
||||
if v.Kind() != reflect.Bool {
|
||||
return InvalidTypeError{js.TypeBoolean, v.Type()}
|
||||
}
|
||||
v.SetBool(x.Bool())
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeNumber decodes a JS number into the provided reflect.Value, truncating as necessary.
|
||||
func decodeNumber(x js.Value, v reflect.Value) error {
|
||||
switch v.Kind() {
|
||||
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
v.SetInt(int64(x.Float()))
|
||||
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
|
||||
v.SetUint(uint64(x.Float()))
|
||||
case reflect.Float32, reflect.Float64:
|
||||
v.SetFloat(x.Float())
|
||||
default:
|
||||
return InvalidTypeError{js.TypeNumber, v.Type()}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeString decodes a JS string into the provided reflect.Value.
|
||||
func decodeString(x js.Value, v reflect.Value) error {
|
||||
if v.Kind() != reflect.String {
|
||||
return InvalidTypeError{js.TypeString, v.Type()}
|
||||
}
|
||||
v.SetString(x.String())
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeSymbol decodes a JS symbol into the provided reflect.Value.
|
||||
func decodeSymbol(x js.Value, v reflect.Value) error {
|
||||
// TODO Decode it into a symbol type.
|
||||
return InvalidTypeError{js.TypeSymbol, v.Type()}
|
||||
}
|
||||
|
||||
// decodeArray decodes a JS array into the provided reflect.Value.
|
||||
func decodeArray(x js.Value, v reflect.Value) error {
|
||||
jsLen := x.Length()
|
||||
|
||||
switch v.Kind() {
|
||||
case reflect.Array:
|
||||
if jsLen != v.Len() {
|
||||
return InvalidArrayError{v.Len(), jsLen}
|
||||
}
|
||||
case reflect.Slice:
|
||||
newSlice := reflect.MakeSlice(v.Type(), jsLen, jsLen)
|
||||
v.Set(newSlice)
|
||||
default:
|
||||
return InvalidTypeError{js.TypeObject, v.Type()}
|
||||
}
|
||||
|
||||
for i := 0; i < jsLen; i++ {
|
||||
err := FromJSValue(x.Index(i), v.Index(i).Addr().Interface())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeObject decodes a JS object into the provided reflect.Value.
|
||||
func decodeObject(x js.Value, v reflect.Value) error {
|
||||
switch v.Kind() {
|
||||
case reflect.Struct:
|
||||
return decodeObjectIntoStruct(x, v)
|
||||
case reflect.Map:
|
||||
return decodeObjectIntoMap(x, v)
|
||||
default:
|
||||
return InvalidTypeError{js.TypeObject, v.Type()}
|
||||
}
|
||||
}
|
||||
|
||||
// decodeObject decodes a JS object into the provided reflect.Value struct.
|
||||
func decodeObjectIntoStruct(x js.Value, v reflect.Value) error {
|
||||
for i := 0; i < v.Type().NumField(); i++ {
|
||||
fieldType := v.Type().Field(i)
|
||||
if fieldType.PkgPath != "" {
|
||||
continue
|
||||
}
|
||||
|
||||
name := fieldType.Name
|
||||
tagName, tagOK := fieldType.Tag.Lookup("wasm")
|
||||
|
||||
if tagOK {
|
||||
name = tagName
|
||||
}
|
||||
|
||||
err := decodeValue(x.Get(name), v.Field(i))
|
||||
if err != nil {
|
||||
if tagOK {
|
||||
return fmt.Errorf("in field %s (JS %s): %w", fieldType.Name, tagName, err)
|
||||
}
|
||||
return fmt.Errorf("in field %s: %w", fieldType.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decodeObjectIntoMap(x js.Value, v reflect.Value) error {
|
||||
mapType := v.Type()
|
||||
keyType := mapType.Key()
|
||||
valType := mapType.Elem()
|
||||
|
||||
switch keyType.Kind() {
|
||||
case reflect.String:
|
||||
case reflect.Interface:
|
||||
if keyType.NumMethod() != 0 {
|
||||
return InvalidTypeError{js.TypeObject, mapType}
|
||||
}
|
||||
default:
|
||||
return InvalidTypeError{js.TypeObject, mapType}
|
||||
}
|
||||
|
||||
// TODO: Use Object API
|
||||
obj, err := Global().Get("Object")
|
||||
if err != nil {
|
||||
panic("Object not found")
|
||||
}
|
||||
|
||||
var keys []string
|
||||
err = FromJSValue(obj.Call("keys", x), &keys)
|
||||
if err != nil {
|
||||
panic("Object.keys returned non-string-array.")
|
||||
}
|
||||
|
||||
for _, k := range keys {
|
||||
valuePtr := reflect.New(valType).Interface()
|
||||
err := FromJSValue(x.Get(k), valuePtr)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v.SetMapIndex(reflect.ValueOf(k), reflect.ValueOf(valuePtr).Elem())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// decodeFunction decodes a JS function into the provided reflect.Value.
|
||||
func decodeFunction(x js.Value, v reflect.Value) error {
|
||||
funcType := v.Type()
|
||||
outCount := funcType.NumOut()
|
||||
|
||||
switch outCount {
|
||||
case 0, 1:
|
||||
case 2:
|
||||
if funcType.Out(1) != errorType {
|
||||
return ErrMultipleReturnValue
|
||||
}
|
||||
default:
|
||||
return ErrMultipleReturnValue
|
||||
}
|
||||
|
||||
v.Set(reflect.MakeFunc(funcType, func(args []reflect.Value) []reflect.Value {
|
||||
argsJS := make([]interface{}, 0, len(args))
|
||||
for _, v := range args {
|
||||
argsJS = append(argsJS, ToJSValue(v.Interface()))
|
||||
}
|
||||
|
||||
jsReturn := x.Invoke(argsJS...)
|
||||
if outCount == 0 {
|
||||
return []reflect.Value{}
|
||||
}
|
||||
|
||||
returnPtr := reflect.New(funcType.Out(0)).Interface()
|
||||
err := FromJSValue(jsReturn, returnPtr)
|
||||
|
||||
returnVal := reflect.ValueOf(returnPtr).Elem()
|
||||
if err != nil {
|
||||
if outCount == 1 {
|
||||
panic("error decoding JS return value: " + err.Error())
|
||||
}
|
||||
|
||||
return []reflect.Value{returnVal, reflect.ValueOf(err)}
|
||||
}
|
||||
|
||||
switch outCount {
|
||||
case 1:
|
||||
return []reflect.Value{returnVal}
|
||||
case 2:
|
||||
return []reflect.Value{returnVal, reflect.ValueOf(nil)}
|
||||
default:
|
||||
panic("unexpected amount of return values")
|
||||
}
|
||||
}))
|
||||
return nil
|
||||
}
|
||||
|
||||
// createInterface creates a representation of the provided js.Value.
|
||||
func createInterface(x js.Value) interface{} {
|
||||
switch x.Type() {
|
||||
case js.TypeUndefined, js.TypeNull:
|
||||
return nil
|
||||
case js.TypeBoolean:
|
||||
return x.Bool()
|
||||
case js.TypeNumber:
|
||||
return x.Float()
|
||||
case js.TypeString:
|
||||
return x.String()
|
||||
case js.TypeSymbol:
|
||||
// We can't convert it to a Go value in a meaningful way.
|
||||
return x
|
||||
case js.TypeObject:
|
||||
if isArray(x) {
|
||||
return createArray(x)
|
||||
}
|
||||
return createObject(x)
|
||||
case js.TypeFunction:
|
||||
var a func(...interface{}) (interface{}, error)
|
||||
err := FromJSValue(x, &a)
|
||||
if err != nil {
|
||||
panic("error creating function: " + err.Error())
|
||||
}
|
||||
return a
|
||||
default:
|
||||
panic("unknown JS type: " + x.Type().String())
|
||||
}
|
||||
}
|
||||
|
||||
// createArray creates a slice of interface representing the js.Value.
|
||||
func createArray(x js.Value) interface{} {
|
||||
result := make([]interface{}, x.Length())
|
||||
for i := range result {
|
||||
result[i] = createInterface(x.Index(i))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// createObject creates a representation of the provided JS object.
|
||||
func createObject(x js.Value) interface{} {
|
||||
// TODO: Use Object API
|
||||
obj, err := Global().Get("Object")
|
||||
if err != nil {
|
||||
panic("Object not found")
|
||||
}
|
||||
|
||||
var keys []string
|
||||
err = FromJSValue(obj.Call("keys", x), &keys)
|
||||
if err != nil {
|
||||
panic("Object.keys returned non-string-array.")
|
||||
}
|
||||
|
||||
result := make(map[string]interface{}, len(keys))
|
||||
for _, v := range keys {
|
||||
result[v] = createInterface(x.Get(v))
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
// isArray calls the JS function Array.isArray to check if the provided js.Value is an array.
|
||||
func isArray(x js.Value) bool {
|
||||
arr, err := Global().Get("Array")
|
||||
if err != nil {
|
||||
panic("Array not found")
|
||||
}
|
||||
|
||||
return arr.Call("isArray", x).Bool()
|
||||
}
|
||||
|
||||
// initializePointerIfNil checks if the pointer is nil and initializes it as necessary.
|
||||
func initializePointerIfNil(v reflect.Value) {
|
||||
if v.Kind() != reflect.Ptr {
|
||||
return
|
||||
}
|
||||
if v.IsNil() {
|
||||
v.Set(reflect.New(v.Type().Elem()))
|
||||
}
|
||||
initializePointerIfNil(v.Elem())
|
||||
}
|
@ -0,0 +1,164 @@
|
||||
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))
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
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
|
||||
}
|
||||
|
||||
// mapToJSObject converts the provided map to a JS object.
|
||||
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
|
||||
}
|
||||
|
||||
// structToJSObject converts a struct to a JS object.
|
||||
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
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package wasm
|
||||
|
||||
import "syscall/js"
|
||||
|
||||
// Magic values to communicate with the JS library.
|
||||
const (
|
||||
globalIdent = "__go_wasm__"
|
||||
readyHint = "__ready__"
|
||||
funcWrapperName = "__wrapper__"
|
||||
)
|
||||
|
||||
var (
|
||||
bridge Object
|
||||
funcWrapper js.Value
|
||||
)
|
||||
|
||||
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")
|
||||
}
|
||||
|
||||
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.
|
||||
// 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)
|
||||
}
|
Loading…
Reference in New Issue