diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ba4f4a4b7894a65da5d08fefc3473e0823ca8f03..e77bc987fd3842bc495a43069a874e67346fd36e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -20,7 +20,6 @@ stages: - test - build - tag - - version_check - doc-update - version_check diff --git a/go.mod b/go.mod index 5b856648089fdb19d8553ffcde893199ddba7de1..aa9e2404c8d26c12cda1e5d690bfb08728bf56fd 100644 --- a/go.mod +++ b/go.mod @@ -25,38 +25,24 @@ require ( github.com/desertbit/timer v0.0.0-20180107155436-c41aec40b27f // indirect github.com/elliotchance/orderedmap v1.4.0 // indirect github.com/forPelevin/gomoji v1.1.8 // indirect - github.com/fsnotify/fsnotify v1.5.4 // indirect github.com/gobwas/ws v1.1.0 // indirect github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 // indirect github.com/golang/protobuf v1.5.2 // indirect github.com/gorilla/websocket v1.5.0 // indirect - github.com/hashicorp/hcl v1.0.0 // indirect github.com/improbable-eng/grpc-web v0.15.0 // indirect - github.com/inconshreveable/mousetrap v1.0.0 // indirect github.com/json-iterator/go v1.1.12 // indirect github.com/klauspost/compress v1.11.7 // indirect github.com/klauspost/cpuid/v2 v2.1.0 // indirect - github.com/magiconair/properties v1.8.6 // indirect github.com/mattn/go-isatty v0.0.14 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect - github.com/mitchellh/mapstructure v1.5.0 // indirect github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 // indirect github.com/oasisprotocol/curve25519-voi v0.0.0-20221003100820-41fad3beba17 // indirect github.com/oasisprotocol/deoxysii v0.0.0-20220228165953-2091330c22b7 // indirect - github.com/pelletier/go-toml v1.9.5 // indirect - github.com/pelletier/go-toml/v2 v2.0.2 // indirect - github.com/pkg/profile v1.6.0 // indirect github.com/rivo/uniseg v0.4.3 // indirect github.com/rs/cors v1.8.2 // indirect github.com/sethvargo/go-diceware v0.3.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/soheilhy/cmux v0.1.5 // indirect - github.com/spf13/afero v1.9.2 // indirect - github.com/spf13/cast v1.5.0 // indirect - github.com/spf13/cobra v1.5.0 // indirect - github.com/spf13/pflag v1.0.5 // indirect - github.com/spf13/viper v1.12.0 // indirect - github.com/subosito/gotenv v1.4.0 // indirect github.com/ttacon/builder v0.0.0-20170518171403-c099f663e1c2 // indirect github.com/ttacon/libphonenumber v1.2.1 // indirect github.com/tyler-smith/go-bip39 v1.1.0 // indirect @@ -77,9 +63,6 @@ require ( google.golang.org/genproto v0.0.0-20220822174746-9e6da59bd2fc // indirect google.golang.org/grpc v1.49.0 // indirect google.golang.org/protobuf v1.28.1 // indirect - gopkg.in/ini.v1 v1.66.6 // indirect - gopkg.in/yaml.v2 v2.4.0 // indirect - gopkg.in/yaml.v3 v3.0.1 // indirect nhooyr.io/websocket v1.8.7 // indirect src.agwa.name/tlshacks v0.0.0-20220518131152-d2c6f4e2b780 // indirect ) diff --git a/indexedDb/channels/implementation.go b/indexedDb/channels/implementation.go index e4074b69089879aa2de81e56a990dac805fc75af..ee099fbfc1d8b55c9b575ce56ad3e0a8973685dd 100644 --- a/indexedDb/channels/implementation.go +++ b/indexedDb/channels/implementation.go @@ -40,6 +40,8 @@ type wasmModel struct { db *idb.Database cipher cryptoChannel.Cipher receivedMessageCB MessageReceivedCallback + deletedMessageCB DeletedMessageCallback + mutedUserCB MutedUserCallback updateMux sync.Mutex } @@ -489,8 +491,22 @@ func (w *wasmModel) GetMessage( // DeleteMessage removes a message with the given messageID from storage. func (w *wasmModel) DeleteMessage(messageID message.ID) error { msgId := js.ValueOf(base64.StdEncoding.EncodeToString(messageID.Bytes())) - return indexedDb.DeleteIndex(w.db, messageStoreName, + + err := indexedDb.DeleteIndex(w.db, messageStoreName, messageStoreMessageIndex, pkeyName, msgId) + if err != nil { + return err + } + + go w.deletedMessageCB(messageID) + + return nil +} + +// MuteUser is called whenever a user is muted or unmuted. +func (w *wasmModel) MuteUser( + channelID *id.ID, pubKey ed25519.PublicKey, unmute bool) { + go w.mutedUserCB(channelID, pubKey, unmute) } // msgIDLookup gets the UUID of the Message with the given messageID. @@ -511,5 +527,4 @@ func (w *wasmModel) msgIDLookup(messageID message.ID) (*Message, error) { return nil, err } return resultMsg, nil - } diff --git a/indexedDb/channels/implementation_test.go b/indexedDb/channels/implementation_test.go index 79a02de774ae701b6b579d356690f931f7a53457..19b0f313dd4bc11325150838007dd689b873a031 100644 --- a/indexedDb/channels/implementation_test.go +++ b/indexedDb/channels/implementation_test.go @@ -10,6 +10,7 @@ package channels import ( + "crypto/ed25519" "encoding/json" "fmt" "github.com/hack-pad/go-indexeddb/idb" @@ -36,12 +37,14 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -func dummyCallback(uint64, *id.ID, bool) {} +func dummyReceivedMessageCB(uint64, *id.ID, bool) {} +func dummyDeletedMessageCB(message.ID) {} +func dummyMutedUserCB(*id.ID, ed25519.PublicKey, bool) {} // Happy path, insert message and look it up func TestWasmModel_msgIDLookup(t *testing.T) { cipher, err := cryptoChannel.NewCipher( - []byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG()) + []byte("testPass"), []byte("testSalt"), 128, csprng.NewSystemRNG()) if err != nil { t.Fatalf("Failed to create cipher") } @@ -56,9 +59,10 @@ func TestWasmModel_msgIDLookup(t *testing.T) { testString := "TestWasmModel_msgIDLookup" + cs testMsgId := message.DeriveChannelMessageID(&id.ID{1}, 0, []byte(testString)) - eventModel, err := newWASMModel(testString, c, dummyCallback) - if err != nil { - t.Fatalf("%+v", err) + eventModel, err2 := newWASMModel(testString, c, + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) + if err2 != nil { + t.Fatalf("%+v", err2) } testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), nil, @@ -69,9 +73,9 @@ func TestWasmModel_msgIDLookup(t *testing.T) { t.Fatalf("%+v", err) } - msg, err := eventModel.msgIDLookup(testMsgId) - if err != nil { - t.Fatalf("%+v", err) + msg, err2 := eventModel.msgIDLookup(testMsgId) + if err2 != nil { + t.Fatalf("%+v", err2) } if msg.ID == 0 { t.Fatalf("Expected to get a UUID!") @@ -85,7 +89,8 @@ func TestWasmModel_DeleteMessage(t *testing.T) { storage.GetLocalStorage().Clear() testString := "TestWasmModel_DeleteMessage" testMsgId := message.DeriveChannelMessageID(&id.ID{1}, 0, []byte(testString)) - eventModel, err := newWASMModel(testString, nil, dummyCallback) + eventModel, err := newWASMModel(testString, nil, + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) if err != nil { t.Fatalf("%+v", err) } @@ -127,7 +132,7 @@ func TestWasmModel_DeleteMessage(t *testing.T) { // Test wasmModel.UpdateSentStatus happy path and ensure fields don't change. func Test_wasmModel_UpdateSentStatus(t *testing.T) { cipher, err := cryptoChannel.NewCipher( - []byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG()) + []byte("testPass"), []byte("testSalt"), 128, csprng.NewSystemRNG()) if err != nil { t.Fatalf("Failed to create cipher") } @@ -141,9 +146,10 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) { testString := "Test_wasmModel_UpdateSentStatus" + cs testMsgId := message.DeriveChannelMessageID( &id.ID{1}, 0, []byte(testString)) - eventModel, err := newWASMModel(testString, c, dummyCallback) - if err != nil { - t.Fatalf("%+v", err) + eventModel, err2 := newWASMModel(testString, c, + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) + if err2 != nil { + t.Fatalf("%+v", err2) } // Store a test message @@ -170,17 +176,17 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) { uuid, nil, nil, nil, nil, nil, &expectedStatus) // Check the resulting status - results, err = indexedDb.Dump(eventModel.db, messageStoreName) - if err != nil { - t.Fatalf("%+v", err) + results, err2 = indexedDb.Dump(eventModel.db, messageStoreName) + if err2 != nil { + t.Fatalf("%+v", err2) } if len(results) != 1 { t.Fatalf("Expected 1 message to exist") } resultMsg := &Message{} - err = json.Unmarshal([]byte(results[0]), resultMsg) - if err != nil { - t.Fatalf("%+v", err) + err2 = json.Unmarshal([]byte(results[0]), resultMsg) + if err2 != nil { + t.Fatalf("%+v", err2) } if resultMsg.Status != uint8(expectedStatus) { t.Fatalf("Unexpected Status: %v", resultMsg.Status) @@ -197,7 +203,7 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) { // Smoke test wasmModel.JoinChannel/wasmModel.LeaveChannel happy paths. func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) { cipher, err := cryptoChannel.NewCipher( - []byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG()) + []byte("testPass"), []byte("testSalt"), 128, csprng.NewSystemRNG()) if err != nil { t.Fatalf("Failed to create cipher") } @@ -208,9 +214,10 @@ func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) { } t.Run("Test_wasmModel_JoinChannel_LeaveChannel"+cs, func(t *testing.T) { storage.GetLocalStorage().Clear() - eventModel, err := newWASMModel("test", c, dummyCallback) - if err != nil { - t.Fatalf("%+v", err) + eventModel, err2 := newWASMModel("test", c, + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) + if err2 != nil { + t.Fatalf("%+v", err2) } testChannel := &cryptoBroadcast.Channel{ @@ -227,17 +234,17 @@ func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) { } eventModel.JoinChannel(testChannel) eventModel.JoinChannel(testChannel2) - results, err := indexedDb.Dump(eventModel.db, channelsStoreName) - if err != nil { - t.Fatalf("%+v", err) + results, err2 := indexedDb.Dump(eventModel.db, channelsStoreName) + if err2 != nil { + t.Fatalf("%+v", err2) } if len(results) != 2 { t.Fatalf("Expected 2 channels to exist") } eventModel.LeaveChannel(testChannel.ReceptionID) - results, err = indexedDb.Dump(eventModel.db, channelsStoreName) - if err != nil { - t.Fatalf("%+v", err) + results, err2 = indexedDb.Dump(eventModel.db, channelsStoreName) + if err2 != nil { + t.Fatalf("%+v", err2) } if len(results) != 1 { t.Fatalf("Expected 1 channels to exist") @@ -249,7 +256,7 @@ func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) { // Test UUID gets returned when different messages are added. func Test_wasmModel_UUIDTest(t *testing.T) { cipher, err := cryptoChannel.NewCipher( - []byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG()) + []byte("testPass"), []byte("testSalt"), 128, csprng.NewSystemRNG()) if err != nil { t.Fatalf("Failed to create cipher") } @@ -261,9 +268,10 @@ func Test_wasmModel_UUIDTest(t *testing.T) { t.Run("Test_wasmModel_UUIDTest"+cs, func(t *testing.T) { storage.GetLocalStorage().Clear() testString := "testHello" + cs - eventModel, err := newWASMModel(testString, c, dummyCallback) - if err != nil { - t.Fatalf("%+v", err) + eventModel, err2 := newWASMModel(testString, c, + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) + if err2 != nil { + t.Fatalf("%+v", err2) } uuids := make([]uint64, 10) @@ -295,7 +303,7 @@ func Test_wasmModel_UUIDTest(t *testing.T) { // Tests if the same message ID being sent always returns the same UUID. func Test_wasmModel_DuplicateReceives(t *testing.T) { cipher, err := cryptoChannel.NewCipher( - []byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG()) + []byte("testPass"), []byte("testSalt"), 128, csprng.NewSystemRNG()) if err != nil { t.Fatalf("Failed to create cipher") } @@ -307,9 +315,10 @@ func Test_wasmModel_DuplicateReceives(t *testing.T) { t.Run("Test_wasmModel_DuplicateReceives"+cs, func(t *testing.T) { storage.GetLocalStorage().Clear() testString := "testHello" - eventModel, err := newWASMModel(testString, c, dummyCallback) - if err != nil { - t.Fatalf("%+v", err) + eventModel, err2 := newWASMModel(testString, c, + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) + if err2 != nil { + t.Fatalf("%+v", err2) } uuids := make([]uint64, 10) @@ -342,7 +351,7 @@ func Test_wasmModel_DuplicateReceives(t *testing.T) { // result is as expected. func Test_wasmModel_deleteMsgByChannel(t *testing.T) { cipher, err := cryptoChannel.NewCipher( - []byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG()) + []byte("testPass"), []byte("testSalt"), 128, csprng.NewSystemRNG()) if err != nil { t.Fatalf("Failed to create cipher") } @@ -356,9 +365,10 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) { testString := "test_deleteMsgByChannel" totalMessages := 10 expectedMessages := 5 - eventModel, err := newWASMModel(testString, c, dummyCallback) - if err != nil { - t.Fatalf("%+v", err) + eventModel, err2 := newWASMModel(testString, c, + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) + if err2 != nil { + t.Fatalf("%+v", err2) } // Create a test channel id @@ -384,9 +394,9 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) { } // Check pre-results - result, err := indexedDb.Dump(eventModel.db, messageStoreName) - if err != nil { - t.Fatalf("%+v", err) + result, err2 := indexedDb.Dump(eventModel.db, messageStoreName) + if err2 != nil { + t.Fatalf("%+v", err2) } if len(result) != totalMessages { t.Errorf("Expected %d messages, got %d", totalMessages, len(result)) @@ -414,7 +424,7 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) { // Inserts will not fail, they simply will not happen. func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) { cipher, err := cryptoChannel.NewCipher( - []byte("testpass"), []byte("testsalt"), 128, csprng.NewSystemRNG()) + []byte("testPass"), []byte("testSalt"), 128, csprng.NewSystemRNG()) if err != nil { t.Fatalf("Failed to create cipher") } @@ -426,29 +436,30 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) { t.Run("TestWasmModel_receiveHelper_UniqueIndex"+cs, func(t *testing.T) { storage.GetLocalStorage().Clear() testString := fmt.Sprintf("test_receiveHelper_UniqueIndex_%d", i) - eventModel, err := newWASMModel(testString, c, dummyCallback) - if err != nil { - t.Fatal(err) + eventModel, err2 := newWASMModel(testString, c, + dummyReceivedMessageCB, dummyDeletedMessageCB, dummyMutedUserCB) + if err2 != nil { + t.Fatal(err2) } // Ensure index is unique - txn, err := eventModel.db.Transaction( + txn, err2 := eventModel.db.Transaction( idb.TransactionReadOnly, messageStoreName) - if err != nil { - t.Fatal(err) + if err2 != nil { + t.Fatal(err2) } - store, err := txn.ObjectStore(messageStoreName) - if err != nil { - t.Fatal(err) + store, err2 := txn.ObjectStore(messageStoreName) + if err2 != nil { + t.Fatal(err2) } - idx, err := store.Index(messageStoreMessageIndex) - if err != nil { - t.Fatal(err) + idx, err2 := store.Index(messageStoreMessageIndex) + if err2 != nil { + t.Fatal(err2) } - if isUnique, err2 := idx.Unique(); !isUnique { + if isUnique, err3 := idx.Unique(); !isUnique { t.Fatalf("Index is not unique!") - } else if err2 != nil { - t.Fatal(err2) + } else if err3 != nil { + t.Fatal(err3) } // First message insert should succeed diff --git a/indexedDb/channels/init.go b/indexedDb/channels/init.go index 12d6bb8840f3d75c8778b871ac883a84ad008ada..54867ea9d4668195b6bb97e7341f0366b4efcecd 100644 --- a/indexedDb/channels/init.go +++ b/indexedDb/channels/init.go @@ -10,11 +10,13 @@ package channels import ( + "crypto/ed25519" "github.com/hack-pad/go-indexeddb/idb" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/v4/channels" cryptoChannel "gitlab.com/elixxir/crypto/channel" + "gitlab.com/elixxir/crypto/message" "gitlab.com/elixxir/xxdk-wasm/indexedDb" "gitlab.com/elixxir/xxdk-wasm/storage" "gitlab.com/xx_network/primitives/id" @@ -36,13 +38,24 @@ const ( // update is true if the row is old and was edited. type MessageReceivedCallback func(uuid uint64, channelID *id.ID, update bool) +// DeletedMessageCallback is called any time a message is deleted. +type DeletedMessageCallback func(messageID message.ID) + +// MutedUserCallback is called any time a user is muted or unmuted. unmute is +// true if the user has been unmuted and false if they have been muted. +type MutedUserCallback func( + channelID *id.ID, pubKey ed25519.PublicKey, unmute bool) + // NewWASMEventModelBuilder returns an EventModelBuilder which allows // the channel manager to define the path but the callback is the same // across the board. func NewWASMEventModelBuilder(encryption cryptoChannel.Cipher, - cb MessageReceivedCallback) channels.EventModelBuilder { + messageReceivedCB MessageReceivedCallback, + deletedMessageCB DeletedMessageCallback, + mutedUserCB MutedUserCallback) channels.EventModelBuilder { fn := func(path string) (channels.EventModel, error) { - return NewWASMEventModel(path, encryption, cb) + return NewWASMEventModel( + path, encryption, messageReceivedCB, deletedMessageCB, mutedUserCB) } return fn } @@ -50,14 +63,19 @@ func NewWASMEventModelBuilder(encryption cryptoChannel.Cipher, // NewWASMEventModel returns a [channels.EventModel] backed by a wasmModel. // The name should be a base64 encoding of the users public key. func NewWASMEventModel(path string, encryption cryptoChannel.Cipher, - cb MessageReceivedCallback) (channels.EventModel, error) { + messageReceivedCB MessageReceivedCallback, + deletedMessageCB DeletedMessageCallback, + mutedUserCB MutedUserCallback) (channels.EventModel, error) { databaseName := path + databaseSuffix - return newWASMModel(databaseName, encryption, cb) + return newWASMModel(databaseName, + encryption, messageReceivedCB, deletedMessageCB, mutedUserCB) } // newWASMModel creates the given [idb.Database] and returns a wasmModel. func newWASMModel(databaseName string, encryption cryptoChannel.Cipher, - cb MessageReceivedCallback) (*wasmModel, error) { + messageReceivedCB MessageReceivedCallback, + deletedMessageCB DeletedMessageCallback, + mutedUserCB MutedUserCallback) (*wasmModel, error) { // Attempt to open database object ctx, cancel := indexedDb.NewContext() defer cancel() @@ -122,7 +140,13 @@ func newWASMModel(databaseName string, encryption cryptoChannel.Cipher, if err != nil { return nil, err } - wrapper := &wasmModel{db: db, receivedMessageCB: cb, cipher: encryption} + wrapper := &wasmModel{ + db: db, + cipher: encryption, + receivedMessageCB: messageReceivedCB, + deletedMessageCB: deletedMessageCB, + mutedUserCB: mutedUserCB, + } return wrapper, nil } diff --git a/indexedDb/dm/implementation.go b/indexedDb/dm/implementation.go index afd4e3bff44d739d7a8d3d687563c390fde7f743..b5c023d8f2ad920783533527608ce36d9d8c0ea9 100644 --- a/indexedDb/dm/implementation.go +++ b/indexedDb/dm/implementation.go @@ -102,7 +102,8 @@ func (w *wasmModel) Receive(messageID message.ID, nickname string, text []byte, parentErr := errors.New("failed to Receive") // If there is no extant Conversation, create one. - _, err := indexedDb.Get(w.db, conversationStoreName, utils.CopyBytesToJS(pubKey)) + _, err := indexedDb.Get( + w.db, conversationStoreName, utils.CopyBytesToJS(pubKey)) if err != nil { if strings.Contains(err.Error(), indexedDb.ErrDoesNotExist) { err = w.joinConversation(nickname, pubKey, dmToken, codeset) @@ -144,7 +145,8 @@ func (w *wasmModel) ReceiveText(messageID message.ID, nickname, text string, parentErr := errors.New("failed to ReceiveText") // If there is no extant Conversation, create one. - _, err := indexedDb.Get(w.db, conversationStoreName, utils.CopyBytesToJS(pubKey)) + _, err := indexedDb.Get( + w.db, conversationStoreName, utils.CopyBytesToJS(pubKey)) if err != nil { if strings.Contains(err.Error(), indexedDb.ErrDoesNotExist) { err = w.joinConversation(nickname, pubKey, dmToken, codeset) @@ -188,7 +190,8 @@ func (w *wasmModel) ReceiveReply(messageID, reactionTo message.ID, nickname, parentErr := errors.New("failed to ReceiveReply") // If there is no extant Conversation, create one. - _, err := indexedDb.Get(w.db, conversationStoreName, utils.CopyBytesToJS(pubKey)) + _, err := indexedDb.Get( + w.db, conversationStoreName, utils.CopyBytesToJS(pubKey)) if err != nil { if strings.Contains(err.Error(), indexedDb.ErrDoesNotExist) { err = w.joinConversation(nickname, pubKey, dmToken, codeset) @@ -226,13 +229,14 @@ func (w *wasmModel) ReceiveReply(messageID, reactionTo message.ID, nickname, return uuid } -func (w *wasmModel) ReceiveReaction(messageID, reactionTo message.ID, nickname, +func (w *wasmModel) ReceiveReaction(messageID, _ message.ID, nickname, reaction string, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8, timestamp time.Time, round rounds.Round, status dm.Status) uint64 { parentErr := errors.New("failed to ReceiveText") // If there is no extant Conversation, create one. - _, err := indexedDb.Get(w.db, conversationStoreName, utils.CopyBytesToJS(pubKey)) + _, err := indexedDb.Get( + w.db, conversationStoreName, utils.CopyBytesToJS(pubKey)) if err != nil { if strings.Contains(err.Error(), indexedDb.ErrDoesNotExist) { err = w.joinConversation(nickname, pubKey, dmToken, codeset) diff --git a/indexedDb/dm/init.go b/indexedDb/dm/init.go index 380df647c810d7fb6239cee9c4e7c1fc4181e4ca..29b80911804004539beb210dc85533afc7c98f31 100644 --- a/indexedDb/dm/init.go +++ b/indexedDb/dm/init.go @@ -35,7 +35,8 @@ const ( // MessageReceivedCallback is called any time a message is received or updated. // // update is true if the row is old and was edited. -type MessageReceivedCallback func(uuid uint64, pubKey ed25519.PublicKey, update bool) +type MessageReceivedCallback func( + uuid uint64, pubKey ed25519.PublicKey, update bool) // NewWASMEventModel returns a [channels.EventModel] backed by a wasmModel. // The name should be a base64 encoding of the users public key. diff --git a/wasm/channels.go b/wasm/channels.go index 2535e7c0965eed81a1ac6f939d75812732d16042..2248b1b31d768b84982d893654e7d1b6f959cd5b 100644 --- a/wasm/channels.go +++ b/wasm/channels.go @@ -10,10 +10,12 @@ package wasm import ( + "crypto/ed25519" "encoding/base64" "encoding/json" "errors" "gitlab.com/elixxir/client/v4/channels" + "gitlab.com/elixxir/crypto/message" channelsDb "gitlab.com/elixxir/xxdk-wasm/indexedDb/channels" "gitlab.com/xx_network/primitives/id" "sync" @@ -319,14 +321,23 @@ func LoadChannelsManager(_ js.Value, args []js.Value) any { // 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 +// - args[2] - The received message callback, which is called everytime a +// message is added or changed in the database. It is a 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] - The deleted message callback, which is called everytime a +// message is deleted from the database. It is a function that takes in the +// same parameters as [indexedDb.DeletedMessageCallback]. On the Javascript +// side, the message ID is returned as a Uint8Array. +// - args[4] - The muted user callback, which is called everytime a user is +// muted or unmuted. It is a function that takes in the same parameters as +// [indexedDb.MutedUserCallback]. On the Javascript side, the channel ID and +// user public key are returned as Uint8Array. +// - args[5] - ID of [ChannelDbCipher] object in tracker (int). Create this // object with [NewChannelsDatabaseCipher] and get its id with // [ChannelDbCipher.GetID]. // @@ -338,15 +349,17 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any { cmixID := args[0].Int() privateIdentity := utils.CopyBytesToGo(args[1]) messageReceivedCB := args[2] - cipherID := args[3].Int() + deletedMessageCB := args[3] + mutedUserCB := args[4] + cipherID := args[5].Int() cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID) if err != nil { utils.Throw(utils.TypeError, err) } - return newChannelsManagerWithIndexedDb( - cmixID, privateIdentity, messageReceivedCB, cipher) + return newChannelsManagerWithIndexedDb(cmixID, privateIdentity, + messageReceivedCB, deletedMessageCB, mutedUserCB, cipher) } // NewChannelsManagerWithIndexedDbUnsafe creates a new [ChannelsManager] from a @@ -366,13 +379,22 @@ func NewChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any { // 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[2] - The received message callback, which is called everytime a +// message is added or changed in the database. It is a 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] - The deleted message callback, which is called everytime a +// message is deleted from the database. It is a function that takes in the +// same parameters as [indexedDb.DeletedMessageCallback]. On the Javascript +// side, the message ID is returned as a Uint8Array. +// - args[4] - The muted user callback, which is called everytime a user is +// muted or unmuted. It is a function that takes in the same parameters as +// [indexedDb.MutedUserCallback]. On the Javascript side, the channel ID and +// user public key are returned as Uint8Array. // // Returns a promise: // - Resolves to a Javascript representation of the [ChannelsManager] object. @@ -381,19 +403,32 @@ func NewChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) any { cmixID := args[0].Int() privateIdentity := utils.CopyBytesToGo(args[1]) messageReceivedCB := args[2] + deletedMessageCB := args[3] + mutedUserCB := args[4] - return newChannelsManagerWithIndexedDb( - cmixID, privateIdentity, messageReceivedCB, nil) + return newChannelsManagerWithIndexedDb(cmixID, privateIdentity, + messageReceivedCB, deletedMessageCB, mutedUserCB, nil) } func newChannelsManagerWithIndexedDb(cmixID int, privateIdentity []byte, - cb js.Value, cipher *bindings.ChannelDbCipher) any { + messageReceivedCB, deletedMessageCB, mutedUserCB js.Value, + cipher *bindings.ChannelDbCipher) any { - messageReceivedCB := func(uuid uint64, channelID *id.ID, update bool) { - cb.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), update) + messageReceived := func(uuid uint64, channelID *id.ID, update bool) { + messageReceivedCB.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), update) } - model := channelsDb.NewWASMEventModelBuilder(cipher, messageReceivedCB) + deletedMessage := func(messageID message.ID) { + deletedMessageCB.Invoke(utils.CopyBytesToJS(messageID.Marshal())) + } + + mutedUser := func(channelID *id.ID, pubKey ed25519.PublicKey, unmute bool) { + mutedUserCB.Invoke(utils.CopyBytesToJS(channelID.Marshal()), + utils.CopyBytesToJS(pubKey), unmute) + } + + model := channelsDb.NewWASMEventModelBuilder( + cipher, messageReceived, deletedMessage, mutedUser) promiseFn := func(resolve, reject func(args ...any) js.Value) { cm, err := bindings.NewChannelsManagerGoEventModel( @@ -421,14 +456,23 @@ func newChannelsManagerWithIndexedDb(cmixID int, privateIdentity []byte, // 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 +// - args[2] - The received message callback, which is called everytime a +// message is added or changed in the database. It is a 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] - The deleted message callback, which is called everytime a +// message is deleted from the database. It is a function that takes in the +// same parameters as [indexedDb.DeletedMessageCallback]. On the Javascript +// side, the message ID is returned as a Uint8Array. +// - args[4] - The muted user callback, which is called everytime a user is +// muted or unmuted. It is a function that takes in the same parameters as +// [indexedDb.MutedUserCallback]. On the Javascript side, the channel ID and +// user public key are returned as Uint8Array. +// - args[5] - ID of [ChannelDbCipher] object in tracker (int). Create this // object with [NewChannelsDatabaseCipher] and get its id with // [ChannelDbCipher.GetID]. // @@ -440,15 +484,17 @@ func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any { cmixID := args[0].Int() storageTag := args[1].String() messageReceivedCB := args[2] - cipherID := args[3].Int() + deletedMessageCB := args[3] + mutedUserCB := args[4] + cipherID := args[5].Int() cipher, err := bindings.GetChannelDbCipherTrackerFromID(cipherID) if err != nil { utils.Throw(utils.TypeError, err) } - return loadChannelsManagerWithIndexedDb( - cmixID, storageTag, messageReceivedCB, cipher) + return loadChannelsManagerWithIndexedDb(cmixID, storageTag, + messageReceivedCB, deletedMessageCB, mutedUserCB, cipher) } // LoadChannelsManagerWithIndexedDbUnsafe loads an existing [ChannelsManager] @@ -466,13 +512,22 @@ func LoadChannelsManagerWithIndexedDb(_ js.Value, args []js.Value) any { // 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[2] - The received message callback, which is called everytime a +// message is added or changed in the database. It is a 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] - The deleted message callback, which is called everytime a +// message is deleted from the database. It is a function that takes in the +// same parameters as [indexedDb.DeletedMessageCallback]. On the Javascript +// side, the message ID is returned as a Uint8Array. +// - args[4] - The muted user callback, which is called everytime a user is +// muted or unmuted. It is a function that takes in the same parameters as +// [indexedDb.MutedUserCallback]. On the Javascript side, the channel ID and +// user public key are returned as Uint8Array. // // Returns a promise: // - Resolves to a Javascript representation of the [ChannelsManager] object. @@ -481,18 +536,32 @@ func LoadChannelsManagerWithIndexedDbUnsafe(_ js.Value, args []js.Value) any { cmixID := args[0].Int() storageTag := args[1].String() messageReceivedCB := args[2] + deletedMessageCB := args[3] + mutedUserCB := args[4] - return loadChannelsManagerWithIndexedDb( - cmixID, storageTag, messageReceivedCB, nil) + return loadChannelsManagerWithIndexedDb(cmixID, storageTag, + messageReceivedCB, deletedMessageCB, mutedUserCB, 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) + messageReceivedCB, deletedMessageCB, mutedUserCB js.Value, + cipher *bindings.ChannelDbCipher) any { + + messageReceived := func(uuid uint64, channelID *id.ID, update bool) { + messageReceivedCB.Invoke(uuid, utils.CopyBytesToJS(channelID.Marshal()), update) } - model := channelsDb.NewWASMEventModelBuilder(cipher, messageReceivedCB) + deletedMessage := func(messageID message.ID) { + deletedMessageCB.Invoke(utils.CopyBytesToJS(messageID.Marshal())) + } + + mutedUser := func(channelID *id.ID, pubKey ed25519.PublicKey, unmute bool) { + mutedUserCB.Invoke(utils.CopyBytesToJS(channelID.Marshal()), + utils.CopyBytesToJS(pubKey), unmute) + } + + model := channelsDb.NewWASMEventModelBuilder( + cipher, messageReceived, deletedMessage, mutedUser) promiseFn := func(resolve, reject func(args ...any) js.Value) { cm, err := bindings.LoadChannelsManagerGoEventModel( @@ -870,14 +939,14 @@ func ValidForever(js.Value, []js.Value) any { func (cm *ChannelsManager) SendGeneric(_ js.Value, args []js.Value) any { marshalledChanId := utils.CopyBytesToGo(args[0]) messageType := args[1].Int() - message := utils.CopyBytesToGo(args[2]) + msg := utils.CopyBytesToGo(args[2]) leaseTimeMS := int64(args[3].Int()) tracked := args[4].Bool() cmixParamsJSON := utils.CopyBytesToGo(args[5]) promiseFn := func(resolve, reject func(args ...any) js.Value) { sendReport, err := cm.api.SendGeneric(marshalledChanId, messageType, - message, leaseTimeMS, tracked, cmixParamsJSON) + msg, leaseTimeMS, tracked, cmixParamsJSON) if err != nil { reject(utils.JsTrace(err)) } else { @@ -913,13 +982,13 @@ func (cm *ChannelsManager) SendGeneric(_ js.Value, args []js.Value) any { // - Rejected with an error if sending fails. func (cm *ChannelsManager) SendMessage(_ js.Value, args []js.Value) any { marshalledChanId := utils.CopyBytesToGo(args[0]) - message := args[1].String() + msg := args[1].String() leaseTimeMS := int64(args[2].Int()) cmixParamsJSON := utils.CopyBytesToGo(args[3]) promiseFn := func(resolve, reject func(args ...any) js.Value) { sendReport, err := cm.api.SendMessage( - marshalledChanId, message, leaseTimeMS, cmixParamsJSON) + marshalledChanId, msg, leaseTimeMS, cmixParamsJSON) if err != nil { reject(utils.JsTrace(err)) } else { @@ -962,13 +1031,13 @@ func (cm *ChannelsManager) SendMessage(_ js.Value, args []js.Value) any { // - Rejected with an error if sending fails. func (cm *ChannelsManager) SendReply(_ js.Value, args []js.Value) any { marshalledChanId := utils.CopyBytesToGo(args[0]) - message := args[1].String() + msg := args[1].String() messageToReactTo := utils.CopyBytesToGo(args[2]) leaseTimeMS := int64(args[3].Int()) cmixParamsJSON := utils.CopyBytesToGo(args[4]) promiseFn := func(resolve, reject func(args ...any) js.Value) { - sendReport, err := cm.api.SendReply(marshalledChanId, message, + sendReport, err := cm.api.SendReply(marshalledChanId, msg, messageToReactTo, leaseTimeMS, cmixParamsJSON) if err != nil { reject(utils.JsTrace(err)) @@ -1059,14 +1128,14 @@ func (cm *ChannelsManager) SendReaction(_ js.Value, args []js.Value) any { func (cm *ChannelsManager) SendAdminGeneric(_ js.Value, args []js.Value) any { marshalledChanId := utils.CopyBytesToGo(args[0]) messageType := args[1].Int() - message := utils.CopyBytesToGo(args[2]) + msg := utils.CopyBytesToGo(args[2]) leaseTimeMS := int64(args[3].Int()) tracked := args[4].Bool() cmixParamsJSON := utils.CopyBytesToGo(args[5]) promiseFn := func(resolve, reject func(args ...any) js.Value) { sendReport, err := cm.api.SendAdminGeneric(marshalledChanId, - messageType, message, leaseTimeMS, tracked, cmixParamsJSON) + messageType, msg, leaseTimeMS, tracked, cmixParamsJSON) if err != nil { reject(utils.JsTrace(err)) } else { @@ -1615,6 +1684,7 @@ func (emb *eventModelBuilder) Build(path string) bindings.EventModel { updateFromMessageID: utils.WrapCB(emJs, "UpdateFromMessageID"), getMessage: utils.WrapCB(emJs, "GetMessage"), deleteMessage: utils.WrapCB(emJs, "DeleteMessage"), + muteUser: utils.WrapCB(emJs, "MuteUser"), } } @@ -1630,6 +1700,7 @@ type eventModel struct { updateFromMessageID func(args ...any) js.Value getMessage func(args ...any) js.Value deleteMessage func(args ...any) js.Value + muteUser func(args ...any) js.Value } // JoinChannel is called whenever a channel is joined locally. @@ -1839,7 +1910,7 @@ func (em *eventModel) GetMessage(messageID []byte) ([]byte, error) { // the database. // // Parameters: -// - messageID - The bytes of the [channel.MessageID] of the message. +// - 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() { @@ -1849,6 +1920,17 @@ func (em *eventModel) DeleteMessage(messageID []byte) error { return nil } +// MuteUser mutes the given user or unmutes them. +// +// Parameters: +// - channelID - The bytes of the [id.ID] of the channel the user is being +// muted in. +// - pubKey - The [ed25519.PublicKey] of the user that is muted or unmuted. +func (em *eventModel) MuteUser(channelID, pubkey []byte, unmute bool) { + em.muteUser( + utils.CopyBytesToJS(channelID), utils.CopyBytesToJS(pubkey), unmute) +} + // MessageAndError contains a message returned by eventModel.GetMessage or any // possible error that occurs during lookup. Only one field should be present at // a time; if an error occurs, ModelMessage should be empty.