rclone/fs/rc/js/main.go
Nick Craig-Wood aab9aa8a2e js: add experimental interface for integrating rclone into browsers
This works by compiling rclone to wasm and exporting the RC api to
javascript.
2020-08-10 17:32:21 +01:00

145 lines
3.5 KiB
Go

// Rclone as a wasm library
//
// This library exports the core rc functionality
// +build js
package main
import (
"context"
"encoding/json"
"log"
"net/http"
"runtime"
"syscall/js"
"github.com/pkg/errors"
"github.com/rclone/rclone/fs"
"github.com/rclone/rclone/fs/rc"
// Core functionality we need
_ "github.com/rclone/rclone/fs/operations"
_ "github.com/rclone/rclone/fs/sync"
// _ "github.com/rclone/rclone/backend/all" // import all backends
// Backends
_ "github.com/rclone/rclone/backend/memory"
)
var (
document js.Value
jsJSON js.Value
)
func getElementById(name string) js.Value {
node := document.Call("getElementById", name)
if node.IsUndefined() {
log.Fatalf("Couldn't find element %q", name)
}
return node
}
func time() int {
return js.Global().Get("Date").New().Call("getTime").Int()
}
func paramToValue(in rc.Params) (out js.Value) {
return js.Value{}
}
// errorValue turns an error into a js.Value
func errorValue(method string, in js.Value, err error) js.Value {
fs.Errorf(nil, "rc: %q: error: %v", method, err)
// Adjust the error return for some well known errors
errOrig := errors.Cause(err)
status := http.StatusInternalServerError
switch {
case errOrig == fs.ErrorDirNotFound || errOrig == fs.ErrorObjectNotFound:
status = http.StatusNotFound
case rc.IsErrParamInvalid(err) || rc.IsErrParamNotFound(err):
status = http.StatusBadRequest
}
return js.ValueOf(map[string]interface{}{
"status": status,
"error": err.Error(),
"input": in,
"path": method,
})
}
// rcCallback is a callback for javascript to access the api
//
// FIXME should this should return a promise so we can return errors properly?
func rcCallback(this js.Value, args []js.Value) interface{} {
ctx := context.Background() // FIXME
log.Printf("rcCallback: this=%v args=%v", this, args)
if len(args) != 2 {
return errorValue("", js.Undefined(), errors.New("need two parameters to rc call"))
}
method := args[0].String()
inRaw := args[1]
var in = rc.Params{}
switch inRaw.Type() {
case js.TypeNull:
case js.TypeObject:
inJSON := jsJSON.Call("stringify", inRaw).String()
err := json.Unmarshal([]byte(inJSON), &in)
if err != nil {
return errorValue(method, inRaw, errors.Wrap(err, "couldn't unmarshal input"))
}
default:
return errorValue(method, inRaw, errors.New("in parameter must be null or object"))
}
call := rc.Calls.Get(method)
if call == nil {
return errorValue(method, inRaw, errors.Errorf("method %q not found", method))
}
out, err := call.Fn(ctx, in)
if err != nil {
return errorValue(method, inRaw, errors.Wrap(err, "method call failed"))
}
if out == nil {
return nil
}
var out2 map[string]interface{}
err = rc.Reshape(&out2, out)
if err != nil {
return errorValue(method, inRaw, errors.Wrap(err, "result reshape failed"))
}
return js.ValueOf(out2)
}
func main() {
log.Printf("Running on goos/goarch = %s/%s", runtime.GOOS, runtime.GOARCH)
if js.Global().IsUndefined() {
log.Fatalf("Didn't find Global - not running in browser")
}
document = js.Global().Get("document")
if document.IsUndefined() {
log.Fatalf("Didn't find document - not running in browser")
}
jsJSON = js.Global().Get("JSON")
if jsJSON.IsUndefined() {
log.Fatalf("can't find JSON")
}
// Set rc
js.Global().Set("rc", js.FuncOf(rcCallback))
// Signal that it is valid
rcValidResolve := js.Global().Get("rcValidResolve")
if rcValidResolve.IsUndefined() {
log.Fatalf("Didn't find rcValidResolve")
}
rcValidResolve.Invoke()
// Wait forever
select {}
}