Skip to content
Snippets Groups Projects
channels.go 75.4 KiB
Newer Older
Jono Wenger's avatar
Jono Wenger committed
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation                                             //
//                                                                            //
// 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
package wasm

import (
	"encoding/base64"
Jono Wenger's avatar
Jono Wenger committed
	"encoding/json"
	"errors"
Jono Wenger's avatar
Jono Wenger committed
	"gitlab.com/elixxir/client/v4/channels"
Jake Taylor's avatar
Jake Taylor committed
	channelsDb "gitlab.com/elixxir/xxdk-wasm/indexedDb/channels"
	"gitlab.com/xx_network/primitives/id"
	"sync"
Jono Wenger's avatar
Jono Wenger committed
	"gitlab.com/elixxir/xxdk-wasm/utils"
)

////////////////////////////////////////////////////////////////////////////////
// Basic Channel API                                                          //
////////////////////////////////////////////////////////////////////////////////

// ChannelsManager wraps the [bindings.ChannelsManager] object so its methods
// can be wrapped to be Javascript compatible.
type ChannelsManager struct {
	api *bindings.ChannelsManager
}

// newChannelsManagerJS creates a new Javascript compatible object
// (map[string]any) that matches the [ChannelsManager] structure.
func newChannelsManagerJS(api *bindings.ChannelsManager) map[string]any {
Jono Wenger's avatar
Jono Wenger committed
	cm := ChannelsManager{api}
	channelsManagerMap := map[string]any{
Jono Wenger's avatar
Jono Wenger committed
		// Basic Channel API
		"GetID":           js.FuncOf(cm.GetID),
		"GenerateChannel": js.FuncOf(cm.GenerateChannel),
		"JoinChannel":     js.FuncOf(cm.JoinChannel),
		"GetChannels":     js.FuncOf(cm.GetChannels),
		"LeaveChannel":    js.FuncOf(cm.LeaveChannel),
		"ReplayChannel":   js.FuncOf(cm.ReplayChannel),
Jono Wenger's avatar
Jono Wenger committed

		// Share URL
		"GetShareURL": js.FuncOf(cm.GetShareURL),
Jono Wenger's avatar
Jono Wenger committed

		// Channel Sending Methods and Reports
Jono Wenger's avatar
Jono Wenger committed
		"SendGeneric":           js.FuncOf(cm.SendGeneric),
		"SendAdminGeneric":      js.FuncOf(cm.SendAdminGeneric),
		"SendMessage":           js.FuncOf(cm.SendMessage),
		"SendReply":             js.FuncOf(cm.SendReply),
		"SendReaction":          js.FuncOf(cm.SendReaction),
Jono Wenger's avatar
Jono Wenger committed
		"DeleteMessage":         js.FuncOf(cm.DeleteMessage),
		"PinMessage":            js.FuncOf(cm.PinMessage),
		"MuteUser":              js.FuncOf(cm.MuteUser),
Jono Wenger's avatar
Jono Wenger committed
		"GetIdentity":           js.FuncOf(cm.GetIdentity),
		"ExportPrivateIdentity": js.FuncOf(cm.ExportPrivateIdentity),
		"GetStorageTag":         js.FuncOf(cm.GetStorageTag),
		"SetNickname":           js.FuncOf(cm.SetNickname),
		"DeleteNickname":        js.FuncOf(cm.DeleteNickname),
		"GetNickname":           js.FuncOf(cm.GetNickname),
Jono Wenger's avatar
Jono Wenger committed
		"Muted":                 js.FuncOf(cm.Muted),
Jono Wenger's avatar
Jono Wenger committed
		"GetMutedUsers":         js.FuncOf(cm.GetMutedUsers),
		"IsChannelAdmin":        js.FuncOf(cm.IsChannelAdmin),
		"ExportChannelAdminKey": js.FuncOf(cm.ExportChannelAdminKey),
		"VerifyChannelAdminKey": js.FuncOf(cm.VerifyChannelAdminKey),
		"ImportChannelAdminKey": js.FuncOf(cm.ImportChannelAdminKey),
		"DeleteChannelAdminKey": js.FuncOf(cm.DeleteChannelAdminKey),
Jono Wenger's avatar
Jono Wenger committed

		// Channel Receiving Logic and Callback Registration
		"RegisterReceiveHandler": js.FuncOf(cm.RegisterReceiveHandler),
	}

	return channelsManagerMap
}

// GetID returns the ID for this [ChannelsManager] in the [ChannelsManager]
// tracker.
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - Tracker ID (int).
func (cm *ChannelsManager) GetID(js.Value, []js.Value) any {
	return cm.api.GetID()
Jono Wenger's avatar
Jono Wenger committed
// GenerateChannelIdentity creates a new private channel identity
// ([channel.PrivateIdentity]) from scratch and assigns it a codename.
//
// The public component can be retrieved as JSON via
// [GetPublicChannelIdentityFromPrivate].
Jono Wenger's avatar
Jono Wenger committed
//
// Parameters:
//   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
//     using [Cmix.GetID].
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - Marshalled bytes of [channel.PrivateIdentity] (Uint8Array).
//   - Throws a TypeError if generating the identity fails.
func GenerateChannelIdentity(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	pi, err := bindings.GenerateChannelIdentity(args[0].Int())
Jono Wenger's avatar
Jono Wenger committed
	if err != nil {
		utils.Throw(utils.TypeError, err)
Jono Wenger's avatar
Jono Wenger committed
		return nil
	}

	return utils.CopyBytesToJS(pi)
}

// identityMap stores identities previously generated by ConstructIdentity.
var identityMap sync.Map

// ConstructIdentity creates a codename in a public [channel.Identity] from an
// extant identity for a given codeset version.
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - The Ed25519 public key (Uint8Array).
//   - args[1] - The version of the codeset used to generate the identity (int).
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - JSON of [channel.Identity] (Uint8Array).
//   - Throws a TypeError if constructing the identity fails.
func ConstructIdentity(_ js.Value, args []js.Value) any {
	// Note: This function is similar to constructIdentity below except that it
	//  uses a sync.Map backend to increase efficiency for identities that were
	//  already generated in this browser session.

	pubKey := utils.CopyBytesToGo(args[0])
	pubKeyBase64 := base64.StdEncoding.EncodeToString(pubKey)
	identityObj, exists := identityMap.Load(pubKeyBase64)
	if exists {
		return utils.CopyBytesToJS(identityObj.([]byte))
	}

	identity, err := bindings.ConstructIdentity(
		utils.CopyBytesToGo(args[0]), args[1].Int())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	identityMap.Store(base64.StdEncoding.EncodeToString(pubKey), identity)

	return utils.CopyBytesToJS(identity)
}

// constructIdentity constructs a [channel.Identity] from a user's public key
// and codeset version. This function is retain for benchmarking purposes.
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - The Ed25519 public key (Uint8Array).
//   - args[1] - The version of the codeset used to generate the identity (int).
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - JSON of [channel.Identity] (Uint8Array).
//   - Throws a TypeError if constructing the identity fails.
func constructIdentity(_ js.Value, args []js.Value) any {
	identity, err := bindings.ConstructIdentity(
		utils.CopyBytesToGo(args[0]), args[1].Int())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return utils.CopyBytesToJS(identity)
}

Jono Wenger's avatar
Jono Wenger committed
// ImportPrivateIdentity generates a new [channel.PrivateIdentity] from exported
// data.
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - The password used to encrypt the identity (string).
//   - args[2] - The encrypted data from [ChannelsManager.ExportPrivateIdentity]
//     (Uint8Array).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - JSON of [channel.PrivateIdentity] (Uint8Array).
//   - Throws a TypeError if importing the identity fails.
func ImportPrivateIdentity(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	password := args[0].String()
	data := utils.CopyBytesToGo(args[1])

	pi, err := bindings.ImportPrivateIdentity(password, data)
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
Jono Wenger's avatar
Jono Wenger committed
	return utils.CopyBytesToJS(pi)
Jono Wenger's avatar
Jono Wenger committed
// GetPublicChannelIdentity constructs a public identity ([channel.Identity])
// from a bytes version and returns it JSON marshaled.
Jono Wenger's avatar
Jono Wenger committed
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - Bytes of the public identity ([channel.Identity]) (Uint8Array).
Jono Wenger's avatar
Jono Wenger committed
//
Jono Wenger's avatar
Jono Wenger committed
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - JSON of the constructed [channel.Identity] (Uint8Array).
//   - Throws a TypeError if unmarshalling the bytes or marshalling the identity
//     fails.
func GetPublicChannelIdentity(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	marshaledPublic := utils.CopyBytesToGo(args[0])
	pi, err := bindings.GetPublicChannelIdentity(marshaledPublic)
	if err != nil {
		utils.Throw(utils.TypeError, err)
Jono Wenger's avatar
Jono Wenger committed
		return nil
Jono Wenger's avatar
Jono Wenger committed
	return utils.CopyBytesToJS(pi)
}

// GetPublicChannelIdentityFromPrivate returns the public identity
// ([channel.Identity]) contained in the given private identity
// ([channel.PrivateIdentity]).
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - Bytes of the private identity
//     ([channel.PrivateIdentity]) (Uint8Array).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - JSON of the public identity ([channel.Identity]) (Uint8Array).
//   - Throws a TypeError if unmarshalling the bytes or marshalling the identity
//     fails.
func GetPublicChannelIdentityFromPrivate(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	marshaledPrivate := utils.CopyBytesToGo(args[0])
Jono Wenger's avatar
Jono Wenger committed
	identity, err :=
		bindings.GetPublicChannelIdentityFromPrivate(marshaledPrivate)
Jono Wenger's avatar
Jono Wenger committed
	if err != nil {
		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(identity)
}

// NewChannelsManager creates a new [ChannelsManager] from a new private
// identity ([channel.PrivateIdentity]).
//
// This is for creating a manager for an identity for the first time. For
// generating a new one channel identity, use [GenerateChannelIdentity]. To
// reload this channel manager, use [LoadChannelsManager], passing in the
// storage tag retrieved by [ChannelsManager.GetStorageTag].
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
//     using [Cmix.GetID].
//   - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is
//     generated by [GenerateChannelIdentity] (Uint8Array).
//   - args[2] - A function that initialises and returns a Javascript object
//     that matches the [bindings.EventModel] interface. The function must match
//     the Build function in [bindings.EventModelBuilder].
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - Javascript representation of the [ChannelsManager] object.
//   - Throws a TypeError if creating the manager fails.
func NewChannelsManager(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	privateIdentity := utils.CopyBytesToGo(args[1])

	em := &eventModelBuilder{args[2].Invoke}
Jono Wenger's avatar
Jono Wenger committed

	cm, err := bindings.NewChannelsManager(args[0].Int(), privateIdentity, em)
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return newChannelsManagerJS(cm)
// LoadChannelsManager loads an existing [ChannelsManager] for the given storage
// tag.
//
// This is for loading a manager for an identity that has already been created.
// The channel manager should have previously been created with
// [NewChannelsManager] and the storage is retrievable with
// [ChannelsManager.GetStorageTag].
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
//     using [Cmix.GetID].
//   - args[1] - The storage tag associated with the previously created channel
//     manager and retrieved with [ChannelsManager.GetStorageTag] (string).
//   - args[2] - A function that initialises and returns a Javascript object
//     that matches the [bindings.EventModel] interface. The function must match
//     the Build function in [bindings.EventModelBuilder].
Jono Wenger's avatar
Jono Wenger committed
//   - Javascript representation of the [ChannelsManager] object.
//   - Throws a TypeError if loading the manager fails.
func LoadChannelsManager(_ js.Value, args []js.Value) any {
	em := &eventModelBuilder{args[2].Invoke}
	cm, err := bindings.LoadChannelsManager(args[0].Int(), args[1].String(), em)
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return newChannelsManagerJS(cm)
}

Jono Wenger's avatar
Jono Wenger committed
// NewChannelsManagerWithIndexedDb creates a new [ChannelsManager] from a new
// private identity ([channel.PrivateIdentity]) and using indexedDb as a backend
// to manage the event model.
//
// This is for creating a manager for an identity for the first time. For
// generating a new one channel identity, use [GenerateChannelIdentity]. To
// reload this channel manager, use [LoadChannelsManagerWithIndexedDb], passing
Jono Wenger's avatar
Jono Wenger committed
// in the storage tag retrieved by [ChannelsManager.GetStorageTag].
// This function initialises an indexedDb database.
//
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
//     using [Cmix.GetID].
//   - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is
//     generated by [GenerateChannelIdentity] (Uint8Array).
//   - args[2] - Function that takes in the same parameters as
//     [indexedDb.MessageReceivedCallback]. On the Javascript side, the UUID is
//     returned as an int and the channelID as a Uint8Array. The row in the
//     database that was updated can be found using the UUID. The channel ID is
//     provided so that the recipient can filter if they want to the processes
//     the update now or not. An "update" bool is present which tells you if the
//     row is new or if it is an edited old row.
//   - args[3] - ID of [ChannelDbCipher] object in tracker (int). Create this
//     object with [NewChannelsDatabaseCipher] and get its id with
//     [ChannelDbCipher.GetID].
Jono Wenger's avatar
Jono Wenger committed
//   - Resolves to a Javascript representation of the [ChannelsManager] object.
//   - Rejected with an error if loading indexedDb or the manager fails.
//   - Throws a TypeError if the cipher ID does not correspond to a cipher.
func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
	cmixID := args[0].Int()
	privateIdentity := utils.CopyBytesToGo(args[1])
	messageReceivedCB := args[2]
	cipherID := args[3].Int()

	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
	if err != nil {
		utils.Throw(utils.TypeError, err)
	}

	return newChannelsManagerWithIndexedDb(
		cmixID, privateIdentity, messageReceivedCB, cipher)
}

// NewChannelsManagerWithIndexedDbUnsafe creates a new [ChannelsManager] from a
// new private identity ([channel.PrivateIdentity]) and using indexedDb as a
// backend to manage the event model. However, the data is written in plain text
// and not encrypted. It is recommended that you do not use this in production.
//
// This is for creating a manager for an identity for the first time. For
// generating a new one channel identity, use [GenerateChannelIdentity]. To
Jono Wenger's avatar
Jono Wenger committed
// reload this channel manager, use [LoadChannelsManagerWithIndexedDbUnsafe],
// passing in the storage tag retrieved by [ChannelsManager.GetStorageTag].
//
// This function initialises an indexedDb database.
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
//     using [Cmix.GetID].
//   - args[1] - Bytes of a private identity ([channel.PrivateIdentity]) that is
//     generated by [GenerateChannelIdentity] (Uint8Array).
//   - args[2] - Function that takes in the same parameters as
//     [indexedDb.MessageReceivedCallback]. On the Javascript side, the UUID is
//     returned as an int and the channelID as a Uint8Array. The row in the
//     database that was updated can be found using the UUID. The channel ID is
//     provided so that the recipient can filter if they want to the processes
//     the update now or not. An "update" bool is present which tells you if
//     the row is new or if it is an edited old row
Jono Wenger's avatar
Jono Wenger committed
//   - Resolves to a Javascript representation of the [ChannelsManager] object.
//   - Rejected with an error if loading indexedDb or the manager fails.
func NewChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	cmixID := args[0].Int()
Jono Wenger's avatar
Jono Wenger committed
	privateIdentity := utils.CopyBytesToGo(args[1])
	messageReceivedCB := args[2]
	return newChannelsManagerWithIndexedDb(
		cmixID, privateIdentity, messageReceivedCB, nil)
}

func newChannelsManagerWithIndexedDb(cmixID int, privateIdentity []byte,
	cb js.Value, cipher *bindings.ChannelDbCipher) any {
	messageReceivedCB := func(uuid uint64, channelID *id.ID, update bool) {
		cb.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), update)
Jake Taylor's avatar
Jake Taylor committed
	model := channelsDb.NewWASMEventModelBuilder(cipher, messageReceivedCB)
	promiseFn := func(resolve, reject func(args ...any) js.Value) {
		cm, err := bindings.NewChannelsManagerGoEventModel(
			cmixID, privateIdentity, model)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(newChannelsManagerJS(cm))
		}
	return utils.CreatePromise(promiseFn)
// LoadChannelsManagerWithIndexedDb loads an existing [ChannelsManager] using
// an existing indexedDb database as a backend to manage the event model.
Jono Wenger's avatar
Jono Wenger committed
//
// This is for loading a manager for an identity that has already been created.
// The channel manager should have previously been created with
// [NewChannelsManagerWithIndexedDb] and the storage is retrievable with
Jono Wenger's avatar
Jono Wenger committed
// [ChannelsManager.GetStorageTag].
Jono Wenger's avatar
Jono Wenger committed
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
//     using [Cmix.GetID].
//   - args[1] - The storage tag associated with the previously created channel
//     manager and retrieved with [ChannelsManager.GetStorageTag] (string).
//   - args[2] - Function that takes in the same parameters as
//     [indexedDb.MessageReceivedCallback]. On the Javascript side, the UUID is
//     returned as an int and the channelID as a Uint8Array. The row in the
//     database that was updated can be found using the UUID. The channel ID is
//     provided so that the recipient can filter if they want to the processes
//     the update now or not. An "update" bool is present which tells you if the
//     row is new or if it is an edited old row.
//   - args[3] - ID of [ChannelDbCipher] object in tracker (int). Create this
//     object with [NewChannelsDatabaseCipher] and get its id with
//     [ChannelDbCipher.GetID].
Jono Wenger's avatar
Jono Wenger committed
//   - Resolves to a Javascript representation of the [ChannelsManager] object.
//   - Rejected with an error if loading indexedDb or the manager fails.
//   - Throws a TypeError if the cipher ID does not correspond to a cipher.
func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any {
	cmixID := args[0].Int()
	storageTag := args[1].String()
	messageReceivedCB := args[2]
	cipherID := args[3].Int()

	cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID)
	if err != nil {
		utils.Throw(utils.TypeError, err)
	}

	return loadChannelsManagerWithIndexedDb(
		cmixID, storageTag, messageReceivedCB, cipher)
}

// LoadChannelsManagerWithIndexedDbUnsafe loads an existing [ChannelsManager]
// using an existing indexedDb database as a backend to manage the event model.
// This should only be used to load unsafe channel managers created by
// [NewChannelsManagerWithIndexedDbUnsafe].
//
// This is for loading a manager for an identity that has already been created.
// The channel manager should have previously been created with
// [NewChannelsManagerWithIndexedDb] and the storage is retrievable with
// [ChannelsManager.GetStorageTag].
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved
//     using [Cmix.GetID].
//   - args[1] - The storage tag associated with the previously created channel
//     manager and retrieved with [ChannelsManager.GetStorageTag] (string).
//   - args[2] - Function that takes in the same parameters as
//     [indexedDb.MessageReceivedCallback]. On the Javascript side, the UUID is
//     returned as an int and the channelID as a Uint8Array. The row in the
//     database that was updated can be found using the UUID. The channel ID is
//     provided so that the recipient can filter if they want to the processes
//     the update now or not. An "update" bool is present which tells you if
//     the row is new or if it is an edited old row
Jono Wenger's avatar
Jono Wenger committed
//
Jono Wenger's avatar
Jono Wenger committed
//   - Resolves to a Javascript representation of the [ChannelsManager] object.
//   - Rejected with an error if loading indexedDb or the manager fails.
func LoadChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	cmixID := args[0].Int()
	storageTag := args[1].String()
	messageReceivedCB := args[2]
	return loadChannelsManagerWithIndexedDb(
		cmixID, storageTag, messageReceivedCB, nil)
}

func loadChannelsManagerWithIndexedDb(cmixID int, storageTag string,
	cb js.Value, cipher *bindings.ChannelDbCipher) any {
	messageReceivedCB := func(uuid uint64, channelID *id.ID, updated bool) {
		cb.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), updated)
Jake Taylor's avatar
Jake Taylor committed
	model := channelsDb.NewWASMEventModelBuilder(cipher, messageReceivedCB)
	promiseFn := func(resolve, reject func(args ...any) js.Value) {
		cm, err := bindings.LoadChannelsManagerGoEventModel(
			cmixID, storageTag, model)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(newChannelsManagerJS(cm))
		}
	return utils.CreatePromise(promiseFn)
////////////////////////////////////////////////////////////////////////////////
// Channel Actions                                                            //
////////////////////////////////////////////////////////////////////////////////

Jono Wenger's avatar
Jono Wenger committed
// DecodePublicURL decodes the channel URL into a channel pretty print. This
// function can only be used for public channel URLs. To get the privacy level
// of a channel URL, use [GetShareUrlType].
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - The channel's share URL (string). Should be received from
//     another user or generated via [ChannelsManager.GetShareURL].
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - The channel pretty print (string).
func DecodePublicURL(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	c, err := bindings.DecodePublicURL(args[0].String())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return c
}

// DecodePrivateURL decodes the channel URL, using the password, into a channel
// pretty print. This function can only be used for private or secret channel
// URLs. To get the privacy level of a channel URL, use [GetShareUrlType].
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - The channel's share URL (string). Should be received from
//     another user or generated via [ChannelsManager.GetShareURL].
//   - args[1] - The password needed to decrypt the secret data in the URL
//     (string).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - The channel pretty print (string)
func DecodePrivateURL(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	c, err := bindings.DecodePrivateURL(args[0].String(), args[1].String())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return c
}

// GetChannelJSON returns the JSON of the channel for the given pretty print.
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - The pretty print of the channel (string).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - JSON of the [broadcast.Channel] object (Uint8Array).
Jono Wenger's avatar
Jono Wenger committed
//
// Example JSON of [broadcast.Channel]:
Jono Wenger's avatar
Jono Wenger committed
//
//	{
//	  "ReceptionID": "Ja/+Jh+1IXZYUOn+IzE3Fw/VqHOscomD0Q35p4Ai//kD",
//	  "Name": "My_Channel",
//	  "Description": "Here is information about my channel.",
//	  "Salt": "+tlrU/htO6rrV3UFDfpQALUiuelFZ+Cw9eZCwqRHk+g=",
//	  "RsaPubKeyHash": "PViT1mYkGBj6AYmE803O2RpA7BX24EjgBdldu3pIm4o=",
//	  "RsaPubKeyLength": 5,
//	  "RSASubPayloads": 1,
//	  "Secret": "JxZt/wPx2luoPdHY6jwbXqNlKnixVU/oa9DgypZOuyI=",
//	  "Level": 0
//	}
func GetChannelJSON(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	c, err := bindings.GetChannelJSON(args[0].String())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

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

Jono Wenger's avatar
Jono Wenger committed
// GetChannelInfo returns the info about a channel from its public description.
Jono Wenger's avatar
Jono Wenger committed
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - The pretty print of the channel (string).
Jono Wenger's avatar
Jono Wenger committed
//
// The pretty print will be of the format:
Jono Wenger's avatar
Jono Wenger committed
//
//	<Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=>
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - JSON of [bindings.ChannelInfo], which describes all relevant channel info
//     (Uint8Array).
//   - Throws a TypeError if getting the channel info fails.
func GetChannelInfo(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	ci, err := bindings.GetChannelInfo(args[0].String())
Jono Wenger's avatar
Jono Wenger committed
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

Jono Wenger's avatar
Jono Wenger committed
	return utils.CopyBytesToJS(ci)
// GenerateChannel creates a new channel with the user as the admin and returns
// the pretty print of the channel. This function only create a channel and does
// not join it.
//
// The private key is saved to storage and can be accessed with
// [ChannelsManager.ExportChannelAdminKey].
//
// Parameters:
//   - args[0] - The name of the new channel (string). The name must be between
//     3 and 24 characters inclusive. It can only include upper and lowercase
//     Unicode letters, digits 0 through 9, and underscores (_). It cannot be
//     changed once a channel is created.
//   - args[1] - The description of a channel (string). The description is
//     optional but cannot be longer than 144 characters and can include all
//     Unicode characters. It cannot be changed once a channel is created.
//   - args[2] - The [broadcast.PrivacyLevel] of the channel (int). 0 = public,
//     1 = private, and 2 = secret. Refer to the comment below for more
//     information.
//
// Returns:
//   - The pretty print of the channel (string).
//   - Throws a TypeError if generating the channel fails.
//
// The [broadcast.PrivacyLevel] of a channel indicates the level of channel
// information revealed when sharing it via URL. For any channel besides public
// channels, the secret information is encrypted and a password is required to
// share and join a channel.
//   - A privacy level of [broadcast.Public] reveals all the information
//     including the name, description, privacy level, public key and salt.
//   - A privacy level of [broadcast.Private] reveals only the name and
//     description.
//   - A privacy level of [broadcast.Secret] reveals nothing.
func (cm *ChannelsManager) GenerateChannel(_ js.Value, args []js.Value) any {
	prettyPrint, err := cm.api.GenerateChannel(
		args[0].String(), args[1].String(), args[2].Int())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return prettyPrint
}

// JoinChannel joins the given channel. It will return the error
// [channels.ChannelAlreadyExistsErr] if the channel has already been joined.
Jono Wenger's avatar
Jono Wenger committed
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - A portable channel string. Should be received from another user
//     or generated via [ChannelsManager.GenerateChannel] (string).
Jono Wenger's avatar
Jono Wenger committed
//
// The pretty print will be of the format:
Jono Wenger's avatar
Jono Wenger committed
//
//	<Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=>
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - JSON of [bindings.ChannelInfo], which describes all relevant channel info
//     (Uint8Array).
//   - Throws a TypeError if joining the channel fails.
func (cm *ChannelsManager) JoinChannel(_ js.Value, args []js.Value) any {
	ci, err := cm.api.JoinChannel(args[0].String())
Jono Wenger's avatar
Jono Wenger committed
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}
Jono Wenger's avatar
Jono Wenger committed
	return utils.CopyBytesToJS(ci)
}

// LeaveChannel leaves the given channel. It will return the error
// [channels.ChannelDoesNotExistsErr] if the channel was not previously joined.
Jono Wenger's avatar
Jono Wenger committed
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - Throws a TypeError if the channel does not exist.
func (cm *ChannelsManager) LeaveChannel(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	marshalledChanId := utils.CopyBytesToGo(args[0])

	err := cm.api.LeaveChannel(marshalledChanId)
Jono Wenger's avatar
Jono Wenger committed
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

// ReplayChannel replays all messages from the channel within the network's
// memory (~3 weeks) over the event model.
//
// Returns the error [channels.ChannelDoesNotExistsErr] if the channel was not
// previously joined.
//
Jono Wenger's avatar
Jono Wenger committed
// Parameters:
//   - args[0] - Marshalled bytes of the channel's [id.ID] (Uint8Array).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - Throws a TypeError if the replay fails.
func (cm *ChannelsManager) ReplayChannel(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	marshalledChanId := utils.CopyBytesToGo(args[0])

	err := cm.api.ReplayChannel(marshalledChanId)
Jono Wenger's avatar
Jono Wenger committed
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return nil
}

// GetChannels returns the IDs of all channels that have been joined.
//
// Returns:
//   - JSON of an array of marshalled [id.ID] (Uint8Array).
//   - Throws a TypeError if getting the channels fails.
//
// JSON Example:
//
//	{
//	  "U4x/lrFkvxuXu59LtHLon1sUhPJSCcnZND6SugndnVID",
//	  "15tNdkKbYXoMn58NO6VbDMDWFEyIhTWEGsvgcJsHWAgD"
//	}
func (cm *ChannelsManager) GetChannels(js.Value, []js.Value) any {
	channelList, err := cm.api.GetChannels()
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return utils.CopyBytesToJS(channelList)
}

Jono Wenger's avatar
Jono Wenger committed
////////////////////////////////////////////////////////////////////////////////
// Channel Share URL                                                          //
////////////////////////////////////////////////////////////////////////////////

// ShareURL is returned by GetShareURL containing the sharable URL and a
// password, if the channel is private.
Jono Wenger's avatar
Jono Wenger committed
type ShareURL struct {
	URL      string `json:"url"`
	Password string `json:"password"`
}

// GetShareURL generates a URL that can be used to share this channel with
// others on the given host.
//
// A URL comes in one of three forms based on the privacy level set when
// generating the channel. Each privacy level hides more information than the
// last with the lowest level revealing everything and the highest level
// revealing nothing. For any level above the lowest, a password is returned,
// which will be required when decoding the URL.
//
// The maxUses is the maximum number of times this URL can be used to join a
// channel. If it is set to 0, then it can be shared unlimited times. The max
// uses is set as a URL parameter using the key [broadcast.MaxUsesKey]. Note
// that this number is also encoded in the secret data for private and secret
// URLs, so if the number is changed in the URL, it will be verified when
// calling [DecodePublicURL] and [DecodePrivateURL]. There is no enforcement for
Jono Wenger's avatar
Jono Wenger committed
// public URLs.
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - ID of [Cmix] object in tracker (int).
//   - args[1] - The URL to append the channel info to (string).
//   - args[2] - The maximum number of uses the link can be used (0 for
//     unlimited) (int).
//   - args[3] - Marshalled bytes of the channel [id.ID] (Uint8Array).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
Jono Wenger's avatar
Jono Wenger committed
//   - JSON of [bindings.ShareURL] (Uint8Array).
//   - Throws a TypeError if generating the URL fails.
func (cm *ChannelsManager) GetShareURL(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	cmixID := args[0].Int()
	host := args[1].String()
	maxUses := args[2].Int()
	marshalledChanId := utils.CopyBytesToGo(args[3])

	su, err := cm.api.GetShareURL(cmixID, host, maxUses, marshalledChanId)
Jono Wenger's avatar
Jono Wenger committed
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return utils.CopyBytesToJS(su)
}

// GetShareUrlType determines the [broadcast.PrivacyLevel] of the channel URL.
// If the URL is an invalid channel URL, an error is returned.
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - The channel share URL (string).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns:
//   - An int that corresponds to the [broadcast.PrivacyLevel] as outlined
//     below (int).
Jono Wenger's avatar
Jono Wenger committed
//   - Throws a TypeError if parsing the URL fails.
Jono Wenger's avatar
Jono Wenger committed
//
// Possible returns:
Jono Wenger's avatar
Jono Wenger committed
//
//	0 = public channel
//	1 = private channel
//	2 = secret channel
func GetShareUrlType(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	level, err := bindings.GetShareUrlType(args[0].String())
	if err != nil {
		utils.Throw(utils.TypeError, err)
		return nil
	}

	return level
}

Jono Wenger's avatar
Jono Wenger committed
////////////////////////////////////////////////////////////////////////////////
// Channel Sending Methods and Reports                                        //
////////////////////////////////////////////////////////////////////////////////

// ValidForever returns the value to use for validUntil when you want a message
// to be available for the maximum amount of time.
//
// Returns:
//   - The maximum amount of time (int).
func ValidForever(js.Value, []js.Value) any {
	return bindings.ValidForever()
}

Jono Wenger's avatar
Jono Wenger committed
// SendGeneric is used to send a raw message over a channel. In general, it
// should be wrapped in a function that defines the wire protocol.
//
// If the final message, before being sent over the wire, is too long, this
// will return an error. Due to the underlying encoding using compression,
// it is not possible to define the largest payload that can be sent, but it
// will always be possible to send a payload of 802 bytes at minimum.
Jono Wenger's avatar
Jono Wenger committed
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//   - args[1] - The message type of the message. This will be a valid
//     [channels.MessageType] (int).
//   - args[2] - The contents of the message (Uint8Array).
//   - args[3] - The lease of the message. This will be how long the
//     message is available from the network, in milliseconds (int). As per the
//     [channels.Manager] documentation, this has different meanings depending
//     on the use case. These use cases may be generic enough that they will not
//     be enumerated here. Use [ValidForever] to last the max message life.
Jono Wenger's avatar
Jono Wenger committed
//   - args[4] - Set tracked to true if the message should be tracked in the
//     sendTracker, which allows messages to be shown locally before they are
//     received on the network. In general, all messages that will be displayed
//     to the user should be tracked while all actions should not be (boolean).
//   - args[5] - JSON of [xxdk.CMIXParams]. If left empty
Jono Wenger's avatar
Jono Wenger committed
//     [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns a promise:
Jono Wenger's avatar
Jono Wenger committed
//   - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
//   - Rejected with an error if sending fails.
func (cm *ChannelsManager) SendGeneric(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	marshalledChanId := utils.CopyBytesToGo(args[0])
	messageType := args[1].Int()
	message := utils.CopyBytesToGo(args[2])
	leaseTimeMS := int64(args[3].Int())
Jono Wenger's avatar
Jono Wenger committed
	tracked := args[4].Bool()
	cmixParamsJSON := utils.CopyBytesToGo(args[5])
	promiseFn := func(resolve, reject func(args ...any) js.Value) {
Jono Wenger's avatar
Jono Wenger committed
		sendReport, err := cm.api.SendGeneric(marshalledChanId, messageType,
			message, leaseTimeMS, tracked, cmixParamsJSON)
Jono Wenger's avatar
Jono Wenger committed
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(utils.CopyBytesToJS(sendReport))
		}
	}

	return utils.CreatePromise(promiseFn)
}

// SendMessage is used to send a formatted message over a channel.
Jono Wenger's avatar
Jono Wenger committed
// Due to the underlying encoding using compression, it isn't possible to define
// the largest payload that can be sent, but it will always be possible to send
// a payload of 798 bytes at minimum.
//
// The message will auto delete validUntil after the round it is sent in,
// lasting forever if [channels.ValidForever] is used.
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//   - args[1] - The contents of the message (string).
//   - args[2] - The lease of the message. This will be how long the
//     message is available from the network, in milliseconds (int). As per the
//     [channels.Manager] documentation, this has different meanings depending
//     on the use case. These use cases may be generic enough that they will not
//     be enumerated here. Use [ValidForever] to last the max message life.
Jono Wenger's avatar
Jono Wenger committed
//   - args[3] - JSON of [xxdk.CMIXParams]. If left empty
//     [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns a promise:
Jono Wenger's avatar
Jono Wenger committed
//   - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
//   - Rejected with an error if sending fails.
func (cm *ChannelsManager) SendMessage(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	marshalledChanId := utils.CopyBytesToGo(args[0])
	message := args[1].String()
	leaseTimeMS := int64(args[2].Int())
	cmixParamsJSON := utils.CopyBytesToGo(args[3])

	promiseFn := func(resolve, reject func(args ...any) js.Value) {
		sendReport, err := cm.api.SendMessage(
Jono Wenger's avatar
Jono Wenger committed
			marshalledChanId, message, leaseTimeMS, cmixParamsJSON)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(utils.CopyBytesToJS(sendReport))
		}
	}

	return utils.CreatePromise(promiseFn)
}

// SendReply is used to send a formatted message over a channel.
//
// Due to the underlying encoding using compression, it is not possible to
// define the largest payload that can be sent, but it will always be possible
// to send a payload of 766 bytes at minimum.
Jono Wenger's avatar
Jono Wenger committed
//
// If the message ID that the reply is sent to does not exist, then the other
// side will post the message as a normal message and not as a reply.
Jono Wenger's avatar
Jono Wenger committed
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//   - args[1] - The contents of the message. The message should be at most 510
//     bytes. This is expected to be Unicode, and thus a string data type is
//     expected (string).
//   - args[2] - JSON of [channel.MessageID] of the message you wish to reply
//     to. This may be found in the [bindings.ChannelSendReport] if replying to
//     your own. Alternatively, if reacting to another user's message, you may
Jono Wenger's avatar
Jono Wenger committed
//     retrieve it via the [bindings.ChannelMessageReceptionCallback] registered
//     using  RegisterReceiveHandler (Uint8Array).
//   - args[3] - The lease of the message. This will be how long the
//     message is available from the network, in milliseconds (int). As per the
//     [channels.Manager] documentation, this has different meanings depending
//     on the use case. These use cases may be generic enough that they will not
//     be enumerated here. Use [ValidForever] to last the max message life.
Jono Wenger's avatar
Jono Wenger committed
//   - args[4] - JSON of [xxdk.CMIXParams]. If left empty
//     [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns a promise:
Jono Wenger's avatar
Jono Wenger committed
//   - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
//   - Rejected with an error if sending fails.
func (cm *ChannelsManager) SendReply(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	marshalledChanId := utils.CopyBytesToGo(args[0])
	message := args[1].String()
	messageToReactTo := utils.CopyBytesToGo(args[2])
	leaseTimeMS := int64(args[3].Int())
	cmixParamsJSON := utils.CopyBytesToGo(args[4])

	promiseFn := func(resolve, reject func(args ...any) js.Value) {
		sendReport, err := cm.api.SendReply(marshalledChanId, message,
Jono Wenger's avatar
Jono Wenger committed
			messageToReactTo, leaseTimeMS, cmixParamsJSON)
		if err != nil {
			reject(utils.JsTrace(err))
		} else {
			resolve(utils.CopyBytesToJS(sendReport))
		}
	}

	return utils.CreatePromise(promiseFn)
}

// SendReaction is used to send a reaction to a message over a channel. The
// reaction must be a single emoji with no other characters, and will be
// rejected otherwise.
//
// Clients will drop the reaction if they do not recognize the reactTo message.
Jono Wenger's avatar
Jono Wenger committed
//
// Parameters:
Jono Wenger's avatar
Jono Wenger committed
//   - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//   - args[1] - The user's reaction. This should be a single emoji with no
//     other characters. As such, a Unicode string is expected (string).
//   - args[2] - JSON of [channel.MessageID] of the message you wish to reply
//     to. This may be found in the [bindings.ChannelSendReport] if replying to
//     your own. Alternatively, if reacting to another user's message, you may
Jono Wenger's avatar
Jono Wenger committed
//     retrieve it via the ChannelMessageReceptionCallback registered using
//     RegisterReceiveHandler (Uint8Array).
//   - args[3] - JSON of [xxdk.CMIXParams]. If left empty
//     [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
Jono Wenger's avatar
Jono Wenger committed
//
// Returns a promise:
Jono Wenger's avatar
Jono Wenger committed
//   - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
//   - Rejected with an error if sending fails.
func (cm *ChannelsManager) SendReaction(_ js.Value, args []js.Value) any {
Jono Wenger's avatar
Jono Wenger committed
	marshalledChanId := utils.CopyBytesToGo(args[0])
	reaction := args[1].String()
	messageToReactTo := utils.CopyBytesToGo(args[2])