Skip to content
Snippets Groups Projects
follow.go 13.5 KiB
Newer Older
Jono Wenger's avatar
Jono Wenger committed
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation                                             //
Jono Wenger's avatar
Jono Wenger committed
//                                                                            //
// Use of this source code is governed by a license that can be found in the  //
// LICENSE file.                                                              //
Jono Wenger's avatar
Jono Wenger committed
////////////////////////////////////////////////////////////////////////////////

//go:build js && wasm

Jono Wenger's avatar
Jono Wenger committed
package wasm
Jono Wenger's avatar
Jono Wenger committed

import (
Jono Wenger's avatar
Jono Wenger committed
	"gitlab.com/elixxir/xxdk-wasm/storage"
Jono Wenger's avatar
Jono Wenger committed
	"gitlab.com/elixxir/xxdk-wasm/utils"
Jono Wenger's avatar
Jono Wenger committed
	"syscall/js"
)

// StartNetworkFollower kicks off the tracking of the network. It starts long-
// running network threads and returns an object for checking state and
// stopping those threads.
//
// Call this when returning from sleep and close when going back to sleep.
//
// These threads may become a significant drain on battery when offline, ensure
// they are stopped if there is no internet access.
//
// Threads Started:
//   - Network Follower (/network/follow.go)
//     tracks the network events and hands them off to workers for handling.
//   - Historical Round Retrieval (/network/rounds/historical.go)
//     retrieves data about rounds that are too old to be stored by the client.
//   - Message Retrieval Worker Group (/network/rounds/retrieve.go)
//     requests all messages in a given round from the gateway of the last
//     nodes.
//   - Message Handling Worker Group (/network/message/handle.go)
//     decrypts and partitions messages when signals via the Switchboard.
//   - Health Tracker (/network/health),
//     via the network instance, tracks the state of the network.
//   - Garbled Messages (/network/message/garbled.go)
//     can be signaled to check all recent messages that could be decoded. It
//     uses a message store on disk for persistence.
//   - Critical Messages (/network/message/critical.go)
//     ensures all protocol layer mandatory messages are sent. It uses a message
//     store on disk for persistence.
//   - KeyExchange Trigger (/keyExchange/trigger.go)
//     responds to sent rekeys and executes them.
Jono Wenger's avatar
Jono Wenger committed
//   - KeyExchange Confirm (/keyExchange/confirm.go)
//     responds to confirmations of successful rekey operations.
Jono Wenger's avatar
Jono Wenger committed
//   - Auth Callback (/auth/callback.go)
//     handles both auth confirm and requests.
//
// Parameters:
//   - args[0] - Timeout when stopping threads in milliseconds (int).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
//   - Throws a TypeError if starting the network follower fails.
func (c *Cmix) StartNetworkFollower(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	err := c.api.StartNetworkFollower(args[0].Int())
	if err != nil {
Jono Wenger's avatar
Jono Wenger committed
		utils.Throw(utils.TypeError, err)
Jono Wenger's avatar
Jono Wenger committed
		return nil
Jono Wenger's avatar
Jono Wenger committed
	}

Jono Wenger's avatar
Jono Wenger committed
	storage.IncrementNumClientsRunning()
Jono Wenger's avatar
Jono Wenger committed
	return nil
}

// StopNetworkFollower stops the network follower if it is running.
//
// If the network follower is running and this fails, the [Cmix] object will
Jono Wenger's avatar
Jono Wenger committed
// most likely be in an unrecoverable state and need to be trashed.
//
// Returns:
//   - Throws a TypeError if the follower is in the wrong state to stop or if it
//     fails to stop.
func (c *Cmix) StopNetworkFollower(js.Value, []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	err := c.api.StopNetworkFollower()
	if err != nil {
Jono Wenger's avatar
Jono Wenger committed
		utils.Throw(utils.TypeError, err)
Jono Wenger's avatar
Jono Wenger committed
		return nil
Jono Wenger's avatar
Jono Wenger committed
	}

Jono Wenger's avatar
Jono Wenger committed
	storage.DecrementNumClientsRunning()
Jono Wenger's avatar
Jono Wenger committed
	return nil
}

// SetTrackNetworkPeriod allows changing the frequency that follower threads
// are started.
//
// Parameters:
//   - args[0] - The duration of the period, in milliseconds (int).
func (c *Cmix) SetTrackNetworkPeriod(_ js.Value, args []js.Value) any {
	c.api.SetTrackNetworkPeriod(args[0].Int())
	return nil
}

Jono Wenger's avatar
Jono Wenger committed
// WaitForNetwork will block until either the network is healthy or the passed
// timeout is reached. It will return true if the network is healthy.
//
// Parameters:
//   - args[0] - Timeout when stopping threads in milliseconds (int).
Jono Wenger's avatar
Jono Wenger committed
//
Jono Wenger's avatar
Jono Wenger committed
// Returns a promise:
//   - A promise that resolves if the network is healthy and rejects if the
//     network is not healthy.
func (c *Cmix) WaitForNetwork(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	timeoutMS := args[0].Int()
	promiseFn := func(resolve, reject func(args ...any) js.Value) {
Jono Wenger's avatar
Jono Wenger committed
		if c.api.WaitForNetwork(timeoutMS) {
Jono Wenger's avatar
Jono Wenger committed
			resolve()
		} else {
			reject()
		}
	}

	return utils.CreatePromise(promiseFn)
Jono Wenger's avatar
Jono Wenger committed
}

// ReadyToSend determines if the network is ready to send messages on. It
// returns true if the network is healthy and if the client has registered with
// at least 70% of the nodes. Returns false otherwise.
//
// Returns:
//   - Returns true if network is ready to send on (boolean).
func (c *Cmix) ReadyToSend(js.Value, []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
// NetworkFollowerStatus gets the state of the network follower. It returns a
// status with the following values:
//
//	Stopped  - 0
//	Running  - 2000
//	Stopping - 3000
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
//   - Network status code (int).
func (c *Cmix) NetworkFollowerStatus(js.Value, []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	return c.api.NetworkFollowerStatus()
}

// GetNodeRegistrationStatus returns the current state of node registration.
//
// Returns:
//   - JSON of [bindings.NodeRegistrationReport] containing the number of nodes
//     that the user is registered with and the number of nodes present in the
//     NDF.
//   - An error if it cannot get the node registration status. The most likely
//     cause is that the network is unhealthy.
func (c *Cmix) GetNodeRegistrationStatus(js.Value, []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	b, err := c.api.GetNodeRegistrationStatus()
	if err != nil {
Jono Wenger's avatar
Jono Wenger committed
		utils.Throw(utils.TypeError, err)
Jono Wenger's avatar
Jono Wenger committed
		return nil
Jono Wenger's avatar
Jono Wenger committed
	}

Jono Wenger's avatar
Jono Wenger committed
	return utils.CopyBytesToJS(b)
Jono Wenger's avatar
Jono Wenger committed
}

Jono Wenger's avatar
Jono Wenger committed
// IsReady returns true if at least the given percent of node registrations have
// completed. If not all have completed, then it returns false and howClose will
// be a percent (0-1) of node registrations completed.
//
// Parameters:
//   - args[0] - The percentage of nodes required to be registered with to be
//     ready. This is a number between 0 and 1 (float64).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
//   - JSON of [bindings.IsReadyInfo] (Uint8Array).
//   - Throws TypeError if getting the information fails.
func (c *Cmix) IsReady(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	isReadyInfo, err := c.api.IsReady(args[0].Float())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return utils.CopyBytesToJS(isReadyInfo)
}

// PauseNodeRegistrations stops all node registrations and returns a function to
// resume them.
//
// Parameters:
//   - args[0] - The timeout, in milliseconds, to wait when stopping threads
//     before failing (int).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
//   - Throws TypeError if pausing fails.
func (c *Cmix) PauseNodeRegistrations(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	err := c.api.PauseNodeRegistrations(args[0].Int())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

// ChangeNumberOfNodeRegistrations changes the number of parallel node
// registrations up to the initialized maximum.
//
// Parameters:
//   - args[0] - The number of parallel node registrations (int).
//   - args[1] - The timeout, in milliseconds, to wait when changing node
//     registrations before failing (int).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
//   - Throws TypeError if changing registrations fails.
func (c *Cmix) ChangeNumberOfNodeRegistrations(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	err := c.api.ChangeNumberOfNodeRegistrations(args[0].Int(), args[1].Int())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

Jono Wenger's avatar
Jono Wenger committed
// HasRunningProcessies checks if any background threads are running and returns
// true if one or more are.
//
Jono Wenger's avatar
Jono Wenger committed
// This is meant to be used when [Cmix.NetworkFollowerStatus] returns Stopping.
// Due to the handling of comms on iOS, where the OS can block indefinitely, it
// may not enter the stopped state appropriately. This can be used instead.
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
//   - True if there are running processes (boolean).
func (c *Cmix) HasRunningProcessies(js.Value, []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	return c.api.HasRunningProcessies()
}

// IsHealthy returns true if the network is read to be in a healthy state where
// messages can be sent.
//
// Returns:
//   - True if the network is healthy (boolean).
func (c *Cmix) IsHealthy(js.Value, []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	return c.api.IsHealthy()
}

// GetRunningProcesses returns the names of all running processes at the time
// of this call. Note that this list may change and is subject to race
// conditions if multiple threads are in the process of starting or stopping.
//
// Returns:
//   - JSON of strings (Uint8Array).
//   - Throws TypeError if getting the processes fails.
//
// JSON Example:
//
//	{
//	  "FileTransfer{BatchBuilderThread, FilePartSendingThread#0, FilePartSendingThread#1, FilePartSendingThread#2, FilePartSendingThread#3}",
//	  "MessageReception Worker 0"
//	}
func (c *Cmix) GetRunningProcesses(js.Value, []js.Value) any {
	list, err := c.api.GetRunningProcesses()
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return utils.CopyBytesToJS(list)
}

Jono Wenger's avatar
Jono Wenger committed
// networkHealthCallback adheres to the [bindings.NetworkHealthCallback]
// interface.
type networkHealthCallback struct {
	callback func(args ...any) js.Value
Jono Wenger's avatar
Jono Wenger committed
}

// Callback receives notification if network health changes.
//
// Parameters:
//   - health - Returns true if the network is healthy and false otherwise
//     (boolean).
Jono Wenger's avatar
Jono Wenger committed
func (nhc *networkHealthCallback) Callback(health bool) { nhc.callback(health) }

// AddHealthCallback adds a callback that gets called whenever the network
// health changes. Returns a registration ID that can be used to unregister.
//
// Parameters:
//   - args[0] - Javascript object that has functions that implement the
//     [bindings.NetworkHealthCallback] interface.
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
//   - A registration ID that can be used to unregister the callback (int).
func (c *Cmix) AddHealthCallback(_ js.Value, args []js.Value) any {
	return c.api.AddHealthCallback(
Jono Wenger's avatar
Jono Wenger committed
		&networkHealthCallback{utils.WrapCB(args[0], "Callback")})
Jono Wenger's avatar
Jono Wenger committed
}

// RemoveHealthCallback removes a health callback using its registration ID.
//
// Parameters:
//   - args[0] - Callback registration ID (int).
func (c *Cmix) RemoveHealthCallback(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	c.api.RemoveHealthCallback(int64(args[0].Int()))
	return nil
}

// clientError adheres to the [bindings.ClientError] interface.
type clientError struct {
	report func(args ...any) js.Value
Jono Wenger's avatar
Jono Wenger committed
}

// Report handles errors from the network follower threads.
Jono Wenger's avatar
Jono Wenger committed
func (ce *clientError) Report(source, message, trace string) {
	ce.report(source, message, trace)
}

// RegisterClientErrorCallback registers the callback to handle errors from the
// long-running threads controlled by StartNetworkFollower and
// StopNetworkFollower.
//
// Parameters:
//   - args[0] - Javascript object that has functions that implement the
//     [bindings.ClientError] interface.
func (c *Cmix) RegisterClientErrorCallback(_ js.Value, args []js.Value) any {
	c.api.RegisterClientErrorCallback(
		&clientError{utils.WrapCB(args[0], "Report")})
Jono Wenger's avatar
Jono Wenger committed
	return nil
}
Jono Wenger's avatar
Jono Wenger committed

// trackServicesCallback adheres to the [bindings.TrackServicesCallback]
// interface.
type trackServicesCallback struct {
	callback func(args ...any) js.Value
// Callback is the callback for [Cmix.TrackServices]. This will pass to the user
// a JSON-marshalled list of backend services. If there was an error retrieving
// or marshalling the service list, there is an error for the second parameter,
// which will be non-null.
//
// Parameters:
//   - marshalData - Returns the JSON of [message.ServiceList] (Uint8Array).
//   - err - Returns an error on failure (Error).
//
// Example JSON:
//
//	[
//	  {
//	    "Id": "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD", // bytes of id.ID encoded as base64 string
//	    "Services": [
//	      {
//	        "Identifier": "AQID",                             // bytes encoded as base64 string
//	        "Tag": "TestTag 1",                               // string
//	        "Metadata": "BAUG"                                // bytes encoded as base64 string
//	      }
//	    ]
//	  },
//	  {
//	    "Id": "AAAAAAAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD",
//	    "Services": [
//	      {
//	        "Identifier": "AQID",
//	        "Tag": "TestTag 2",
//	        "Metadata": "BAUG"
//	      }
//	    ]
//	  },
//	]
Jono Wenger's avatar
Jono Wenger committed
func (tsc *trackServicesCallback) Callback(marshalData []byte, err error) {
	tsc.callback(utils.CopyBytesToJS(marshalData), utils.JsTrace(err))
}

// TrackServicesWithIdentity will return via a callback the list of services the
// backend keeps track of for the provided identity. This may be passed into
// other bindings call which may need context on the available services for this
// single identity. This will only return services for the given identity.
//
// Parameters:
//   - args[0] - ID of [E2e] object in tracker (int).
//   - args[1] - Javascript object that has functions that implement the
//     [bindings.ClientError] interface.
//
// Returns:
//   - Throws TypeError if the [E2e] ID is invalid.
func (c *Cmix) TrackServicesWithIdentity(_ js.Value, args []js.Value) any {
	err := c.api.TrackServicesWithIdentity(args[0].Int(),
		&trackServicesCallback{utils.WrapCB(args[0], "Callback")})
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

Jono Wenger's avatar
Jono Wenger committed
// TrackServices will return, via a callback, the list of services that the
// backend keeps track of, which is formally referred to as a
// [message.ServiceList]. This may be passed into other bindings call that may
// need context on the available services for this client.
//
// Parameters:
//   - args[0] - Javascript object that has functions that implement the
//     [bindings.TrackServicesCallback] interface.
func (c *Cmix) TrackServices(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	c.api.TrackServices(
		&trackServicesCallback{utils.WrapCB(args[0], "Callback")})
	return nil
}