From 6bd56b8706b15bb9b44d1ec29f61b92ab2ee4121 Mon Sep 17 00:00:00 2001
From: Jono Wenger <jono@elixxir.io>
Date: Mon, 31 Oct 2022 22:57:30 +0000
Subject: [PATCH] Fix Purge

---
 storage/localStorage.go      | 55 ++++++++++++++++++++++++++++--------
 storage/localStorage_test.go | 33 ++++++++++++++++++----
 storage/purge.go             | 12 ++++----
 utils/utils.go               | 12 +++++---
 4 files changed, 86 insertions(+), 26 deletions(-)

diff --git a/storage/localStorage.go b/storage/localStorage.go
index b0fcf33e..cf7aa2d2 100644
--- a/storage/localStorage.go
+++ b/storage/localStorage.go
@@ -11,6 +11,9 @@ package storage
 
 import (
 	"encoding/base64"
+	"encoding/json"
+	jww "github.com/spf13/jwalterweatherman"
+	"gitlab.com/elixxir/xxdk-wasm/utils"
 	"os"
 	"strings"
 	"syscall/js"
@@ -26,6 +29,11 @@ const localStorageWasmPrefix = "xxdkWasmStorage/"
 type LocalStorage struct {
 	// The Javascript value containing the localStorage object
 	v js.Value
+
+	// The prefix appended to each key name. This is so that all keys created by
+	// this structure can be deleted without affecting other keys in local
+	// storage.
+	prefix string
 }
 
 // jsStorage is the global that stores Javascript as window.localStorage.
@@ -34,11 +42,19 @@ type LocalStorage struct {
 //    https://html.spec.whatwg.org/multipage/webstorage.html#dom-localstorage-dev
 //  - Documentation:
 //    https://developer.mozilla.org/en-US/docs/Web/API/Window/localStorage
-var jsStorage = LocalStorage{js.Global().Get("localStorage")}
+var jsStorage = newLocalStorage(localStorageWasmPrefix)
+
+// newLocalStorage creates a new LocalStorage object with the specified prefix.
+func newLocalStorage(prefix string) *LocalStorage {
+	return &LocalStorage{
+		v:      js.Global().Get("localStorage"),
+		prefix: prefix,
+	}
+}
 
 // GetLocalStorage returns Javascript's local storage.
 func GetLocalStorage() *LocalStorage {
-	return &jsStorage
+	return jsStorage
 }
 
 // GetItem returns a key's value from the local storage given its name. Returns
@@ -50,7 +66,7 @@ func GetLocalStorage() *LocalStorage {
 //  - Documentation:
 //    https://developer.mozilla.org/en-US/docs/Web/API/Storage/getItem
 func (ls *LocalStorage) GetItem(keyName string) ([]byte, error) {
-	keyValue := ls.getItem(localStorageWasmPrefix + keyName)
+	keyValue := ls.getItem(ls.prefix + keyName)
 	if keyValue.IsNull() {
 		return nil, os.ErrNotExist
 	}
@@ -72,7 +88,7 @@ func (ls *LocalStorage) GetItem(keyName string) ([]byte, error) {
 //    https://developer.mozilla.org/en-US/docs/Web/API/Storage/setItem
 func (ls *LocalStorage) SetItem(keyName string, keyValue []byte) {
 	encodedKeyValue := base64.StdEncoding.EncodeToString(keyValue)
-	ls.setItem(localStorageWasmPrefix+keyName, encodedKeyValue)
+	ls.setItem(ls.prefix+keyName, encodedKeyValue)
 }
 
 // RemoveItem removes a key's value from local storage given its name. If there
@@ -84,7 +100,7 @@ func (ls *LocalStorage) SetItem(keyName string, keyValue []byte) {
 //  - Documentation:
 //    https://developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
 func (ls *LocalStorage) RemoveItem(keyName string) {
-	ls.removeItem(localStorageWasmPrefix + keyName)
+	ls.removeItem(ls.prefix + keyName)
 }
 
 // Clear clears all the keys in storage. Underneath, it calls
@@ -101,14 +117,14 @@ func (ls *LocalStorage) Clear() {
 // ClearPrefix clears all keys with the given prefix.
 func (ls *LocalStorage) ClearPrefix(prefix string) {
 	// Get a copy of all key names at once
-	keys := js.Global().Get("Object").Call("keys", ls.v)
+	keys := ls.keys()
 
 	// Loop through each key
 	for i := 0; i < keys.Length(); i++ {
 		if v := keys.Index(i); !v.IsNull() {
-			keyName := strings.TrimPrefix(v.String(), localStorageWasmPrefix)
+			keyName := strings.TrimPrefix(v.String(), ls.prefix)
 			if strings.HasPrefix(keyName, prefix) {
-				ls.RemoveItem(keyName)
+				ls.removeItem(v.String())
 			}
 		}
 	}
@@ -117,14 +133,14 @@ func (ls *LocalStorage) ClearPrefix(prefix string) {
 // ClearWASM clears all the keys in storage created by WASM.
 func (ls *LocalStorage) ClearWASM() {
 	// Get a copy of all key names at once
-	keys := js.Global().Get("Object").Call("keys", ls.v)
+	keys := ls.keys()
 
 	// Loop through each key
 	for i := 0; i < keys.Length(); i++ {
 		if v := keys.Index(i); !v.IsNull() {
 			keyName := v.String()
-			if strings.HasPrefix(keyName, localStorageWasmPrefix) {
-				ls.RemoveItem(strings.TrimPrefix(keyName, localStorageWasmPrefix))
+			if strings.HasPrefix(keyName, ls.prefix) {
+				ls.RemoveItem(strings.TrimPrefix(keyName, ls.prefix))
 			}
 		}
 	}
@@ -145,7 +161,21 @@ func (ls *LocalStorage) Key(n int) (string, error) {
 		return "", os.ErrNotExist
 	}
 
-	return strings.TrimPrefix(keyName.String(), localStorageWasmPrefix), nil
+	return strings.TrimPrefix(keyName.String(), ls.prefix), nil
+}
+
+// Keys returns a list of all key names in local storage.
+func (ls *LocalStorage) Keys() []string {
+	keyNamesJson := utils.JSON.Call("stringify", ls.keys())
+
+	var keyNames []string
+	err := json.Unmarshal([]byte(keyNamesJson.String()), &keyNames)
+	if err != nil {
+		jww.FATAL.Panicf(
+			"Failed to JSON unmarshal localStorage key name list: %+v", err)
+	}
+
+	return keyNames
 }
 
 // Length returns the number of keys in localStorage. Underneath, it accesses
@@ -166,3 +196,4 @@ func (ls *LocalStorage) removeItem(keyName string)        { ls.v.Call("removeIte
 func (ls *LocalStorage) clear()                           { ls.v.Call("clear") }
 func (ls *LocalStorage) key(n int) js.Value               { return ls.v.Call("key", n) }
 func (ls *LocalStorage) length() js.Value                 { return ls.v.Get("length") }
+func (ls *LocalStorage) keys() js.Value                   { return utils.Object.Call("keys", ls.v) }
diff --git a/storage/localStorage_test.go b/storage/localStorage_test.go
index 6e96814e..5c347308 100644
--- a/storage/localStorage_test.go
+++ b/storage/localStorage_test.go
@@ -88,7 +88,8 @@ func TestLocalStorage_Clear(t *testing.T) {
 // Tests that LocalStorage.ClearPrefix deletes only the keys with the given
 // prefix.
 func TestLocalStorage_ClearPrefix(t *testing.T) {
-	jsStorage.clear()
+	s := newLocalStorage("")
+	s.clear()
 	prng := rand.New(rand.NewSource(11))
 	var yesPrefix, noPrefix []string
 	prefix := "keyNamePrefix/"
@@ -102,18 +103,18 @@ func TestLocalStorage_ClearPrefix(t *testing.T) {
 			noPrefix = append(noPrefix, keyName)
 		}
 
-		jsStorage.SetItem(keyName, []byte(strconv.Itoa(i)))
+		s.SetItem(keyName, []byte(strconv.Itoa(i)))
 	}
 
-	jsStorage.ClearPrefix(prefix)
+	s.ClearPrefix(prefix)
 
 	for _, keyName := range noPrefix {
-		if _, err := jsStorage.GetItem(keyName); err != nil {
+		if _, err := s.GetItem(keyName); err != nil {
 			t.Errorf("Could not get keyName %q: %+v", keyName, err)
 		}
 	}
 	for _, keyName := range yesPrefix {
-		keyValue, err := jsStorage.GetItem(keyName)
+		keyValue, err := s.GetItem(keyName)
 		if err == nil || !errors.Is(err, os.ErrNotExist) {
 			t.Errorf("Found keyName %q: %q", keyName, keyValue)
 		}
@@ -235,3 +236,25 @@ func TestLocalStorage_Length(t *testing.T) {
 		}
 	}
 }
+
+// Tests that LocalStorage.Keys return a list that contains all the added keys.
+func TestLocalStorage_Keys(t *testing.T) {
+	s := newLocalStorage("")
+	s.clear()
+	values := map[string][]byte{
+		"key1": []byte("key value"),
+		"key2": {0, 1, 2, 3, 4, 5, 6, 7, 8, 9},
+		"key3": {0, 49, 0, 0, 0, 38, 249, 93},
+	}
+
+	for keyName, keyValue := range values {
+		s.SetItem(keyName, keyValue)
+	}
+
+	keys := s.Keys()
+	for i, keyName := range keys {
+		if _, exists := values[keyName]; !exists {
+			t.Errorf("Key %q does not exist (%d).", keyName, i)
+		}
+	}
+}
diff --git a/storage/purge.go b/storage/purge.go
index b0d8fc88..3aa1d47e 100644
--- a/storage/purge.go
+++ b/storage/purge.go
@@ -53,6 +53,8 @@ func DecrementNumClientsRunning() {
 func Purge(_ js.Value, args []js.Value) interface{} {
 	storageDirectory := args[0].String()
 	userPassword := args[1].String()
+	// Clear all EKV from local storage
+	GetLocalStorage().ClearPrefix("speakeasyapp")
 
 	// Check the password
 	if !verifyPassword(userPassword) {
@@ -61,11 +63,11 @@ func Purge(_ js.Value, args []js.Value) interface{} {
 	}
 
 	// Verify all Cmix followers are stopped
-	if n := atomic.LoadUint64(&numClientsRunning); n != 0 {
-		utils.Throw(utils.TypeError, errors.Errorf(
-			"%d cMix followers running; all need to be stopped", n))
-		return nil
-	}
+	// if n := atomic.LoadUint64(&numClientsRunning); n != 0 {
+	// 	utils.Throw(utils.TypeError, errors.Errorf(
+	// 		"%d cMix followers running; all need to be stopped", n))
+	// 	return nil
+	// }
 
 	// Get all indexedDb database names
 	databaseList, err := GetIndexedDbList()
diff --git a/utils/utils.go b/utils/utils.go
index 07b9782d..c2ee6b41 100644
--- a/utils/utils.go
+++ b/utils/utils.go
@@ -16,19 +16,23 @@ import (
 )
 
 var (
-	// Error is the Javascript Error object. It used to create new Javascript
+	// Error is the Javascript Error type. It used to create new Javascript
 	// errors.
 	Error = js.Global().Get("Error")
 
-	// JSON is the Javascript JSON object. It is used to perform JSON operations
+	// JSON is the Javascript JSON type. It is used to perform JSON operations
 	// on the Javascript layer.
 	JSON = js.Global().Get("JSON")
 
-	// Promise is the Javascript Promise object. It is used to generate new
+	// Object is the Javascript Object type. It is used to perform Object
+	// operations on the Javascript layer.
+	Object = js.Global().Get("Object")
+
+	// Promise is the Javascript Promise type. It is used to generate new
 	// promises.
 	Promise = js.Global().Get("Promise")
 
-	// Uint8Array is the Javascript Uint8Array object. It is used to create new
+	// Uint8Array is the Javascript Uint8Array type. It is used to create new
 	// Uint8Array.
 	Uint8Array = js.Global().Get("Uint8Array")
 )
-- 
GitLab