Compare commits
3 Commits
Author | SHA1 | Date |
---|---|---|
Chan Wen Xu | 38d86b9e81 | |
Chan Wen Xu | 5fa7e5c21d | |
Chan Wen Xu | f22fcefe08 |
@ -1,4 +0,0 @@
|
|||||||
node_modules
|
|
||||||
.gocache
|
|
||||||
wasm_exec.js
|
|
||||||
*.wasm
|
|
@ -1,3 +0,0 @@
|
|||||||
example
|
|
||||||
wasm
|
|
||||||
.gocache
|
|
@ -1,21 +0,0 @@
|
|||||||
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.
|
|
@ -1 +0,0 @@
|
|||||||
dist/main.js
|
|
@ -1,13 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html>
|
|
||||||
|
|
||||||
<head>
|
|
||||||
<meta charset="utf-8" />
|
|
||||||
<title>Golang-WASM</title>
|
|
||||||
</head>
|
|
||||||
|
|
||||||
<body>
|
|
||||||
<script src="main.js"></script>
|
|
||||||
</body>
|
|
||||||
|
|
||||||
</html>
|
|
@ -1,3 +0,0 @@
|
|||||||
module example
|
|
||||||
|
|
||||||
go 1.16
|
|
File diff suppressed because it is too large
Load Diff
@ -1,15 +0,0 @@
|
|||||||
{
|
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,20 +0,0 @@
|
|||||||
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()
|
|
||||||
}
|
|
@ -1,8 +0,0 @@
|
|||||||
import wasm from './api/main.go';
|
|
||||||
|
|
||||||
const { hello, helloName } = wasm;
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
console.log(await hello());
|
|
||||||
console.log(await helloName("world"));
|
|
||||||
})()
|
|
@ -1,53 +0,0 @@
|
|||||||
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
@ -1,79 +0,0 @@
|
|||||||
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;
|
|
||||||
}
|
|
@ -1,85 +0,0 @@
|
|||||||
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);`);
|
|
||||||
})();
|
|
||||||
}
|
|
@ -1,411 +1,11 @@
|
|||||||
package wasm
|
package wasm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"reflect"
|
|
||||||
"syscall/js"
|
"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 {
|
func FromJSValue(x js.Value, out interface{}) error {
|
||||||
v := reflect.ValueOf(out)
|
// TODO
|
||||||
if v.Kind() != reflect.Ptr || v.IsNil() {
|
return fmt.Errorf("unimplemented")
|
||||||
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())
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue