-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathwapc.go
132 lines (105 loc) · 2.98 KB
/
wapc.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
//go:build js && wasm
package wapc
import (
"errors"
"syscall/js"
)
var (
jswaPC = js.Global().Get("wapc")
array = js.Global().Get("Array")
uint8Array = js.Global().Get("Uint8Array")
)
func init() {
js.Global().Set("__guest_call", js.FuncOf(guestCall))
}
type Function func(payload []byte) ([]byte, error)
type Functions map[string]Function
var allFunctions = Functions{}
func RegisterFunction(name string, fn Function) {
allFunctions[name] = fn
}
func RegisterFunctions(functions Functions) {
for name, fn := range functions {
RegisterFunction(name, fn)
}
}
// HostCall invokes an operation on the host. The host uses namespace and
// operation to route the payload to the appropriate operation. The host will
// return a response payload if successful.
func HostCall(binding, namespace, operation string, payload []byte) ([]byte, error) {
result := jswaPC.Call("__host_call", stringToJS(binding), stringToJS(namespace), stringToJS(operation), bytesToJS(payload))
// No validation of the result here as that is kind of expensive.
var response []byte
if respValue := result.Index(0); !respValue.IsNull() {
response = bytesFromJS(respValue)
}
var err error
if errValue := result.Index(1); !errValue.IsNull() {
err = errors.New(errValue.String())
}
return response, err
}
func guestCall(_ js.Value, args []js.Value) any {
switch {
// Make sure there are 2 arguments.
case len(args) != 2:
return false
// Make sure the 1st one is a string.
case args[0].Type() != js.TypeString:
return false
// Make sure the 2nd one is an object.
case args[1].Type() != js.TypeObject:
return false
// Make sure the 2nd one is derived from Uint8Array, meaning a []byte.
case !args[1].InstanceOf(uint8Array):
return false
}
// Get the operation.
operation := args[0].String()
// Copy the payload over from the host to this guest.
payload := bytesFromJS(args[1])
// Find the function that matches the operation name.
fn, ok := allFunctions[operation]
if !ok {
guestError(`Could not find function "` + operation + `"`)
return false
}
// Call the function in a goroutine to allow it to perform non-blocking calls.
go func() {
response, err := fn(payload)
if err != nil {
guestError(err.Error())
return
}
guestResponse(response)
}()
return true
}
// guestResponse sets the guest response.
func guestResponse(payload []byte) {
jswaPC.Call("__guest_response", bytesToJS(payload))
}
// guestError sets the guest error.
func guestError(message string) {
jswaPC.Call("__guest_error", stringToJS(message))
}
// bytesFromJS converts a js.Value to a []byte.
func bytesFromJS(v js.Value) []byte {
length := v.Length()
if length == 0 {
return nil
}
s := make([]byte, length)
js.CopyBytesToGo(s, v)
return s
}
// bytesToJS converts a []byte to a js.Value.
func bytesToJS(d []byte) js.Value {
a := uint8Array.New(len(d))
js.CopyBytesToJS(a, d)
return a
}
// stringToJS converts a string to a js.Value.
func stringToJS(s string) js.Value {
return bytesToJS([]byte(s))
}