//////////////////////////////////////////////////////////////////////////////// // Copyright © 2022 xx foundation // // // // Use of this source code is governed by a license that can be found in the // // LICENSE file. // //////////////////////////////////////////////////////////////////////////////// //go:build js && wasm package main import ( "crypto/ed25519" "encoding/json" "time" "github.com/pkg/errors" jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/v4/dm" cryptoChannel "gitlab.com/elixxir/crypto/channel" "gitlab.com/elixxir/crypto/fastRNG" wDm "gitlab.com/elixxir/xxdk-wasm/indexedDb/worker/dm" "gitlab.com/elixxir/xxdk-wasm/worker" "gitlab.com/xx_network/crypto/csprng" ) var zeroUUID = []byte{0, 0, 0, 0, 0, 0, 0, 0} // manager handles the event model and the message callbacks, which is used to // send information between the event model and the main thread. type manager struct { mh *worker.ThreadManager model dm.EventModel } // registerCallbacks registers all the reception callbacks to manage messages // from the main thread for the channels.EventModel. func (m *manager) registerCallbacks() { m.mh.RegisterCallback(wDm.NewWASMEventModelTag, m.newWASMEventModelCB) m.mh.RegisterCallback(wDm.ReceiveTag, m.receiveCB) m.mh.RegisterCallback(wDm.ReceiveTextTag, m.receiveTextCB) m.mh.RegisterCallback(wDm.ReceiveReplyTag, m.receiveReplyCB) m.mh.RegisterCallback(wDm.ReceiveReactionTag, m.receiveReactionCB) m.mh.RegisterCallback(wDm.UpdateSentStatusTag, m.updateSentStatusCB) m.mh.RegisterCallback(wDm.BlockSenderTag, m.blockSenderCB) m.mh.RegisterCallback(wDm.UnblockSenderTag, m.unblockSenderCB) m.mh.RegisterCallback(wDm.GetConversationTag, m.getConversationCB) m.mh.RegisterCallback(wDm.GetConversationsTag, m.getConversationsCB) } // newWASMEventModelCB is the callback for NewWASMEventModel. Returns an empty // slice on success or an error message on failure. func (m *manager) newWASMEventModelCB(data []byte) ([]byte, error) { var msg wDm.NewWASMEventModelMessage err := json.Unmarshal(data, &msg) if err != nil { return []byte{}, errors.Errorf( "failed to JSON unmarshal %T from main thread: %+v", msg, err) } // Create new encryption cipher rng := fastRNG.NewStreamGenerator(12, 1024, csprng.NewSystemRNG) encryption, err := cryptoChannel.NewCipherFromJSON( []byte(msg.EncryptionJSON), rng.GetStream()) if err != nil { return []byte{}, errors.Errorf("failed to JSON unmarshal channel "+ "cipher from main thread: %+v", err) } m.model, err = NewWASMEventModel(msg.Path, encryption, m.messageReceivedCallback, m.storeDatabaseName, m.storeEncryptionStatus) if err != nil { return []byte(err.Error()), nil } return []byte{}, nil } // messageReceivedCallback sends calls to the MessageReceivedCallback in the // main thread. // // messageReceivedCallback adhere to the MessageReceivedCallback type. func (m *manager) messageReceivedCallback( uuid uint64, pubKey ed25519.PublicKey, update bool) { // Package parameters for sending msg := &wDm.MessageReceivedCallbackMessage{ UUID: uuid, PubKey: pubKey, Update: update, } data, err := json.Marshal(msg) if err != nil { jww.ERROR.Printf( "Could not JSON marshal MessageReceivedCallbackMessage: %+v", err) return } // Send it to the main thread m.mh.SendMessage(wDm.MessageReceivedCallbackTag, data) } // storeDatabaseName sends the database name to the main thread and waits for // the response. This function mocks the behavior of storage.StoreIndexedDb. // // storeDatabaseName adheres to the storeDatabaseNameFn type. func (m *manager) storeDatabaseName(databaseName string) error { // Register response callback with channel that will wait for the response responseChan := make(chan []byte) m.mh.RegisterCallback(wDm.StoreDatabaseNameTag, func(data []byte) ([]byte, error) { responseChan <- data return nil, nil }) // Send encryption status to main thread m.mh.SendMessage(wDm.StoreDatabaseNameTag, []byte(databaseName)) // Wait for response select { case response := <-responseChan: if len(response) > 0 { return errors.New(string(response)) } case <-time.After(worker.ResponseTimeout): return errors.Errorf("[WW] Timed out after %s waiting for response "+ "about storing the database name in local storage in the main "+ "thread", worker.ResponseTimeout) } return nil } // storeEncryptionStatus sends the database name and encryption status to the // main thread and waits for the response. If the value has not been previously // saved, it returns the saves encryption status. This function mocks the // behavior of storage.StoreIndexedDbEncryptionStatus. // // storeEncryptionStatus adheres to the storeEncryptionStatusFn type. func (m *manager) storeEncryptionStatus( databaseName string, encryption bool) (bool, error) { // Package parameters for sending msg := &wDm.EncryptionStatusMessage{ DatabaseName: databaseName, EncryptionStatus: encryption, } data, err := json.Marshal(msg) if err != nil { return false, err } // Register response callback with channel that will wait for the response responseChan := make(chan []byte) m.mh.RegisterCallback(wDm.EncryptionStatusTag, func(data []byte) ([]byte, error) { responseChan <- data return nil, nil }) // Send encryption status to main thread m.mh.SendMessage(wDm.EncryptionStatusTag, data) // Wait for response var response wDm.EncryptionStatusReply select { case responseData := <-responseChan: if err = json.Unmarshal(responseData, &response); err != nil { return false, err } case <-time.After(worker.ResponseTimeout): return false, errors.Errorf("timed out after %s waiting for "+ "response about the database encryption status from local "+ "storage in the main thread", worker.ResponseTimeout) } // If the response contain an error, return it if response.Error != "" { return false, errors.New(response.Error) } // Return the encryption status return response.EncryptionStatus, nil } // receiveCB is the callback for wasmModel.Receive. Returns a UUID of 0 on error // or the JSON marshalled UUID (uint64) on success. func (m *manager) receiveCB(data []byte) ([]byte, error) { var msg wDm.TransferMessage err := json.Unmarshal(data, &msg) if err != nil { return zeroUUID, errors.Errorf( "failed to JSON unmarshal %T from main thread: %+v", msg, err) } uuid := m.model.Receive( msg.MessageID, msg.Nickname, msg.Text, msg.PartnerKey, msg.SenderKey, msg.DmToken, msg.Codeset, msg.Timestamp, msg.Round, msg.MType, msg.Status) uuidData, err := json.Marshal(uuid) if err != nil { return zeroUUID, errors.Errorf("failed to JSON marshal UUID : %+v", err) } return uuidData, nil } // receiveTextCB is the callback for wasmModel.ReceiveText. Returns a UUID of 0 // on error or the JSON marshalled UUID (uint64) on success. func (m *manager) receiveTextCB(data []byte) ([]byte, error) { var msg wDm.TransferMessage err := json.Unmarshal(data, &msg) if err != nil { return []byte{}, errors.Errorf( "failed to JSON unmarshal %T from main thread: %+v", msg, err) } uuid := m.model.ReceiveText( msg.MessageID, msg.Nickname, string(msg.Text), msg.PartnerKey, msg.SenderKey, msg.DmToken, msg.Codeset, msg.Timestamp, msg.Round, msg.Status) uuidData, err := json.Marshal(uuid) if err != nil { return []byte{}, errors.Errorf("failed to JSON marshal UUID : %+v", err) } return uuidData, nil } // receiveReplyCB is the callback for wasmModel.ReceiveReply. Returns a UUID of // 0 on error or the JSON marshalled UUID (uint64) on success. func (m *manager) receiveReplyCB(data []byte) ([]byte, error) { var msg wDm.TransferMessage err := json.Unmarshal(data, &msg) if err != nil { return zeroUUID, errors.Errorf( "failed to JSON unmarshal %T from main thread: %+v", msg, err) } uuid := m.model.ReceiveReply(msg.MessageID, msg.ReactionTo, msg.Nickname, string(msg.Text), msg.PartnerKey, msg.SenderKey, msg.DmToken, msg.Codeset, msg.Timestamp, msg.Round, msg.Status) uuidData, err := json.Marshal(uuid) if err != nil { return zeroUUID, errors.Errorf("failed to JSON marshal UUID : %+v", err) } return uuidData, nil } // receiveReactionCB is the callback for wasmModel.ReceiveReaction. Returns a // UUID of 0 on error or the JSON marshalled UUID (uint64) on success. func (m *manager) receiveReactionCB(data []byte) ([]byte, error) { var msg wDm.TransferMessage err := json.Unmarshal(data, &msg) if err != nil { return zeroUUID, errors.Errorf( "failed to JSON unmarshal %T from main thread: %+v", msg, err) } uuid := m.model.ReceiveReaction(msg.MessageID, msg.ReactionTo, msg.Nickname, string(msg.Text), msg.PartnerKey, msg.SenderKey, msg.DmToken, msg.Codeset, msg.Timestamp, msg.Round, msg.Status) uuidData, err := json.Marshal(uuid) if err != nil { return zeroUUID, errors.Errorf("failed to JSON marshal UUID : %+v", err) } return uuidData, nil } // updateSentStatusCB is the callback for wasmModel.UpdateSentStatus. Always // returns nil; meaning, no response is supplied (or expected). func (m *manager) updateSentStatusCB(data []byte) ([]byte, error) { var msg wDm.TransferMessage err := json.Unmarshal(data, &msg) if err != nil { return nil, errors.Errorf( "failed to JSON unmarshal %T from main thread: %+v", msg, err) } m.model.UpdateSentStatus( msg.UUID, msg.MessageID, msg.Timestamp, msg.Round, msg.Status) return nil, nil } // blockSenderCB is the callback for wasmModel.BlockSender. Always // returns nil; meaning, no response is supplied (or expected). func (m *manager) blockSenderCB(data []byte) ([]byte, error) { m.model.BlockSender(data) return nil, nil } // unblockSenderCB is the callback for wasmModel.UnblockSender. Always // returns nil; meaning, no response is supplied (or expected). func (m *manager) unblockSenderCB(data []byte) ([]byte, error) { m.model.UnblockSender(data) return nil, nil } // getConversationCB is the callback for wasmModel.GetConversation. // Returns nil on error or the JSON marshalled Conversation on success. func (m *manager) getConversationCB(data []byte) ([]byte, error) { result := m.model.GetConversation(data) return json.Marshal(result) } // getConversationsCB is the callback for wasmModel.GetConversations. // Returns nil on error or the JSON marshalled list of Conversation on success. func (m *manager) getConversationsCB(_ []byte) ([]byte, error) { result := m.model.GetConversations() return json.Marshal(result) }