From 04b0ed0d6128e7241cbad83846118b7961fc6282 Mon Sep 17 00:00:00 2001 From: Jono Wenger <jono@elixxir.io> Date: Tue, 22 Nov 2022 00:56:38 +0000 Subject: [PATCH] XX-4333 / Mute User --- go.mod | 6 +- go.sum | 12 +- indexedDb/implementation.go | 237 +++++++++++++++++++++++-------- indexedDb/implementation_test.go | 16 +-- wasm/channels.go | 155 +++++++++++++++++++- 5 files changed, 346 insertions(+), 80 deletions(-) diff --git a/go.mod b/go.mod index 13848078..92c0dc44 100644 --- a/go.mod +++ b/go.mod @@ -7,10 +7,10 @@ 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 v1.5.1-0.20221121234059-3f75d507e3c8 + gitlab.com/elixxir/client v1.5.1-0.20221122004847-08fb6e6d3c0e gitlab.com/elixxir/crypto v0.0.7-0.20221121233335-83f145891bc7 - gitlab.com/elixxir/primitives v0.0.3-0.20221110181119-e83320a48b13 - gitlab.com/xx_network/crypto v0.0.5-0.20221110181048-76f0c556fe95 + gitlab.com/elixxir/primitives v0.0.3-0.20221114231218-cc461261a6af + gitlab.com/xx_network/crypto v0.0.5-0.20221121220724-8eefdbb0eb46 gitlab.com/xx_network/primitives v0.0.4-0.20221110180011-fd6ea3058225 golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa ) diff --git a/go.sum b/go.sum index 4fcea68d..6faf0c78 100644 --- a/go.sum +++ b/go.sum @@ -368,20 +368,20 @@ 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 v1.5.1-0.20221121234059-3f75d507e3c8 h1:DWzodsczpQh9Y5415grILfP/xd0ol0mKczUyTJ5Gp0k= -gitlab.com/elixxir/client v1.5.1-0.20221121234059-3f75d507e3c8/go.mod h1:ZUvPwnyqsvQaiZCkWKb4coWa5nwv6XxRthV/64rZK4s= +gitlab.com/elixxir/client v1.5.1-0.20221122004847-08fb6e6d3c0e h1:HZuKqQqgwKjXzMVY3/n0nlAd/+0aFRvJKD4/LIrNEew= +gitlab.com/elixxir/client v1.5.1-0.20221122004847-08fb6e6d3c0e/go.mod h1:ZUvPwnyqsvQaiZCkWKb4coWa5nwv6XxRthV/64rZK4s= gitlab.com/elixxir/comms v0.0.4-0.20221110181420-84bca6216fe4 h1:bLRjVCyMVde4n2hTVgoyyIAWrKI4CevpChchkPeb6A0= gitlab.com/elixxir/comms v0.0.4-0.20221110181420-84bca6216fe4/go.mod h1:XhI2/CMng+xcH3mAs+1aPz29PSNu1079XMJ8V+xxihw= gitlab.com/elixxir/crypto v0.0.7-0.20221121233335-83f145891bc7 h1:yhc8jQ27JKypdRE41NpfJPaYRS0sNkOwugaIyoscDiU= gitlab.com/elixxir/crypto v0.0.7-0.20221121233335-83f145891bc7/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.20221110181119-e83320a48b13 h1:U3tbClFN5BLYlAoMj+o6VWAs9akbFiJstMGCuk1aB94= -gitlab.com/elixxir/primitives v0.0.3-0.20221110181119-e83320a48b13/go.mod h1:DUnCTXYKgjpro5+6ITySKIf+qzW2vhW40IVHMimdsqw= +gitlab.com/elixxir/primitives v0.0.3-0.20221114231218-cc461261a6af h1:xcPqknK1ehNb9xwcutTdoR0YgD7DC/ySh9z49tIpSxQ= +gitlab.com/elixxir/primitives v0.0.3-0.20221114231218-cc461261a6af/go.mod h1:DUnCTXYKgjpro5+6ITySKIf+qzW2vhW40IVHMimdsqw= gitlab.com/xx_network/comms v0.0.4-0.20221110181111-4f0694876936 h1:eQQ4zUvGWIzCWdBJ6qlysWUMwrc2tM8GripFqdT1SAs= gitlab.com/xx_network/comms v0.0.4-0.20221110181111-4f0694876936/go.mod h1:+RfHgk75ywMvmucOpPS7rSUlsnbPyBuLsr13tsthUTE= -gitlab.com/xx_network/crypto v0.0.5-0.20221110181048-76f0c556fe95 h1:rC6lx6sD6u617Qu0ZndKZQRjXuRkyrI9Q6Y0Ki+dnK4= -gitlab.com/xx_network/crypto v0.0.5-0.20221110181048-76f0c556fe95/go.mod h1:acWUBKCpae/XVaQF7J9RnLAlBT13i5r7gnON+mrIxBk= +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.20221110180011-fd6ea3058225 h1:TAn87e6Zt9KwcSnWKyIul5eu8T0RHY9FDubCGs3G0dw= gitlab.com/xx_network/primitives v0.0.4-0.20221110180011-fd6ea3058225/go.mod h1:rP/2IsqIFHapuIB4mstXKItvwoJRQ9Wlms/NGeutHsk= gitlab.com/xx_network/ring v0.0.3-0.20220902183151-a7d3b15bc981 h1:1s0vX9BbkiD0IVXwr3LOaTBcq1wBrWcUWMBK0s8r0Z0= diff --git a/indexedDb/implementation.go b/indexedDb/implementation.go index 59192d26..5cd3503b 100644 --- a/indexedDb/implementation.go +++ b/indexedDb/implementation.go @@ -205,7 +205,7 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID, messageID cryptoChannel.MessageID, nickname, text string, pubKey ed25519.PublicKey, codeset uint8, timestamp time.Time, lease time.Duration, round rounds.Round, - mType channels.MessageType, status channels.SentStatus) uint64 { + mType channels.MessageType, status channels.SentStatus, hidden bool) uint64 { textBytes := []byte(text) var err error @@ -220,7 +220,8 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID, msgToInsert := buildMessage( channelID.Marshal(), messageID.Bytes(), nil, nickname, - textBytes, pubKey, codeset, timestamp, lease, round.ID, mType, status) + textBytes, pubKey, codeset, timestamp, lease, round.ID, mType, + false, hidden, status) uuid, err := w.receiveHelper(msgToInsert, false) if err != nil { @@ -241,7 +242,7 @@ func (w *wasmModel) ReceiveReply(channelID *id.ID, messageID cryptoChannel.MessageID, replyTo cryptoChannel.MessageID, nickname, text string, pubKey ed25519.PublicKey, codeset uint8, timestamp time.Time, lease time.Duration, round rounds.Round, - mType channels.MessageType, status channels.SentStatus) uint64 { + mType channels.MessageType, status channels.SentStatus, hidden bool) uint64 { textBytes := []byte(text) var err error @@ -256,7 +257,7 @@ func (w *wasmModel) ReceiveReply(channelID *id.ID, msgToInsert := buildMessage(channelID.Marshal(), messageID.Bytes(), replyTo.Bytes(), nickname, textBytes, pubKey, codeset, timestamp, lease, - round.ID, mType, status) + round.ID, mType, false, hidden, status) uuid, err := w.receiveHelper(msgToInsert, false) @@ -277,7 +278,7 @@ func (w *wasmModel) ReceiveReaction(channelID *id.ID, messageID cryptoChannel.MessageID, reactionTo cryptoChannel.MessageID, nickname, reaction string, pubKey ed25519.PublicKey, codeset uint8, timestamp time.Time, lease time.Duration, round rounds.Round, - mType channels.MessageType, status channels.SentStatus) uint64 { + mType channels.MessageType, status channels.SentStatus, hidden bool) uint64 { textBytes := []byte(reaction) var err error @@ -292,7 +293,8 @@ func (w *wasmModel) ReceiveReaction(channelID *id.ID, msgToInsert := buildMessage( channelID.Marshal(), messageID.Bytes(), reactionTo.Bytes(), nickname, - textBytes, pubKey, codeset, timestamp, lease, round.ID, mType, status) + textBytes, pubKey, codeset, timestamp, lease, round.ID, mType, + false, hidden, status) uuid, err := w.receiveHelper(msgToInsert, false) if err != nil { @@ -302,15 +304,52 @@ func (w *wasmModel) ReceiveReaction(channelID *id.ID, return uuid } -// UpdateSentStatus is called whenever the [channels.SentStatus] of a message -// has changed. At this point the message ID goes from empty/unknown to -// populated. +// UpdateFromMessageID is called whenever a message with the message ID is +// modified. // -// TODO: Potential race condition due to separate get/update operations. -func (w *wasmModel) UpdateSentStatus(uuid uint64, - messageID cryptoChannel.MessageID, timestamp time.Time, round rounds.Round, - status channels.SentStatus) { - parentErr := errors.New("failed to UpdateSentStatus") +// The API needs to return the UUID of the modified message that can be +// referenced at a later time. +// +// timestamp, round, pinned, and hidden are all nillable and may be updated +// based upon the UUID at a later date. If a nil value is passed, then make +// no update. +func (w *wasmModel) UpdateFromMessageID(messageID cryptoChannel.MessageID, + timestamp *time.Time, round *rounds.Round, pinned, hidden *bool, + status *channels.SentStatus) uint64 { + parentErr := errors.New("failed to UpdateFromMessageID") + + // FIXME: this is a bit of race condition without the mux. + // This should be done via the transactions (i.e., make a + // special version of receiveHelper) + w.updateMux.Lock() + defer w.updateMux.Unlock() + + msgIDStr := base64.StdEncoding.EncodeToString(messageID.Marshal()) + currentMsgObj, err := w.getIndex(messageStoreName, + messageStoreMessageIndex, js.ValueOf(msgIDStr)) + if err != nil { + jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, + "Failed to get message by index: %+v", err)) + return 0 + } + + currentMsg := utils.JsToJson(currentMsgObj) + uuid, err := w.updateMessage(currentMsg, &messageID, timestamp, + round, pinned, hidden, status) + if err != nil { + jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, + "Unable to updateMessage: %+v", err)) + } + return uuid +} + +// messageID, timestamp, round, pinned, and hidden are all nillable and may +// be updated based upon the UUID at a later date. If a nil value is passed, +// then make no update. +func (w *wasmModel) UpdateFromUUID(uuid uint64, messageID *cryptoChannel.MessageID, + timestamp *time.Time, round *rounds.Round, pinned, hidden *bool, + status *channels.SentStatus) { + parentErr := errors.New("failed to UpdateFromUUID") // FIXME: this is a bit of race condition without the mux. // This should be done via the transactions (i.e., make a @@ -324,36 +363,64 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64, // Use the key to get the existing Message currentMsg, err := w.get(messageStoreName, key) if err != nil { + jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, + "Failed to get message: %+v", err)) return } - // Extract the existing Message and update the Status + _, err = w.updateMessage(currentMsg, messageID, timestamp, + round, pinned, hidden, status) + if err != nil { + jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, + "Unable to updateMessage: %+v", err)) + } +} + +// updateMessage is a helper for updating a stored message. +func (w *wasmModel) updateMessage(currentMsgJson string, + messageID *cryptoChannel.MessageID, timestamp *time.Time, + round *rounds.Round, pinned, hidden *bool, + status *channels.SentStatus) (uint64, error) { + newMessage := &Message{} - err = json.Unmarshal([]byte(currentMsg), newMessage) + err := json.Unmarshal([]byte(currentMsgJson), newMessage) if err != nil { - return + return 0, nil } - newMessage.Status = uint8(status) - if !messageID.Equals(cryptoChannel.MessageID{}) { - newMessage.MessageID = messageID.Bytes() + + if status != nil { + newMessage.Status = uint8(*status) + } + if messageID != nil { + newMessage.MessageID = messageID.Marshal() } - if round.ID != 0 { + if round != nil { newMessage.Round = uint64(round.ID) } - if !timestamp.Equal(time.Time{}) { - newMessage.Timestamp = timestamp + if timestamp != nil { + newMessage.Timestamp = *timestamp + } + + if pinned != nil { + newMessage.Pinned = *pinned + } + + if hidden != nil { + newMessage.Hidden = *hidden } // Store the updated Message - _, err = w.receiveHelper(newMessage, true) + uuid, err := w.receiveHelper(newMessage, true) if err != nil { - jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error())) + return 0, err } channelID := &id.ID{} copy(channelID[:], newMessage.ChannelID) go w.receivedMessageCB(uuid, channelID, true) + + return uuid, nil } // buildMessage is a private helper that converts typical [channels.EventModel] @@ -365,7 +432,7 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64, func buildMessage(channelID, messageID, parentID []byte, nickname string, text []byte, pubKey ed25519.PublicKey, codeset uint8, timestamp time.Time, lease time.Duration, round id.Round, mType channels.MessageType, - status channels.SentStatus) *Message { + pinned, hidden bool, status channels.SentStatus) *Message { return &Message{ MessageID: messageID, Nickname: nickname, @@ -374,8 +441,8 @@ func buildMessage(channelID, messageID, parentID []byte, nickname string, Timestamp: timestamp, Lease: lease, Status: uint8(status), - Hidden: false, - Pinned: false, + Hidden: hidden, + Pinned: pinned, Text: text, Type: uint16(mType), Round: uint64(round), @@ -435,9 +502,9 @@ func (w *wasmModel) receiveHelper(newMessage *Message, isUpdate bool) (uint64, if res.IsUndefined() { msgID := cryptoChannel.MessageID{} copy(msgID[:], newMessage.MessageID) - uuid, errLookup := w.msgIDLookup(msgID) - if uuid != 0 && errLookup == nil { - return uuid, nil + msg, errLookup := w.msgIDLookup(msgID) + if msg.ID != 0 && errLookup == nil { + return msg.ID, nil } return 0, errors.Errorf("uuid lookup failure: %+v", err) } @@ -447,6 +514,48 @@ func (w *wasmModel) receiveHelper(newMessage *Message, isUpdate bool) (uint64, return uuid, nil } +// GetMessage returns the message with the given [channel.MessageID]. +func (w *wasmModel) GetMessage(messageID cryptoChannel.MessageID) (channels.ModelMessage, error) { + lookupResult, err := w.msgIDLookup(messageID) + if err != nil { + return channels.ModelMessage{}, err + } + + var channelId *id.ID + if lookupResult.ChannelID != nil { + channelId, err = id.Unmarshal(lookupResult.ChannelID) + if err != nil { + return channels.ModelMessage{}, err + } + } + + var parentMsgId cryptoChannel.MessageID + if lookupResult.ParentMessageID != nil { + parentMsgId, err = cryptoChannel.UnmarshalMessageID(lookupResult.ParentMessageID) + if err != nil { + return channels.ModelMessage{}, err + } + } + + return channels.ModelMessage{ + UUID: lookupResult.ID, + Nickname: lookupResult.Nickname, + MessageID: messageID, + ChannelID: channelId, + ParentMessageID: parentMsgId, + Timestamp: lookupResult.Timestamp, + Lease: lookupResult.Lease, + Status: channels.SentStatus(lookupResult.Status), + Hidden: lookupResult.Hidden, + Pinned: lookupResult.Pinned, + Content: lookupResult.Text, + Type: channels.MessageType(lookupResult.Type), + Round: id.Round(lookupResult.Round), + PubKey: lookupResult.Pubkey, + CodesetVersion: lookupResult.CodesetVersion, + }, nil +} + // get is a generic private helper for getting values from the given // [idb.ObjectStore]. func (w *wasmModel) get(objectStoreName string, key js.Value) (string, error) { @@ -486,54 +595,70 @@ func (w *wasmModel) get(objectStoreName string, key js.Value) (string, error) { return resultStr, nil } -func (w *wasmModel) msgIDLookup(messageID cryptoChannel.MessageID) (uint64, - error) { - parentErr := errors.Errorf("failed to get %s/%s", messageStoreName, - messageID) +// getIndex is a generic private helper for getting values from the given +// [idb.ObjectStore] using a [idb.Index]. +func (w *wasmModel) getIndex(objectStoreName string, + indexName string, key js.Value) (js.Value, error) { + + parentErr := errors.Errorf("failed to getIndex %s/%s/%s", + objectStoreName, indexName, key) // Prepare the Transaction - txn, err := w.db.Transaction(idb.TransactionReadOnly, messageStoreName) + txn, err := w.db.Transaction(idb.TransactionReadOnly, objectStoreName) if err != nil { - return 0, errors.WithMessagef(parentErr, + return js.Null(), errors.WithMessagef(parentErr, "Unable to create Transaction: %+v", err) } - store, err := txn.ObjectStore(messageStoreName) + store, err := txn.ObjectStore(objectStoreName) if err != nil { - return 0, errors.WithMessagef(parentErr, + return js.Null(), errors.WithMessagef(parentErr, "Unable to get ObjectStore: %+v", err) } - idx, err := store.Index(messageStoreMessageIndex) + idx, err := store.Index(indexName) if err != nil { - return 0, errors.WithMessagef(parentErr, + return js.Null(), errors.WithMessagef(parentErr, "Unable to get index: %+v", err) } - msgIDStr := base64.StdEncoding.EncodeToString(messageID.Bytes()) - - keyReq, err := idx.Get(js.ValueOf(msgIDStr)) + // Perform the operation + getRequest, err := idx.Get(key) if err != nil { - return 0, errors.WithMessagef(parentErr, - "Unable to get keyReq: %+v", err) + return js.Null(), errors.WithMessagef(parentErr, + "Unable to Get from ObjectStore: %+v", err) } + // Wait for the operation to return ctx, cancel := newContext() - keyObj, err := keyReq.Await(ctx) + resultObj, err := getRequest.Await(ctx) cancel() if err != nil { - return 0, errors.WithMessagef(parentErr, + return js.Null(), errors.WithMessagef(parentErr, "Unable to get from ObjectStore: %+v", err) } - // Process result into string - resultStr := utils.JsToJson(keyObj) - jww.DEBUG.Printf("Index lookup of %s/%s/%s: %s", messageStoreName, - messageStoreMessageIndex, msgIDStr, resultStr) + jww.DEBUG.Printf("Got via index from %s/%s/%s: %s", + objectStoreName, indexName, key, resultObj.String()) + return resultObj, nil +} - uuid := uint64(0) - if !keyObj.IsUndefined() { - uuid = uint64(keyObj.Get("id").Int()) +func (w *wasmModel) msgIDLookup(messageID cryptoChannel.MessageID) (*Message, + error) { + msgIDStr := base64.StdEncoding.EncodeToString(messageID.Marshal()) + keyObj, err := w.getIndex(messageStoreName, + messageStoreMessageIndex, js.ValueOf(msgIDStr)) + if err != nil { + return nil, err + } else if keyObj.IsUndefined() { + return nil, errors.Errorf("no message for %s found", msgIDStr) } - return uuid, nil + + // Process result into string + resultMsg := &Message{} + err = json.Unmarshal([]byte(utils.JsToJson(keyObj)), resultMsg) + if err != nil { + return nil, err + } + return resultMsg, nil } // dump returns the given [idb.ObjectStore] contents to string slice for diff --git a/indexedDb/implementation_test.go b/indexedDb/implementation_test.go index d4f6226f..8cff2ba6 100644 --- a/indexedDb/implementation_test.go +++ b/indexedDb/implementation_test.go @@ -47,7 +47,7 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) { // Store a test message testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil, testString, []byte(testString), []byte{8, 6, 7, 5}, 0, netTime.Now(), - time.Second, 0, 0, channels.Sent) + time.Second, 0, 0, false, false, channels.Sent) uuid, err := eventModel.receiveHelper(testMsg, false) if err != nil { t.Fatalf("%+v", err) @@ -64,8 +64,8 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) { // Update the sentStatus expectedStatus := channels.Failed - eventModel.UpdateSentStatus(uuid, testMsgId, netTime.Now(), - rounds.Round{ID: 8675309}, expectedStatus) + eventModel.UpdateFromUUID(uuid, nil, nil, + nil, nil, nil, &expectedStatus) // Check the resulting status results, err = eventModel.dump(messageStoreName) @@ -147,7 +147,7 @@ func Test_wasmModel_UUIDTest(t *testing.T) { rnd := rounds.Round{ID: id.Round(42)} uuid := eventModel.ReceiveMessage(channelID, msgID, "test", testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, 0, - netTime.Now(), time.Hour, rnd, 0, channels.Sent) + netTime.Now(), time.Hour, rnd, 0, channels.Sent, false) uuids[i] = uuid } @@ -182,7 +182,7 @@ func Test_wasmModel_DuplicateReceives(t *testing.T) { rnd := rounds.Round{ID: id.Round(42)} uuid := eventModel.ReceiveMessage(channelID, msgID, "test", testString+fmt.Sprintf("%d", i), []byte{8, 6, 7, 5}, 0, - netTime.Now(), time.Hour, rnd, 0, channels.Sent) + netTime.Now(), time.Hour, rnd, 0, channels.Sent, false) uuids[i] = uuid } @@ -226,7 +226,7 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) { testMsgId := channel.MakeMessageID([]byte(testStr), &id.ID{1}) eventModel.ReceiveMessage(thisChannel, testMsgId, testStr, testStr, []byte{8, 6, 7, 5}, 0, netTime.Now(), time.Second, - rounds.Round{ID: id.Round(0)}, 0, channels.Sent) + rounds.Round{ID: id.Round(0)}, 0, channels.Sent, false) } // Check pre-results @@ -286,7 +286,7 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) { testMsgId := channel.MakeMessageID([]byte(testString), &id.ID{1}) testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil, testString, []byte(testString), []byte{8, 6, 7, 5}, 0, netTime.Now(), - time.Second, 0, 0, channels.Sent) + time.Second, 0, 0, false, false, channels.Sent) _, err = eventModel.receiveHelper(testMsg, false) if err != nil { t.Fatal(err) @@ -309,7 +309,7 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) { testMsgId2 := channel.MakeMessageID([]byte(testString), &id.ID{2}) testMsg = buildMessage([]byte(testString), testMsgId2.Bytes(), nil, testString, []byte(testString), []byte{8, 6, 7, 5}, 0, netTime.Now(), - time.Second, 0, 0, channels.Sent) + time.Second, 0, 0, false, false, channels.Sent) primaryKey, err := eventModel.receiveHelper(testMsg, false) if err != nil { t.Fatal(err) diff --git a/wasm/channels.go b/wasm/channels.go index 5eb4e3c7..ce831a27 100644 --- a/wasm/channels.go +++ b/wasm/channels.go @@ -1,6 +1,6 @@ //////////////////////////////////////////////////////////////////////////////// // Copyright © 2022 xx foundation // -// // +// // // Use of this source code is governed by a license that can be found in the // // LICENSE file. // //////////////////////////////////////////////////////////////////////////////// @@ -53,12 +53,16 @@ func newChannelsManagerJS(api *bindings.ChannelsManager) map[string]interface{} "SendMessage": js.FuncOf(cm.SendMessage), "SendReply": js.FuncOf(cm.SendReply), "SendReaction": js.FuncOf(cm.SendReaction), + "DeleteMessage": js.FuncOf(cm.DeleteMessage), + "PinMessage": js.FuncOf(cm.PinMessage), + "MuteUser": js.FuncOf(cm.MuteUser), "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), + "Muted": js.FuncOf(cm.Muted), // Channel Receiving Logic and Callback Registration "RegisterReceiveHandler": js.FuncOf(cm.RegisterReceiveHandler), @@ -1105,6 +1109,122 @@ func (ch *ChannelsManager) SendReaction(_ js.Value, args []js.Value) interface{} return utils.CreatePromise(promiseFn) } +// DeleteMessage 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] - The PEM-encoded admin RSA private key for the channel +// (Uint8Array). If a user is trying to delete their own message, make this +// empty. +// - args[1] - Marshalled bytes of channel [id.ID] (Uint8Array). +// - args[2] - The marshalled [channel.MessageID] of the message you want to +// delete (Uint8Array). +// - args[3] - Set to true to un-delete the message (boolean). +// - args[4] - JSON of [xxdk.CMIXParams]. This may be empty, and +// [GetDefaultCMixParams] will be used internally (Uint8Array). +// +// Returns: +// - Resolves to the JSON of [bindings.ChannelSendReport] (Uint8Array). +// - Rejected with an error if sending fails. +func (ch *ChannelsManager) DeleteMessage(_ js.Value, args []js.Value) interface{} { + adminPrivateKey := utils.CopyBytesToGo(args[0]) + channelIdBytes := utils.CopyBytesToGo(args[1]) + targetMessageIdBytes := utils.CopyBytesToGo(args[2]) + undoAction := args[3].Bool() + cmixParamsJSON := utils.CopyBytesToGo(args[4]) + + promiseFn := func(resolve, reject func(args ...interface{}) js.Value) { + sendReport, err := ch.api.DeleteMessage(adminPrivateKey, channelIdBytes, + targetMessageIdBytes, undoAction, cmixParamsJSON) + if err != nil { + reject(utils.JsTrace(err)) + } else { + resolve(utils.CopyBytesToJS(sendReport)) + } + } + + 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. +// +// Clients will drop the pin if they do not recognize the target message. +// +// Parameters: +// - args[0] - The PEM-encoded admin RSA private key for the channel +// (Uint8Array). +// - args[1] - Marshalled bytes of channel [id.ID] (Uint8Array). +// - args[2] - The marshalled [channel.MessageID] of the message you want to +// pin (Uint8Array). +// - args[3] - Set to true to un-delete the message (boolean). +// - args[4] - JSON of [xxdk.CMIXParams]. This may be empty, and +// [GetDefaultCMixParams] will be used internally (Uint8Array). +// +// Returns: +// - []byte - JSON of [ChannelSendReport]. +func (ch *ChannelsManager) PinMessage(_ js.Value, args []js.Value) interface{} { + adminPrivateKey := utils.CopyBytesToGo(args[0]) + channelIdBytes := utils.CopyBytesToGo(args[1]) + targetMessageIdBytes := utils.CopyBytesToGo(args[2]) + undoAction := args[3].Bool() + cmixParamsJSON := utils.CopyBytesToGo(args[4]) + + promiseFn := func(resolve, reject func(args ...interface{}) js.Value) { + sendReport, err := ch.api.PinMessage(adminPrivateKey, channelIdBytes, + targetMessageIdBytes, undoAction, cmixParamsJSON) + if err != nil { + reject(utils.JsTrace(err)) + } else { + resolve(utils.CopyBytesToJS(sendReport)) + } + } + + return utils.CreatePromise(promiseFn) +} + +// 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 undoAction is true, then the targeted user will be unmuted. +// +// Parameters: +// - args[0] - The PEM-encoded admin RSA private key for the channel +// (Uint8Array). +// - args[1] - Marshalled bytes of channel [id.ID] (Uint8Array). +// - mutedUserPubKeyBytes - The [ed25519.PublicKey] of the user you want to +// mute. +// - args[3] - Set to true to un-delete the message (boolean). +// - args[4] - JSON of [xxdk.CMIXParams]. This may be empty, and +// [GetDefaultCMixParams] will be used internally (Uint8Array). +// +// Returns: +// - []byte - JSON of [ChannelSendReport]. +func (ch *ChannelsManager) MuteUser(_ js.Value, args []js.Value) interface{} { + adminPrivateKey := utils.CopyBytesToGo(args[0]) + channelIdBytes := utils.CopyBytesToGo(args[1]) + mutedUserPubKeyBytes := utils.CopyBytesToGo(args[2]) + undoAction := args[3].Bool() + cmixParamsJSON := utils.CopyBytesToGo(args[4]) + + promiseFn := func(resolve, reject func(args ...interface{}) js.Value) { + sendReport, err := ch.api.MuteUser(adminPrivateKey, channelIdBytes, + mutedUserPubKeyBytes, undoAction, 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. // @@ -1224,6 +1344,27 @@ func IsNicknameValid(_ js.Value, args []js.Value) interface{} { return nil } +// Muted returns true if the user is currently muted in the given channel. +// +// Parameters: +// - args[0] - Marshalled bytes if the channel's [id.ID] (Uint8Array). +// +// Returns: +// - Returns true if the user is muted in the channel and false otherwise +// (boolean). +// - Throws a TypeError if the channel ID cannot be unmarshalled. +func (ch *ChannelsManager) Muted(_ js.Value, args []js.Value) interface{} { + channelIDBytes := utils.CopyBytesToGo(args[0]) + + muted, err := ch.api.Muted(channelIDBytes) + if err != nil { + utils.Throw(utils.TypeError, err) + return nil + } + + return muted +} + //////////////////////////////////////////////////////////////////////////////// // Channel Receiving Logic and Callback Registration // //////////////////////////////////////////////////////////////////////////////// @@ -1345,11 +1486,11 @@ func (em *eventModel) LeaveChannel(channelID []byte) { // 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 { + status int64, hidden bool) int64 { uuid := em.receiveMessage(utils.CopyBytesToJS(channelID), utils.CopyBytesToJS(messageID), nickname, text, utils.CopyBytesToJS(pubKey), codeset, timestamp, lease, roundId, - msgType, status) + msgType, status, hidden) return int64(uuid.Int()) } @@ -1389,11 +1530,11 @@ func (em *eventModel) ReceiveMessage(channelID, messageID []byte, nickname, // 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 { + roundId, msgType, status int64, hidden bool) int64 { uuid := em.receiveReply(utils.CopyBytesToJS(channelID), utils.CopyBytesToJS(messageID), utils.CopyBytesToJS(reactionTo), senderUsername, text, utils.CopyBytesToJS(pubKey), codeset, - timestamp, lease, roundId, msgType, status) + timestamp, lease, roundId, msgType, status, hidden) return int64(uuid.Int()) } @@ -1433,11 +1574,11 @@ func (em *eventModel) ReceiveReply(channelID, messageID, reactionTo []byte, // 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 { + lease, roundId, msgType, status int64, hidden bool) int64 { uuid := em.receiveReaction(utils.CopyBytesToJS(channelID), utils.CopyBytesToJS(messageID), utils.CopyBytesToJS(reactionTo), senderUsername, reaction, utils.CopyBytesToJS(pubKey), codeset, - timestamp, lease, roundId, msgType, status) + timestamp, lease, roundId, msgType, status, hidden) return int64(uuid.Int()) } -- GitLab