diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 8551215fa5b651bb93a247cde5ab89d4b8f5f7e6..0000000000000000000000000000000000000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -Copyright (c) 2022, xx network SEZC - -Redistribution and use in source and binary forms, with or without -modification, are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/indexedDb/implementation.go b/indexedDb/implementation.go deleted file mode 100644 index 632bd1317f09a9ebfb17163ae8f690f9c9986350..0000000000000000000000000000000000000000 --- a/indexedDb/implementation.go +++ /dev/null @@ -1,360 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// 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 -// +build js,wasm - -package indexedDb - -import ( - "context" - "encoding/base64" - "encoding/json" - "github.com/hack-pad/go-indexeddb/idb" - "github.com/pkg/errors" - jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/xxdk-wasm/utils" - "syscall/js" - "time" - - "gitlab.com/elixxir/client/channels" - "gitlab.com/elixxir/client/cmix/rounds" - cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast" - cryptoChannel "gitlab.com/elixxir/crypto/channel" - "gitlab.com/xx_network/primitives/id" -) - -// dbTimeout is the global timeout for operations with the storage context.Contact -const dbTimeout = time.Second - -// wasmModel implements [channels.EventModel] interface which uses the channels -// system passed an object which adheres to in order to get events on the channel. -type wasmModel struct { - db *idb.Database -} - -// newContext builds a context for database operations -func newContext() (context.Context, context.CancelFunc) { - return context.WithTimeout(context.Background(), dbTimeout) -} - -// JoinChannel is called whenever a channel is joined locally. -func (w *wasmModel) JoinChannel(channel *cryptoBroadcast.Channel) { - parentErr := errors.New("failed to JoinChannel") - - // Build object - newChannel := Channel{ - Id: channel.ReceptionID.Marshal(), - Name: channel.Name, - Description: channel.Description, - } - - // Convert to jsObject - newChannelJson, err := json.Marshal(&newChannel) - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Unable to marshal Channel: %+v", err)) - return - } - channelObj, err := utils.JsonToJS(newChannelJson) - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Unable to marshal Channel: %+v", err)) - return - } - - // Prepare the Transaction - txn, err := w.db.Transaction(idb.TransactionReadWrite, channelsStoreName) - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Unable to create Transaction: %+v", err)) - return - } - store, err := txn.ObjectStore(channelsStoreName) - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Unable to get ObjectStore: %+v", err)) - return - } - - // Perform the operation - _, err = store.Add(channelObj) - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Unable to Add Channel: %+v", err)) - return - } - - // Wait for the operation to return - ctx, cancel := newContext() - err = txn.Await(ctx) - cancel() - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Adding Channel failed: %+v", err)) - return - } - jww.DEBUG.Printf("Successfully added channel: %s", - channel.ReceptionID.String()) -} - -// LeaveChannel is called whenever a channel is left locally. -func (w *wasmModel) LeaveChannel(channelID *id.ID) { - parentErr := errors.New("failed to LeaveChannel") - - // Prepare the Transaction - txn, err := w.db.Transaction(idb.TransactionReadWrite, channelsStoreName) - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Unable to create Transaction: %+v", err)) - return - } - store, err := txn.ObjectStore(channelsStoreName) - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Unable to get ObjectStore: %+v", err)) - return - } - - // Perform the operation - _, err = store.Delete(js.ValueOf(channelID.String())) - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Unable to Delete Channel: %+v", err)) - return - } - - // Wait for the operation to return - ctx, cancel := newContext() - err = txn.Await(ctx) - cancel() - if err != nil { - jww.ERROR.Printf("%+v", errors.WithMessagef(parentErr, - "Deleting Channel failed: %+v", err)) - return - } - jww.DEBUG.Printf("Successfully deleted channel: %s", channelID.String()) -} - -// ReceiveMessage is called whenever a message is received 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. -func (w *wasmModel) ReceiveMessage(channelID *id.ID, messageID cryptoChannel.MessageID, - senderUsername string, text string, timestamp time.Time, lease time.Duration, - _ rounds.Round, status channels.SentStatus) { - parentErr := errors.New("failed to ReceiveMessage") - - err := w.receiveHelper(buildMessage(channelID.Marshal(), messageID.Bytes(), - nil, senderUsername, text, timestamp, lease, status)) - if err != nil { - jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error())) - } -} - -// ReceiveReply is called whenever a message is received which 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 -// Messages may arrive our of order, so a reply in theory can arrive before -// the initial message, as a result it may be important to buffer replies. -func (w *wasmModel) ReceiveReply(channelID *id.ID, messageID cryptoChannel.MessageID, - replyTo cryptoChannel.MessageID, senderUsername string, text string, - timestamp time.Time, lease time.Duration, _ rounds.Round, status channels.SentStatus) { - parentErr := errors.New("failed to ReceiveReply") - - err := w.receiveHelper(buildMessage(channelID.Marshal(), messageID.Bytes(), - replyTo.Bytes(), senderUsername, text, timestamp, lease, status)) - if err != nil { - jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error())) - } -} - -// ReceiveReaction is called whenever a reaction to a message is received -// on a given channel. It may be called multiple times on the same reaction, -// it is incumbent on the user of the API to filter such called by message ID -// Messages may arrive our of order, so a reply in theory can arrive before -// the initial message, as a result it may be important to buffer reactions. -func (w *wasmModel) ReceiveReaction(channelID *id.ID, messageID cryptoChannel.MessageID, - reactionTo cryptoChannel.MessageID, senderUsername string, reaction string, - timestamp time.Time, lease time.Duration, _ rounds.Round, status channels.SentStatus) { - parentErr := errors.New("failed to ReceiveReaction") - - err := w.receiveHelper(buildMessage(channelID.Marshal(), messageID.Bytes(), - reactionTo.Bytes(), senderUsername, reaction, timestamp, lease, status)) - if err != nil { - jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error())) - } -} - -// UpdateSentStatus is called whenever the SentStatus of a message -// has changed -// TODO: Potential race condition due to separate get/update operations -func (w *wasmModel) UpdateSentStatus(messageID cryptoChannel.MessageID, - status channels.SentStatus) { - parentErr := errors.New("failed to UpdateSentStatus") - - // Convert messageID to the key generated by json.Marshal - key := js.ValueOf(base64.StdEncoding.EncodeToString(messageID[:])) - - // Use the key to get the existing Message - currentMsg, err := w.get(messageStoreName, key) - if err != nil { - return - } - - // Extract the existing Message and update the Status - newMessage := &Message{} - err = json.Unmarshal([]byte(currentMsg), newMessage) - if err != nil { - return - } - newMessage.Status = uint8(status) - - // Store the updated Message - err = w.receiveHelper(newMessage) - if err != nil { - jww.ERROR.Printf("%+v", errors.Wrap(parentErr, err.Error())) - } -} - -// buildMessage is a private helper that converts typical [channels.EventModel] -// inputs into a basic Message structure for insertion into storage -func buildMessage(channelID []byte, messageID []byte, - parentId []byte, senderUsername string, text string, - timestamp time.Time, lease time.Duration, status channels.SentStatus) *Message { - return &Message{ - Id: messageID, - SenderUsername: senderUsername, - ChannelId: channelID, - ParentMessageId: parentId, - Timestamp: timestamp, - Lease: lease, - Status: uint8(status), - Hidden: false, - Pinned: false, - Text: text, - } -} - -// receiveHelper is a private helper for receiving any sort of message -func (w *wasmModel) receiveHelper(newMessage *Message) error { - // Convert to jsObject - newMessageJson, err := json.Marshal(newMessage) - if err != nil { - return errors.Errorf("Unable to marshal Message: %+v", err) - } - messageObj, err := utils.JsonToJS(newMessageJson) - if err != nil { - return errors.Errorf("Unable to marshal Message: %+v", err) - } - - // Prepare the Transaction - txn, err := w.db.Transaction(idb.TransactionReadWrite, messageStoreName) - if err != nil { - return errors.Errorf("Unable to create Transaction: %+v", err) - } - store, err := txn.ObjectStore(messageStoreName) - if err != nil { - return errors.Errorf("Unable to get ObjectStore: %+v", err) - } - - // Perform the upsert (put) operation - _, err = store.Put(messageObj) - if err != nil { - return errors.Errorf("Unable to upsert Message: %+v", err) - } - - // Wait for the operation to return - ctx, cancel := newContext() - err = txn.Await(ctx) - cancel() - if err != nil { - return errors.Errorf("Upserting Message failed: %+v", err) - } - jww.DEBUG.Printf("Successfully stored message from %s", - newMessage.SenderUsername) - return 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) { - parentErr := errors.Errorf("failed to get %s/%s", objectStoreName, key) - - // Prepare the Transaction - txn, err := w.db.Transaction(idb.TransactionReadOnly, objectStoreName) - if err != nil { - return "", errors.WithMessagef(parentErr, - "Unable to create Transaction: %+v", err) - } - store, err := txn.ObjectStore(objectStoreName) - if err != nil { - return "", errors.WithMessagef(parentErr, - "Unable to get ObjectStore: %+v", err) - } - - // Perform the operation - getRequest, err := store.Get(key) - if err != nil { - return "", errors.WithMessagef(parentErr, - "Unable to Get from ObjectStore: %+v", err) - } - - // Wait for the operation to return - ctx, cancel := newContext() - resultObj, err := getRequest.Await(ctx) - cancel() - if err != nil { - return "", errors.WithMessagef(parentErr, - "Unable to get from ObjectStore: %+v", err) - } - - // Process result into string - resultStr := utils.JsToJson(resultObj) - jww.DEBUG.Printf("Got from %s/%s: %s", objectStoreName, key, resultStr) - return resultStr, nil -} - -// dump given [idb.ObjectStore] contents to string slice for debugging purposes -func (w *wasmModel) dump(objectStoreName string) ([]string, error) { - parentErr := errors.Errorf("failed to dump %s", objectStoreName) - - txn, err := w.db.Transaction(idb.TransactionReadOnly, objectStoreName) - if err != nil { - return nil, errors.WithMessagef(parentErr, - "Unable to create Transaction: %+v", err) - } - store, err := txn.ObjectStore(objectStoreName) - if err != nil { - return nil, errors.WithMessagef(parentErr, - "Unable to get ObjectStore: %+v", err) - } - cursorRequest, err := store.OpenCursor(idb.CursorNext) - if err != nil { - return nil, errors.WithMessagef(parentErr, - "Unable to open Cursor: %+v", err) - } - - // Run the query - jww.DEBUG.Printf("%s values:", objectStoreName) - results := make([]string, 0) - ctx, cancel := newContext() - err = cursorRequest.Iter(ctx, func(cursor *idb.CursorWithValue) error { - value, err := cursor.Value() - if err != nil { - return err - } - valueStr := utils.JsToJson(value) - results = append(results, valueStr) - jww.DEBUG.Printf("- %v", valueStr) - return nil - }) - cancel() - if err != nil { - return nil, errors.WithMessagef(parentErr, - "Unable to dump ObjectStore: %+v", err) - } - return results, nil -} diff --git a/indexedDb/implementation_test.go b/indexedDb/implementation_test.go deleted file mode 100644 index a7b005abb782c0151bb0045d0eb9239c611ac947..0000000000000000000000000000000000000000 --- a/indexedDb/implementation_test.go +++ /dev/null @@ -1,118 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// Copyright © 2022 xx foundation // -// // -// Use of this source code is governed by a license that can be found in the // -// LICENSE file // -//////////////////////////////////////////////////////////////////////////////// - -package indexedDb - -import ( - "encoding/json" - jww "github.com/spf13/jwalterweatherman" - "gitlab.com/elixxir/client/channels" - cryptoBroadcast "gitlab.com/elixxir/crypto/broadcast" - "gitlab.com/elixxir/crypto/channel" - "gitlab.com/xx_network/primitives/id" - "os" - "testing" - "time" -) - -func TestMain(m *testing.M) { - jww.SetStdoutThreshold(jww.LevelDebug) - os.Exit(m.Run()) -} - -// Test UpdateSentStatus happy path and ensure fields don't change -func TestWasmModel_UpdateSentStatus(t *testing.T) { - testString := "test" - testMsgId := channel.MakeMessageID([]byte(testString)) - eventModel, err := newWasmModel(testString) - if err != nil { - t.Fatalf("%+v", err) - } - - // Store a test message - testMsg := buildMessage([]byte(testString), testMsgId.Bytes(), - nil, testString, testString, time.Now(), time.Second, channels.Sent) - err = eventModel.receiveHelper(testMsg) - if err != nil { - t.Fatalf("%+v", err) - } - - // Ensure one message is stored - results, err := eventModel.dump(messageStoreName) - if err != nil { - t.Fatalf("%+v", err) - } - if len(results) != 1 { - t.Fatalf("Expected 1 message to exist") - } - - // Update the sentStatus - expectedStatus := channels.Failed - eventModel.UpdateSentStatus(testMsgId, expectedStatus) - - // Check the resulting status - results, err = eventModel.dump(messageStoreName) - if err != nil { - t.Fatalf("%+v", err) - } - 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) - } - if resultMsg.Status != uint8(expectedStatus) { - t.Fatalf("Unexpected Status: %v", resultMsg.Status) - } - - // Make sure other fields didn't change - if resultMsg.SenderUsername != testString { - t.Fatalf("Unexpected SenderUsername: %v", resultMsg.SenderUsername) - } -} - -// Smoke test JoinChannel/LeaveChannel happy paths -func TestWasmModel_JoinChannel_LeaveChannel(t *testing.T) { - eventModel, err := newWasmModel("test") - if err != nil { - t.Fatalf("%+v", err) - } - - testChannel := &cryptoBroadcast.Channel{ - ReceptionID: id.NewIdFromString("test", id.Generic, t), - Name: "test", - Description: "test", - Salt: nil, - RsaPubKey: nil, - } - testChannel2 := &cryptoBroadcast.Channel{ - ReceptionID: id.NewIdFromString("test2", id.Generic, t), - Name: "test2", - Description: "test2", - Salt: nil, - RsaPubKey: nil, - } - eventModel.JoinChannel(testChannel) - eventModel.JoinChannel(testChannel2) - results, err := eventModel.dump(channelsStoreName) - if err != nil { - t.Fatalf("%+v", err) - } - if len(results) != 2 { - t.Fatalf("Expected 2 channels to exist") - } - eventModel.LeaveChannel(testChannel.ReceptionID) - results, err = eventModel.dump(channelsStoreName) - if err != nil { - t.Fatalf("%+v", err) - } - if len(results) != 1 { - t.Fatalf("Expected 1 channels to exist") - } -} diff --git a/indexedDb/init.go b/indexedDb/init.go deleted file mode 100644 index f256b935111a330c30779eb697a1f6da4195dc84..0000000000000000000000000000000000000000 --- a/indexedDb/init.go +++ /dev/null @@ -1,110 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// 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 -// +build js,wasm - -package indexedDb - -import ( - "github.com/hack-pad/go-indexeddb/idb" - "github.com/pkg/errors" - jww "github.com/spf13/jwalterweatherman" - "syscall/js" - - "gitlab.com/elixxir/client/channels" -) - -const ( - // databaseSuffix to be appended to the name of the database - databaseSuffix = "_messenger" - // currentVersion of the IndexDb runtime. Used for migration purposes. - currentVersion uint = 1 -) - -// NewWasmEventModel returns a [channels.EventModel] backed by a wasmModel -func NewWasmEventModel(username string) (channels.EventModel, error) { - databaseName := username + databaseSuffix - return newWasmModel(databaseName) -} - -// newWasmModel creates the given [idb.Database] and returns a wasmModel -func newWasmModel(databaseName string) (*wasmModel, error) { - // Attempt to open database object - ctx, cancel := newContext() - defer cancel() - openRequest, _ := idb.Global().Open(ctx, databaseName, currentVersion, - func(db *idb.Database, oldVersion, newVersion uint) error { - if oldVersion == newVersion { - jww.INFO.Printf("IndexDb version is current: v%d", - newVersion) - return nil - } - - jww.INFO.Printf("IndexDb upgrade required: v%d -> v%d", - oldVersion, newVersion) - - if oldVersion == 0 && newVersion == 1 { - return v1Upgrade(db) - } - - return errors.Errorf("Invalid version upgrade path: v%d -> v%d", - oldVersion, newVersion) - }) - - // Wait for database open to finish - db, err := openRequest.Await(ctx) - - return &wasmModel{db: db}, err -} - -// v1Upgrade performs the v0 -> v1 database upgrade. -// This can never be changed without permanently breaking backwards compatibility. -func v1Upgrade(db *idb.Database) error { - storeOpts := idb.ObjectStoreOptions{ - KeyPath: js.ValueOf(pkeyName), - AutoIncrement: false, - } - indexOpts := idb.IndexOptions{ - Unique: false, - MultiEntry: false, - } - - // Build Message ObjectStore and Indexes - messageStore, err := db.CreateObjectStore(messageStoreName, storeOpts) - if err != nil { - return err - } - _, err = messageStore.CreateIndex(messageStoreChannelIndex, - js.ValueOf(messageStoreChannel), indexOpts) - if err != nil { - return err - } - _, err = messageStore.CreateIndex(messageStoreParentIndex, - js.ValueOf(messageStoreParent), indexOpts) - if err != nil { - return err - } - _, err = messageStore.CreateIndex(messageStoreTimestampIndex, - js.ValueOf(messageStoreTimestamp), indexOpts) - if err != nil { - return err - } - _, err = messageStore.CreateIndex(messageStorePinnedIndex, - js.ValueOf(messageStorePinned), indexOpts) - if err != nil { - return err - } - - // Build Channel ObjectStore - _, err = db.CreateObjectStore(channelsStoreName, storeOpts) - if err != nil { - return err - } - - return nil -} diff --git a/indexedDb/model.go b/indexedDb/model.go deleted file mode 100644 index 9354e67a869c87f70824dc52fecc67a1058671d2..0000000000000000000000000000000000000000 --- a/indexedDb/model.go +++ /dev/null @@ -1,60 +0,0 @@ -//////////////////////////////////////////////////////////////////////////////// -// 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 -// +build js,wasm - -package indexedDb - -import ( - "time" -) - -const ( - // Text representation of primary key value (keyPath). - pkeyName = "id" - - // Text representation of the names of the various [idb.ObjectStore]. - messageStoreName = "messages" - channelsStoreName = "channels" - - // Message index names. - messageStoreChannelIndex = "channel_id_index" - messageStoreParentIndex = "parent_message_id_index" - messageStoreTimestampIndex = "timestamp_index" - messageStorePinnedIndex = "pinned_index" - - // Message keyPath names (must match json struct tags). - messageStoreChannel = "channel_id" - messageStoreParent = "parent_message_id" - messageStoreTimestamp = "timestamp" - messageStorePinned = "pinned" -) - -// Message defines the IndexedDb representation of a single Message. -// A Message belongs to one Channel. -// A Message may belong to one Message (Parent). -type Message struct { - Id []byte `json:"id"` // Matches pkeyName - SenderUsername string `json:"sender_username"` - ChannelId []byte `json:"channel_id"` // Index - ParentMessageId []byte `json:"parent_message_id"` // Index - Timestamp time.Time `json:"timestamp"` // Index - Lease time.Duration `json:"lease"` - Status uint8 `json:"status"` - Hidden bool `json:"hidden"` - Pinned bool `json:"pinned"` // Index - Text string `json:"text"` -} - -// Channel defines the IndexedDb representation of a single Channel -// A Channel has many Message. -type Channel struct { - Id []byte `json:"id"` // Matches pkeyName - Name string `json:"name"` - Description string `json:"description"` -}