Skip to content
Snippets Groups Projects
Commit 1e6c0b38 authored by Jake Taylor's avatar Jake Taylor
Browse files

refactor indexedDb to be more receptive to upcoming project changes

parent 86e3f7d2
No related branches found
No related tags found
2 merge requests!60Revert "Fail a test to be sure it works",!41refactor indexedDb to be more receptive to upcoming project changes
......@@ -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
}
......@@ -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)
}
......
......@@ -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
}
......
......@@ -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"`
}
......
////////////////////////////////////////////////////////////////////////////////
// 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
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment