From 0de1f5328f4618b04d3c4714be15abce071d93df Mon Sep 17 00:00:00 2001
From: Jono Wenger <jono@elixxir.io>
Date: Tue, 16 Aug 2022 15:11:19 -0700
Subject: [PATCH] Implement fileTransfer.go

---
 main.go                   |   4 +
 wasm/backup.go            |   2 +
 wasm/e2e.go               |   8 +-
 wasm/fileTransfer.go      | 333 ++++++++++++++++++++++++++++++++++++++
 wasm/fileTransfer_test.go |  54 +++++++
 5 files changed, 397 insertions(+), 4 deletions(-)
 create mode 100644 wasm/fileTransfer.go
 create mode 100644 wasm/fileTransfer_test.go

diff --git a/main.go b/main.go
index 62eafe71..a7edc16b 100644
--- a/main.go
+++ b/main.go
@@ -88,6 +88,10 @@ func main() {
 	js.Global().Set("UpdateCommonErrors",
 		js.FuncOf(wasm.UpdateCommonErrors))
 
+	// bindings/fileTransfer.go
+	js.Global().Set("InitFileTransfer",
+		js.FuncOf(wasm.InitFileTransfer))
+
 	<-make(chan bool)
 	os.Exit(0)
 }
diff --git a/wasm/backup.go b/wasm/backup.go
index cfa426ef..33a3edcf 100644
--- a/wasm/backup.go
+++ b/wasm/backup.go
@@ -5,6 +5,8 @@
 // LICENSE file                                                               //
 ////////////////////////////////////////////////////////////////////////////////
 
+//go:build js && wasm
+
 package wasm
 
 import (
diff --git a/wasm/e2e.go b/wasm/e2e.go
index 4a754fe7..46196fdf 100644
--- a/wasm/e2e.go
+++ b/wasm/e2e.go
@@ -24,7 +24,7 @@ type E2e struct {
 // that matches the E2e structure.
 func newE2eJS(api *bindings.E2e) map[string]interface{} {
 	e := E2e{api}
-	e2e := map[string]interface{}{
+	e2eMap := map[string]interface{}{
 		// e2e.go
 		"GetID":               js.FuncOf(e.GetID),
 		"GetContact":          js.FuncOf(e.GetContact),
@@ -63,7 +63,7 @@ func newE2eJS(api *bindings.E2e) map[string]interface{} {
 		"DeletePartnerCallback":   js.FuncOf(e.DeletePartnerCallback),
 	}
 
-	return e2e
+	return e2eMap
 }
 
 // GetID returns the ID for this [bindings.E2e] in the e2eTracker.
@@ -87,8 +87,8 @@ func (e *E2e) GetID(js.Value, []js.Value) interface{} {
 //  - args[3] - JSON of [xxdk.E2EParams] (Uint8Array).
 //
 // Returns:
-//  - Javascript representation of the E2e object
-//  - Throws a TypeError if logging in fails
+//  - Javascript representation of the E2e object.
+//  - Throws a TypeError if logging in fails.
 func Login(_ js.Value, args []js.Value) interface{} {
 	callbacks := newAuthCallbacks(args[1])
 	identity := CopyBytesToGo(args[2])
diff --git a/wasm/fileTransfer.go b/wasm/fileTransfer.go
new file mode 100644
index 00000000..d1879a16
--- /dev/null
+++ b/wasm/fileTransfer.go
@@ -0,0 +1,333 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+//go:build js && wasm
+
+package wasm
+
+import (
+	"gitlab.com/elixxir/client/bindings"
+	"syscall/js"
+)
+
+////////////////////////////////////////////////////////////////////////////////
+// File Transfer Structs and Interfaces                                       //
+////////////////////////////////////////////////////////////////////////////////
+
+// FileTransfer wraps the [bindings.FileTransfer] object so its methods can be
+// wrapped to be Javascript compatible.
+type FileTransfer struct {
+	api *bindings.FileTransfer
+}
+
+// newFileTransferJS creates a new Javascript compatible object
+// (map[string]interface{}) that matches the FileTransfer structure.
+func newFileTransferJS(api *bindings.FileTransfer) map[string]interface{} {
+	ft := FileTransfer{api}
+	ftMap := map[string]interface{}{
+		// Main functions
+		"Send":      js.FuncOf(ft.Send),
+		"Receive":   js.FuncOf(ft.Receive),
+		"CloseSend": js.FuncOf(ft.CloseSend),
+
+		// Callback registration functions
+		"RegisterSentProgressCallback":     js.FuncOf(ft.RegisterSentProgressCallback),
+		"RegisterReceivedProgressCallback": js.FuncOf(ft.RegisterReceivedProgressCallback),
+
+		// Utility functions
+		"MaxFileNameLen": js.FuncOf(ft.MaxFileNameLen),
+		"MaxFileTypeLen": js.FuncOf(ft.MaxFileTypeLen),
+		"MaxFileSize":    js.FuncOf(ft.MaxFileSize),
+		"MaxPreviewSize": js.FuncOf(ft.MaxPreviewSize),
+	}
+
+	return ftMap
+}
+
+// receiveFileCallback wraps Javascript callbacks to adhere to the
+// [bindings.ReceiveFileCallback] interface.
+type receiveFileCallback struct {
+	callback func(args ...interface{}) js.Value
+}
+
+func (rfc *receiveFileCallback) Callback(payload []byte, err error) {
+	rfc.callback(CopyBytesToJS(payload), err.Error())
+}
+
+// fileTransferSentProgressCallback wraps Javascript callbacks to adhere to the
+// [bindings.FileTransferSentProgressCallback] interface.
+type fileTransferSentProgressCallback struct {
+	callback func(args ...interface{}) js.Value
+}
+
+func (spc *fileTransferSentProgressCallback) Callback(
+	payload []byte, t *bindings.FilePartTracker, err error) {
+	spc.callback(CopyBytesToJS(payload), newFilePartTrackerJS(t), err.Error())
+}
+
+// fileTransferReceiveProgressCallback wraps Javascript callbacks to adhere to
+// the [bindings.FileTransferReceiveProgressCallback] interface.
+type fileTransferReceiveProgressCallback struct {
+	callback func(args ...interface{}) js.Value
+}
+
+func (rpc *fileTransferReceiveProgressCallback) Callback(
+	payload []byte, t *bindings.FilePartTracker, err error) {
+	rpc.callback(CopyBytesToJS(payload), newFilePartTrackerJS(t), err.Error())
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Main functions                                                             //
+////////////////////////////////////////////////////////////////////////////////
+
+// InitFileTransfer creates a bindings-level file transfer manager.
+//
+// Parameters:
+//  - args[0] - ID of E2e object in tracker (int).
+//  - args[1] - Javascript object that has functions that implement the
+//    [bindings.ReceiveFileCallback] interface.
+//  - args[2] - JSON of [fileTransfer.e2e.Params] (Uint8Array).
+//  - args[3] - JSON of [fileTransfer.Params] (Uint8Array).
+//
+// Returns:
+//  - Javascript representation of the FileTransfer object.
+//  - Throws a TypeError initialising the file transfer manager fails.
+func InitFileTransfer(_ js.Value, args []js.Value) interface{} {
+	rfc := &receiveFileCallback{args[1].Get("Callback").Invoke}
+	e2eFileTransferParamsJson := CopyBytesToGo(args[2])
+	fileTransferParamsJson := CopyBytesToGo(args[3])
+
+	api, err := bindings.InitFileTransfer(
+		args[0].Int(), rfc, e2eFileTransferParamsJson, fileTransferParamsJson)
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return newFileTransferJS(api)
+}
+
+// Send is the bindings-level function for sending a file.
+//
+// Parameters:
+//  - args[0] - JSON of [bindings.FileSend] (Uint8Array).
+//  - args[1] - marshalled recipient [id.ID] (Uint8Array).
+//  - args[2] - number of retries allowed (float)
+//  - args[3] - Javascript object that has functions that implement the
+//    [bindings.FileTransferSentProgressCallback] interface.
+//  - args[4] - duration to wait between progress callbacks triggering (string).
+//    Reference [time.ParseDuration] for info on valid duration strings.
+//
+// Returns:
+//  - A unique ID for this file transfer (Uint8Array).
+//  - Throws a TypeError if sending fails.
+func (f *FileTransfer) Send(_ js.Value, args []js.Value) interface{} {
+	payload := CopyBytesToGo(args[0])
+	recipientID := CopyBytesToGo(args[1])
+	retry := float32(args[2].Float())
+	spc := &fileTransferSentProgressCallback{args[3].Get("Callback").Invoke}
+
+	ftID, err := f.api.Send(payload, recipientID, retry, spc, args[4].String())
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return CopyBytesToJS(ftID)
+}
+
+// Receive returns the full file on the completion of the transfer. It deletes
+// internal references to the data and unregisters any attached progress
+// callbacks. Returns an error if the transfer is not complete, the full file
+// cannot be verified, or if the transfer cannot be found.
+//
+// Receive can only be called once the progress callback returns that the
+// file transfer is complete.
+//
+// Parameters:
+//  - args[0] - file transfer ID (Uint8Array).
+//
+// Returns:
+//  - File contents (Uint8Array).
+//  - Throws a TypeError the file transfer is incomplete or Receive has already
+//    been called.
+func (f *FileTransfer) Receive(_ js.Value, args []js.Value) interface{} {
+	file, err := f.api.Receive(CopyBytesToGo(args[0]))
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return CopyBytesToJS(file)
+}
+
+// CloseSend deletes a file from the internal storage once a transfer has
+// completed or reached the retry limit. Returns an error if the transfer has
+// not run out of retries.
+//
+// This function should be called once a transfer completes or errors out (as
+// reported by the progress callback).
+//
+// Parameters:
+//  - args[0] - file transfer ID (Uint8Array).
+//
+// Returns:
+//  - Throws a TypeError if the file transfer is incomplete.
+func (f *FileTransfer) CloseSend(_ js.Value, args []js.Value) interface{} {
+	err := f.api.CloseSend(CopyBytesToGo(args[0]))
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return nil
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Callback Registration Functions                                            //
+////////////////////////////////////////////////////////////////////////////////
+
+// RegisterSentProgressCallback allows for the registration of a callback to
+// track the progress of an individual sent file transfer.
+//
+// SentProgressCallback is auto registered on Send; this function should be
+// called when resuming clients or registering extra callbacks.
+//
+// Parameters:
+//  - args[0] - file transfer ID (Uint8Array).
+//  - args[1] - Javascript object that has functions that implement the
+//    [bindings.FileTransferSentProgressCallback] interface.
+//  - args[2] - duration to wait between progress callbacks triggering (string).
+//    Reference [time.ParseDuration] for info on valid duration strings.
+//
+// Returns:
+//  - Throws a TypeError if registering the callback fails.
+func (f *FileTransfer) RegisterSentProgressCallback(
+	_ js.Value, args []js.Value) interface{} {
+	tidBytes := CopyBytesToGo(args[0])
+	spc := &fileTransferSentProgressCallback{args[1].Get("Callback").Invoke}
+
+	err := f.api.RegisterSentProgressCallback(tidBytes, spc, args[2].String())
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return nil
+}
+
+// RegisterReceivedProgressCallback allows for the registration of a callback to
+// track the progress of an individual received file transfer.
+//
+// This should be done when a new transfer is received on the ReceiveCallback.
+//
+// Parameters:
+//  - args[0] - file transfer ID (Uint8Array).
+//  - args[1] - Javascript object that has functions that implement the
+//    [bindings.FileTransferReceiveProgressCallback] interface.
+//  - args[2] - duration to wait between progress callbacks triggering (string).
+//    Reference [time.ParseDuration] for info on valid duration strings.
+//
+// Returns:
+//  - Throws a TypeError if registering the callback fails.
+func (f *FileTransfer) RegisterReceivedProgressCallback(
+	_ js.Value, args []js.Value) interface{} {
+	tidBytes := CopyBytesToGo(args[0])
+	rpc := &fileTransferReceiveProgressCallback{args[1].Get("Callback").Invoke}
+
+	err := f.api.RegisterReceivedProgressCallback(
+		tidBytes, rpc, args[2].String())
+	if err != nil {
+		Throw(TypeError, err.Error())
+		return nil
+	}
+
+	return nil
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// Utility Functions                                                          //
+////////////////////////////////////////////////////////////////////////////////
+
+// MaxFileNameLen returns the max number of bytes allowed for a file name.
+//
+// Returns:
+//  - int
+func (f *FileTransfer) MaxFileNameLen(js.Value, []js.Value) interface{} {
+	return f.api.MaxFileNameLen()
+}
+
+// MaxFileTypeLen returns the max number of bytes allowed for a file type.
+//
+// Returns:
+//  - int
+func (f *FileTransfer) MaxFileTypeLen(js.Value, []js.Value) interface{} {
+	return f.api.MaxFileTypeLen()
+}
+
+// MaxFileSize returns the max number of bytes allowed for a file.
+//
+// Returns:
+//  - int
+func (f *FileTransfer) MaxFileSize(js.Value, []js.Value) interface{} {
+	return f.api.MaxFileSize()
+}
+
+// MaxPreviewSize returns the max number of bytes allowed for a file preview.
+//
+// Returns:
+//  - int
+func (f *FileTransfer) MaxPreviewSize(js.Value, []js.Value) interface{} {
+	return f.api.MaxPreviewSize()
+}
+
+////////////////////////////////////////////////////////////////////////////////
+// File Part Tracker                                                          //
+////////////////////////////////////////////////////////////////////////////////
+
+// FilePartTracker wraps the [bindings.FilePartTracker] object so its methods
+// can be wrapped to be Javascript compatible.
+type FilePartTracker struct {
+	api *bindings.FilePartTracker
+}
+
+// newFilePartTrackerJS creates a new Javascript compatible object
+// (map[string]interface{}) that matches the filePartTracker structure.
+func newFilePartTrackerJS(api *bindings.FilePartTracker) map[string]interface{} {
+	fpt := FilePartTracker{api}
+	ftMap := map[string]interface{}{
+		"GetPartStatus": js.FuncOf(fpt.GetPartStatus),
+		"GetNumParts":   js.FuncOf(fpt.GetNumParts),
+	}
+
+	return ftMap
+}
+
+// GetPartStatus returns the status of the file part with the given part number.
+//
+// The possible values for the status are:
+//  - 0 < Part does not exist
+//  - 0 = unsent
+//  - 1 = arrived (sender has sent a part, and it has arrived)
+//  - 2 = received (receiver has received a part)
+//
+// Parameters:
+//  - args[0] - index of part (int).
+//
+// Returns:
+//  - Part status (int).
+func (fpt *FilePartTracker) GetPartStatus(_ js.Value, args []js.Value) interface{} {
+	return fpt.api.GetPartStatus(args[0].Int())
+}
+
+// GetNumParts returns the total number of file parts in the transfer.
+//
+// Returns:
+//  - int
+func (fpt *FilePartTracker) GetNumParts(js.Value, []js.Value) interface{} {
+	return fpt.api.GetNumParts()
+}
diff --git a/wasm/fileTransfer_test.go b/wasm/fileTransfer_test.go
new file mode 100644
index 00000000..c6929088
--- /dev/null
+++ b/wasm/fileTransfer_test.go
@@ -0,0 +1,54 @@
+////////////////////////////////////////////////////////////////////////////////
+// Copyright © 2020 xx network SEZC                                           //
+//                                                                            //
+// Use of this source code is governed by a license that can be found in the  //
+// LICENSE file                                                               //
+////////////////////////////////////////////////////////////////////////////////
+
+package wasm
+
+import (
+	"gitlab.com/elixxir/client/bindings"
+	"reflect"
+	"testing"
+)
+
+// Tests that the map representing FileTransfer returned by newFileTransferJS
+// contains all of the methods on FileTransfer.
+func Test_newFileTransferJS(t *testing.T) {
+	ftType := reflect.TypeOf(&FileTransfer{})
+
+	ft := newFileTransferJS(&bindings.FileTransfer{})
+	if len(ft) != ftType.NumMethod() {
+		t.Errorf("File Transfer JS object does not have all methods."+
+			"\nexpected: %d\nreceived: %d", ftType.NumMethod(), len(ft))
+	}
+
+	for i := 0; i < ftType.NumMethod(); i++ {
+		method := ftType.Method(i)
+
+		if _, exists := ft[method.Name]; !exists {
+			t.Errorf("Method %s does not exist.", method.Name)
+		}
+	}
+}
+
+// Tests that the map representing FilePartTracker returned by
+// newFilePartTrackerJS contains all of the methods on FilePartTracker.
+func Test_newFilePartTrackerJS(t *testing.T) {
+	fptType := reflect.TypeOf(&FilePartTracker{})
+
+	fpt := newFilePartTrackerJS(&bindings.FilePartTracker{})
+	if len(fpt) != fptType.NumMethod() {
+		t.Errorf("File part tracker JS object does not have all methods."+
+			"\nexpected: %d\nreceived: %d", fptType.NumMethod(), len(fpt))
+	}
+
+	for i := 0; i < fptType.NumMethod(); i++ {
+		method := fptType.Method(i)
+
+		if _, exists := fpt[method.Name]; !exists {
+			t.Errorf("Method %s does not exist.", method.Name)
+		}
+	}
+}
-- 
GitLab