Something went wrong on our end
-
Jono Wenger authoredJono Wenger authored
channels.go 56.73 KiB
////////////////////////////////////////////////////////////////////////////////
// Copyright © 2022 xx foundation //
// //
// 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 (
"encoding/base64"
"gitlab.com/xx_network/primitives/id"
"sync"
"syscall/js"
"gitlab.com/elixxir/client/v4/bindings"
"gitlab.com/elixxir/xxdk-wasm/indexedDb"
"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 {
cm := ChannelsManager{api}
channelsManagerMap := map[string]any{
// Basic Channel API
"GetID": js.FuncOf(cm.GetID),
"JoinChannel": js.FuncOf(cm.JoinChannel),
"GetChannels": js.FuncOf(cm.GetChannels),
"LeaveChannel": js.FuncOf(cm.LeaveChannel),
"ReplayChannel": js.FuncOf(cm.ReplayChannel),
// Share URL
"GetShareURL": js.FuncOf(cm.GetShareURL),
// Channel Sending Methods and Reports
"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),
"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),
// Channel Receiving Logic and Callback Registration
"RegisterReceiveHandler": js.FuncOf(cm.RegisterReceiveHandler),
}
return channelsManagerMap
}
// GetID returns the ID for this [ChannelsManager] in the [ChannelsManager]
// tracker.
//
// Returns:
// - Tracker ID (int).
func (ch *ChannelsManager) GetID(js.Value, []js.Value) any {
return ch.api.GetID()
}
// GenerateChannelIdentity creates a new private channel identity
// ([channel.PrivateIdentity]). The public component can be retrieved as JSON
// via [GetPublicChannelIdentityFromPrivate].
//
// Parameters:
// - args[0] - ID of [Cmix] object in tracker (int).
//
// Returns:
// - Marshalled bytes of [channel.PrivateIdentity] (Uint8Array).
// - Throws a TypeError if generating the identity fails.
func GenerateChannelIdentity(_ js.Value, args []js.Value) any {
pi, err := bindings.GenerateChannelIdentity(args[0].Int())
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(pi)
}
// identityMap stores identities previously generated by ConstructIdentity.
var identityMap sync.Map
// ConstructIdentity constructs a [channel.Identity] from a user's public key
// and codeset version.
//
// Parameters:
// - args[0] - The Ed25519 public key (Uint8Array).
// - args[1] - The version of the codeset used to generate the identity (int).
//
// Returns:
// - 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:
// - args[0] - The Ed25519 public key (Uint8Array).
// - args[1] - The version of the codeset used to generate the identity (int).
//
// Returns:
// - 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)
}
// ImportPrivateIdentity generates a new [channel.PrivateIdentity] from exported
// data.
//
// Parameters:
// - args[0] - The password used to encrypt the identity (string).
// - args[2] - The encrypted data (Uint8Array).
//
// Returns:
// - JSON of [channel.PrivateIdentity] (Uint8Array).
// - Throws a TypeError if importing the identity fails.
func ImportPrivateIdentity(_ js.Value, args []js.Value) any {
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
}
return utils.CopyBytesToJS(pi)
}
// GetPublicChannelIdentity constructs a public identity ([channel.Identity])
// from a bytes version and returns it JSON marshaled.
//
// Parameters:
// - args[0] - Bytes of the public identity ([channel.Identity]) (Uint8Array).
//
// Returns:
// - 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 {
marshaledPublic := utils.CopyBytesToGo(args[0])
pi, err := bindings.GetPublicChannelIdentity(marshaledPublic)
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(pi)
}
// GetPublicChannelIdentityFromPrivate returns the public identity
// ([channel.Identity]) contained in the given private identity
// ([channel.PrivateIdentity]).
//
// Parameters:
// - args[0] - Bytes of the private identity
// (channel.PrivateIdentity]) (Uint8Array).
//
// Returns:
// - 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 {
marshaledPrivate := utils.CopyBytesToGo(args[0])
identity, err :=
bindings.GetPublicChannelIdentityFromPrivate(marshaledPrivate)
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(identity)
}
// eventModelBuilder adheres to the [bindings.EventModelBuilder] interface.
type eventModelBuilder struct {
build func(args ...any) js.Value
}
// Build initializes and returns the event model. It wraps a Javascript object
// that has all the methods in [bindings.EventModel] to make it adhere to the Go
// interface [bindings.EventModel].
func (emb *eventModelBuilder) Build(path string) bindings.EventModel {
emJs := emb.build(path)
return &eventModel{
joinChannel: utils.WrapCB(emJs, "JoinChannel"),
leaveChannel: utils.WrapCB(emJs, "LeaveChannel"),
receiveMessage: utils.WrapCB(emJs, "ReceiveMessage"),
receiveReply: utils.WrapCB(emJs, "ReceiveReply"),
receiveReaction: utils.WrapCB(emJs, "ReceiveReaction"),
updateSentStatus: utils.WrapCB(emJs, "UpdateSentStatus"),
}
}
// 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:
// - 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].
//
// Returns:
// - Javascript representation of the [ChannelsManager] object.
// - Throws a TypeError if creating the manager fails.
func NewChannelsManager(_ js.Value, args []js.Value) any {
privateIdentity := utils.CopyBytesToGo(args[1])
em := &eventModelBuilder{args[2].Invoke}
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].
//
// 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:
// - 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].
//
// Returns:
// - 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)
}
// 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
// in the storage tag retrieved by [ChannelsManager.GetStorageTag].
//
// This function initialises an indexedDb database.
//
// Parameters:
// - 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].
//
// Returns a promise:
// - 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
// reload this channel manager, use [LoadChannelsManagerWithIndexedDbUnsafe],
// passing in the storage tag retrieved by [ChannelsManager.GetStorageTag].
//
// This function initialises an indexedDb database.
//
// Parameters:
// - 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
//
// Returns a promise:
// - 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 {
cmixID := args[0].Int()
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)
}
model := indexedDb.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.
//
// 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:
// - 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].
//
// Returns a promise:
// - 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:
// - 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
//
// Returns a promise:
// - 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 {
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)
}
model := indexedDb.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)
}
// GenerateChannel is used to create a channel a new channel of which you are
// the admin. It is only for making new channels, not joining existing ones.
//
// It returns a pretty print of the channel and the private key.
//
// The name cannot be longer that __ characters. The description cannot be
// longer than __ and can only use ______ characters.
//
// Parameters:
// - args[0] - ID of [Cmix] object in tracker (int).
// - args[1] - 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[2] - 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[3] - The [broadcast.PrivacyLevel] of the channel (int). 0 = public,
// 1 = private, and 2 = secret. Refer to the comment below for more
// information.
//
// Returns:
// - JSON of [bindings.ChannelGeneration], which describes a generated
// channel. It contains both the public channel info and the private key for
// the channel in PEM format (Uint8Array).
// - 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 GenerateChannel(_ js.Value, args []js.Value) any {
gen, err := bindings.GenerateChannel(
args[0].Int(), args[1].String(), args[2].String(), args[3].Int())
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(gen)
}
// GetSavedChannelPrivateKeyUNSAFE loads the private key from storage for the
// given channel ID.
//
// NOTE: This function is unsafe and only for debugging purposes only.
//
// Parameters:
// - args[0] - ID of [Cmix] object in tracker (int).
// - args[1] - The [id.ID] of the channel in base 64 encoding (string).
//
// Returns:
// - The PEM file of the private key (string).
// - Throws a TypeError if retrieving the [Cmix] object or the private key
// fails.
func GetSavedChannelPrivateKeyUNSAFE(_ js.Value, args []js.Value) any {
privKey, err := bindings.GetSavedChannelPrivateKeyUNSAFE(
args[0].Int(), args[1].String())
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return privKey
}
// 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:
// - args[0] - The channel's share URL (string). Should be received from
// another user or generated via [ChannelsManager.GetShareURL].
//
// Returns:
// - The channel pretty print (string).
func DecodePublicURL(_ js.Value, args []js.Value) any {
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:
// - 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).
//
// Returns:
// - The channel pretty print (string)
func DecodePrivateURL(_ js.Value, args []js.Value) any {
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:
// - args[0] - The pretty print of the channel (string).
//
// Returns:
// - JSON of the [broadcast.Channel] object (Uint8Array).
//
// Example JSON of [broadcast.Channel]:
//
// {
// "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 {
c, err := bindings.GetChannelJSON(args[0].String())
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(c)
}
// GetChannelInfo returns the info about a channel from its public description.
//
// Parameters:
// - args[0] - The pretty print of the channel (string).
//
// The pretty print will be of the format:
//
// <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=>
//
// Returns:
// - 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 {
ci, err := bindings.GetChannelInfo(args[0].String())
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(ci)
}
// JoinChannel joins the given channel. It will fail if the channel has already
// been joined.
//
// Parameters:
// - args[0] - A portable channel string. Should be received from another user
// or generated via [GenerateChannel] (string).
//
// The pretty print will be of the format:
//
// <Speakeasy-v3:Test_Channel|description:Channel description.|level:Public|created:1666718081766741100|secrets:+oHcqDbJPZaT3xD5NcdLY8OjOMtSQNKdKgLPmr7ugdU=|rCI0wr01dHFStjSFMvsBzFZClvDIrHLL5xbCOPaUOJ0=|493|1|7cBhJxVfQxWo+DypOISRpeWdQBhuQpAZtUbQHjBm8NQ=>
//
// Returns:
// - JSON of [bindings.ChannelInfo], which describes all relevant channel info
// (Uint8Array).
// - Throws a TypeError if joining the channel fails.
func (ch *ChannelsManager) JoinChannel(_ js.Value, args []js.Value) any {
ci, err := ch.api.JoinChannel(args[0].String())
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(ci)
}
// 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 (ch *ChannelsManager) GetChannels(js.Value, []js.Value) any {
channelList, err := ch.api.GetChannels()
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(channelList)
}
// LeaveChannel leaves the given channel. It will return an error if the channel
// was not previously joined.
//
// Parameters:
// - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//
// Returns:
// - Throws a TypeError if the channel does not exist.
func (ch *ChannelsManager) LeaveChannel(_ js.Value, args []js.Value) any {
marshalledChanId := utils.CopyBytesToGo(args[0])
err := ch.api.LeaveChannel(marshalledChanId)
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.
//
// Parameters:
// - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array).
//
// Returns:
// - Throws a TypeError if the replay fails.
func (ch *ChannelsManager) ReplayChannel(_ js.Value, args []js.Value) any {
marshalledChanId := utils.CopyBytesToGo(args[0])
err := ch.api.ReplayChannel(marshalledChanId)
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return nil
}
////////////////////////////////////////////////////////////////////////////////
// Channel Share URL //
////////////////////////////////////////////////////////////////////////////////
// ShareURL is returned by GetShareURL containing the sharable URL and a
// password, if the channel is private.
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, is will be verified when
// calling [DecodePublicURL] or [DecodePrivateURL]. There is no enforcement for
// public URLs.
//
// Parameters:
// - 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).
//
// Returns:
// - JSON of [bindings.ShareURL] (Uint8Array).
// - Throws a TypeError if generating the URL fails.
func (ch *ChannelsManager) GetShareURL(_ js.Value, args []js.Value) any {
cmixID := args[0].Int()
host := args[1].String()
maxUses := args[2].Int()
marshalledChanId := utils.CopyBytesToGo(args[3])
su, err := ch.api.GetShareURL(cmixID, host, maxUses, marshalledChanId)
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:
// - args[0] - The channel share URL (string).
//
// Returns:
// - An int that corresponds to the [broadcast.PrivacyLevel] as outlined
// below.
// - Throws a TypeError if parsing the URL fails.
//
// Possible returns:
//
// 0 = public channel
// 1 = private channel
// 2 = secret channel
func GetShareUrlType(_ js.Value, args []js.Value) any {
level, err := bindings.GetShareUrlType(args[0].String())
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return level
}
////////////////////////////////////////////////////////////////////////////////
// Channel Sending Methods and Reports //
////////////////////////////////////////////////////////////////////////////////
// SendGeneric is used to send a raw message over a channel. In general, it
// should be wrapped in a function which 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 isn't 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. The meaning of validUntil depends
// on the use case.
//
// Parameters:
// - 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
// valid until, in milliseconds. 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 (int).
// - args[4] - JSON of [xxdk.CMIXParams]. If left empty
// [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
//
// Returns a promise:
// - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
// - Rejected with an error if sending fails.
func (ch *ChannelsManager) SendGeneric(_ js.Value, args []js.Value) any {
marshalledChanId := utils.CopyBytesToGo(args[0])
messageType := args[1].Int()
message := 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 := ch.api.SendGeneric(
marshalledChanId, messageType, message, leaseTimeMS, cmixParamsJSON)
if err != nil {
reject(utils.JsTrace(err))
} else {
resolve(utils.CopyBytesToJS(sendReport))
}
}
return utils.CreatePromise(promiseFn)
}
// SendAdminGeneric is used to send a raw message over a channel encrypted with
// admin keys, identifying it as sent by the admin. 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. The
// message must be at most 510 bytes long.
//
// Parameters:
// - args[0] - The PEM-encode admin RSA private key (Uint8Array).
// - args[1] - Marshalled bytes of the channel [id.ID] (Uint8Array).
// - args[2] - The message type of the message. This will be a valid
// [channels.MessageType] (int).
// - args[3] - The contents of the message (Uint8Array).
// - args[4] - The lease of the message. This will be how long the message is
// valid until, in milliseconds. 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 (int).
// - args[5] - JSON of [xxdk.CMIXParams]. If left empty
// [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
//
// Returns a promise:
// - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
// - Rejected with an error if sending fails.
func (ch *ChannelsManager) SendAdminGeneric(_ js.Value, args []js.Value) any {
adminPrivateKey := utils.CopyBytesToGo(args[0])
marshalledChanId := utils.CopyBytesToGo(args[1])
messageType := args[2].Int()
message := utils.CopyBytesToGo(args[3])
leaseTimeMS := int64(args[4].Int())
cmixParamsJSON := utils.CopyBytesToGo(args[5])
promiseFn := func(resolve, reject func(args ...any) js.Value) {
sendReport, err := ch.api.SendAdminGeneric(adminPrivateKey,
marshalledChanId, messageType, message, leaseTimeMS, cmixParamsJSON)
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.
// 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:
// - 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
// valid until, in milliseconds. 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 (int).
// - args[3] - JSON of [xxdk.CMIXParams]. If left empty
// [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
//
// Returns a promise:
// - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
// - Rejected with an error if sending fails.
func (ch *ChannelsManager) SendMessage(_ js.Value, args []js.Value) any {
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 := ch.api.SendMessage(
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 isn't 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.
//
// If the message ID the reply is sent to is nonexistent, the other side will
// post the message as a normal message and not a reply. The message will auto
// delete validUntil after the round it is sent in, lasting forever if
// [channels.ValidForever] is used.
//
// Parameters:
// - 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
// 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
// valid until, in milliseconds. 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 (int).
// - args[4] - JSON of [xxdk.CMIXParams]. If left empty
// [bindings.GetDefaultCMixParams] will be used internally (Uint8Array).
//
// Returns a promise:
// - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
// - Rejected with an error if sending fails.
func (ch *ChannelsManager) SendReply(_ js.Value, args []js.Value) any {
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 := ch.api.SendReply(marshalledChanId, message,
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.
// Users will drop the reaction if they do not recognize the reactTo message.
//
// Parameters:
// - 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
// 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).
//
// Returns a promise:
// - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array).
// - Rejected with an error if sending fails.
func (ch *ChannelsManager) SendReaction(_ js.Value, args []js.Value) any {
marshalledChanId := utils.CopyBytesToGo(args[0])
reaction := args[1].String()
messageToReactTo := utils.CopyBytesToGo(args[2])
cmixParamsJSON := utils.CopyBytesToGo(args[3])
promiseFn := func(resolve, reject func(args ...any) js.Value) {
sendReport, err := ch.api.SendReaction(
marshalledChanId, reaction, messageToReactTo, cmixParamsJSON)
if err != nil {
reject(utils.JsTrace(err))
} else {
resolve(utils.CopyBytesToJS(sendReport))
}
}
return utils.CreatePromise(promiseFn)
}
// GetIdentity returns the marshaled public identity ([channel.Identity]) that
// the channel is using.
//
// Returns:
// - JSON of the [channel.Identity] (Uint8Array).
// - Throws TypeError if marshalling the identity fails.
func (ch *ChannelsManager) GetIdentity(js.Value, []js.Value) any {
i, err := ch.api.GetIdentity()
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(i)
}
// ExportPrivateIdentity encrypts and exports the private identity to a portable
// string.
//
// Parameters:
// - args[0] - Password to encrypt the identity with (string).
//
// Returns:
// - JSON of the encrypted private identity (Uint8Array).
// - Throws TypeError if exporting the identity fails.
func (ch *ChannelsManager) ExportPrivateIdentity(_ js.Value, args []js.Value) any {
i, err := ch.api.ExportPrivateIdentity(args[0].String())
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(i)
}
// GetStorageTag returns the storage tag needed to reload the manager.
//
// Returns:
// - Storage tag (string).
func (ch *ChannelsManager) GetStorageTag(js.Value, []js.Value) any {
return ch.api.GetStorageTag()
}
// SetNickname sets the nickname for a given channel. The nickname must be valid
// according to [IsNicknameValid].
//
// Parameters:
// - args[0] - The nickname to set (string).
// - args[1] - Marshalled bytes if the channel's [id.ID] (Uint8Array).
//
// Returns:
// - Throws TypeError if unmarshalling the ID fails or the nickname is
// invalid.
func (ch *ChannelsManager) SetNickname(_ js.Value, args []js.Value) any {
err := ch.api.SetNickname(args[0].String(), utils.CopyBytesToGo(args[1]))
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return nil
}
// DeleteNickname deletes the nickname for a given channel.
//
// Parameters:
// - args[0] - Marshalled bytes if the channel's [id.ID] (Uint8Array).
//
// Returns:
// - Throws TypeError if deleting the nickname fails.
func (ch *ChannelsManager) DeleteNickname(_ js.Value, args []js.Value) any {
err := ch.api.DeleteNickname(utils.CopyBytesToGo(args[0]))
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return nil
}
// GetNickname returns the nickname set for a given channel. Returns an error if
// there is no nickname set.
//
// Parameters:
// - args[0] - Marshalled bytes if the channel's [id.ID] (Uint8Array).
//
// Returns:
// - The nickname (string).
// - Throws TypeError if the channel has no nickname set.
func (ch *ChannelsManager) GetNickname(_ js.Value, args []js.Value) any {
nickname, err := ch.api.GetNickname(utils.CopyBytesToGo(args[0]))
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return nickname
}
// IsNicknameValid checks if a nickname is valid.
//
// Rules:
// 1. A nickname must not be longer than 24 characters.
// 2. A nickname must not be shorter than 1 character.
//
// Parameters:
// - args[0] - Nickname to check (string).
//
// Returns:
// - A Javascript Error object if the nickname is invalid with the reason why.
// - Null if the nickname is valid.
func IsNicknameValid(_ js.Value, args []js.Value) any {
err := bindings.IsNicknameValid(args[0].String())
if err != nil {
return utils.JsError(err)
}
return nil
}
////////////////////////////////////////////////////////////////////////////////
// Channel Receiving Logic and Callback Registration //
////////////////////////////////////////////////////////////////////////////////
// channelMessageReceptionCallback wraps Javascript callbacks to adhere to the
// [bindings.ChannelMessageReceptionCallback] interface.
type channelMessageReceptionCallback struct {
callback func(args ...any) js.Value
}
// Callback returns the context for a channel message.
//
// Parameters:
// - receivedChannelMessageReport - Returns the JSON of
// [bindings.ReceivedChannelMessageReport] (Uint8Array).
// - err - Returns an error on failure (Error).
//
// Returns:
// - It must return a unique UUID for the message that it can be referenced by
// later (int).
func (cmrCB *channelMessageReceptionCallback) Callback(
receivedChannelMessageReport []byte, err error) int {
uuid := cmrCB.callback(
utils.CopyBytesToJS(receivedChannelMessageReport), utils.JsTrace(err))
return uuid.Int()
}
// RegisterReceiveHandler is used to register handlers for non-default message
// types. They can be processed by modules. It is important that such modules
// sync up with the event model implementation.
//
// There can only be one handler per [channels.MessageType], and this will
// return an error on any re-registration.
//
// Parameters:
// - args[0] - The message type of the message. This will be a valid
// [channels.MessageType] (int).
// - args[1] - Javascript object that has functions that implement the
// [bindings.ChannelMessageReceptionCallback] interface. This callback will
// be executed when a channel message of the messageType is received.
//
// Returns:
// - Throws a TypeError if registering the handler fails.
func (ch *ChannelsManager) RegisterReceiveHandler(_ js.Value, args []js.Value) any {
messageType := args[0].Int()
listenerCb := &channelMessageReceptionCallback{
utils.WrapCB(args[1], "Callback")}
err := ch.api.RegisterReceiveHandler(messageType, listenerCb)
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return nil
}
////////////////////////////////////////////////////////////////////////////////
// Event Model Logic //
////////////////////////////////////////////////////////////////////////////////
// eventModel wraps Javascript callbacks to adhere to the [bindings.EventModel]
// interface.
type eventModel struct {
joinChannel func(args ...any) js.Value
leaveChannel func(args ...any) js.Value
receiveMessage func(args ...any) js.Value
receiveReply func(args ...any) js.Value
receiveReaction func(args ...any) js.Value
updateSentStatus func(args ...any) js.Value
}
// JoinChannel is called whenever a channel is joined locally.
//
// Parameters:
// - channel - Returns the pretty print representation of a channel (string).
func (em *eventModel) JoinChannel(channel string) {
em.joinChannel(channel)
}
// LeaveChannel is called whenever a channel is left locally.
//
// Parameters:
// - ChannelId - Marshalled bytes of the channel [id.ID] (Uint8Array).
func (em *eventModel) LeaveChannel(channelID []byte) {
em.leaveChannel(utils.CopyBytesToJS(channelID))
}
// ReceiveMessage is called whenever a message is received on a given channel.
// It may be called multiple times on the same message. It is incumbent on the
// user of the API to filter such called by message ID.
//
// Parameters:
// - channelID - Marshalled bytes of the channel [id.ID] (Uint8Array).
// - messageID - The bytes of the [channel.MessageID] of the received message
// (Uint8Array).
// - nickname - The nickname of the sender of the message (string).
// - text - The content of the message (string).
// - pubKey - The sender's Ed25519 public key (Uint8Array).
// - codeset - The codeset version (int).
// - timestamp - Time the message was received; represented as nanoseconds
// since unix epoch (int).
// - lease - The number of nanoseconds that the message is valid for (int).
// - roundId - The ID of the round that the message was received on (int).
// - msgType - The type of message ([channels.MessageType]) to send (int).
// - status - The [channels.SentStatus] of the message (int).
//
// Statuses will be enumerated as such:
//
// Sent = 0
// Delivered = 1
// Failed = 2
//
// Returns:
// - A non-negative unique UUID for the message that it can be referenced by
// later with [eventModel.UpdateSentStatus].
func (em *eventModel) ReceiveMessage(channelID, messageID []byte, nickname,
text string, pubKey []byte, codeset int, timestamp, lease, roundId, msgType,
status int64) int64 {
uuid := em.receiveMessage(utils.CopyBytesToJS(channelID),
utils.CopyBytesToJS(messageID), nickname, text,
utils.CopyBytesToJS(pubKey), codeset, timestamp, lease, roundId,
msgType, status)
return int64(uuid.Int())
}
// ReceiveReply is called whenever a message is received that is a reply on a
// given channel. It may be called multiple times on the same message. It is
// incumbent on the user of the API to filter such called by message ID.
//
// Messages may arrive our of order, so a reply in theory can arrive before the
// initial message. As a result, it may be important to buffer replies.
//
// Parameters:
// - channelID - Marshalled bytes of the channel [id.ID] (Uint8Array).
// - messageID - The bytes of the [channel.MessageID] of the received message
// (Uint8Array).
// - reactionTo - The [channel.MessageID] for the message that received a
// reply (Uint8Array).
// - senderUsername - The username of the sender of the message (string).
// - text - The content of the message (string).
// - pubKey - The sender's Ed25519 public key (Uint8Array).
// - codeset - The codeset version (int).
// - timestamp - Time the message was received; represented as nanoseconds
// since unix epoch (int).
// - lease - The number of nanoseconds that the message is valid for (int).
// - roundId - The ID of the round that the message was received on (int).
// - msgType - The type of message ([channels.MessageType]) to send (int).
// - status - The [channels.SentStatus] of the message (int).
//
// Statuses will be enumerated as such:
//
// Sent = 0
// Delivered = 1
// Failed = 2
//
// Returns:
// - A non-negative unique UUID for the message that it can be referenced by
// later with [eventModel.UpdateSentStatus].
func (em *eventModel) ReceiveReply(channelID, messageID, reactionTo []byte,
senderUsername, text string, pubKey []byte, codeset int, timestamp, lease,
roundId, msgType, status int64) int64 {
uuid := em.receiveReply(utils.CopyBytesToJS(channelID),
utils.CopyBytesToJS(messageID), utils.CopyBytesToJS(reactionTo),
senderUsername, text, utils.CopyBytesToJS(pubKey), codeset,
timestamp, lease, roundId, msgType, status)
return int64(uuid.Int())
}
// ReceiveReaction is called whenever a reaction to a message is received on a
// given channel. It may be called multiple times on the same reaction. It is
// incumbent on the user of the API to filter such called by message ID.
//
// Messages may arrive our of order, so a reply in theory can arrive before the
// initial message. As a result, it may be important to buffer reactions.
//
// Parameters:
// - channelID - Marshalled bytes of the channel [id.ID] (Uint8Array).
// - messageID - The bytes of the [channel.MessageID] of the received message
// (Uint8Array).
// - reactionTo - The [channel.MessageID] for the message that received a
// reply (Uint8Array).
// - senderUsername - The username of the sender of the message (string).
// - reaction - The contents of the reaction message (string).
// - pubKey - The sender's Ed25519 public key (Uint8Array).
// - codeset - The codeset version (int).
// - timestamp - Time the message was received; represented as nanoseconds
// since unix epoch (int).
// - lease - The number of nanoseconds that the message is valid for (int).
// - roundId - The ID of the round that the message was received on (int).
// - msgType - The type of message ([channels.MessageType]) to send (int).
// - status - The [channels.SentStatus] of the message (int).
//
// Statuses will be enumerated as such:
//
// Sent = 0
// Delivered = 1
// Failed = 2
//
// Returns:
// - A non-negative unique UUID for the message that it can be referenced by
// later with [eventModel.UpdateSentStatus].
func (em *eventModel) ReceiveReaction(channelID, messageID, reactionTo []byte,
senderUsername, reaction string, pubKey []byte, codeset int, timestamp,
lease, roundId, msgType, status int64) int64 {
uuid := em.receiveReaction(utils.CopyBytesToJS(channelID),
utils.CopyBytesToJS(messageID), utils.CopyBytesToJS(reactionTo),
senderUsername, reaction, utils.CopyBytesToJS(pubKey), codeset,
timestamp, lease, roundId, msgType, status)
return int64(uuid.Int())
}
// UpdateSentStatus is called whenever the sent status of a message has
// changed.
//
// Parameters:
// - uuid - The unique identifier for the message (int).
// - messageID - The bytes of the [channel.MessageID] of the received message
// (Uint8Array).
// - timestamp - Time the message was received; represented as nanoseconds
// since unix epoch (int).
// - roundId - The ID of the round that the message was received on (int).
// - status - The [channels.SentStatus] of the message (int).
//
// Statuses will be enumerated as such:
//
// Sent = 0
// Delivered = 1
// Failed = 2
func (em *eventModel) UpdateSentStatus(
uuid int64, messageID []byte, timestamp, roundID, status int64) {
em.updateSentStatus(
uuid, utils.CopyBytesToJS(messageID), timestamp, roundID, status)
}
////////////////////////////////////////////////////////////////////////////////
// Channel Cipher //
////////////////////////////////////////////////////////////////////////////////
// ChannelDbCipher wraps the [bindings.ChannelDbCipher] object so its methods
// can be wrapped to be Javascript compatible.
type ChannelDbCipher struct {
api *bindings.ChannelDbCipher
}
// newChannelDbCipherJS creates a new Javascript compatible object
// (map[string]any) that matches the [ChannelDbCipher] structure.
func newChannelDbCipherJS(api *bindings.ChannelDbCipher) map[string]any {
c := ChannelDbCipher{api}
channelDbCipherMap := map[string]any{
"GetID": js.FuncOf(c.GetID),
"Encrypt": js.FuncOf(c.Encrypt),
"Decrypt": js.FuncOf(c.Decrypt),
}
return channelDbCipherMap
}
// NewChannelsDatabaseCipher constructs a [ChannelDbCipher] object.
//
// Parameters:
// - args[0] - The tracked [Cmix] object ID (int).
// - args[1] - The password for storage. This should be the same password
// passed into [NewCmix] (Uint8Array).
// - args[2] - The maximum size of a payload to be encrypted. A payload passed
// into [ChannelDbCipher.Encrypt] that is larger than this value will result
// in an error (int).
//
// Returns:
// - JavaScript representation of the [ChannelDbCipher] object.
// - Throws a TypeError if creating the cipher fails.
func NewChannelsDatabaseCipher(_ js.Value, args []js.Value) any {
cmixId := args[0].Int()
password := utils.CopyBytesToGo(args[1])
plaintTextBlockSize := args[2].Int()
cipher, err := bindings.NewChannelsDatabaseCipher(
cmixId, password, plaintTextBlockSize)
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return newChannelDbCipherJS(cipher)
}
// GetID returns the ID for this [bindings.ChannelDbCipher] in the
// channelDbCipherTracker.
//
// Returns:
// - Tracker ID (int).
func (c *ChannelDbCipher) GetID(js.Value, []js.Value) any {
return c.api.GetID()
}
// Encrypt will encrypt the raw data. It will return a ciphertext. Padding is
// done on the plaintext so all encrypted data looks uniform at rest.
//
// Parameters:
// - args[0] - The data to be encrypted (Uint8Array). This must be smaller
// than the block size passed into [NewChannelsDatabaseCipher]. If it is
// larger, this will return an error.
//
// Returns:
// - The ciphertext of the plaintext passed in (Uint8Array).
// - Throws a TypeError if it fails to encrypt the plaintext.
func (c *ChannelDbCipher) Encrypt(_ js.Value, args []js.Value) any {
ciphertext, err := c.api.Encrypt(utils.CopyBytesToGo(args[0]))
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(ciphertext)
}
// Decrypt will decrypt the passed in encrypted value. The plaintext will be
// returned by this function. Any padding will be discarded within this
// function.
//
// Parameters:
// - args[0] - the encrypted data returned by [ChannelDbCipher.Encrypt]
// (Uint8Array).
//
// Returns:
// - The plaintext of the ciphertext passed in (Uint8Array).
// - Throws a TypeError if it fails to encrypt the plaintext.
func (c *ChannelDbCipher) Decrypt(_ js.Value, args []js.Value) any {
plaintext, err := c.api.Decrypt(utils.CopyBytesToGo(args[0]))
if err != nil {
utils.Throw(utils.TypeError, err)
return nil
}
return utils.CopyBytesToJS(plaintext)
}