From 1e6c0b38a09dae2a193633e66bda3620d6ab2751 Mon Sep 17 00:00:00 2001 From: Jake Taylor <jake@elixxir.io> Date: Thu, 15 Dec 2022 13:49:51 -0600 Subject: [PATCH] refactor indexedDb to be more receptive to upcoming project changes --- indexedDb/{ => channels}/implementation.go | 211 ++-------------- .../{ => channels}/implementation_test.go | 23 +- indexedDb/{ => channels}/init.go | 7 +- indexedDb/{ => channels}/model.go | 4 +- indexedDb/utils.go | 226 ++++++++++++++++++ 5 files changed, 262 insertions(+), 209 deletions(-) rename indexedDb/{ => channels}/implementation.go (68%) rename indexedDb/{ => channels}/implementation_test.go (94%) rename indexedDb/{ => channels}/init.go (97%) rename indexedDb/{ => channels}/model.go (97%) create mode 100644 indexedDb/utils.go diff --git a/indexedDb/implementation.go b/indexedDb/channels/implementation.go similarity index 68% rename from indexedDb/implementation.go rename to indexedDb/channels/implementation.go index 273a1ec4..46e68fd4 100644 --- a/indexedDb/implementation.go +++ b/indexedDb/channels/implementation.go @@ -7,13 +7,13 @@ //go:build js && wasm -package indexedDb +package channels import ( - "context" "crypto/ed25519" "encoding/base64" "encoding/json" + "gitlab.com/elixxir/xxdk-wasm/indexedDb" "sync" "syscall/js" "time" @@ -30,10 +30,6 @@ import ( "gitlab.com/xx_network/primitives/id" ) -// dbTimeout is the global timeout for operations with the storage -// [context.Context]. -const dbTimeout = time.Second - // wasmModel implements [channels.EventModel] interface, which uses the channels // system passed an object that adheres to in order to get events on the // channel. @@ -44,11 +40,6 @@ type wasmModel struct { updateMux sync.Mutex } -// 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") @@ -74,38 +65,11 @@ func (w *wasmModel) JoinChannel(channel *cryptoBroadcast.Channel) { return } - // Prepare the Transaction - txn, err := w.db.Transaction(idb.TransactionReadWrite, channelsStoreName) + _, err = indexedDb.Put(w.db, channelsStoreName, channelObj) 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 + "Unable to put Channel: %+v", err)) } - - // Perform the operation - _, err = store.Put(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) } // LeaveChannel is called whenever a channel is left locally. @@ -135,7 +99,7 @@ func (w *wasmModel) LeaveChannel(channelID *id.ID) { } // Wait for the operation to return - ctx, cancel := newContext() + ctx, cancel := indexedDb.NewContext() err = txn.Await(ctx) cancel() if err != nil { @@ -183,7 +147,7 @@ func (w *wasmModel) deleteMsgByChannel(channelID *id.ID) error { if err != nil { return errors.WithMessagef(parentErr, "Unable to open Cursor: %+v", err) } - ctx, cancel := newContext() + ctx, cancel := indexedDb.NewContext() err = cursorRequest.Iter(ctx, func(cursor *idb.CursorWithValue) error { _, err := cursor.Delete() @@ -255,8 +219,8 @@ func (w *wasmModel) ReceiveReply(channelID *id.ID, } msgToInsert := buildMessage(channelID.Marshal(), messageID.Bytes(), - replyTo.Bytes(), nickname, textBytes, pubKey, codeset, timestamp, lease, - round.ID, mType, status) + replyTo.Bytes(), nickname, textBytes, pubKey, codeset, + timestamp, lease, round.ID, mType, status) uuid, err := w.receiveHelper(msgToInsert, false) @@ -322,7 +286,7 @@ func (w *wasmModel) UpdateSentStatus(uuid uint64, key := js.ValueOf(uuid) // Use the key to get the existing Message - currentMsg, err := w.get(messageStoreName, key) + currentMsg, err := indexedDb.Get(w.db, messageStoreName, key) if err != nil { return } @@ -404,33 +368,14 @@ func (w *wasmModel) receiveHelper(newMessage *Message, isUpdate bool) (uint64, messageObj.Delete("id") } - // Prepare the Transaction - txn, err := w.db.Transaction(idb.TransactionReadWrite, messageStoreName) - if err != nil { - return 0, errors.Errorf("Unable to create Transaction: %+v", - err) - } - store, err := txn.ObjectStore(messageStoreName) - if err != nil { - return 0, errors.Errorf("Unable to get ObjectStore: %+v", err) - } - - // Perform the upsert (put) operation - addReq, err := store.Put(messageObj) - if err != nil { - return 0, errors.Errorf("Unable to upsert Message: %+v", err) - } - - // Wait for the operation to return - ctx, cancel := newContext() - err = txn.Await(ctx) - cancel() + // Store message to database + addReq, err := indexedDb.Put(w.db, messageStoreName, messageObj) if err != nil { - return 0, errors.Errorf("Upserting Message failed: %+v", err) + return 0, errors.Errorf("Unable to put Message: %+v", err) } res, err := addReq.Result() if err != nil { - return 0, errors.Errorf("Getting result from request failed: %+v", err) + return 0, errors.Errorf("Unable to get Message result: %+v", err) } // NOTE: Sometimes the insert fails to return an error but hits a duplicate @@ -450,135 +395,19 @@ func (w *wasmModel) receiveHelper(newMessage *Message, isUpdate bool) (uint64, return uuid, 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 -} - +// msgIDLookup gets the UUID of the Message with the given messageID. 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() + msgIDStr := js.ValueOf(base64.StdEncoding.EncodeToString(messageID.Bytes())) + resultObj, err := indexedDb.GetIndex(w.db, messageStoreName, + messageStoreMessageIndex, msgIDStr) if err != nil { - return 0, errors.WithMessagef(parentErr, - "Unable to get from ObjectStore: %+v", err) + return 0, 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()) + if !resultObj.IsUndefined() { + uuid = uint64(resultObj.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) { - 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/channels/implementation_test.go similarity index 94% rename from indexedDb/implementation_test.go rename to indexedDb/channels/implementation_test.go index c50be8a4..221494e3 100644 --- a/indexedDb/implementation_test.go +++ b/indexedDb/channels/implementation_test.go @@ -7,12 +7,13 @@ //go:build js && wasm -package indexedDb +package channels import ( "encoding/json" "fmt" "github.com/hack-pad/go-indexeddb/idb" + "gitlab.com/elixxir/xxdk-wasm/indexedDb" "gitlab.com/elixxir/xxdk-wasm/storage" "gitlab.com/xx_network/primitives/netTime" "os" @@ -54,7 +55,7 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) { } // Ensure one message is stored - results, err := eventModel.dump(messageStoreName) + results, err := indexedDb.Dump(eventModel.db, messageStoreName) if err != nil { t.Fatalf("%+v", err) } @@ -68,7 +69,7 @@ func Test_wasmModel_UpdateSentStatus(t *testing.T) { rounds.Round{ID: 8675309}, expectedStatus) // Check the resulting status - results, err = eventModel.dump(messageStoreName) + results, err = indexedDb.Dump(eventModel.db, messageStoreName) if err != nil { t.Fatalf("%+v", err) } @@ -112,7 +113,7 @@ func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) { } eventModel.JoinChannel(testChannel) eventModel.JoinChannel(testChannel2) - results, err := eventModel.dump(channelsStoreName) + results, err := indexedDb.Dump(eventModel.db, channelsStoreName) if err != nil { t.Fatalf("%+v", err) } @@ -120,7 +121,7 @@ func Test_wasmModel_JoinChannel_LeaveChannel(t *testing.T) { t.Fatalf("Expected 2 channels to exist") } eventModel.LeaveChannel(testChannel.ReceptionID) - results, err = eventModel.dump(channelsStoreName) + results, err = indexedDb.Dump(eventModel.db, channelsStoreName) if err != nil { t.Fatalf("%+v", err) } @@ -151,8 +152,6 @@ func Test_wasmModel_UUIDTest(t *testing.T) { uuids[i] = uuid } - _, _ = eventModel.dump(messageStoreName) - for i := 0; i < 10; i++ { for j := i + 1; j < 10; j++ { if uuids[i] == uuids[j] { @@ -186,8 +185,6 @@ func Test_wasmModel_DuplicateReceives(t *testing.T) { uuids[i] = uuid } - _, _ = eventModel.dump(messageStoreName) - for i := 0; i < 10; i++ { for j := i + 1; j < 10; j++ { if uuids[i] != uuids[j] { @@ -230,7 +227,7 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) { } // Check pre-results - result, err := eventModel.dump(messageStoreName) + result, err := indexedDb.Dump(eventModel.db, messageStoreName) if err != nil { t.Fatalf("%+v", err) } @@ -245,7 +242,7 @@ func Test_wasmModel_deleteMsgByChannel(t *testing.T) { } // Check final results - result, err = eventModel.dump(messageStoreName) + result, err = indexedDb.Dump(eventModel.db, messageStoreName) if err != nil { t.Fatalf("%+v", err) } @@ -298,7 +295,7 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) { if err != nil { t.Fatalf("%+v", err) } - results, err := eventModel.dump(messageStoreName) + results, err := indexedDb.Dump(eventModel.db, messageStoreName) if err != nil { t.Fatalf("%+v", err) } @@ -327,7 +324,7 @@ func TestWasmModel_receiveHelper_UniqueIndex(t *testing.T) { // The update to duplicate message ID won't fail, // but it just silently shouldn't happen - results, err = eventModel.dump(messageStoreName) + results, err = indexedDb.Dump(eventModel.db, messageStoreName) if err != nil { t.Fatalf("%+v", err) } diff --git a/indexedDb/init.go b/indexedDb/channels/init.go similarity index 97% rename from indexedDb/init.go rename to indexedDb/channels/init.go index 543ae0ae..0b52434f 100644 --- a/indexedDb/init.go +++ b/indexedDb/channels/init.go @@ -7,7 +7,7 @@ //go:build js && wasm -package indexedDb +package channels import ( "github.com/hack-pad/go-indexeddb/idb" @@ -15,6 +15,7 @@ import ( jww "github.com/spf13/jwalterweatherman" "gitlab.com/elixxir/client/v4/channels" cryptoChannel "gitlab.com/elixxir/crypto/channel" + "gitlab.com/elixxir/xxdk-wasm/indexedDb" "gitlab.com/elixxir/xxdk-wasm/storage" "gitlab.com/xx_network/primitives/id" "syscall/js" @@ -58,7 +59,7 @@ func NewWASMEventModel(path string, encryption cryptoChannel.Cipher, func newWASMModel(databaseName string, encryption cryptoChannel.Cipher, cb MessageReceivedCallback) (*wasmModel, error) { // Attempt to open database object - ctx, cancel := newContext() + ctx, cancel := indexedDb.NewContext() defer cancel() openRequest, err := idb.Global().Open(ctx, databaseName, currentVersion, func(db *idb.Database, oldVersion, newVersion uint) error { @@ -206,7 +207,7 @@ func (w *wasmModel) hackTestDb() error { if helper != nil { return helper } - result, err := w.get(messageStoreName, js.ValueOf(msgId)) + result, err := indexedDb.Get(w.db, messageStoreName, js.ValueOf(msgId)) if err != nil { return err } diff --git a/indexedDb/model.go b/indexedDb/channels/model.go similarity index 97% rename from indexedDb/model.go rename to indexedDb/channels/model.go index c6e29184..02a3ebd4 100644 --- a/indexedDb/model.go +++ b/indexedDb/channels/model.go @@ -7,7 +7,7 @@ //go:build js && wasm -package indexedDb +package channels import ( "time" @@ -60,7 +60,7 @@ type Message struct { Round uint64 `json:"round"` // User cryptographic Identity struct -- could be pulled out - Pubkey []byte `json:"pubkey"` // Index + Pubkey []byte `json:"pubkey"` CodesetVersion uint8 `json:"codeset_version"` } diff --git a/indexedDb/utils.go b/indexedDb/utils.go new file mode 100644 index 00000000..7d7e7e6b --- /dev/null +++ b/indexedDb/utils.go @@ -0,0 +1,226 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 + +// This file contains several generic IndexedDB helper functions that +// may be useful for any IndexedDB implementations. + +package indexedDb + +import ( + "context" + "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" +) + +// dbTimeout is the global timeout for operations with the storage +// [context.Context]. +const dbTimeout = time.Second + +// NewContext builds a context for indexedDb operations. +func NewContext() (context.Context, context.CancelFunc) { + return context.WithTimeout(context.Background(), dbTimeout) +} + +// Get is a generic helper for getting values from the given [idb.ObjectStore]. +func Get(db *idb.Database, objectStoreName string, key js.Value) (string, error) { + parentErr := errors.Errorf("failed to Get %s/%s", objectStoreName, key) + + // Prepare the Transaction + txn, err := 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 +} + +// GetIndex is a generic helper for getting values from the given +// [idb.ObjectStore] using the given [idb.Index]. +func GetIndex(db *idb.Database, objectStoreName string, + indexName string, key js.Value) (js.Value, error) { + parentErr := errors.Errorf("failed to GetIndex %s/%s/%s", + objectStoreName, indexName, key) + + // Prepare the Transaction + txn, err := db.Transaction(idb.TransactionReadOnly, objectStoreName) + if err != nil { + return js.Value{}, errors.WithMessagef(parentErr, + "Unable to create Transaction: %+v", err) + } + store, err := txn.ObjectStore(objectStoreName) + if err != nil { + return js.Value{}, errors.WithMessagef(parentErr, + "Unable to get ObjectStore: %+v", err) + } + idx, err := store.Index(indexName) + if err != nil { + return js.Value{}, errors.WithMessagef(parentErr, + "Unable to get Index: %+v", err) + } + + // Perform the operation + getRequest, err := idx.Get(key) + if err != nil { + return js.Value{}, 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 js.Value{}, 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: %s", + objectStoreName, indexName, key, resultStr) + return resultObj, nil +} + +// Put is a generic helper for putting values into the given [idb.ObjectStore]. +// Equivalent to insert if not exists else update. +func Put(db *idb.Database, objectStoreName string, value js.Value) (*idb.Request, error) { + // Prepare the Transaction + txn, err := db.Transaction(idb.TransactionReadWrite, objectStoreName) + if err != nil { + return nil, errors.Errorf("Unable to create Transaction: %+v", err) + } + store, err := txn.ObjectStore(objectStoreName) + if err != nil { + return nil, errors.Errorf("Unable to get ObjectStore: %+v", err) + } + + // Perform the operation + request, err := store.Put(value) + if err != nil { + return nil, errors.Errorf("Unable to Put: %+v", err) + } + + // Wait for the operation to return + ctx, cancel := NewContext() + err = txn.Await(ctx) + cancel() + if err != nil { + return nil, errors.Errorf("Putting value failed: %+v", err) + } + jww.DEBUG.Printf("Successfully put value in %s: %v", + objectStoreName, value.String()) + return request, nil +} + +// Delete is a generic helper for removing values from the given [idb.ObjectStore]. +func Delete(db *idb.Database, objectStoreName string, key js.Value) error { + parentErr := errors.Errorf("failed to Delete %s/%s", objectStoreName, key) + + // Prepare the Transaction + txn, err := 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 + deleteRequest, err := store.Delete(key) + if err != nil { + return errors.WithMessagef(parentErr, + "Unable to Get from ObjectStore: %+v", err) + } + + // Wait for the operation to return + ctx, cancel := NewContext() + err = deleteRequest.Await(ctx) + cancel() + if err != nil { + return errors.WithMessagef(parentErr, + "Unable to delete from ObjectStore: %+v", err) + } + return nil +} + +// Dump returns the given [idb.ObjectStore] contents to string slice for +// testing and debugging purposes. +func Dump(db *idb.Database, objectStoreName string) ([]string, error) { + parentErr := errors.Errorf("failed to Dump %s", objectStoreName) + + txn, err := 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 +} -- GitLab