diff --git a/indexedDb/implementation.go b/indexedDb/implementation.go index a2a76124e0df109c4ae1fb5920acf23020684fba..d8053bc9119bcadd21dac1a743b0af2ff47e3acf 100644 --- a/indexedDb/implementation.go +++ b/indexedDb/implementation.go @@ -11,6 +11,7 @@ package indexedDb import ( "context" + "encoding/base64" "encoding/json" "sync" "syscall/js" @@ -154,29 +155,31 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID, status channels.SentStatus) uint64 { parentErr := errors.New("failed to ReceiveMessage") - uuid, err := w.receiveHelper(buildMessage(channelID.Marshal(), + msgToInsert := buildMessage(channelID.Marshal(), messageID.Bytes(), nil, nickname, text, identity, - timestamp, lease, status)) + timestamp, lease, status) + + // Attempt a lookup on the MessageID if it is non-zero to find + // an existing entry for it. This occurs any time a sender + // receives their own message from the mixnet. + if !messageID.Equals(cryptoChannel.MessageID{}) { + uuid, err := w.msgIDLookup(messageID) + if err != nil { + // NOTE: No stack is OK here + jww.WARN.Printf(err.Error()) + } + msgToInsert.ID = uuid + } + + uuid, err := w.receiveHelper(msgToInsert) if err != nil { jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error())) } - if checkZero(messageID.Bytes()) { - jww.FATAL.Panicf("Empty message ID is impossible!") - } go w.receivedMessageCB(uuid, channelID) return uuid } -func checkZero(b []byte) bool { - for i := 0; i < len(b); i++ { - if b[i] != 0 { - return false - } - } - return true -} - // ReceiveReply is called whenever a message is received that is a reply on a // given channel. It may be called multiple times on the same message; it is // incumbent on the user of the API to filter such called by message ID. @@ -196,9 +199,6 @@ func (w *wasmModel) ReceiveReply(channelID *id.ID, if err != nil { jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error())) } - if checkZero(messageID.Bytes()) { - jww.FATAL.Panicf("Empty message ID is impossible!") - } go w.receivedMessageCB(uuid, channelID) return uuid } @@ -221,15 +221,13 @@ func (w *wasmModel) ReceiveReaction(channelID *id.ID, messageID cryptoChannel.Me if err != nil { jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error())) } - if checkZero(messageID.Bytes()) { - jww.FATAL.Panicf("Empty message ID is impossible!") - } go w.receivedMessageCB(uuid, channelID) return uuid } -// UpdateSentStatus is called whenever the [channels.SentStatus] of a message -// has changed. +// UpdateSentStatus is called whenever the [channels.SentStatus] of a +// message has changed. At this point the message ID goes from +// empty/unknown to populated. // 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) { @@ -257,6 +255,7 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64, messageID cryptoChannel.Messag return } newMessage.Status = uint8(status) + newMessage.MessageID = messageID.Bytes() // Store the updated Message _, err = w.receiveHelper(newMessage) @@ -265,9 +264,6 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64, messageID cryptoChannel.Messag } channelID := &id.ID{} copy(channelID[:], newMessage.ChannelID) - if checkZero(messageID.Bytes()) { - jww.FATAL.Panicf("Empty message ID is impossible!") - } go w.receivedMessageCB(uuid, channelID) } @@ -316,7 +312,7 @@ func (w *wasmModel) receiveHelper(newMessage *Message) (uint64, // NOTE: This is weird, but correct. When the "ID" field is 0, we // unset it from the JSValue so that it is auto-populated and - // incremented + // incremented. if newMessage.ID == 0 { messageObj.JSValue().Delete("id") } @@ -393,6 +389,56 @@ 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) + + // Prepare the Transaction + txn, err := w.db.Transaction(idb.TransactionReadOnly, messageStoreName) + if err != nil { + return 0, errors.WithMessagef(parentErr, + "Unable to create Transaction: %+v", err) + } + store, err := txn.ObjectStore(messageStoreName) + if err != nil { + return 0, errors.WithMessagef(parentErr, + "Unable to get ObjectStore: %+v", err) + } + idx, err := store.Index(messageStoreMessageIndex) + if err != nil { + return 0, errors.WithMessagef(parentErr, + "Unable to get index: %+v", err) + } + + msgIDStr := base64.StdEncoding.EncodeToString(messageID.Bytes()) + + keyReq, err := idx.Get(js.ValueOf(msgIDStr)) + if err != nil { + return 0, errors.WithMessagef(parentErr, + "Unable to get keyReq: %+v", err) + } + // Wait for the operation to return + ctx, cancel := newContext() + keyObj, err := keyReq.Await(ctx) + cancel() + if err != nil { + return 0, 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) + + uuid := uint64(0) + if !keyObj.IsUndefined() { + uuid = uint64(keyObj.Get("id").Int()) + } + return uuid, nil +} + // dump returns the given [idb.ObjectStore] contents to string slice for // debugging purposes. func (w *wasmModel) dump(objectStoreName string) ([]string, error) { diff --git a/indexedDb/implementation_test.go b/indexedDb/implementation_test.go index 5b47174f9a6447d4fa38ff24641d09e7f4751e23..9b728f28d1fe0d400c0c1aa0ef9d93b2e71b2aea 100644 --- a/indexedDb/implementation_test.go +++ b/indexedDb/implementation_test.go @@ -129,7 +129,6 @@ func TestWasmModel_JoinChannel_LeaveChannel(t *testing.T) { // Test wasmModel.UpdateSentStatus happy path and ensure fields don't change. func TestWasmModel_UUIDTest(t *testing.T) { testString := "testHello" - testMsgId := channel.MakeMessageID([]byte(testString)) eventModel, err := newWASMModel(testString, dummyCallback) if err != nil { t.Fatalf("%+v", err) @@ -147,15 +146,13 @@ func TestWasmModel_UUIDTest(t *testing.T) { for i := 0; i < 10; i++ { // Store a test message - testMsg := buildMessage([]byte(testString), - testMsgId.Bytes(), - nil, testString, testString+fmt.Sprintf("%d", i), cid, - time.Now(), - time.Second, channels.Sent) - uuid, err := eventModel.receiveHelper(testMsg) - if err != nil { - t.Fatalf("%+v", err) - } + channelID := id.NewIdFromBytes([]byte(testString), t) + msgID := channel.MessageID{} + copy(msgID[:], []byte(testString+fmt.Sprintf("%d", i))) + rnd := rounds.Round{ID: id.Round(42)} + uuid := eventModel.ReceiveMessage(channelID, msgID, + "test", testString+fmt.Sprintf("%d", i), cid, time.Now(), + time.Hour, rnd, channels.Sent) uuids[i] = uuid }