Skip to content
Snippets Groups Projects

fix for latest client release

Merged Jake Taylor requested to merge release into master
Compare and Show latest version
21 files
+ 1138
749
Compare changes
  • Side-by-side
  • Inline
Files
21
@@ -11,10 +11,8 @@ package main
import (
"crypto/ed25519"
"encoding/base64"
"encoding/json"
"strings"
"sync"
"strconv"
"syscall/js"
"time"
@@ -36,13 +34,14 @@ import (
// wasmModel implements [channels.EventModel] interface, which uses the channels
// system passed an object that adheres to in order to get events on the
// channel.
// NOTE: This model is NOT thread safe - it is the responsibility of the
// caller to ensure that its methods are called sequentially.
type wasmModel struct {
db *idb.Database
cipher cryptoChannel.Cipher
receivedMessageCB wChannels.MessageReceivedCallback
deletedMessageCB wChannels.DeletedMessageCallback
mutedUserCB wChannels.MutedUserCallback
updateMux sync.Mutex
}
// JoinChannel is called whenever a channel is joined locally.
@@ -122,8 +121,7 @@ func (w *wasmModel) deleteMsgByChannel(channelID *id.ID) error {
}
// Perform the operation
channelIdStr := base64.StdEncoding.EncodeToString(channelID.Marshal())
keyRange, err := idb.NewKeyRangeOnly(js.ValueOf(channelIdStr))
keyRange, err := idb.NewKeyRangeOnly(impl.EncodeBytes(channelID.Marshal()))
cursorRequest, err := index.OpenCursorRange(keyRange, idb.CursorNext)
if err != nil {
return errors.WithMessagef(parentErr, "Unable to open Cursor: %+v", err)
@@ -167,7 +165,7 @@ func (w *wasmModel) ReceiveMessage(channelID *id.ID, messageID message.ID,
textBytes, pubKey, dmToken, codeset, timestamp, lease, round.ID, mType,
false, hidden, status)
uuid, err := w.receiveHelper(msgToInsert, false)
uuid, err := w.upsertMessage(msgToInsert)
if err != nil {
jww.ERROR.Printf("Failed to receive Message: %+v", err)
}
@@ -203,7 +201,7 @@ func (w *wasmModel) ReceiveReply(channelID *id.ID, messageID,
replyTo.Bytes(), nickname, textBytes, pubKey, dmToken, codeset,
timestamp, lease, round.ID, mType, hidden, false, status)
uuid, err := w.receiveHelper(msgToInsert, false)
uuid, err := w.upsertMessage(msgToInsert)
if err != nil {
jww.ERROR.Printf("Failed to receive reply: %+v", err)
@@ -240,7 +238,7 @@ func (w *wasmModel) ReceiveReaction(channelID *id.ID, messageID,
textBytes, pubKey, dmToken, codeset, timestamp, lease, round.ID, mType,
false, hidden, status)
uuid, err := w.receiveHelper(msgToInsert, false)
uuid, err := w.upsertMessage(msgToInsert)
if err != nil {
jww.ERROR.Printf("Failed to receive reaction: %+v", err)
}
@@ -258,24 +256,25 @@ func (w *wasmModel) UpdateFromUUID(uuid uint64, messageID *message.ID,
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
// special version of receiveHelper)
w.updateMux.Lock()
defer w.updateMux.Unlock()
// Convert messageID to the key generated by json.Marshal
key := js.ValueOf(uuid)
// Use the key to get the existing Message
currentMsg, err := impl.Get(w.db, messageStoreName, key)
msgObj, err := impl.Get(w.db, messageStoreName, key)
if err != nil {
jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
"Unable to get message: %+v", err))
return
}
_, err = w.updateMessage(utils.JsToJson(currentMsg), messageID, timestamp,
currentMsg, err := valueToMessage(msgObj)
if err != nil {
jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
"Failed to marshal Message: %+v", err))
return
}
_, err = w.updateMessage(currentMsg, messageID, timestamp,
round, pinned, hidden, status)
if err != nil {
jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
@@ -297,22 +296,21 @@ func (w *wasmModel) UpdateFromMessageID(messageID message.ID,
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 := impl.GetIndex(w.db, messageStoreName,
messageStoreMessageIndex, js.ValueOf(msgIDStr))
msgObj, err := impl.GetIndex(w.db, messageStoreName,
messageStoreMessageIndex, impl.EncodeBytes(messageID.Marshal()))
if err != nil {
jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
"Failed to get message by index: %+v", err))
return 0
}
currentMsg := utils.JsToJson(currentMsgObj)
currentMsg, err := valueToMessage(msgObj)
if err != nil {
jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr,
"Failed to marshal Message: %+v", err))
return 0
}
uuid, err := w.updateMessage(currentMsg, &messageID, timestamp,
round, pinned, hidden, status)
if err != nil {
@@ -322,88 +320,82 @@ func (w *wasmModel) UpdateFromMessageID(messageID message.ID,
return uuid
}
// buildMessage is a private helper that converts typical [channels.EventModel]
// inputs into a basic Message structure for insertion into storage.
//
// NOTE: ID is not set inside this function because we want to use the
// autoincrement key by default. If you are trying to overwrite an existing
// message, then you need to set it manually yourself.
func buildMessage(channelID, messageID, parentID []byte, nickname string,
text []byte, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8,
timestamp time.Time, lease time.Duration, round id.Round,
mType channels.MessageType, pinned, hidden bool,
status channels.SentStatus) *Message {
return &Message{
MessageID: messageID,
Nickname: nickname,
ChannelID: channelID,
ParentMessageID: parentID,
Timestamp: timestamp,
Lease: strconv.FormatInt(int64(lease), 10),
Status: uint8(status),
Hidden: hidden,
Pinned: pinned,
Text: text,
Type: uint16(mType),
Round: uint64(round),
// User Identity Info
Pubkey: pubKey,
DmToken: dmToken,
CodesetVersion: codeset,
}
}
// updateMessage is a helper for updating a stored message.
func (w *wasmModel) updateMessage(currentMsgJson string, messageID *message.ID,
func (w *wasmModel) updateMessage(currentMsg *Message, messageID *message.ID,
timestamp *time.Time, round *rounds.Round, pinned, hidden *bool,
status *channels.SentStatus) (uint64, error) {
newMessage := &Message{}
err := json.Unmarshal([]byte(currentMsgJson), newMessage)
if err != nil {
return 0, err
}
if status != nil {
newMessage.Status = uint8(*status)
currentMsg.Status = uint8(*status)
}
if messageID != nil {
newMessage.MessageID = messageID.Bytes()
currentMsg.MessageID = messageID.Bytes()
}
if round != nil {
newMessage.Round = uint64(round.ID)
currentMsg.Round = uint64(round.ID)
}
if timestamp != nil {
newMessage.Timestamp = *timestamp
currentMsg.Timestamp = *timestamp
}
if pinned != nil {
newMessage.Pinned = *pinned
currentMsg.Pinned = *pinned
}
if hidden != nil {
newMessage.Hidden = *hidden
currentMsg.Hidden = *hidden
}
// Store the updated Message
uuid, err := w.receiveHelper(newMessage, true)
uuid, err := w.upsertMessage(currentMsg)
if err != nil {
return 0, err
}
channelID := &id.ID{}
copy(channelID[:], newMessage.ChannelID)
copy(channelID[:], currentMsg.ChannelID)
go w.receivedMessageCB(uuid, channelID, true)
return uuid, nil
}
// buildMessage is a private helper that converts typical [channels.EventModel]
// inputs into a basic Message structure for insertion into storage.
//
// NOTE: ID is not set inside this function because we want to use the
// autoincrement key by default. If you are trying to overwrite an existing
// message, then you need to set it manually yourself.
func buildMessage(channelID, messageID, parentID []byte, nickname string,
text []byte, pubKey ed25519.PublicKey, dmToken uint32, codeset uint8,
timestamp time.Time, lease time.Duration, round id.Round,
mType channels.MessageType, pinned, hidden bool,
status channels.SentStatus) *Message {
return &Message{
MessageID: messageID,
Nickname: nickname,
ChannelID: channelID,
ParentMessageID: parentID,
Timestamp: timestamp,
Lease: lease,
Status: uint8(status),
Hidden: hidden,
Pinned: pinned,
Text: text,
Type: uint16(mType),
Round: uint64(round),
// User Identity Info
Pubkey: pubKey,
DmToken: dmToken,
CodesetVersion: codeset,
}
}
// receiveHelper is a private helper for receiving any sort of message.
func (w *wasmModel) receiveHelper(
newMessage *Message, isUpdate bool) (uint64, error) {
// upsertMessage is a helper function that will update an existing record
// if Message.ID is specified. Otherwise, it will perform an insert.
func (w *wasmModel) upsertMessage(msg *Message) (uint64, error) {
// Convert to jsObject
newMessageJson, err := json.Marshal(newMessage)
newMessageJson, err := json.Marshal(msg)
if err != nil {
return 0, errors.Errorf("Unable to marshal Message: %+v", err)
}
@@ -412,42 +404,29 @@ func (w *wasmModel) receiveHelper(
return 0, errors.Errorf("Unable to marshal Message: %+v", err)
}
// Unset the primaryKey for inserts so that it can be auto-populated and
// incremented
if !isUpdate {
messageObj.Delete("id")
}
// Store message to database
result, err := impl.Put(w.db, messageStoreName, messageObj)
if err != nil && !strings.Contains(err.Error(),
"at least one key does not satisfy the uniqueness requirements") {
// Only return non-unique constraint errors so that the case
// below this one can be hit and handle duplicate entries properly.
msgIdObj, err := impl.Put(w.db, messageStoreName, messageObj)
if err != nil {
return 0, errors.Errorf("Unable to put Message: %+v", err)
}
// NOTE: Sometimes the insert fails to return an error but hits a duplicate
// insert, so this fallthrough returns the UUID entry in that case.
if result.IsUndefined() {
msgID := message.ID{}
copy(msgID[:], newMessage.MessageID)
msg, errLookup := w.msgIDLookup(msgID)
if errLookup == nil && msg.ID != 0 {
return msg.ID, nil
}
return 0, errors.Errorf("uuid lookup failure: %+v", err)
}
uuid := uint64(result.Int())
uuid := msgIdObj.Int()
jww.DEBUG.Printf("Successfully stored message %d", uuid)
return uuid, nil
return uint64(uuid), nil
}
// GetMessage returns the message with the given [channel.MessageID].
func (w *wasmModel) GetMessage(
messageID message.ID) (channels.ModelMessage, error) {
lookupResult, err := w.msgIDLookup(messageID)
msgIDStr := impl.EncodeBytes(messageID.Marshal())
resultObj, err := impl.GetIndex(w.db, messageStoreName,
messageStoreMessageIndex, msgIDStr)
if err != nil {
return channels.ModelMessage{}, err
}
lookupResult, err := valueToMessage(resultObj)
if err != nil {
return channels.ModelMessage{}, err
}
@@ -468,6 +447,15 @@ func (w *wasmModel) GetMessage(
}
}
lease := time.Duration(0)
if len(lookupResult.Lease) > 0 {
leaseInt, err := strconv.ParseInt(lookupResult.Lease, 10, 64)
if err != nil {
return channels.ModelMessage{}, err
}
lease = time.Duration(leaseInt)
}
return channels.ModelMessage{
UUID: lookupResult.ID,
Nickname: lookupResult.Nickname,
@@ -475,7 +463,7 @@ func (w *wasmModel) GetMessage(
ChannelID: channelId,
ParentMessageID: parentMsgId,
Timestamp: lookupResult.Timestamp,
Lease: lookupResult.Lease,
Lease: lease,
Status: channels.SentStatus(lookupResult.Status),
Hidden: lookupResult.Hidden,
Pinned: lookupResult.Pinned,
@@ -489,10 +477,8 @@ 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()))
err := impl.DeleteIndex(
w.db, messageStoreName, messageStoreMessageIndex, pkeyName, msgId)
err := impl.DeleteIndex(w.db, messageStoreName,
messageStoreMessageIndex, pkeyName, impl.EncodeBytes(messageID.Marshal()))
if err != nil {
return err
}
@@ -508,20 +494,10 @@ func (w *wasmModel) MuteUser(
go w.mutedUserCB(channelID, pubKey, unmute)
}
// msgIDLookup gets the UUID of the Message with the given messageID.
func (w *wasmModel) msgIDLookup(messageID message.ID) (*Message, error) {
msgIDStr := js.ValueOf(base64.StdEncoding.EncodeToString(messageID.Bytes()))
resultObj, err := impl.GetIndex(w.db, messageStoreName,
messageStoreMessageIndex, msgIDStr)
if err != nil {
return nil, err
} else if resultObj.IsUndefined() {
return nil, errors.Errorf("no message for %s found", msgIDStr)
}
// Process result into string
// valueToMessage is a helper for converting js.Value to Message.
func valueToMessage(msgObj js.Value) (*Message, error) {
resultMsg := &Message{}
err = json.Unmarshal([]byte(utils.JsToJson(resultObj)), resultMsg)
err := json.Unmarshal([]byte(utils.JsToJson(msgObj)), resultMsg)
if err != nil {
return nil, err
}
Loading