From 47b309c4d64e94fdafd5928123099ae16fa8ae66 Mon Sep 17 00:00:00 2001 From: Jono Wenger <jono@elixxir.io> Date: Wed, 17 Aug 2022 10:31:25 -0700 Subject: [PATCH] Implement ud.go --- main.go | 95 +++++++------ wasm/e2e.go | 18 +-- wasm/ud.go | 365 ++++++++++++++++++++++++++++++++++++++++++++++++ wasm/ud_test.go | 36 +++++ 4 files changed, 461 insertions(+), 53 deletions(-) create mode 100644 wasm/ud.go create mode 100644 wasm/ud_test.go diff --git a/main.go b/main.go index d53e5c47..ccbe089a 100644 --- a/main.go +++ b/main.go @@ -19,14 +19,38 @@ import ( func main() { fmt.Println("Go Web Assembly") + // wasm/backup.go + js.Global().Set("NewCmixFromBackup", js.FuncOf(wasm.NewCmixFromBackup)) + js.Global().Set("InitializeBackup", js.FuncOf(wasm.InitializeBackup)) + js.Global().Set("ResumeBackup", js.FuncOf(wasm.ResumeBackup)) + + // wasm/broadcast.go + js.Global().Set("NewBroadcastChannel", js.FuncOf(wasm.NewBroadcastChannel)) + // wasm/cmix.go js.Global().Set("NewCmix", js.FuncOf(wasm.NewCmix)) js.Global().Set("LoadCmix", js.FuncOf(wasm.LoadCmix)) + // wasm/dummy.go + js.Global().Set("NewDummyTrafficManager", + js.FuncOf(wasm.NewDummyTrafficManager)) + // wasm/e2e.go js.Global().Set("Login", js.FuncOf(wasm.Login)) js.Global().Set("LoginEphemeral", js.FuncOf(wasm.LoginEphemeral)) + // wasm/errors.go + js.Global().Set("CreateUserFriendlyErrorMessage", + js.FuncOf(wasm.CreateUserFriendlyErrorMessage)) + js.Global().Set("UpdateCommonErrors", + js.FuncOf(wasm.UpdateCommonErrors)) + + // wasm/fileTransfer.go + js.Global().Set("InitFileTransfer", js.FuncOf(wasm.InitFileTransfer)) + + // wasm/group.go + js.Global().Set("NewGroupChat", js.FuncOf(wasm.NewGroupChat)) + // wasm/identity.go js.Global().Set("StoreReceptionIdentity", js.FuncOf(wasm.StoreReceptionIdentity)) @@ -41,6 +65,15 @@ func main() { js.Global().Set("GetFactsFromContact", js.FuncOf(wasm.GetFactsFromContact)) + // wasm/logging.go + js.Global().Set("LogLevel", js.FuncOf(wasm.LogLevel)) + js.Global().Set("RegisterLogWriter", js.FuncOf(wasm.RegisterLogWriter)) + js.Global().Set("EnableGrpcLogs", js.FuncOf(wasm.EnableGrpcLogs)) + + // wasm/ndf.go + js.Global().Set("DownloadAndVerifySignedNdfWithUrl", + js.FuncOf(wasm.DownloadAndVerifySignedNdfWithUrl)) + // wasm/params.go js.Global().Set("GetDefaultCMixParams", js.FuncOf(wasm.GetDefaultCMixParams)) @@ -53,61 +86,35 @@ func main() { js.Global().Set("GetDefaultE2eFileTransferParams", js.FuncOf(wasm.GetDefaultE2eFileTransferParams)) - // wasm/logging.go - js.Global().Set("LogLevel", js.FuncOf(wasm.LogLevel)) - js.Global().Set("RegisterLogWriter", js.FuncOf(wasm.RegisterLogWriter)) - js.Global().Set("EnableGrpcLogs", js.FuncOf(wasm.EnableGrpcLogs)) - - // wasm/ndf.go - js.Global().Set("DownloadAndVerifySignedNdfWithUrl", - js.FuncOf(wasm.DownloadAndVerifySignedNdfWithUrl)) - - // wasm/version.go - js.Global().Set("GetVersion", js.FuncOf(wasm.GetVersion)) - js.Global().Set("GetGitVersion", js.FuncOf(wasm.GetGitVersion)) - js.Global().Set("GetDependencies", js.FuncOf(wasm.GetDependencies)) - - // wasm/secrets.go - js.Global().Set("GenerateSecret", js.FuncOf(wasm.GenerateSecret)) - - // wasm/dummy.go - js.Global().Set("NewDummyTrafficManager", - js.FuncOf(wasm.NewDummyTrafficManager)) - - // bindings/broadcast.go - js.Global().Set("NewBroadcastChannel", js.FuncOf(wasm.NewBroadcastChannel)) - - // bindings/backup.go - js.Global().Set("NewCmixFromBackup", js.FuncOf(wasm.NewCmixFromBackup)) - js.Global().Set("InitializeBackup", js.FuncOf(wasm.InitializeBackup)) - js.Global().Set("ResumeBackup", js.FuncOf(wasm.ResumeBackup)) - - // bindings/errors.go - js.Global().Set("CreateUserFriendlyErrorMessage", - js.FuncOf(wasm.CreateUserFriendlyErrorMessage)) - js.Global().Set("UpdateCommonErrors", - js.FuncOf(wasm.UpdateCommonErrors)) - - // bindings/fileTransfer.go - js.Global().Set("InitFileTransfer", js.FuncOf(wasm.InitFileTransfer)) - - // bindings/group.go - js.Global().Set("NewGroupChat", js.FuncOf(wasm.NewGroupChat)) - - // bindings/restlike.go + // wasm/restlike.go js.Global().Set("RestlikeRequest", js.FuncOf(wasm.RestlikeRequest)) js.Global().Set("RestlikeRequestAuth", js.FuncOf(wasm.RestlikeRequestAuth)) - // bindings/restlikeSingle.go + // wasm/restlikeSingle.go js.Global().Set("RequestRestLike", js.FuncOf(wasm.RequestRestLike)) js.Global().Set("AsyncRequestRestLike", js.FuncOf(wasm.AsyncRequestRestLike)) - // bindings/single.go + // wasm/secrets.go + js.Global().Set("GenerateSecret", js.FuncOf(wasm.GenerateSecret)) + + // wasm/single.go js.Global().Set("TransmitSingleUse", js.FuncOf(wasm.TransmitSingleUse)) js.Global().Set("Listen", js.FuncOf(wasm.Listen)) + // wasm/ud.go + js.Global().Set("NewOrLoadUd", js.FuncOf(wasm.NewOrLoadUd)) + js.Global().Set("NewUdManagerFromBackup", + js.FuncOf(wasm.NewUdManagerFromBackup)) + js.Global().Set("LookupUD", js.FuncOf(wasm.LookupUD)) + js.Global().Set("SearchUD", js.FuncOf(wasm.SearchUD)) + + // wasm/version.go + js.Global().Set("GetVersion", js.FuncOf(wasm.GetVersion)) + js.Global().Set("GetGitVersion", js.FuncOf(wasm.GetGitVersion)) + js.Global().Set("GetDependencies", js.FuncOf(wasm.GetDependencies)) + <-make(chan bool) os.Exit(0) } diff --git a/wasm/e2e.go b/wasm/e2e.go index de160033..b0f79041 100644 --- a/wasm/e2e.go +++ b/wasm/e2e.go @@ -66,10 +66,10 @@ func newE2eJS(api *bindings.E2e) map[string]interface{} { return e2eMap } -// GetID returns the ID for this [bindings.E2e] in the e2eTracker. +// GetID returns the ID for this E2e in the E2e tracker. // // Returns: -// - int of the ID +// - int func (e *E2e) GetID(js.Value, []js.Value) interface{} { return e.api.GetID() } @@ -110,15 +110,15 @@ func Login(_ js.Value, args []js.Value) interface{} { // in here. If callbacks is left nil, a default [auth.Callbacks] will be used. // // Parameters: -// - args[0] - ID of Cmix object in tracker (int) -// - args[1] - Javascript object that has functions that implement the -// [bindings.AuthCallbacks] interface -// - args[2] - JSON of the [xxdk.ReceptionIdentity] object (Uint8Array) -// - args[3] - JSON of [xxdk.E2EParams] (Uint8Array) +// - args[0] - ID of Cmix object in tracker (int). +// - args[1] - Javascript object that has functions that implement the. +// [bindings.AuthCallbacks] interface. +// - args[2] - JSON of the [xxdk.ReceptionIdentity] object (Uint8Array). +// - 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 LoginEphemeral(_ js.Value, args []js.Value) interface{} { callbacks := newAuthCallbacks(args[1]) identity := CopyBytesToGo(args[2]) diff --git a/wasm/ud.go b/wasm/ud.go new file mode 100644 index 00000000..503781e9 --- /dev/null +++ b/wasm/ud.go @@ -0,0 +1,365 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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" +) + +//////////////////////////////////////////////////////////////////////////////// +// Structs and Interfaces // +//////////////////////////////////////////////////////////////////////////////// + +// UserDiscovery wraps the [bindings.UserDiscovery] object so its methods can be +// wrapped to be Javascript compatible. +type UserDiscovery struct { + api *bindings.UserDiscovery +} + +// newE2eJS creates a new Javascript compatible object (map[string]interface{}) +// that matches the E2e structure. +func newUserDiscoveryJS(api *bindings.UserDiscovery) map[string]interface{} { + ud := UserDiscovery{api} + udMap := map[string]interface{}{ + "GetID": js.FuncOf(ud.GetID), + "GetFacts": js.FuncOf(ud.GetFacts), + "GetContact": js.FuncOf(ud.GetContact), + "ConfirmFact": js.FuncOf(ud.ConfirmFact), + "SendRegisterFact": js.FuncOf(ud.SendRegisterFact), + "PermanentDeleteAccount": js.FuncOf(ud.PermanentDeleteAccount), + "RemoveFact": js.FuncOf(ud.RemoveFact), + } + + return udMap +} + +// GetID returns the ID for this UserDiscovery in the UserDiscovery tracker. +// +// Returns: +// - int +func (ud *UserDiscovery) GetID(js.Value, []js.Value) interface{} { + return ud.api.GetID() +} + +// udNetworkStatus wraps Javascript callbacks to adhere to the +// [bindings.UdNetworkStatus] interface. +type udNetworkStatus struct { + udNetworkStatus func(args ...interface{}) js.Value +} + +func (uns *udNetworkStatus) UdNetworkStatus() int { + return uns.udNetworkStatus().Int() +} + +//////////////////////////////////////////////////////////////////////////////// +// Manager functions // +//////////////////////////////////////////////////////////////////////////////// + +// NewOrLoadUd loads an existing Manager from storage or creates a new one if +// there is no extant storage information. Parameters need be provided to +// specify how to connect to the User Discovery service. These parameters may be +// used to contact either the UD server hosted by the xx network team or a +// custom third-party operated server. For the former, all the information may +// be pulled from the NDF using the bindings. +// +// Params +// - args[0] - ID of E2e object in tracker (int). +// - args[1] - Javascript object that has functions that implement the +// [bindings.UdNetworkStatus] interface. This is the network follower +// function wrapped in [bindings.UdNetworkStatus]. +// - args[2] - the username the user wants to register with UD. If the user is +// already registered, this field may be blank (string). +// - args[3] - the registration validation signature; a signature provided by +// the network (i.e., the client registrar). This may be nil; however, UD may +// return an error in some cases (e.g., in a production level environment) +// (Uint8Array). +// - args[4] - the TLS certificate for the UD server this call will connect +// with. You may use the UD server run by the xx network team by using +// E2e.GetUdCertFromNdf (Uint8Array). +// - args[5] - marshalled [contact.Contact]. This represents the contact file +// of the server this call will connect with. You may use the UD server run +// by the xx network team by using E2e.GetUdContactFromNdf (Uint8Array). +// - args[6] - the IP address of the UD server this call will connect with. You +// may use the UD server run by the xx network team by using +// E2e.GetUdAddressFromNdf (string). +// +// Returns: +// - Javascript representation of the UserDiscovery object that is registered +// to the specified UD service. +// - Throws a TypeError if creating or loading fails. +func NewOrLoadUd(_ js.Value, args []js.Value) interface{} { + e2eID := args[0].Int() + follower := &udNetworkStatus{args[1].Get("UdNetworkStatus").Invoke} + username := args[2].String() + registrationValidationSignature := CopyBytesToGo(args[3]) + cert := CopyBytesToGo(args[4]) + contactFile := CopyBytesToGo(args[5]) + address := args[6].String() + + api, err := bindings.NewOrLoadUd(e2eID, follower, username, + registrationValidationSignature, cert, contactFile, address) + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return newUserDiscoveryJS(api) +} + +// NewUdManagerFromBackup builds a new user discover manager from a backup. It +// will construct a manager that is already registered and restore already +// registered facts into store. +// +// Note that it can take in both ane email address and a phone number or both. +// However, at least one fact must be specified; providing no facts will return +// an error. +// +// Parameters: +// - args[0] - ID of E2e object in tracker (int). +// - args[1] - Javascript object that has functions that implement the +// [bindings.UdNetworkStatus] interface. This is the network follower +// function wrapped in [bindings.UdNetworkStatus]. +// - args[2] - JSON of [fact.Fact] email address that is registered with UD +// (Uint8Array). +// - args[3] - JSON of [fact.Fact] phone number that is registered with UD +// (Uint8Array). +// - args[4] - the TLS certificate for the UD server this call will connect +// with. You may use the UD server run by the xx network team by using +// E2e.GetUdCertFromNdf (Uint8Array). +// - args[5] - marshalled [contact.Contact]. This represents the contact file +// of the server this call will connect with. You may use the UD server run +// by the xx network team by using E2e.GetUdContactFromNdf (Uint8Array). +// - args[6] - the IP address of the UD server this call will connect with. You +// may use the UD server run by the xx network team by using +// E2e.GetUdAddressFromNdf (string). +// +// Returns: +// - Javascript representation of the UserDiscovery object that is loaded from +// backup. +// - Throws a TypeError if getting UD from backup fails. +func NewUdManagerFromBackup(_ js.Value, args []js.Value) interface{} { + e2eID := args[0].Int() + follower := &udNetworkStatus{args[1].Get("UdNetworkStatus").Invoke} + emailFactJson := CopyBytesToGo(args[2]) + phoneFactJson := CopyBytesToGo(args[3]) + cert := CopyBytesToGo(args[4]) + contactFile := CopyBytesToGo(args[5]) + address := args[6].String() + + api, err := bindings.NewUdManagerFromBackup(e2eID, follower, emailFactJson, + phoneFactJson, cert, contactFile, address) + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return newUserDiscoveryJS(api) +} + +// GetFacts returns a JSON marshalled list of [fact.Fact] objects that exist +// within the Store's registeredFacts map. +// +// Returns: +// - JSON of [fact.FactList] (Uint8Array). +func (ud *UserDiscovery) GetFacts(js.Value, []js.Value) interface{} { + return CopyBytesToJS(ud.api.GetFacts()) +} + +// GetContact returns the marshalled bytes of the [contact.Contact] for UD as +// retrieved from the NDF. +// +// Returns: +// - JSON of [contact.Contact] (Uint8Array). +// - Throws TypeError if getting the contact fails. +func (ud *UserDiscovery) GetContact(js.Value, []js.Value) interface{} { + c, err := ud.api.GetContact() + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return CopyBytesToJS(c) +} + +// ConfirmFact confirms a fact first registered via SendRegisterFact. The +// confirmation ID comes from SendRegisterFact while the code will come over the +// associated communications system. +// +// Parameters: +// - args[0] - confirmation ID (string). +// - args[1] - code (string). +// +// Returns: +// - Throws TypeError if confirming the fact fails. +func (ud *UserDiscovery) ConfirmFact(_ js.Value, args []js.Value) interface{} { + err := ud.api.ConfirmFact(args[0].String(), args[1].String()) + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return nil +} + +// SendRegisterFact adds a fact for the user to user discovery. Will only +// succeed if the user is already registered and the system does not have the +// fact currently registered for any user. +// +// This does not complete the fact registration process, it returns a +// confirmation ID instead. Over the communications system the fact is +// associated with, a code will be sent. This confirmation ID needs to be called +// along with the code to finalize the fact. +// +// Parameters: +// - args[0] - JSON of [fact.Fact] (Uint8Array). +// +// Returns: +// - The confirmation ID (string). +// - Throws TypeError if sending the fact fails. +func (ud *UserDiscovery) SendRegisterFact(_ js.Value, args []js.Value) interface{} { + confirmationID, err := ud.api.SendRegisterFact(CopyBytesToGo(args[0])) + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return confirmationID +} + +// PermanentDeleteAccount removes the username associated with this user from +// the UD service. This will only take a username type fact, and the fact must +// be associated with this user. +// +// Parameters: +// - args[0] - JSON of [fact.Fact] (Uint8Array). +// +// Returns: +// - Throws TypeError if deletion fails. +func (ud *UserDiscovery) PermanentDeleteAccount(_ js.Value, args []js.Value) interface{} { + err := ud.api.PermanentDeleteAccount(CopyBytesToGo(args[0])) + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return nil +} + +// RemoveFact removes a previously confirmed fact. This will fail if the fact +// passed in is not UD service does not associate this fact with this user. +// +// Parameters: +// - args[0] - JSON of [fact.Fact] (Uint8Array). +// +// Returns: +// - Throws TypeError if removing the fact fails. +func (ud *UserDiscovery) RemoveFact(_ js.Value, args []js.Value) interface{} { + err := ud.api.RemoveFact(CopyBytesToGo(args[0])) + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return nil +} + +//////////////////////////////////////////////////////////////////////////////// +// User Discovery Lookup // +//////////////////////////////////////////////////////////////////////////////// + +// udLookupCallback wraps Javascript callbacks to adhere to the +// [bindings.UdLookupCallback] interface. +type udLookupCallback struct { + callback func(args ...interface{}) js.Value +} + +func (ulc *udLookupCallback) Callback(contactBytes []byte, err error) { + ulc.callback(CopyBytesToJS(contactBytes), err.Error()) +} + +// LookupUD returns the public key of the passed ID as known by the user +// discovery system or returns by the timeout. +// +// Parameters: +// - args[0] - ID of E2e object in tracker (int). +// - args[1] - JSON of User Discovery's [contact.Contact] (Uint8Array). +// - args[2] - Javascript object that has functions that implement the +// [bindings.UdLookupCallback] interface. +// - args[3] - JSON of [id.ID] for the user to look up (Uint8Array). +// - args[4] - JSON of [single.RequestParams] (Uint8Array). +// +// Returns: +// - JSON of [bindings.SingleUseSendReport], which can be passed into +// Cmix.WaitForRoundResult to see if the send succeeded. +// - Throws a TypeError if the lookup fails. +func LookupUD(_ js.Value, args []js.Value) interface{} { + e2eID := args[0].Int() + udContact := CopyBytesToGo(args[1]) + cb := &udLookupCallback{args[2].Get("Callback").Invoke} + lookupId := CopyBytesToGo(args[3]) + singleRequestParamsJSON := CopyBytesToGo(args[4]) + + report, err := bindings.LookupUD( + e2eID, udContact, cb, lookupId, singleRequestParamsJSON) + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return CopyBytesToJS(report) +} + +//////////////////////////////////////////////////////////////////////////////// +// User Discovery Search // +//////////////////////////////////////////////////////////////////////////////// + +// udSearchCallback wraps Javascript callbacks to adhere to the +// [bindings.UdSearchCallback] interface. +type udSearchCallback struct { + callback func(args ...interface{}) js.Value +} + +func (usc *udSearchCallback) Callback(contactListJSON []byte, err error) { + usc.callback(CopyBytesToJS(contactListJSON), err.Error()) +} + +// SearchUD searches user discovery for the passed Facts. The searchCallback +// will return a list of contacts, each having the facts it hit against. This is +// NOT intended to be used to search for multiple users at once; that can have a +// privacy reduction. Instead, it is intended to be used to search for a user +// where multiple pieces of information is known. +// +// Parameters: +// - args[0] - ID of E2e object in tracker (int). +// - args[1] - JSON of User Discovery's [contact.Contact] (Uint8Array). +// - args[2] - JSON of [fact.FactList] (Uint8Array). +// - args[4] - JSON of [single.RequestParams] (Uint8Array). +// +// Returns: +// - JSON of [bindings.SingleUseSendReport], which can be passed into +// Cmix.WaitForRoundResult to see if the send succeeded. +// - Throws a TypeError if the search fails. +func SearchUD(_ js.Value, args []js.Value) interface{} { + e2eID := args[0].Int() + udContact := CopyBytesToGo(args[1]) + cb := &udSearchCallback{args[2].Get("Callback").Invoke} + factListJSON := CopyBytesToGo(args[3]) + singleRequestParamsJSON := CopyBytesToGo(args[4]) + + report, err := bindings.SearchUD( + e2eID, udContact, cb, factListJSON, singleRequestParamsJSON) + if err != nil { + Throw(TypeError, err.Error()) + return nil + } + + return CopyBytesToJS(report) +} diff --git a/wasm/ud_test.go b/wasm/ud_test.go new file mode 100644 index 00000000..cfd3766d --- /dev/null +++ b/wasm/ud_test.go @@ -0,0 +1,36 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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" + "reflect" + "testing" +) + +// Tests that the map representing UserDiscovery returned by newUserDiscoveryJS +// contains all of the methods on UserDiscovery. +func Test_newUserDiscoveryJS(t *testing.T) { + udType := reflect.TypeOf(&UserDiscovery{}) + + ud := newUserDiscoveryJS(&bindings.UserDiscovery{}) + if len(ud) != udType.NumMethod() { + t.Errorf("UserDiscovery JS object does not have all methods."+ + "\nexpected: %d\nreceived: %d", udType.NumMethod(), len(ud)) + } + + for i := 0; i < udType.NumMethod(); i++ { + method := udType.Method(i) + + if _, exists := ud[method.Name]; !exists { + t.Errorf("Method %s does not exist.", method.Name) + } + } +} -- GitLab