github.com/rclone/rclone@v1.66.1-0.20240517100346-7b89735ae726/fs/rc/js/main.go (about)

     1  // Rclone as a wasm library
     2  //
     3  // This library exports the core rc functionality
     4  
     5  //go:build js
     6  
     7  package main
     8  
     9  import (
    10  	"context"
    11  	"encoding/json"
    12  	"errors"
    13  	"fmt"
    14  	"log"
    15  	"net/http"
    16  	"runtime"
    17  	"syscall/js"
    18  
    19  	"github.com/rclone/rclone/fs"
    20  	"github.com/rclone/rclone/fs/rc"
    21  
    22  	// Core functionality we need
    23  	_ "github.com/rclone/rclone/fs/operations"
    24  	_ "github.com/rclone/rclone/fs/sync"
    25  
    26  	//	_ "github.com/rclone/rclone/backend/all" // import all backends
    27  
    28  	// Backends
    29  	_ "github.com/rclone/rclone/backend/memory"
    30  )
    31  
    32  var (
    33  	document js.Value
    34  	jsJSON   js.Value
    35  )
    36  
    37  func getElementById(name string) js.Value {
    38  	node := document.Call("getElementById", name)
    39  	if node.IsUndefined() {
    40  		log.Fatalf("Couldn't find element %q", name)
    41  	}
    42  	return node
    43  }
    44  
    45  func time() int {
    46  	return js.Global().Get("Date").New().Call("getTime").Int()
    47  }
    48  
    49  func paramToValue(in rc.Params) (out js.Value) {
    50  	return js.Value{}
    51  }
    52  
    53  // errorValue turns an error into a js.Value
    54  func errorValue(method string, in js.Value, err error) js.Value {
    55  	fs.Errorf(nil, "rc: %q: error: %v", method, err)
    56  	// Adjust the error return for some well known errors
    57  	status := http.StatusInternalServerError
    58  	switch {
    59  	case errors.Is(err, fs.ErrorDirNotFound) || errors.Is(err, fs.ErrorObjectNotFound):
    60  		status = http.StatusNotFound
    61  	case rc.IsErrParamInvalid(err) || rc.IsErrParamNotFound(err):
    62  		status = http.StatusBadRequest
    63  	}
    64  	return js.ValueOf(map[string]interface{}{
    65  		"status": status,
    66  		"error":  err.Error(),
    67  		"input":  in,
    68  		"path":   method,
    69  	})
    70  }
    71  
    72  // rcCallback is a callback for javascript to access the api
    73  //
    74  // FIXME should this should return a promise so we can return errors properly?
    75  func rcCallback(this js.Value, args []js.Value) interface{} {
    76  	ctx := context.Background() // FIXME
    77  	log.Printf("rcCallback: this=%v args=%v", this, args)
    78  
    79  	if len(args) != 2 {
    80  		return errorValue("", js.Undefined(), errors.New("need two parameters to rc call"))
    81  	}
    82  	method := args[0].String()
    83  	inRaw := args[1]
    84  	var in = rc.Params{}
    85  	switch inRaw.Type() {
    86  	case js.TypeNull:
    87  	case js.TypeObject:
    88  		inJSON := jsJSON.Call("stringify", inRaw).String()
    89  		err := json.Unmarshal([]byte(inJSON), &in)
    90  		if err != nil {
    91  			return errorValue(method, inRaw, fmt.Errorf("couldn't unmarshal input: %w", err))
    92  		}
    93  	default:
    94  		return errorValue(method, inRaw, errors.New("in parameter must be null or object"))
    95  	}
    96  
    97  	call := rc.Calls.Get(method)
    98  	if call == nil {
    99  		return errorValue(method, inRaw, fmt.Errorf("method %q not found", method))
   100  	}
   101  
   102  	out, err := call.Fn(ctx, in)
   103  	if err != nil {
   104  		return errorValue(method, inRaw, fmt.Errorf("method call failed: %w", err))
   105  	}
   106  	if out == nil {
   107  		return nil
   108  	}
   109  	var out2 map[string]interface{}
   110  	err = rc.Reshape(&out2, out)
   111  	if err != nil {
   112  		return errorValue(method, inRaw, fmt.Errorf("result reshape failed: %w", err))
   113  	}
   114  
   115  	return js.ValueOf(out2)
   116  }
   117  
   118  func main() {
   119  	log.Printf("Running on goos/goarch = %s/%s", runtime.GOOS, runtime.GOARCH)
   120  	if js.Global().IsUndefined() {
   121  		log.Fatalf("Didn't find Global - not running in browser")
   122  	}
   123  	document = js.Global().Get("document")
   124  	if document.IsUndefined() {
   125  		log.Fatalf("Didn't find document - not running in browser")
   126  	}
   127  
   128  	jsJSON = js.Global().Get("JSON")
   129  	if jsJSON.IsUndefined() {
   130  		log.Fatalf("can't find JSON")
   131  	}
   132  
   133  	// Set rc
   134  	js.Global().Set("rc", js.FuncOf(rcCallback))
   135  
   136  	// Signal that it is valid
   137  	rcValidResolve := js.Global().Get("rcValidResolve")
   138  	if rcValidResolve.IsUndefined() {
   139  		log.Fatalf("Didn't find rcValidResolve")
   140  	}
   141  	rcValidResolve.Invoke()
   142  
   143  	// Wait forever
   144  	select {}
   145  }