diff --git a/go.mod b/go.mod index 3ed58b94ec254741b1f00ce316c74cafe4edf712..3ddeac92e64e2f19168da50c04e886aa3def5ad2 100644 --- a/go.mod +++ b/go.mod @@ -7,11 +7,11 @@ require ( github.com/hack-pad/go-indexeddb v0.2.0 github.com/pkg/errors v0.9.1 github.com/spf13/jwalterweatherman v1.1.0 - gitlab.com/elixxir/client/v4 v4.3.9-0.20221216005432-2c3c82c2e7e8 - gitlab.com/elixxir/crypto v0.0.7-0.20221214192244-6783272c04a0 + gitlab.com/elixxir/client/v4 v4.3.9-0.20221220181602-504f5e86d446 + gitlab.com/elixxir/crypto v0.0.7-0.20221220181038-c77d1bf49838 gitlab.com/elixxir/primitives v0.0.3-0.20221214192222-988b44a6958a gitlab.com/xx_network/crypto v0.0.5-0.20221121220724-8eefdbb0eb46 - gitlab.com/xx_network/primitives v0.0.4-0.20221209210320-376735467d58 + gitlab.com/xx_network/primitives v0.0.4-0.20221219230308-4b5550a9247d golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa ) diff --git a/go.sum b/go.sum index 6b6298a6e82ad40d6e1dba4f05a9977c9e24da2f..c8222ebed82a201e3a0bcf877e0004b5bedc0cf7 100644 --- a/go.sum +++ b/go.sum @@ -369,12 +369,12 @@ github.com/zeebo/pcg v1.0.1 h1:lyqfGeWiv4ahac6ttHs+I5hwtH/+1mrhlCtVNQM2kHo= github.com/zeebo/pcg v1.0.1/go.mod h1:09F0S9iiKrwn9rlI5yjLkmrug154/YRW6KnnXVDM/l4= gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f h1:yXGvNBqzZwAhDYlSnxPRbgor6JWoOt1Z7s3z1O9JR40= gitlab.com/elixxir/bloomfilter v0.0.0-20211222005329-7d931ceead6f/go.mod h1:H6jztdm0k+wEV2QGK/KYA+MY9nj9Zzatux/qIvDDv3k= -gitlab.com/elixxir/client/v4 v4.3.9-0.20221216005432-2c3c82c2e7e8 h1:MFGMCpHVR7u2vmYcyxB0kmsiElUTXKzB7JlqeCwQE0c= -gitlab.com/elixxir/client/v4 v4.3.9-0.20221216005432-2c3c82c2e7e8/go.mod h1:gxRW2YXDxCzESBnYqMDKH2I25TGv226TDujdQno3JQw= +gitlab.com/elixxir/client/v4 v4.3.9-0.20221220181602-504f5e86d446 h1:18Y4M0/KWLNshps2gyQ97+uCbSul3RezOGtdVM4e07E= +gitlab.com/elixxir/client/v4 v4.3.9-0.20221220181602-504f5e86d446/go.mod h1:rZcfB8jkloBkUqKsdUAifWaGmSmJyZUOmDKnqAwmias= gitlab.com/elixxir/comms v0.0.4-0.20221215214627-7807bfdde33a h1:DuqDqWc5cWjZ3qk98K1Bf9y1dYlyCeIigFmkHWDKc1Q= gitlab.com/elixxir/comms v0.0.4-0.20221215214627-7807bfdde33a/go.mod h1:B2Yek4mCbtN2aXZkyZcUffd3sTEZ5WgKD0mRBSVYtF8= -gitlab.com/elixxir/crypto v0.0.7-0.20221214192244-6783272c04a0 h1:dwCf7wKv2DCuYZZ394bSQWdUOXiABLsEyDvXZUOo83o= -gitlab.com/elixxir/crypto v0.0.7-0.20221214192244-6783272c04a0/go.mod h1:oRh3AwveOEvpk9E3kRcMGK8fImcEnN0PY4jr9HDgQE8= +gitlab.com/elixxir/crypto v0.0.7-0.20221220181038-c77d1bf49838 h1:s93tL+O9LaHow14edbJF0/tAKGvKkoUJCeHPNtkB9iY= +gitlab.com/elixxir/crypto v0.0.7-0.20221220181038-c77d1bf49838/go.mod h1:oRh3AwveOEvpk9E3kRcMGK8fImcEnN0PY4jr9HDgQE8= gitlab.com/elixxir/ekv v0.2.1 h1:dtwbt6KmAXG2Tik5d60iDz2fLhoFBgWwST03p7T+9Is= gitlab.com/elixxir/ekv v0.2.1/go.mod h1:USLD7xeDnuZEavygdrgzNEwZXeLQJK/w1a+htpN+JEU= gitlab.com/elixxir/primitives v0.0.3-0.20221214192222-988b44a6958a h1:F17FfEjS+/uDI/TTYQD21S5JvNZ9+p9bieau2nyLCzo= @@ -383,8 +383,8 @@ gitlab.com/xx_network/comms v0.0.4-0.20221215214252-1275cef8760e h1:l+FiCBP2Lc1+ gitlab.com/xx_network/comms v0.0.4-0.20221215214252-1275cef8760e/go.mod h1:FR/OyruSuob6+xzSZtk+rXlncbRr6nDKFypX3vwtkFc= gitlab.com/xx_network/crypto v0.0.5-0.20221121220724-8eefdbb0eb46 h1:6AHgUpWdJ72RVTTdJSvfThZiYTQNUnrPaTCl/EkRLpg= gitlab.com/xx_network/crypto v0.0.5-0.20221121220724-8eefdbb0eb46/go.mod h1:acWUBKCpae/XVaQF7J9RnLAlBT13i5r7gnON+mrIxBk= -gitlab.com/xx_network/primitives v0.0.4-0.20221209210320-376735467d58 h1:HpeUIf1gIIelLH3LHxEf3/GalecbbtZnOnIegJHALoc= -gitlab.com/xx_network/primitives v0.0.4-0.20221209210320-376735467d58/go.mod h1:wUxbEBGOBJZ/RkAiVAltlC1uIlIrU0dE113Nq7HiOhw= +gitlab.com/xx_network/primitives v0.0.4-0.20221219230308-4b5550a9247d h1:D9hEtiQ7xj0yFBkDkb4X4S95RfNoeXxtB1eE4UuFHtk= +gitlab.com/xx_network/primitives v0.0.4-0.20221219230308-4b5550a9247d/go.mod h1:wUxbEBGOBJZ/RkAiVAltlC1uIlIrU0dE113Nq7HiOhw= gitlab.com/xx_network/ring v0.0.3-0.20220902183151-a7d3b15bc981 h1:1s0vX9BbkiD0IVXwr3LOaTBcq1wBrWcUWMBK0s8r0Z0= gitlab.com/xx_network/ring v0.0.3-0.20220902183151-a7d3b15bc981/go.mod h1:aLzpP2TiZTQut/PVHR40EJAomzugDdHXetbieRClXIM= go.etcd.io/bbolt v1.3.3/go.mod h1:IbVyRI1SCnLcuJnV2u8VeU0CEYM7e686BmAb1XKL+uU= diff --git a/indexedDb/channels/implementation.go b/indexedDb/channels/implementation.go index 8d68e76b561509e29676bcd5d114f7517d3b4822..9fcdf5fdccf867ccedb9eb6f0123db433d83e7c9 100644 --- a/indexedDb/channels/implementation.go +++ b/indexedDb/channels/implementation.go @@ -40,6 +40,13 @@ type wasmModel struct { updateMux sync.Mutex } +// DeleteMessage removes a message with the given messageID from storage. +func (w *wasmModel) DeleteMessage(messageID cryptoChannel.MessageID) error { + msgId := js.ValueOf(base64.StdEncoding.EncodeToString(messageID.Bytes())) + return indexedDb.DeleteIndex(w.db, messageStoreName, + messageStoreMessageIndex, pkeyName, msgId) +} + // JoinChannel is called whenever a channel is joined locally. func (w *wasmModel) JoinChannel(channel *cryptoBroadcast.Channel) { parentErr := errors.New("failed to JoinChannel") @@ -76,35 +83,12 @@ func (w *wasmModel) JoinChannel(channel *cryptoBroadcast.Channel) { func (w *wasmModel) LeaveChannel(channelID *id.ID) { parentErr := errors.New("failed to LeaveChannel") - // Prepare the Transaction - txn, err := w.db.Transaction(idb.TransactionReadWrite, channelsStoreName) - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Unable to create Transaction: %+v", err)) - return - } - store, err := txn.ObjectStore(channelsStoreName) - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Unable to get ObjectStore: %+v", err)) - return - } - - // Perform the operation - _, err = store.Delete(js.ValueOf(channelID.String())) - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Unable to Delete Channel: %+v", err)) - return - } - - // Wait for the operation to return - ctx, cancel := indexedDb.NewContext() - err = txn.Await(ctx) - cancel() + // Delete the channel from storage + err := indexedDb.Delete(w.db, channelsStoreName, + js.ValueOf(channelID.String())) if err != nil { jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Deleting Channel failed: %+v", err)) + "Unable to delete Channel: %+v", err)) return } diff --git a/indexedDb/channels/implementation_test.go b/indexedDb/channels/implementation_test.go index 19f5751eff986c207610154df1cf485f6c82c4b2..0a7ccc77827ee9c32c3e218d67ab0a746ef59e1e 100644 --- a/indexedDb/channels/implementation_test.go +++ b/indexedDb/channels/implementation_test.go @@ -63,8 +63,52 @@ func TestWasmModel_msgIDLookup(t *testing.T) { } } -// Test wasmModel.UpdateSentStatus happy path and ensure fields don't change. -func Test_wasmModel_UpdateSentStatus(t *testing.T) { +// Happy path, insert message and delete it +func TestWasmModel_DeleteMessage(t *testing.T) { + storage.GetLocalStorage().Clear() + testString := "test" + testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1}) + eventModel, err := newWASMModel(testString, nil, dummyCallback) + if err != nil { + t.Fatalf("%+v", err) + } + + // Insert a message + testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil, + testString, []byte(testString), []byte{8, 6, 7, 5}, 0, netTime.Now(), + time.Second, 0, 0, false, false, channels.Sent) + _, err = eventModel.receiveHelper(testMsg, false) + if err != nil { + t.Fatalf("%+v", err) + } + + // Check the resulting status + results, err := indexedDb.Dump(eventModel.db, messageStoreName) + if err != nil { + t.Fatalf("%+v", err) + } + if len(results) != 1 { + t.Fatalf("Expected 1 message to exist") + } + + // Delete the message + err = eventModel.DeleteMessage(testMsgId) + if err != nil { + t.Fatalf("%+v", err) + } + + // Check the resulting status + results, err = indexedDb.Dump(eventModel.db, messageStoreName) + if err != nil { + t.Fatalf("%+v", err) + } + if len(results) != 0 { + t.Fatalf("Expected no messages to exist") + } +} + +// Test wasmModel.UpdateFromUUID happy path and ensure fields don't change. +func Test_wasmModel_UpdateFromUUID(t *testing.T) { storage.GetLocalStorage().Clear() testString := "test" testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1}) diff --git a/indexedDb/utils.go b/indexedDb/utils.go index 1aa6ff45805c1c65b9ad27e51a8b0590e7b8201d..df25de1c7dc4d005d2d7f8da0e034de31df652e5 100644 --- a/indexedDb/utils.go +++ b/indexedDb/utils.go @@ -32,6 +32,7 @@ func NewContext() (context.Context, context.CancelFunc) { } // Get is a generic helper for getting values from the given [idb.ObjectStore]. +// Only usable by primary key. func Get(db *idb.Database, objectStoreName string, key js.Value) (js.Value, error) { parentErr := errors.Errorf("failed to Get %s/%s", objectStoreName, key) @@ -74,7 +75,7 @@ func Get(db *idb.Database, objectStoreName string, key js.Value) (js.Value, erro // GetIndex is a generic helper for getting values from the given // [idb.ObjectStore] using the given [idb.Index]. -func GetIndex(db *idb.Database, objectStoreName string, +func GetIndex(db *idb.Database, objectStoreName, indexName string, key js.Value) (js.Value, error) { parentErr := errors.Errorf("failed to GetIndex %s/%s/%s", objectStoreName, indexName, key) @@ -147,17 +148,18 @@ func Put(db *idb.Database, objectStoreName string, value js.Value) (*idb.Request if err != nil { return nil, errors.Errorf("Putting value failed: %+v", err) } - jww.DEBUG.Printf("Successfully put value in %s: %v", + jww.DEBUG.Printf("Successfully put value in %s: %s", objectStoreName, utils.JsToJson(value)) return request, nil } // Delete is a generic helper for removing values from the given [idb.ObjectStore]. +// Only usable by primary key. func Delete(db *idb.Database, objectStoreName string, key js.Value) error { parentErr := errors.Errorf("failed to Delete %s/%s", objectStoreName, key) // Prepare the Transaction - txn, err := db.Transaction(idb.TransactionReadOnly, objectStoreName) + txn, err := db.Transaction(idb.TransactionReadWrite, objectStoreName) if err != nil { return errors.WithMessagef(parentErr, "Unable to create Transaction: %+v", err) @@ -169,20 +171,44 @@ func Delete(db *idb.Database, objectStoreName string, key js.Value) error { } // Perform the operation - deleteRequest, err := store.Delete(key) + _, err = store.Delete(key) if err != nil { return errors.WithMessagef(parentErr, - "Unable to Get from ObjectStore: %+v", err) + "Unable to Delete from ObjectStore: %+v", err) } // Wait for the operation to return ctx, cancel := NewContext() - err = deleteRequest.Await(ctx) + err = txn.Await(ctx) cancel() if err != nil { return errors.WithMessagef(parentErr, - "Unable to delete from ObjectStore: %+v", err) + "Unable to Delete from ObjectStore: %+v", err) + } + jww.DEBUG.Printf("Successfully deleted value at %s/%s", + objectStoreName, utils.JsToJson(key)) + return nil +} + +// DeleteIndex is a generic helper for removing values from the +// given [idb.ObjectStore] using the given [idb.Index]. Requires passing +// in the name of the primary key for the store. +func DeleteIndex(db *idb.Database, objectStoreName, + indexName, pkeyName string, key js.Value) error { + parentErr := errors.Errorf("failed to DeleteIndex %s/%s", objectStoreName, key) + + value, err := GetIndex(db, objectStoreName, indexName, key) + if err != nil { + return errors.WithMessagef(parentErr, "%+v", err) } + + err = Delete(db, objectStoreName, value.Get(pkeyName)) + if err != nil { + return errors.WithMessagef(parentErr, "%+v", err) + } + + jww.DEBUG.Printf("Successfully deleted value at %s/%s/%s", + objectStoreName, indexName, utils.JsToJson(key)) return nil } diff --git a/main.go b/main.go index 2a08720056a3c07df4d0e17174601e1f2bb34376..7194cffffd42c48edb25f495b372fcc009efe67f 100644 --- a/main.go +++ b/main.go @@ -82,6 +82,7 @@ func main() { js.Global().Set("GetChannelJSON", js.FuncOf(wasm.GetChannelJSON)) js.Global().Set("GetChannelInfo", js.FuncOf(wasm.GetChannelInfo)) js.Global().Set("GetShareUrlType", js.FuncOf(wasm.GetShareUrlType)) + js.Global().Set("ValidForever", js.FuncOf(wasm.ValidForever)) js.Global().Set("IsNicknameValid", js.FuncOf(wasm.IsNicknameValid)) js.Global().Set("NewChannelsDatabaseCipher", js.FuncOf(wasm.NewChannelsDatabaseCipher)) diff --git a/wasm/channels.go b/wasm/channels.go index 429ec45e79c5ce6bb202f087541d2052f107f17b..0c1be8ca72a4dd8f4da7657cc4c2ee72fa405259 100644 --- a/wasm/channels.go +++ b/wasm/channels.go @@ -12,6 +12,7 @@ package wasm import ( "encoding/base64" "encoding/json" + "errors" "gitlab.com/elixxir/client/v4/channels" channelsDb "gitlab.com/elixxir/xxdk-wasm/indexedDb/channels" "gitlab.com/xx_network/primitives/id" @@ -88,11 +89,14 @@ func (cm *ChannelsManager) GetID(js.Value, []js.Value) any { } // GenerateChannelIdentity creates a new private channel identity -// ([channel.PrivateIdentity]). The public component can be retrieved as JSON -// via [GetPublicChannelIdentityFromPrivate]. +// ([channel.PrivateIdentity]) from scratch and assigns it a codename. +// +// The public component can be retrieved as JSON via +// [GetPublicChannelIdentityFromPrivate]. // // Parameters: -// - args[0] - ID of [Cmix] object in tracker (int). +// - args[0] - ID of [Cmix] object in tracker (int). This can be retrieved +// using [Cmix.GetID]. // // Returns: // - Marshalled bytes of [channel.PrivateIdentity] (Uint8Array). @@ -110,8 +114,8 @@ func GenerateChannelIdentity(_ js.Value, args []js.Value) any { // 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. +// ConstructIdentity creates a codename in a public [channel.Identity] from an +// extant identity for a given codeset version. // // Parameters: // - args[0] - The Ed25519 public key (Uint8Array). @@ -170,7 +174,8 @@ func constructIdentity(_ js.Value, args []js.Value) any { // // Parameters: // - args[0] - The password used to encrypt the identity (string). -// - args[2] - The encrypted data (Uint8Array). +// - args[2] - The encrypted data from [ChannelsManager.ExportPrivateIdentity] +// (Uint8Array). // // Returns: // - JSON of [channel.PrivateIdentity] (Uint8Array). @@ -215,7 +220,7 @@ func GetPublicChannelIdentity(_ js.Value, args []js.Value) any { // // Parameters: // - args[0] - Bytes of the private identity -// (channel.PrivateIdentity]) (Uint8Array). +// ([channel.PrivateIdentity]) (Uint8Array). // // Returns: // - JSON of the public identity ([channel.Identity]) (Uint8Array). @@ -267,7 +272,8 @@ func NewChannelsManager(_ js.Value, args []js.Value) any { return newChannelsManagerJS(cm) } -// LoadChannelsManager loads an existing [ChannelsManager]. +// 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 @@ -601,8 +607,9 @@ func GetChannelInfo(_ js.Value, args []js.Value) any { return utils.CopyBytesToJS(ci) } -// GenerateChannel creates a new channel with the user as the admin. This -// function only create a channel and does not join it. +// 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]. @@ -643,12 +650,12 @@ func (cm *ChannelsManager) GenerateChannel(_ js.Value, args []js.Value) any { return prettyPrint } -// JoinChannel joins the given channel. It will fail if the channel has already -// been joined. +// JoinChannel joins the given channel. It will return the error +// [channels.ChannelAlreadyExistsErr] 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). +// or generated via [ChannelsManager.GenerateChannel] (string). // // The pretty print will be of the format: // @@ -668,8 +675,8 @@ func (cm *ChannelsManager) JoinChannel(_ js.Value, args []js.Value) any { return utils.CopyBytesToJS(ci) } -// LeaveChannel leaves the given channel. It will return an error if the channel -// was not previously joined. +// LeaveChannel leaves the given channel. It will return the error +// [channels.ChannelDoesNotExistsErr] if the channel was not previously joined. // // Parameters: // - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array). @@ -691,8 +698,11 @@ func (cm *ChannelsManager) LeaveChannel(_ js.Value, args []js.Value) any { // 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. +// // Parameters: -// - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array). +// - args[0] - Marshalled bytes of the channel's [id.ID] (Uint8Array). // // Returns: // - Throws a TypeError if the replay fails. @@ -754,8 +764,8 @@ type ShareURL struct { // 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 +// URLs, so if the number is changed in the URL, it will be verified when +// calling [DecodePublicURL] and [DecodePrivateURL]. There is no enforcement for // public URLs. // // Parameters: @@ -791,7 +801,7 @@ func (cm *ChannelsManager) GetShareURL(_ js.Value, args []js.Value) any { // // Returns: // - An int that corresponds to the [broadcast.PrivacyLevel] as outlined -// below. +// below (int). // - Throws a TypeError if parsing the URL fails. // // Possible returns: @@ -813,24 +823,33 @@ func GetShareUrlType(_ js.Value, args []js.Value) any { // 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() +} + // 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. +// 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. // // 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[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. // - 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 @@ -863,6 +882,7 @@ func (cm *ChannelsManager) SendGeneric(_ js.Value, args []js.Value) any { } // 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. @@ -873,11 +893,11 @@ func (cm *ChannelsManager) SendGeneric(_ js.Value, args []js.Value) any { // 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[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. // - args[3] - JSON of [xxdk.CMIXParams]. If left empty // [bindings.GetDefaultCMixParams] will be used internally (Uint8Array). // @@ -903,15 +923,14 @@ func (cm *ChannelsManager) SendMessage(_ js.Value, args []js.Value) any { 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. +// 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. // -// 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. +// 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. // // Parameters: // - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array). @@ -923,11 +942,11 @@ func (cm *ChannelsManager) SendMessage(_ js.Value, args []js.Value) any { // 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[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. // - args[4] - JSON of [xxdk.CMIXParams]. If left empty // [bindings.GetDefaultCMixParams] will be used internally (Uint8Array). // @@ -954,10 +973,11 @@ func (cm *ChannelsManager) SendReply(_ js.Value, args []js.Value) any { 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. +// 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. // // Parameters: // - args[0] - Marshalled bytes of the channel [id.ID] (Uint8Array). @@ -1014,11 +1034,11 @@ func (cm *ChannelsManager) SendReaction(_ js.Value, args []js.Value) any { // [channels.MessageType] (int). // - args[2] - The contents of the message (Uint8Array). The message should be // at most 510 bytes. -// - 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[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. // - 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 @@ -1065,8 +1085,7 @@ func (cm *ChannelsManager) SendAdminGeneric(_ js.Value, args []js.Value) any { // - args[0] - Marshalled bytes of channel [id.ID] (Uint8Array). // - args[1] - The marshalled [channel.MessageID] of the message you want to // delete (Uint8Array). -// - args[2] - Set to true to un-delete the message (boolean). -// - args[3] - JSON of [xxdk.CMIXParams]. This may be empty, and +// - args[2] - JSON of [xxdk.CMIXParams]. This may be empty, and // [GetDefaultCMixParams] will be used internally (Uint8Array). // // Returns a promise: @@ -1075,12 +1094,11 @@ func (cm *ChannelsManager) SendAdminGeneric(_ js.Value, args []js.Value) any { func (cm *ChannelsManager) DeleteMessage(_ js.Value, args []js.Value) any { channelIdBytes := utils.CopyBytesToGo(args[0]) targetMessageIdBytes := utils.CopyBytesToGo(args[1]) - undoAction := args[2].Bool() - cmixParamsJSON := utils.CopyBytesToGo(args[3]) + cmixParamsJSON := utils.CopyBytesToGo(args[2]) promiseFn := func(resolve, reject func(args ...any) js.Value) { sendReport, err := cm.api.DeleteMessage( - channelIdBytes, targetMessageIdBytes, undoAction, cmixParamsJSON) + channelIdBytes, targetMessageIdBytes, cmixParamsJSON) if err != nil { reject(utils.JsTrace(err)) } else { @@ -1091,9 +1109,9 @@ func (cm *ChannelsManager) DeleteMessage(_ js.Value, args []js.Value) any { return utils.CreatePromise(promiseFn) } -// PinMessage pins the target message to the top of a channel view for all -// users in the specified channel. Only the channel admin can pin user -// messages; if the user is not an admin of the channel, then the error +// PinMessage pins the target message to the top of a channel view for all users +// in the specified channel. Only the channel admin can pin user messages; if +// the user is not an admin of the channel, then the error // [channels.NotAnAdminErr] is returned. // // If undoAction is true, then the targeted message is unpinned. @@ -1105,7 +1123,9 @@ func (cm *ChannelsManager) DeleteMessage(_ js.Value, args []js.Value) any { // - args[1] - The marshalled [channel.MessageID] of the message you want to // pin (Uint8Array). // - args[2] - Set to true to unpin the message (boolean). -// - args[3] - JSON of [xxdk.CMIXParams]. This may be empty, and +// - args[3] - The time, in milliseconds, that the message should be pinned +// (int). To remain pinned indefinitely, use [ValidForever]. +// - args[4] - JSON of [xxdk.CMIXParams]. This may be empty, and // [GetDefaultCMixParams] will be used internally (Uint8Array). // // Returns a promise: @@ -1115,11 +1135,12 @@ func (cm *ChannelsManager) PinMessage(_ js.Value, args []js.Value) any { channelIdBytes := utils.CopyBytesToGo(args[0]) targetMessageIdBytes := utils.CopyBytesToGo(args[1]) undoAction := args[2].Bool() - cmixParamsJSON := utils.CopyBytesToGo(args[3]) + validUntilMS := args[3].Int() + cmixParamsJSON := utils.CopyBytesToGo(args[4]) promiseFn := func(resolve, reject func(args ...any) js.Value) { - sendReport, err := cm.api.PinMessage( - channelIdBytes, targetMessageIdBytes, undoAction, cmixParamsJSON) + sendReport, err := cm.api.PinMessage(channelIdBytes, + targetMessageIdBytes, undoAction, validUntilMS, cmixParamsJSON) if err != nil { reject(utils.JsTrace(err)) } else { @@ -1131,10 +1152,10 @@ func (cm *ChannelsManager) PinMessage(_ js.Value, args []js.Value) any { } // MuteUser is used to mute a user in a channel. Muting a user will cause all -// future messages from the user being hidden from view. Muted users are also -// unable to send messages. Only the channel admin can mute a user; if the user -// is not an admin of the channel, then the error [channels.NotAnAdminErr] is -// returned. +// future messages from the user being dropped on reception. Muted users are +// also unable to send messages. Only the channel admin can mute a user; if the +// user is not an admin of the channel, then the error [channels.NotAnAdminErr] +// is returned. // // If undoAction is true, then the targeted user will be unmuted. // @@ -1143,7 +1164,9 @@ func (cm *ChannelsManager) PinMessage(_ js.Value, args []js.Value) any { // - args[1] - The [ed25519.PublicKey] of the user you want to mute // (Uint8Array). // - args[2] - Set to true to unmute the message (boolean). -// - args[3] - JSON of [xxdk.CMIXParams]. This may be empty, and +// - args[3] - The time, in milliseconds, that the user should be muted (int). +// To remain muted indefinitely, use [ValidForever]. +// - args[4] - JSON of [xxdk.CMIXParams]. This may be empty, and // [GetDefaultCMixParams] will be used internally (Uint8Array). // // Returns a promise: @@ -1153,11 +1176,12 @@ func (cm *ChannelsManager) MuteUser(_ js.Value, args []js.Value) any { channelIdBytes := utils.CopyBytesToGo(args[0]) mutedUserPubKeyBytes := utils.CopyBytesToGo(args[1]) undoAction := args[2].Bool() - cmixParamsJSON := utils.CopyBytesToGo(args[3]) + validUntilMS := args[3].Int() + cmixParamsJSON := utils.CopyBytesToGo(args[4]) promiseFn := func(resolve, reject func(args ...any) js.Value) { - sendReport, err := cm.api.MuteUser( - channelIdBytes, mutedUserPubKeyBytes, undoAction, cmixParamsJSON) + sendReport, err := cm.api.MuteUser(channelIdBytes, mutedUserPubKeyBytes, + undoAction, validUntilMS, cmixParamsJSON) if err != nil { reject(utils.JsTrace(err)) } else { @@ -1172,8 +1196,8 @@ func (cm *ChannelsManager) MuteUser(_ js.Value, args []js.Value) any { // Other Channel Actions // //////////////////////////////////////////////////////////////////////////////// -// GetIdentity returns the marshaled public identity ([channel.Identity]) that -// the channel is using. +// GetIdentity returns the public identity ([channel.Identity]) of the user +// associated with this channel manager. // // Returns: // - JSON of the [channel.Identity] (Uint8Array). @@ -1188,14 +1212,14 @@ func (cm *ChannelsManager) GetIdentity(js.Value, []js.Value) any { return utils.CopyBytesToJS(i) } -// ExportPrivateIdentity encrypts and exports the private identity to a portable -// string. +// ExportPrivateIdentity encrypts the private identity using the password and +// exports it to a portable string. // // Parameters: -// - args[0] - Password to encrypt the identity with (string). +// - password - The password used to encrypt the private identity (string). // // Returns: -// - JSON of the encrypted private identity (Uint8Array). +// - Encrypted portable private identity (Uint8Array). // - Throws TypeError if exporting the identity fails. func (cm *ChannelsManager) ExportPrivateIdentity(_ js.Value, args []js.Value) any { i, err := cm.api.ExportPrivateIdentity(args[0].String()) @@ -1207,7 +1231,8 @@ func (cm *ChannelsManager) ExportPrivateIdentity(_ js.Value, args []js.Value) an return utils.CopyBytesToJS(i) } -// GetStorageTag returns the storage tag needed to reload the manager. +// GetStorageTag returns the tag where this manager is stored. To be used when +// loading the manager. The storage tag is derived from the public key. // // Returns: // - Storage tag (string). @@ -1235,7 +1260,8 @@ func (cm *ChannelsManager) SetNickname(_ js.Value, args []js.Value) any { return nil } -// DeleteNickname deletes the nickname for a given channel. +// DeleteNickname removes the nickname for a given channel. The name will revert +// back to the codename for this channel instead. // // Parameters: // - args[0] - Marshalled bytes if the channel's [id.ID] (Uint8Array). @@ -1259,7 +1285,7 @@ func (cm *ChannelsManager) DeleteNickname(_ js.Value, args []js.Value) any { // - args[0] - Marshalled bytes if the channel's [id.ID] (Uint8Array). // // Returns: -// - The nickname (string). +// - The nickname set for the channel (string). // - Throws TypeError if the channel has no nickname set. func (cm *ChannelsManager) GetNickname(_ js.Value, args []js.Value) any { nickname, err := cm.api.GetNickname(utils.CopyBytesToGo(args[0])) @@ -1321,7 +1347,7 @@ func (cm *ChannelsManager) Muted(_ js.Value, args []js.Value) any { // - args[0] - Marshalled bytes if the channel's [id.ID] (Uint8Array). // // Returns: -// - []byte - JSON of []ed25519.PublicKey (Uint8Array). Look below for an +// - JSON of an array of ed25519.PublicKey (Uint8Array). Look below for an // example. // - Throws a TypeError if the channel ID cannot be unmarshalled. // @@ -1415,9 +1441,9 @@ func (cm *ChannelsManager) ExportChannelAdminKey(_ js.Value, args []js.Value) an // Returns: // - bool - True if the private key belongs to the channel and false // otherwise. -// - Throws a TypeError with the message [Channels.WrongPasswordErr] for an +// - Throws a TypeError with the message [channels.WrongPasswordErr] for an // invalid password. -// - Throws a TypeError with the message [Channels.ChannelDoesNotExistsErr] i +// - Throws a TypeError with the message [channels.ChannelDoesNotExistsErr] i // the channel has not already been joined. func (cm *ChannelsManager) VerifyChannelAdminKey(_ js.Value, args []js.Value) any { channelID := utils.CopyBytesToGo(args[0]) @@ -1446,11 +1472,11 @@ func (cm *ChannelsManager) VerifyChannelAdminKey(_ js.Value, args []js.Value) an // Returns: // - Throws a TypeError if the password is invalid or the private key does // not match the channel ID. -// - Throws a TypeError with the message [Channels.WrongPasswordErr] for an +// - Throws a TypeError with the message [channels.WrongPasswordErr] for an // invalid password. -// - Throws a TypeError with the message [Channels.ChannelDoesNotExistsErr] if +// - Throws a TypeError with the message [channels.ChannelDoesNotExistsErr] if // the channel has not already been joined. -// - Throws a TypeError with the message [Channels.WrongPrivateKeyErr] if the +// - Throws a TypeError with the message [channels.WrongPrivateKeyErr] if the // private key does not belong to the channel. func (cm *ChannelsManager) ImportChannelAdminKey(_ js.Value, args []js.Value) any { channelID := utils.CopyBytesToGo(args[0]) @@ -1581,6 +1607,7 @@ func (emb *eventModelBuilder) Build(path string) bindings.EventModel { updateFromUUID: utils.WrapCB(emJs, "UpdateFromUUID"), updateFromMessageID: utils.WrapCB(emJs, "UpdateFromMessageID"), getMessage: utils.WrapCB(emJs, "GetMessage"), + deleteMessage: utils.WrapCB(emJs, "DeleteMessage"), } } @@ -1595,6 +1622,7 @@ type eventModel struct { updateFromUUID func(args ...any) js.Value updateFromMessageID func(args ...any) js.Value getMessage func(args ...any) js.Value + deleteMessage func(args ...any) js.Value } // JoinChannel is called whenever a channel is joined locally. @@ -1790,7 +1818,25 @@ func (em *eventModel) GetMessage(messageID []byte) ([]byte, error) { return nil, err } - return utils.CopyBytesToGo(em.getMessage(utils.CopyBytesToJS(messageID))), nil + if mae.Error != "" { + return nil, errors.New(mae.Error) + } + + return json.Marshal(mae.ModelMessage) +} + +// DeleteMessage deletes the message with the given [channel.MessageID] from +// the database. +// +// Parameters: +// - messageID - The bytes of the [channel.MessageID] of the message. +func (em *eventModel) DeleteMessage(messageID []byte) error { + err := em.deleteMessage(utils.CopyBytesToJS(messageID)) + if !err.IsUndefined() { + return js.Error{Value: err} + } + + return nil } // MessageAndError contains a message returned by eventModel.GetMessage or any @@ -1820,7 +1866,7 @@ func (em *eventModel) GetMessage(messageID []byte) ([]byte, error) { // "Error": "" // } type MessageAndError struct { - // MessageJSON should contain the JSON of [channels.ModelMessage]. + // MessageJSON should contain the JSON of channels.ModelMessage. ModelMessage channels.ModelMessage // Error should only be filled when an error occurs on message lookup. diff --git a/wasm/connect.go b/wasm/connect.go index 5d8e347dbb34c9adf4f724eee13a3eda43fc4f57..bfb95a6d882f808277a856dfbda9937c3bb3d687 100644 --- a/wasm/connect.go +++ b/wasm/connect.go @@ -80,10 +80,6 @@ func (c *Cmix) Connect(_ js.Value, args []js.Value) any { // SendE2E is a wrapper for sending specifically to the [Connection]'s // [partner.Manager]. // -// Returns: -// - []byte - the JSON marshalled bytes of the E2ESendReport object, which can -// be passed into [Cmix.WaitForRoundResult] to see if the send succeeded. -// // Parameters: // - args[0] - Message type from [catalog.MessageType] (int). // - args[1] - Message payload (Uint8Array).