Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
Add example use of WASM bridge
Browse files Browse the repository at this point in the history
Signed-off-by: Josh Kim <[email protected]>
  • Loading branch information
jooskim committed Jan 13, 2022
1 parent 9a6d6a8 commit af9e76b
Show file tree
Hide file tree
Showing 3 changed files with 140 additions and 29 deletions.
92 changes: 65 additions & 27 deletions wasm/bus_bridge.go
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
//go:build js && wasm
// +build js,wasm

// Copyright 2021 VMware, Inc.
// SPDX-License-Identifier: BSD-2-Clause

package wasm

import (
"reflect"
"syscall/js"

"github.com/google/uuid"
Expand All @@ -24,10 +26,8 @@ func NewTransportWasmBridge(busRef bus.EventBus) *TransportWasmBridge {
}

func (t *TransportWasmBridge) sendRequestMessageWrapper() js.Func {
jsFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// release the resources allocated for js.Func once this closure goes out of scope
defer jsFunc.Release()

var jsFunc js.Func
jsFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
var destId *uuid.UUID
channel := args[0].String()
payload := args[1].JSValue()
Expand All @@ -47,10 +47,8 @@ func (t *TransportWasmBridge) sendRequestMessageWrapper() js.Func {
}

func (t *TransportWasmBridge) sendResponseMessageWrapper() js.Func {
jsFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
// release the resources allocated for js.Func once this closure goes out of scope
defer jsFunc.Release()

var jsFunc js.Func
jsFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
var destId *uuid.UUID
channel := args[0].String()
payload := args[1].JSValue()
Expand All @@ -70,13 +68,16 @@ func (t *TransportWasmBridge) sendResponseMessageWrapper() js.Func {
}

func (t *TransportWasmBridge) listenStreamWrapper() js.Func {
jsFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
var jsFunc js.Func
jsFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
handler, err := t.busRef.ListenStream(args[0].String())
if err != nil {
errCtor := js.Global().Get("Error")
return errCtor.New(err.Error())
js.Global().Get("console").Call("error", errCtor.New(err.Error()))
return nil
}

jsFuncRefs := make([]js.Func, 0)
closerFuncRef := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
handler.Close()

Expand All @@ -89,18 +90,19 @@ func (t *TransportWasmBridge) listenStreamWrapper() js.Func {
})

// store js.Func handles for resource cleanup later
jsFuncRefs := []js.Func{jsFunc, getResponseHandlerJsCallbackFunc(handler), closerFuncRef}
jsFuncRefs = append(jsFuncRefs, getResponseHandlerJsCallbackFunc(handler), closerFuncRef)

return map[string]interface{}{
"handle": jsFuncRefs[1],
"close": jsFuncRefs[2],
"handle": jsFuncRefs[0],
"close": jsFuncRefs[1],
}
})
return jsFunc
}

func (t *TransportWasmBridge) listenStreamWithIdWrapper() js.Func {
jsFunc := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
var jsFunc js.Func
jsFunc = js.FuncOf(func(this js.Value, args []js.Value) interface{} {
if len(args) <= 1 {
errCtor := js.Global().Get("Error")
js.Global().Get("console").Call("error", errCtor.New("cannot listen to channel. missing destination ID"))
Expand All @@ -119,6 +121,7 @@ func (t *TransportWasmBridge) listenStreamWithIdWrapper() js.Func {
return errCtor.New(err.Error())
}

jsFuncRefs := make([]js.Func, 0)
closerFuncRef := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
handler.Close()

Expand All @@ -131,13 +134,14 @@ func (t *TransportWasmBridge) listenStreamWithIdWrapper() js.Func {
})

// store js.Func handles for resource cleanup later
jsFuncRefs := []js.Func{jsFunc, getResponseHandlerJsCallbackFunc(handler), closerFuncRef}
jsFuncRefs = append(jsFuncRefs, getResponseHandlerJsCallbackFunc(handler), closerFuncRef)

return map[string]interface{}{
"handle": jsFuncRefs[1],
"close": jsFuncRefs[2],
"handle": jsFuncRefs[0],
"close": jsFuncRefs[1],
}
})
return jsFunc
}

func (t *TransportWasmBridge) browserBusWrapper() js.Func {
Expand Down Expand Up @@ -167,20 +171,54 @@ func (t *TransportWasmBridge) SetUpTransportWasmAPI() {

func getResponseHandlerJsCallbackFunc(handler bus.MessageHandler) js.Func {
return js.FuncOf(func(this js.Value, args []js.Value) interface{} {
successCallback := args[0]
hasFailureCallback := len(args) > 1

if (successCallback.Type() != js.TypeFunction) || (hasFailureCallback && args[1].Type() != js.TypeFunction) {
errCtor := js.Global().Get("Error")
js.Global().Get("console").Call("error", errCtor.New("invalid argument. not a function."))
handler.Close()
return nil
}

handler.Handle(func(m *model.Message) {
jsCallback := args[0]
if jsCallback.Type() != js.TypeFunction {
errCtor := js.Global().Get("Error")
js.Global().Get("console").Call("error", errCtor.New("invalid argument. not a function."))
handler.Close()
return
// workaround for js.ValueOf() panicking for slices of primitive types such as []int, []string, []bool, or
// a map containing such containers for their keys. this is quite dirty but hopefully when generics arrive
// we should be able to handle this situation more elegantly & hope go team improves js package.
var payload = m.Payload
switch payload.(type) {
case []string, []int, []bool, []float64, map[string]interface{}:
payload = toWasmSafePayload(payload)
}
jsCallback.Invoke(m.Payload)
successCallback.Invoke(payload)
}, func(e error) {
errCtor := js.Global().Get("Error")
js.Global().Get("console").Call("error", errCtor.New(e.Error()))
handler.Close()
if hasFailureCallback {
args[1].Invoke(e.Error())
} else {
errCtor := js.Global().Get("Error")
js.Global().Get("console").Call("error", errCtor.New(e.Error()))
}
})
return nil
})
}

func toWasmSafePayload(payload interface{}) interface{} {
refl := reflect.ValueOf(payload)
if refl.Kind() == reflect.Map {
for _, keyV := range refl.MapKeys() {
valV := refl.MapIndex(keyV)
refl.SetMapIndex(keyV, reflect.ValueOf(toWasmSafePayload(valV.Interface())))
}
return payload
} else if refl.Kind() == reflect.Slice || refl.Kind() == reflect.Array {
var converted = make([]interface{}, 0)
for i := 0; i < refl.Len(); i++ {
converted = append(converted, refl.Index(i).Interface())
}
return converted
}

// let js.ValueOf() handle the rest
return payload
}
2 changes: 1 addition & 1 deletion wasm/example/sample_wasm_app/build.go
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
package sample_wasm_app
package main

//go:generate bash -c "GOARCH=wasm; GOOS=js; go build -o ${OUT_DIR}/main.wasm main.go"
75 changes: 74 additions & 1 deletion wasm/example/sample_wasm_app/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,16 @@
package main

import (
"crypto/x509"
"encoding/pem"
"errors"
"fmt"
"github.com/vmware/transport-go/model"
"github.com/vmware/transport-go/wasm"
"syscall/js"
"time"

"github.com/vmware/transport-go/bus"
"github.com/vmware/transport-go/wasm"
)

func init() {
Expand All @@ -20,6 +25,8 @@ func init() {
cm.CreateChannel("goBusChan")
cm.CreateChannel("broadcast")

NewX509CertParser()

ticker := time.NewTicker(1 * time.Second)
go func() {
for {
Expand All @@ -32,6 +39,72 @@ func init() {
}()
}

type X509CertParser struct {
requestChannel string
responseChannel string
}

func NewX509CertParser() *X509CertParser {
p := &X509CertParser{
requestChannel: "x509-cert-parser-req",
responseChannel: "x509-cert-parser-resp",
}

// set up bus channels for receiving requests and sending responses.
busInstance := bus.GetBus()
cm := busInstance.GetChannelManager()
cm.CreateChannel(p.requestChannel)
cm.CreateChannel(p.responseChannel)

// set up a channel handler to accept and process requests from JS.
// once parsed or an error occurs, the response will be sent down the responseChannel.
handler, _ := busInstance.ListenRequestStream(p.requestChannel)
handler.Handle(func(message *model.Message) {
jsVal := message.Payload.(js.Value)
cert, err := p.parseFromJsValueString(jsVal)
if err != nil {
_ = busInstance.SendErrorMessage(p.responseChannel, err, nil)
return
}

response := map[string]interface{}{
"issuer": cert.Issuer.String(),
"dnsNames": cert.DNSNames,
"notBefore": cert.NotBefore.String(),
"notAfter": cert.NotAfter.String(),
"isCA": cert.IsCA,
"version": cert.Version,
}
// successful parse results are sent to the response channel for the success handler in the client
// to be invoked with.
_ = busInstance.SendResponseMessage(p.responseChannel, response, nil)
}, func(err error) {
// failed parse results will be sent to the same channel as well, but the error handler will be invoked instead
// in the client.
_ = busInstance.SendErrorMessage(p.responseChannel, err, nil)
})

return p
}

func (p *X509CertParser) parseFromJsValueString(input js.Value) (*x509.Certificate, error) {
var b = []byte(input.String())
var cert *x509.Certificate
var err error

block, _ := pem.Decode(b)
if block == nil {
return nil, errors.New("invalid pem format")
}

switch block.Type {
case "CERTIFICATE":
cert, err = x509.ParseCertificate(block.Bytes)
}

return cert, err
}

func main() {
twa := wasm.NewTransportWasmBridge(bus.GetBus())
twa.SetUpTransportWasmAPI()
Expand Down

0 comments on commit af9e76b

Please sign in to comment.