js: add experimental interface for integrating rclone into browsers
This works by compiling rclone to wasm and exporting the RC api to javascript.
This commit is contained in:
parent
3a14b1d5a9
commit
aab9aa8a2e
8 changed files with 830 additions and 0 deletions
145
fs/rc/js/main.go
Normal file
145
fs/rc/js/main.go
Normal file
|
@ -0,0 +1,145 @@
|
|||
// 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 {}
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue