Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found
Select Git revision
  • 11-22-implement-kv-interface-defined-in-collectiveversionedkvgo
  • @XX-4682/Files
  • Anne/CI-hash
  • Jakub/Emoji-CI-Test
  • NewHostPool
  • XX-4324/CleanWorkingTree
  • XX-4441
  • XX-4461/FileUpload
  • XX-4505/blockuser
  • XX-4602/SilentMessageType
  • XX-4688/DbEncoding
  • dev
  • fast-registration
  • fastReg
  • hotfix/XX-4655
  • hotfix/filetransfercallback
  • hotfix/leave_channel_delete
  • hotfix/update
  • jono/npmTest
  • master
  • project/HavenNotifications
  • release
  • testRSAVerify
  • testing/websockets
  • wasmTest2
  • xx-4287/encryption-helper
  • 06c73fe9e17a189ba0f81ff128120fc197c95e86fc0b45587b297d4ebac3beab
  • 15591a484b25c0f3181fdbbd76c1aaaf51c22ecffb027d8e58206d6b1f3a08f7
  • 1803273e8f7ee5c40abee417ed434f9f46bf3c4cdd16c6766382c27414fe6735
  • 22a695a0cead5f69122bd33d7ceae886844af81158a5146e899a20cfe12cf809
  • 291e9eb4ce787bf7af9c17896d2d89fafe197bee2b93b4fb622fc82392fc01d6
  • 2d58192c15c8d0b0c8d7fd452ccb4e8fb43da8336b1bdb1a996476585dcbe963
  • 3008b96ec6ea621869ab676586f39ee7592c8d42f7298d14d1b5f946f2825591
  • 31e4db3a893e1711ce6bfee845bbaa42b250dafd52a0761f97dde9fa620a88b9
  • 50eb5a589c7c1de0c2e8c83f804c63bf650310ce261bc42c0ab982a0013e0fbf
  • 575cb1e68e945ab74386885c347b60fc3e231367d708675ab010b06d30947bc0
  • 5d94c4042f39cd7284e8f7fb3e2c00900e22ae3b09b77fbb51da1f4f3de41ebc
  • 5fb6b664a40fbb4bb89e08622bb751a355f1f06ae1d1195bda6536808a12ff85
  • 68c89d4a78c1bc895d4ef4db81643a8441298af2175b67838f4a94ab8b753354
  • 75e66ec56f60c804e8f1b3b896697f406953ad7a1247e1e58095cf7c8e38f200
  • 76ba08e2dfa1798412a265404fa271840b52c035869111fce8e8cdb23a036a5a
  • 812b395df518ce096d01d5292596ca26f8fe92d9c4487ddfa515e190a51aa1a1
  • 8718f5b8341cccb99566fb91294d0af31bdbc9cc59cae5f766c32f32234f7c5b
  • 9048c22145ec8770e25c21afb4405368318261457b765a40f5ba5b3038fee353
  • a69a79e8517ae4196c5ddbbf71337460bf5b88464cfe81832152442b3f043937
  • a8bdea48f8fea1cc9e4b88cc20a19b8bb8914bf1d95c20150b6115d1572c1b70
  • bc0bb01e44383b4aae9af3cb4080a4629b39d4609d2105b6d07f6a6f12c5e902
  • d2e1a04c48dc2b4b878a2f72d15d93a21e640ae89b01d2e20c6520d27528e299
  • e73ddf4cf622d5b7747e50e6cb82966e83a0323ac49f214f30587c195ce9a69d
  • eff7aa8ca82523b901fec6a73d8dac9e995481623500fa421a8aa781320a13dd
  • f5069958dd13368620c00c8438d0d9583db34ebdd6224b876d50df6e43e5db45
  • v0.1.1
  • v0.1.10
  • v0.1.11
  • v0.1.12
  • v0.1.13
  • v0.1.2
  • v0.1.3
  • v0.1.5
  • v0.1.6
  • v0.1.8
  • v0.3.0
  • v0.3.10
  • v0.3.11
  • v0.3.12
  • v0.3.13
  • v0.3.14
  • v0.3.15
  • v0.3.16
  • v0.3.17
  • v0.3.18
  • v0.3.20
  • v0.3.21
  • v0.3.22
  • v0.3.3
  • v0.3.4
  • v0.3.5
  • v0.3.6
  • v0.3.7
  • v0.3.8
  • v0.3.9
81 results

Target

Select target project
  • elixxir/xxdk-wasm
1 result
Select Git revision
  • 11-22-implement-kv-interface-defined-in-collectiveversionedkvgo
  • @XX-4682/Files
  • Anne/CI-hash
  • Jakub/Emoji-CI-Test
  • NewHostPool
  • XX-4324/CleanWorkingTree
  • XX-4441
  • XX-4461/FileUpload
  • XX-4505/blockuser
  • XX-4602/SilentMessageType
  • XX-4688/DbEncoding
  • dev
  • fast-registration
  • fastReg
  • hotfix/XX-4655
  • hotfix/filetransfercallback
  • hotfix/leave_channel_delete
  • hotfix/update
  • jono/npmTest
  • master
  • project/HavenNotifications
  • release
  • testRSAVerify
  • testing/websockets
  • wasmTest2
  • xx-4287/encryption-helper
  • 06c73fe9e17a189ba0f81ff128120fc197c95e86fc0b45587b297d4ebac3beab
  • 15591a484b25c0f3181fdbbd76c1aaaf51c22ecffb027d8e58206d6b1f3a08f7
  • 1803273e8f7ee5c40abee417ed434f9f46bf3c4cdd16c6766382c27414fe6735
  • 22a695a0cead5f69122bd33d7ceae886844af81158a5146e899a20cfe12cf809
  • 291e9eb4ce787bf7af9c17896d2d89fafe197bee2b93b4fb622fc82392fc01d6
  • 2d58192c15c8d0b0c8d7fd452ccb4e8fb43da8336b1bdb1a996476585dcbe963
  • 3008b96ec6ea621869ab676586f39ee7592c8d42f7298d14d1b5f946f2825591
  • 31e4db3a893e1711ce6bfee845bbaa42b250dafd52a0761f97dde9fa620a88b9
  • 50eb5a589c7c1de0c2e8c83f804c63bf650310ce261bc42c0ab982a0013e0fbf
  • 575cb1e68e945ab74386885c347b60fc3e231367d708675ab010b06d30947bc0
  • 5d94c4042f39cd7284e8f7fb3e2c00900e22ae3b09b77fbb51da1f4f3de41ebc
  • 5fb6b664a40fbb4bb89e08622bb751a355f1f06ae1d1195bda6536808a12ff85
  • 68c89d4a78c1bc895d4ef4db81643a8441298af2175b67838f4a94ab8b753354
  • 75e66ec56f60c804e8f1b3b896697f406953ad7a1247e1e58095cf7c8e38f200
  • 76ba08e2dfa1798412a265404fa271840b52c035869111fce8e8cdb23a036a5a
  • 812b395df518ce096d01d5292596ca26f8fe92d9c4487ddfa515e190a51aa1a1
  • 8718f5b8341cccb99566fb91294d0af31bdbc9cc59cae5f766c32f32234f7c5b
  • 9048c22145ec8770e25c21afb4405368318261457b765a40f5ba5b3038fee353
  • a69a79e8517ae4196c5ddbbf71337460bf5b88464cfe81832152442b3f043937
  • a8bdea48f8fea1cc9e4b88cc20a19b8bb8914bf1d95c20150b6115d1572c1b70
  • bc0bb01e44383b4aae9af3cb4080a4629b39d4609d2105b6d07f6a6f12c5e902
  • d2e1a04c48dc2b4b878a2f72d15d93a21e640ae89b01d2e20c6520d27528e299
  • e73ddf4cf622d5b7747e50e6cb82966e83a0323ac49f214f30587c195ce9a69d
  • eff7aa8ca82523b901fec6a73d8dac9e995481623500fa421a8aa781320a13dd
  • f5069958dd13368620c00c8438d0d9583db34ebdd6224b876d50df6e43e5db45
  • v0.1.1
  • v0.1.10
  • v0.1.11
  • v0.1.12
  • v0.1.13
  • v0.1.2
  • v0.1.3
  • v0.1.5
  • v0.1.6
  • v0.1.8
  • v0.3.0
  • v0.3.10
  • v0.3.11
  • v0.3.12
  • v0.3.13
  • v0.3.14
  • v0.3.15
  • v0.3.16
  • v0.3.17
  • v0.3.18
  • v0.3.20
  • v0.3.21
  • v0.3.22
  • v0.3.3
  • v0.3.4
  • v0.3.5
  • v0.3.6
  • v0.3.7
  • v0.3.8
  • v0.3.9
81 results
Show changes
Showing
with 301 additions and 1279 deletions
......@@ -13,25 +13,21 @@ import (
"encoding/binary"
"encoding/json"
"fmt"
"os"
"syscall/js"
"github.com/armon/circbuf"
"github.com/pkg/errors"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/xxdk-wasm/logging"
"gitlab.com/elixxir/xxdk-wasm/worker"
"syscall/js"
)
// SEMVER is the current semantic version of the xxDK Logger web worker.
const SEMVER = "0.1.0"
func init() {
// Set up Javascript console listener set at level INFO
ll := logging.NewJsConsoleLogListener(jww.LevelDebug)
logging.AddLogListener(ll.Listen)
jww.SetStdoutThreshold(jww.LevelFatal + 1)
jww.INFO.Printf("xxDK Logger web worker version: v%s", SEMVER)
}
// workerLogFile manages communication with the main thread and writing incoming
// logging messages to the log file.
type workerLogFile struct {
......@@ -40,17 +36,60 @@ type workerLogFile struct {
}
func main() {
jww.INFO.Print("[LOG] Starting xxDK WebAssembly Logger Worker.")
// Set to os.Args because the default is os.Args[1:] and in WASM, args start
// at 0, not 1.
LoggerCmd.SetArgs(os.Args)
js.Global().Set("LogLevel", js.FuncOf(logging.LogLevelJS))
err := LoggerCmd.Execute()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
var LoggerCmd = &cobra.Command{
Use: "Logger",
Short: "Web worker buffer file logger",
Example: "const go = new Go();\ngo.argv = [\"--logLevel=1\"]",
Run: func(cmd *cobra.Command, args []string) {
// Start logger first to capture all logging events
err := logging.EnableLogging(logLevel, -1, 0, "", "")
if err != nil {
fmt.Printf(
"Failed to intialize logging in logging worker: %+v", err)
os.Exit(1)
}
jww.INFO.Printf("xxDK Logger web worker version: v%s", SEMVER)
jww.INFO.Print("[LOG] Starting xxDK WebAssembly Logger Worker.")
wlf := workerLogFile{wtm: worker.NewThreadManager("Logger", false)}
wlf.registerCallbacks()
wlf.wtm.SignalReady()
// Indicate to the Javascript caller that the WASM is ready by resolving
// a promise created by the caller.
js.Global().Get("onWasmInitialized").Invoke()
<-make(chan bool)
fmt.Println("[WW] Closing xxDK WebAssembly Log Worker.")
os.Exit(0)
},
}
var (
logLevel jww.Threshold
)
func init() {
// Initialize all startup flags
LoggerCmd.Flags().IntVarP((*int)(&logLevel), "logLevel", "l", 2,
"Sets the log level output when outputting to the Javascript console. "+
"0 = TRACE, 1 = DEBUG, 2 = INFO, 3 = WARN, 4 = ERROR, "+
"5 = CRITICAL, 6 = FATAL, -1 = disabled.")
}
// registerCallbacks registers all the necessary callbacks for the main thread
......
......@@ -10,39 +10,77 @@
package main
import (
"gitlab.com/elixxir/xxdk-wasm/logging"
"fmt"
"os"
"syscall/js"
"github.com/spf13/cobra"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/wasm-utils/utils"
"gitlab.com/elixxir/xxdk-wasm/logging"
"gitlab.com/elixxir/xxdk-wasm/storage"
"gitlab.com/elixxir/xxdk-wasm/utils"
"gitlab.com/elixxir/xxdk-wasm/wasm"
)
func init() {
// Start logger first to capture all logging events
logging.InitLogger()
func main() {
// Set to os.Args because the default is os.Args[1:] and in WASM, args start
// at 0, not 1.
wasmCmd.SetArgs(os.Args)
// Overwrites setting the log level to INFO done in bindings so that the
// Javascript console can be used
ll := logging.NewJsConsoleLogListener(jww.LevelInfo)
logging.AddLogListener(ll.Listen)
jww.SetStdoutThreshold(jww.LevelFatal + 1)
err := wasmCmd.Execute()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
}
var wasmCmd = &cobra.Command{
Use: "xxdk-wasm",
Short: "WebAssembly bindings for xxDK.",
Example: "const go = new Go();\ngo.argv = [\"--logLevel=1\"]",
Run: func(cmd *cobra.Command, args []string) {
// Start logger first to capture all logging events
err := logging.EnableLogging(logLevel, fileLogLevel, maxLogFileSizeMB,
workerScriptURL, workerName)
if err != nil {
fmt.Printf("Failed to intialize logging: %+v", err)
os.Exit(1)
}
// Check that the WASM binary version is correct
err := storage.CheckAndStoreVersions()
err = storage.CheckAndStoreVersions()
if err != nil {
jww.FATAL.Panicf("WASM binary version error: %+v", err)
}
// Enable all top level bindings functions
setGlobals()
// Indicate to the Javascript caller that the WASM is ready by resolving
// a promise created by the caller, as shown below:
//
// let isReady = new Promise((resolve) => {
// window.onWasmInitialized = resolve;
// });
//
// const go = new Go();
// go.run(result.instance);
// await isReady;
//
// Source: https://github.com/golang/go/issues/49710#issuecomment-986484758
js.Global().Get("onWasmInitialized").Invoke()
<-make(chan bool)
os.Exit(0)
},
}
func main() {
// setGlobals enables all global functions to be accessible to Javascript.
func setGlobals() {
jww.INFO.Printf("Starting xxDK WebAssembly bindings.")
// logging/worker.go
js.Global().Set("GetLogger", js.FuncOf(logging.GetLoggerJS))
// storage/password.go
js.Global().Set("GetOrInitPassword", js.FuncOf(storage.GetOrInitPassword))
js.Global().Set("ChangeExternalPassword",
......@@ -62,6 +100,11 @@ func main() {
js.Global().Set("InitializeBackup", js.FuncOf(wasm.InitializeBackup))
js.Global().Set("ResumeBackup", js.FuncOf(wasm.ResumeBackup))
// wasm/notifications.go
js.Global().Set("LoadNotifications", js.FuncOf(wasm.LoadNotifications))
js.Global().Set("LoadNotificationsDummy",
js.FuncOf(wasm.LoadNotificationsDummy))
// wasm/channels.go
js.Global().Set("GenerateChannelIdentity",
js.FuncOf(wasm.GenerateChannelIdentity))
......@@ -89,10 +132,14 @@ func main() {
js.Global().Set("GetShareUrlType", js.FuncOf(wasm.GetShareUrlType))
js.Global().Set("ValidForever", js.FuncOf(wasm.ValidForever))
js.Global().Set("IsNicknameValid", js.FuncOf(wasm.IsNicknameValid))
js.Global().Set("GetNotificationReportsForMe",
js.FuncOf(wasm.GetNotificationReportsForMe))
js.Global().Set("GetNoMessageErr", js.FuncOf(wasm.GetNoMessageErr))
js.Global().Set("CheckNoMessageErr", js.FuncOf(wasm.CheckNoMessageErr))
js.Global().Set("NewChannelsDatabaseCipher",
js.FuncOf(wasm.NewChannelsDatabaseCipher))
js.Global().Set("GetNotificationReportsForMe",
js.FuncOf(wasm.GetNotificationReportsForMe))
// wasm/dm.go
js.Global().Set("InitChannelsFileTransfer",
......@@ -110,6 +157,8 @@ func main() {
// wasm/cmix.go
js.Global().Set("NewCmix", js.FuncOf(wasm.NewCmix))
js.Global().Set("LoadCmix", js.FuncOf(wasm.LoadCmix))
js.Global().Set("LoadSynchronizedCmix",
js.FuncOf(wasm.LoadSynchronizedCmix))
// wasm/delivery.go
js.Global().Set("SetDashboardURL", js.FuncOf(wasm.SetDashboardURL))
......@@ -157,7 +206,6 @@ func main() {
js.FuncOf(wasm.GetFactsFromContact))
// wasm/logging.go
js.Global().Set("LogLevel", js.FuncOf(wasm.LogLevel))
js.Global().Set("RegisterLogWriter", js.FuncOf(wasm.RegisterLogWriter))
js.Global().Set("EnableGrpcLogs", js.FuncOf(wasm.EnableGrpcLogs))
......@@ -194,6 +242,8 @@ func main() {
js.Global().Set("TransmitSingleUse", js.FuncOf(wasm.TransmitSingleUse))
js.Global().Set("Listen", js.FuncOf(wasm.Listen))
// wasm/sync.go
// wasm/timeNow.go
js.Global().Set("SetTimeSource", js.FuncOf(wasm.SetTimeSource))
js.Global().Set("SetOffset", js.FuncOf(wasm.SetOffset))
......@@ -212,7 +262,32 @@ func main() {
js.Global().Set("GetClientDependencies", js.FuncOf(wasm.GetClientDependencies))
js.Global().Set("GetWasmSemanticVersion", js.FuncOf(wasm.GetWasmSemanticVersion))
js.Global().Set("GetXXDKSemanticVersion", js.FuncOf(wasm.GetXXDKSemanticVersion))
}
<-make(chan bool)
os.Exit(0)
var (
logLevel, fileLogLevel jww.Threshold
maxLogFileSizeMB int
workerScriptURL, workerName string
)
func init() {
// Initialize all startup flags
wasmCmd.Flags().IntVarP((*int)(&logLevel), "logLevel", "l", 2,
"Sets the log level output when outputting to the Javascript console. "+
"0 = TRACE, 1 = DEBUG, 2 = INFO, 3 = WARN, 4 = ERROR, "+
"5 = CRITICAL, 6 = FATAL, -1 = disabled.")
wasmCmd.Flags().IntVarP((*int)(&fileLogLevel), "fileLogLevel", "m", -1,
"The log level when outputting to the file buffer. "+
"0 = TRACE, 1 = DEBUG, 2 = INFO, 3 = WARN, 4 = ERROR, "+
"5 = CRITICAL, 6 = FATAL, -1 = disabled.")
wasmCmd.Flags().IntVarP(&maxLogFileSizeMB, "maxLogFileSize", "s", 5,
"Max file size, in MB, for the file buffer before it rolls over "+
"and starts overwriting the oldest entries.")
wasmCmd.Flags().StringVarP(&workerScriptURL, "workerScriptURL", "w", "",
"URL to the script that executes the worker. If set, it enables the "+
"saving of log file to buffer in Worker instead of in the local "+
"thread. This allows logging to be available after the main WASM "+
"thread crashes.")
wasmCmd.Flags().StringVar(&workerName, "workerName", "xxdkLogFileWorker",
"Name of the logger worker.")
}
......@@ -12,6 +12,8 @@ package storage
import (
"github.com/pkg/errors"
"os"
"gitlab.com/elixxir/wasm-utils/storage"
)
// Key to store if the database is encrypted or not
......@@ -22,12 +24,15 @@ const databaseEncryptionToggleKey = "xxdkWasmDatabaseEncryptionToggle/"
func StoreIndexedDbEncryptionStatus(
databaseName string, encryptionStatus bool) (
loadedEncryptionStatus bool, err error) {
data, err := GetLocalStorage().GetItem(
databaseEncryptionToggleKey + databaseName)
ls := storage.GetLocalStorage()
data, err := ls.Get(databaseEncryptionToggleKey + databaseName)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
GetLocalStorage().SetItem(
databaseEncryptionToggleKey+databaseName, []byte{1})
keyName := databaseEncryptionToggleKey + databaseName
if err = ls.Set(keyName, []byte{1}); err != nil {
return false,
errors.Wrapf(err, "localStorage: failed to set %q", keyName)
}
return encryptionStatus, nil
} else {
return false, err
......
......@@ -11,8 +11,11 @@ package storage
import (
"encoding/json"
"github.com/pkg/errors"
"os"
"github.com/pkg/errors"
"gitlab.com/elixxir/wasm-utils/storage"
)
const indexedDbListKey = "xxDkWasmIndexedDbList"
......@@ -20,7 +23,7 @@ const indexedDbListKey = "xxDkWasmIndexedDbList"
// GetIndexedDbList returns the list of stored indexedDb databases.
func GetIndexedDbList() (map[string]struct{}, error) {
list := make(map[string]struct{})
listBytes, err := GetLocalStorage().GetItem(indexedDbListKey)
listBytes, err := storage.GetLocalStorage().Get(indexedDbListKey)
if err != nil && !errors.Is(err, os.ErrNotExist) {
return nil, err
} else if err == nil {
......@@ -47,7 +50,11 @@ func StoreIndexedDb(databaseName string) error {
return err
}
GetLocalStorage().SetItem(indexedDbListKey, listBytes)
err = storage.GetLocalStorage().Set(indexedDbListKey, listBytes)
if err != nil {
return errors.Wrapf(err,
"localStorage: failed to set %q", indexedDbListKey)
}
return nil
}
////////////////////////////////////////////////////////////////////////////////
// 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 storage
import (
"encoding/base64"
"encoding/json"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/xxdk-wasm/utils"
"os"
"strings"
"syscall/js"
)
// localStorageWasmPrefix is prefixed to every keyName saved to local storage by
// LocalStorage. It allows the identifications and deletion of keys only created
// by this WASM binary while ignoring keys made by other scripts on the same
// page.
const localStorageWasmPrefix = "xxdkWasmStorage/"
// LocalStorage contains the js.Value representation of localStorage.
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.
//
// - Specification:
// 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 = 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
}
// GetItem returns a key's value from the local storage given its name. Returns
// os.ErrNotExist if the key does not exist. Underneath, it calls
// localStorage.GetItem().
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-getitem-dev
// - Documentation:
// https://developer.mozilla.org/en-US/docs/Web/API/Storage/getItem
func (ls *LocalStorage) GetItem(keyName string) ([]byte, error) {
keyValue := ls.getItem(ls.prefix + keyName)
if keyValue.IsNull() {
return nil, os.ErrNotExist
}
decodedKeyValue, err := base64.StdEncoding.DecodeString(keyValue.String())
if err != nil {
return nil, err
}
return decodedKeyValue, nil
}
// SetItem adds a key's value to local storage given its name. Underneath, it
// calls localStorage.SetItem().
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-setitem-dev
// - Documentation:
// 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(ls.prefix+keyName, encodedKeyValue)
}
// RemoveItem removes a key's value from local storage given its name. If there
// is no item with the given key, this function does nothing. Underneath, it
// calls localStorage.RemoveItem().
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-removeitem-dev
// - Documentation:
// https://developer.mozilla.org/en-US/docs/Web/API/Storage/removeItem
func (ls *LocalStorage) RemoveItem(keyName string) {
ls.removeItem(ls.prefix + keyName)
}
// Clear clears all the keys in storage. Underneath, it calls
// localStorage.clear().
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-clear-dev
// - Documentation:
// https://developer.mozilla.org/en-US/docs/Web/API/Storage/clear
func (ls *LocalStorage) Clear() {
ls.clear()
}
// ClearPrefix clears all keys with the given prefix. Returns the number of
// keys cleared.
func (ls *LocalStorage) ClearPrefix(prefix string) int {
// Get a copy of all key names at once
keys := ls.keys()
// Loop through each key
var n int
for i := 0; i < keys.Length(); i++ {
if v := keys.Index(i); !v.IsNull() {
keyName := strings.TrimPrefix(v.String(), ls.prefix)
if strings.HasPrefix(keyName, prefix) {
ls.removeItem(v.String())
n++
}
}
}
return n
}
// ClearWASM clears all the keys in storage created by WASM. Returns the number
// of keys cleared.
func (ls *LocalStorage) ClearWASM() int {
// Get a copy of all key names at once
keys := ls.keys()
// Loop through each key
var n int
for i := 0; i < keys.Length(); i++ {
if v := keys.Index(i); !v.IsNull() {
keyName := v.String()
if strings.HasPrefix(keyName, ls.prefix) {
ls.RemoveItem(strings.TrimPrefix(keyName, ls.prefix))
n++
}
}
}
return n
}
// Key returns the name of the nth key in localStorage. Return os.ErrNotExist if
// the key does not exist. The order of keys is not defined. If there is no item
// with the given key, this function does nothing. Underneath, it calls
// localStorage.key().
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-key-dev
// - Documentation:
// https://developer.mozilla.org/en-US/docs/Web/API/Storage/key
func (ls *LocalStorage) Key(n int) (string, error) {
keyName := ls.key(n)
if keyName.IsNull() {
return "", os.ErrNotExist
}
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
// the property localStorage.length.
//
// - Specification:
// https://html.spec.whatwg.org/multipage/webstorage.html#dom-storage-key-dev
// - Documentation:
// https://developer.mozilla.org/en-US/docs/Web/API/Storage/length
func (ls *LocalStorage) Length() int {
return ls.length().Int()
}
// Wrappers for Javascript Storage methods and properties.
func (ls *LocalStorage) getItem(keyName string) js.Value { return ls.v.Call("getItem", keyName) }
func (ls *LocalStorage) setItem(keyName, keyValue string) { ls.v.Call("setItem", keyName, keyValue) }
func (ls *LocalStorage) removeItem(keyName string) { ls.v.Call("removeItem", keyName) }
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) }
////////////////////////////////////////////////////////////////////////////////
// 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 storage
import (
"bytes"
"github.com/pkg/errors"
"math/rand"
"os"
"strconv"
"testing"
)
// Tests that a value set with LocalStorage.SetItem and retrieved with
// LocalStorage.GetItem matches the original.
func TestLocalStorage_GetItem_SetItem(t *testing.T) {
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, 242, 189, 222, 32, 138, 248, 121,
151, 42, 108, 82, 199, 163, 61, 4, 200, 140, 231, 225, 20, 35, 243,
253, 161, 61, 2, 227, 208, 173, 183, 33, 66, 236, 107, 105, 119, 26,
42, 44, 60, 109, 172, 38, 47, 220, 17, 129, 4, 234, 241, 141, 81,
84, 185, 32, 120, 115, 151, 128, 196, 143, 117, 222, 78, 44, 115,
109, 20, 249, 46, 158, 139, 231, 157, 54, 219, 141, 252},
}
for keyName, keyValue := range values {
jsStorage.SetItem(keyName, keyValue)
loadedValue, err := jsStorage.GetItem(keyName)
if err != nil {
t.Errorf("Failed to load %q: %+v", keyName, err)
}
if !bytes.Equal(keyValue, loadedValue) {
t.Errorf("Loaded value does not match original for %q"+
"\nexpected: %q\nreceived: %q", keyName, keyValue, loadedValue)
}
}
}
// Tests that LocalStorage.GetItem returns the error os.ErrNotExist when the key
// does not exist in storage.
func TestLocalStorage_GetItem_NotExistError(t *testing.T) {
_, err := jsStorage.GetItem("someKey")
if err == nil || !errors.Is(err, os.ErrNotExist) {
t.Errorf("Incorrect error for non existant key."+
"\nexpected: %v\nreceived: %v", os.ErrNotExist, err)
}
}
// Tests that LocalStorage.RemoveItem deletes a key from store and that it
// cannot be retrieved.
func TestLocalStorage_RemoveItem(t *testing.T) {
keyName := "key"
jsStorage.SetItem(keyName, []byte("value"))
jsStorage.RemoveItem(keyName)
_, err := jsStorage.GetItem(keyName)
if err == nil || !errors.Is(err, os.ErrNotExist) {
t.Errorf("Failed to remove %q: %+v", keyName, err)
}
}
// Tests that LocalStorage.Clear deletes all keys from storage.
func TestLocalStorage_Clear(t *testing.T) {
for i := 0; i < 10; i++ {
jsStorage.SetItem(strconv.Itoa(i), []byte(strconv.Itoa(i)))
}
jsStorage.Clear()
l := jsStorage.Length()
if l > 0 {
t.Errorf("Clear did not delete all keys. Found %d keys.", l)
}
}
// Tests that LocalStorage.ClearPrefix deletes only the keys with the given
// prefix.
func TestLocalStorage_ClearPrefix(t *testing.T) {
s := newLocalStorage("")
s.clear()
prng := rand.New(rand.NewSource(11))
const numKeys = 10
var yesPrefix, noPrefix []string
prefix := "keyNamePrefix/"
for i := 0; i < numKeys; i++ {
keyName := "keyNum" + strconv.Itoa(i)
if prng.Intn(2) == 0 {
keyName = prefix + keyName
yesPrefix = append(yesPrefix, keyName)
} else {
noPrefix = append(noPrefix, keyName)
}
s.SetItem(keyName, []byte(strconv.Itoa(i)))
}
n := s.ClearPrefix(prefix)
if n != numKeys/2 {
t.Errorf("Incorrect number of keys.\nexpected: %d\nreceived: %d",
numKeys/2, n)
}
for _, keyName := range noPrefix {
if _, err := s.GetItem(keyName); err != nil {
t.Errorf("Could not get keyName %q: %+v", keyName, err)
}
}
for _, keyName := range yesPrefix {
keyValue, err := s.GetItem(keyName)
if err == nil || !errors.Is(err, os.ErrNotExist) {
t.Errorf("Found keyName %q: %q", keyName, keyValue)
}
}
}
// Tests that LocalStorage.ClearWASM deletes all the WASM keys from storage and
// does not remove any others
func TestLocalStorage_ClearWASM(t *testing.T) {
jsStorage.clear()
prng := rand.New(rand.NewSource(11))
const numKeys = 10
var yesPrefix, noPrefix []string
for i := 0; i < numKeys; i++ {
keyName := "keyNum" + strconv.Itoa(i)
if prng.Intn(2) == 0 {
yesPrefix = append(yesPrefix, keyName)
jsStorage.SetItem(keyName, []byte(strconv.Itoa(i)))
} else {
noPrefix = append(noPrefix, keyName)
jsStorage.setItem(keyName, strconv.Itoa(i))
}
}
n := jsStorage.ClearWASM()
if n != numKeys/2 {
t.Errorf("Incorrect number of keys.\nexpected: %d\nreceived: %d",
numKeys/2, n)
}
for _, keyName := range noPrefix {
if v := jsStorage.getItem(keyName); v.IsNull() {
t.Errorf("Could not get keyName %q.", keyName)
}
}
for _, keyName := range yesPrefix {
keyValue, err := jsStorage.GetItem(keyName)
if err == nil || !errors.Is(err, os.ErrNotExist) {
t.Errorf("Found keyName %q: %q", keyName, keyValue)
}
}
}
// Tests that LocalStorage.Key return all added keys when looping through all
// indexes.
func TestLocalStorage_Key(t *testing.T) {
jsStorage.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 {
jsStorage.SetItem(keyName, keyValue)
}
numKeys := len(values)
for i := 0; i < numKeys; i++ {
keyName, err := jsStorage.Key(i)
if err != nil {
t.Errorf("No key found for index %d: %+v", i, err)
}
if _, exists := values[keyName]; !exists {
t.Errorf("No key with name %q added to storage.", keyName)
}
delete(values, keyName)
}
if len(values) != 0 {
t.Errorf("%d keys not read from storage: %q", len(values), values)
}
}
// Tests that LocalStorage.Key returns the error os.ErrNotExist when the index
// is greater than or equal to the number of keys.
func TestLocalStorage_Key_NotExistError(t *testing.T) {
jsStorage.clear()
jsStorage.SetItem("key", []byte("value"))
_, err := jsStorage.Key(1)
if err == nil || !errors.Is(err, os.ErrNotExist) {
t.Errorf("Incorrect error for non existant key index."+
"\nexpected: %v\nreceived: %v", os.ErrNotExist, err)
}
_, err = jsStorage.Key(2)
if err == nil || !errors.Is(err, os.ErrNotExist) {
t.Errorf("Incorrect error for non existant key index."+
"\nexpected: %v\nreceived: %v", os.ErrNotExist, err)
}
}
// Tests that LocalStorage.Length returns the correct Length when adding and
// removing various keys.
func TestLocalStorage_Length(t *testing.T) {
jsStorage.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},
}
i := 0
for keyName, keyValue := range values {
jsStorage.SetItem(keyName, keyValue)
i++
if jsStorage.Length() != i {
t.Errorf("Incorrect length.\nexpected: %d\nreceived: %d",
i, jsStorage.Length())
}
}
i = len(values)
for keyName := range values {
jsStorage.RemoveItem(keyName)
i--
if jsStorage.Length() != i {
t.Errorf("Incorrect length.\nexpected: %d\nreceived: %d",
i, jsStorage.Length())
}
}
}
// 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)
}
}
}
......@@ -12,16 +12,21 @@ package storage
import (
"crypto/cipher"
"encoding/json"
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/xxdk-wasm/utils"
"gitlab.com/xx_network/crypto/csprng"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/chacha20poly1305"
"io"
"os"
"syscall/js"
"golang.org/x/crypto/argon2"
"golang.org/x/crypto/blake2b"
"golang.org/x/crypto/chacha20poly1305"
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/wasm-utils/exception"
"gitlab.com/elixxir/wasm-utils/storage"
"gitlab.com/elixxir/wasm-utils/utils"
"gitlab.com/xx_network/crypto/csprng"
)
// Data lengths.
......@@ -91,7 +96,7 @@ const (
func GetOrInitPassword(_ js.Value, args []js.Value) any {
internalPassword, err := getOrInit(args[0].String())
if err != nil {
utils.Throw(utils.TypeError, err)
exception.ThrowTrace(err)
return nil
}
......@@ -109,7 +114,7 @@ func GetOrInitPassword(_ js.Value, args []js.Value) any {
func ChangeExternalPassword(_ js.Value, args []js.Value) any {
err := changeExternalPassword(args[0].String(), args[1].String())
if err != nil {
utils.Throw(utils.TypeError, err)
exception.ThrowTrace(err)
return nil
}
......@@ -130,7 +135,7 @@ func VerifyPassword(_ js.Value, args []js.Value) any {
// getOrInit is the private function for GetOrInitPassword that is used for
// testing.
func getOrInit(externalPassword string) ([]byte, error) {
localStorage := GetLocalStorage()
localStorage := storage.GetLocalStorage()
internalPassword, err := getInternalPassword(externalPassword, localStorage)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
......@@ -148,7 +153,7 @@ func getOrInit(externalPassword string) ([]byte, error) {
// changeExternalPassword is the private function for ChangeExternalPassword
// that is used for testing.
func changeExternalPassword(oldExternalPassword, newExternalPassword string) error {
localStorage := GetLocalStorage()
localStorage := storage.GetLocalStorage()
internalPassword, err := getInternalPassword(
oldExternalPassword, localStorage)
if err != nil {
......@@ -159,13 +164,17 @@ func changeExternalPassword(oldExternalPassword, newExternalPassword string) err
if err != nil {
return err
}
localStorage.SetItem(saltKey, salt)
if err = localStorage.Set(saltKey, salt); err != nil {
return errors.Wrapf(err, "localStorage: failed to set %q", saltKey)
}
key := deriveKey(newExternalPassword, salt, defaultParams())
encryptedInternalPassword := encryptPassword(
internalPassword, key, csprng.NewSystemRNG())
localStorage.SetItem(passwordKey, encryptedInternalPassword)
if err = localStorage.Set(passwordKey, encryptedInternalPassword); err != nil {
return errors.Wrapf(err, "localStorage: failed to set %q", passwordKey)
}
return nil
}
......@@ -173,14 +182,14 @@ func changeExternalPassword(oldExternalPassword, newExternalPassword string) err
// verifyPassword is the private function for VerifyPassword that is used for
// testing.
func verifyPassword(externalPassword string) bool {
_, err := getInternalPassword(externalPassword, GetLocalStorage())
_, err := getInternalPassword(externalPassword, storage.GetLocalStorage())
return err == nil
}
// initInternalPassword generates a new internal password, stores an encrypted
// version in local storage, and returns it.
func initInternalPassword(externalPassword string,
localStorage *LocalStorage, csprng io.Reader,
localStorage *storage.LocalStorage, csprng io.Reader,
params argonParams) ([]byte, error) {
internalPassword := make([]byte, internalPasswordLen)
......@@ -198,19 +207,28 @@ func initInternalPassword(externalPassword string,
if err != nil {
return nil, err
}
localStorage.SetItem(saltKey, salt)
if err = localStorage.Set(saltKey, salt); err != nil {
return nil,
errors.Wrapf(err, "localStorage: failed to set %q", saltKey)
}
// Store argon2 parameters
paramsData, err := json.Marshal(params)
if err != nil {
return nil, err
}
localStorage.SetItem(argonParamsKey, paramsData)
if err = localStorage.Set(argonParamsKey, paramsData); err != nil {
return nil,
errors.Wrapf(err, "localStorage: failed to set %q", argonParamsKey)
}
key := deriveKey(externalPassword, salt, params)
encryptedInternalPassword := encryptPassword(internalPassword, key, csprng)
localStorage.SetItem(passwordKey, encryptedInternalPassword)
if err = localStorage.Set(passwordKey, encryptedInternalPassword); err != nil {
return nil,
errors.Wrapf(err, "localStorage: failed to set %q", passwordKey)
}
return internalPassword, nil
}
......@@ -218,18 +236,18 @@ func initInternalPassword(externalPassword string,
// getInternalPassword retrieves the internal password from local storage,
// decrypts it, and returns it.
func getInternalPassword(
externalPassword string, localStorage *LocalStorage) ([]byte, error) {
encryptedInternalPassword, err := localStorage.GetItem(passwordKey)
externalPassword string, localStorage *storage.LocalStorage) ([]byte, error) {
encryptedInternalPassword, err := localStorage.Get(passwordKey)
if err != nil {
return nil, errors.WithMessage(err, getPasswordStorageErr)
}
salt, err := localStorage.GetItem(saltKey)
salt, err := localStorage.Get(saltKey)
if err != nil {
return nil, errors.WithMessage(err, getSaltStorageErr)
}
paramsData, err := localStorage.GetItem(argonParamsKey)
paramsData, err := localStorage.Get(argonParamsKey)
if err != nil {
return nil, errors.WithMessage(err, getParamsStorageErr)
}
......
......@@ -14,9 +14,11 @@ import (
"crypto/rand"
"encoding/base64"
"fmt"
"gitlab.com/xx_network/crypto/csprng"
"strings"
"testing"
"gitlab.com/elixxir/wasm-utils/storage"
"gitlab.com/xx_network/crypto/csprng"
)
// Tests that running getOrInit twice returns the same internal password both
......@@ -77,7 +79,7 @@ func Test_changeExternalPassword(t *testing.T) {
// Tests that verifyPassword returns true for a valid password and false for an
// invalid password
func Test_verifyPassword(t *testing.T) {
GetLocalStorage().Clear()
storage.GetLocalStorage().Clear()
externalPassword := "myPassword"
if _, err := getOrInit(externalPassword); err != nil {
......@@ -97,7 +99,7 @@ func Test_verifyPassword(t *testing.T) {
// the encrypted one saved to local storage.
func Test_initInternalPassword(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
rng := csprng.NewSystemRNG()
internalPassword, err := initInternalPassword(
......@@ -107,14 +109,14 @@ func Test_initInternalPassword(t *testing.T) {
}
// Attempt to retrieve encrypted internal password from storage
encryptedInternalPassword, err := ls.GetItem(passwordKey)
encryptedInternalPassword, err := ls.Get(passwordKey)
if err != nil {
t.Errorf(
"Failed to load encrypted internal password from storage: %+v", err)
}
// Attempt to retrieve salt from storage
salt, err := ls.GetItem(saltKey)
salt, err := ls.Get(saltKey)
if err != nil {
t.Errorf("Failed to load salt from storage: %+v", err)
}
......@@ -138,7 +140,7 @@ func Test_initInternalPassword(t *testing.T) {
// error when read.
func Test_initInternalPassword_CsprngReadError(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
b := bytes.NewBuffer([]byte{})
expectedErr := strings.Split(readInternalPasswordErr, "%")[0]
......@@ -154,7 +156,7 @@ func Test_initInternalPassword_CsprngReadError(t *testing.T) {
// return enough bytes.
func Test_initInternalPassword_CsprngReadNumBytesError(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
b := bytes.NewBuffer(make([]byte, internalPasswordLen/2))
expectedErr := fmt.Sprintf(
......@@ -171,7 +173,7 @@ func Test_initInternalPassword_CsprngReadNumBytesError(t *testing.T) {
// to local storage by initInternalPassword.
func Test_getInternalPassword(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
rng := csprng.NewSystemRNG()
internalPassword, err := initInternalPassword(
......@@ -196,7 +198,7 @@ func Test_getInternalPassword(t *testing.T) {
// loaded from local storage.
func Test_getInternalPassword_LocalStorageGetPasswordError(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
ls.Clear()
expectedErr := strings.Split(getPasswordStorageErr, "%")[0]
......@@ -212,9 +214,11 @@ func Test_getInternalPassword_LocalStorageGetPasswordError(t *testing.T) {
// loaded from local storage.
func Test_getInternalPassword_LocalStorageGetError(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
ls.Clear()
ls.SetItem(passwordKey, []byte("password"))
if err := ls.Set(passwordKey, []byte("password")); err != nil {
t.Fatalf("Failed to set %q: %+v", passwordKey, err)
}
expectedErr := strings.Split(getSaltStorageErr, "%")[0]
......@@ -229,11 +233,17 @@ func Test_getInternalPassword_LocalStorageGetError(t *testing.T) {
// decrypted.
func Test_getInternalPassword_DecryptPasswordError(t *testing.T) {
externalPassword := "myPassword"
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
ls.Clear()
ls.SetItem(saltKey, []byte("salt"))
ls.SetItem(passwordKey, []byte("password"))
ls.SetItem(argonParamsKey, []byte(`{"Time": 1, "Memory": 65536, "Threads": 4}`))
if err := ls.Set(saltKey, []byte("salt")); err != nil {
t.Errorf("failed to set %q: %+v", saltKey, err)
}
if err := ls.Set(passwordKey, []byte("password")); err != nil {
t.Errorf("failed to set %q: %+v", passwordKey, err)
}
if err := ls.Set(argonParamsKey, []byte(`{"Time": 1, "Memory": 65536, "Threads": 4}`)); err != nil {
t.Errorf("failed to set %q: %+v", argonParamsKey, err)
}
expectedErr := strings.Split(decryptPasswordErr, "%")[0]
......
......@@ -10,13 +10,15 @@
package storage
import (
"sync/atomic"
"syscall/js"
"github.com/hack-pad/go-indexeddb/idb"
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/v4/storage/utility"
"gitlab.com/elixxir/xxdk-wasm/utils"
"sync/atomic"
"syscall/js"
"gitlab.com/elixxir/wasm-utils/exception"
"gitlab.com/elixxir/wasm-utils/storage"
)
// numClientsRunning is an atomic that tracks the current number of Cmix
......@@ -49,7 +51,7 @@ func DecrementNumClientsRunning() {
// passed into [wasm.NewCmix].
//
// Returns:
// - Throws a TypeError if the password is incorrect or if not all cMix
// - Throws an error if the password is incorrect or if not all cMix
// followers have been stopped.
func Purge(_ js.Value, args []js.Value) any {
storageDirectory := args[0].String()
......@@ -57,22 +59,21 @@ func Purge(_ js.Value, args []js.Value) any {
// Check the password
if !verifyPassword(userPassword) {
utils.Throw(utils.TypeError, errors.New("invalid password"))
exception.Throwf("invalid password")
return nil
}
// 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))
exception.Throwf("%d cMix followers running; all need to be stopped", n)
return nil
}
// Get all indexedDb database names
databaseList, err := GetIndexedDbList()
if err != nil {
utils.Throw(utils.TypeError, errors.Errorf(
"failed to get list of indexedDb database names: %+v", err))
exception.Throwf(
"failed to get list of indexedDb database names: %+v", err)
return nil
}
jww.DEBUG.Printf("[PURGE] Found %d databases to delete: %s",
......@@ -82,26 +83,34 @@ func Purge(_ js.Value, args []js.Value) any {
for dbName := range databaseList {
_, err = idb.Global().DeleteDatabase(dbName)
if err != nil {
utils.Throw(utils.TypeError, errors.Errorf(
"failed to delete indexedDb database %q: %+v", dbName, err))
exception.Throwf(
"failed to delete indexedDb database %q: %+v", dbName, err)
return nil
}
}
// Get local storage
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
// Clear all local storage saved by this WASM project
n := ls.ClearWASM()
n := ls.Clear()
jww.DEBUG.Printf("[PURGE] Cleared %d WASM keys in local storage", n)
// Clear all EKV from local storage
n = ls.ClearPrefix(storageDirectory)
keys := ls.LocalStorageUNSAFE().KeysPrefix(storageDirectory)
n = len(keys)
for _, keyName := range keys {
ls.LocalStorageUNSAFE().RemoveItem(keyName)
}
jww.DEBUG.Printf("[PURGE] Cleared %d keys with the prefix %q (for EKV)",
n, storageDirectory)
// Clear all NDFs saved to local storage
n = ls.ClearPrefix(utility.NdfStorageKeyNamePrefix)
keys = ls.LocalStorageUNSAFE().KeysPrefix(utility.NdfStorageKeyNamePrefix)
n = len(keys)
for _, keyName := range keys {
ls.LocalStorageUNSAFE().RemoveItem(keyName)
}
jww.DEBUG.Printf("[PURGE] Cleared %d keys with the prefix %q (for NDF)",
n, utility.NdfStorageKeyNamePrefix)
......
......@@ -17,6 +17,7 @@ import (
jww "github.com/spf13/jwalterweatherman"
"gitlab.com/elixxir/client/v4/bindings"
"gitlab.com/elixxir/wasm-utils/storage"
)
// SEMVER is the current semantic version of xxDK WASM.
......@@ -35,11 +36,11 @@ const (
// On first load, only the xxDK WASM and xxDK client versions are stored.
func CheckAndStoreVersions() error {
return checkAndStoreVersions(
SEMVER, bindings.GetVersion(), GetLocalStorage())
SEMVER, bindings.GetVersion(), storage.GetLocalStorage())
}
func checkAndStoreVersions(
currentWasmVer, currentClientVer string, ls *LocalStorage) error {
currentWasmVer, currentClientVer string, ls *storage.LocalStorage) error {
// Get the stored client version, if it exists
storedClientVer, err :=
initOrLoadStoredSemver(clientVerKey, currentClientVer, ls)
......@@ -76,8 +77,12 @@ func checkAndStoreVersions(
// Upgrade path code goes here
// Save current versions
ls.SetItem(clientVerKey, []byte(currentClientVer))
ls.SetItem(semverKey, []byte(currentWasmVer))
if err = ls.Set(clientVerKey, []byte(currentClientVer)); err != nil {
return errors.Wrapf(err, "localStorage: failed to set %q", clientVerKey)
}
if err = ls.Set(semverKey, []byte(currentWasmVer)); err != nil {
return errors.Wrapf(err, "localStorage: failed to set %q", semverKey)
}
return nil
}
......@@ -86,13 +91,16 @@ func checkAndStoreVersions(
// local storage. If no version is stored, then the current version is stored
// and returned.
func initOrLoadStoredSemver(
key, currentVersion string, ls *LocalStorage) (string, error) {
storedVersion, err := ls.GetItem(key)
key, currentVersion string, ls *storage.LocalStorage) (string, error) {
storedVersion, err := ls.Get(key)
if err != nil {
if errors.Is(err, os.ErrNotExist) {
// Save the current version if this is the first run
jww.INFO.Printf("Initialising %s to v%s", key, currentVersion)
ls.SetItem(key, []byte(currentVersion))
if err = ls.Set(key, []byte(currentVersion)); err != nil {
return "",
errors.Wrapf(err, "localStorage: failed to set %q", key)
}
return currentVersion, nil
} else {
// If the item exists, but cannot be loaded, return an error
......
......@@ -11,12 +11,14 @@ package storage
import (
"testing"
"gitlab.com/elixxir/wasm-utils/storage"
)
// Tests that checkAndStoreVersions correct initialises the client and WASM
// versions on first run and upgrades them correctly on subsequent runs.
func Test_checkAndStoreVersions(t *testing.T) {
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
ls.Clear()
oldWasmVer := "0.1"
newWasmVer := "1.0"
......@@ -28,7 +30,7 @@ func Test_checkAndStoreVersions(t *testing.T) {
}
// Check client version
storedClientVer, err := ls.GetItem(clientVerKey)
storedClientVer, err := ls.Get(clientVerKey)
if err != nil {
t.Errorf("Failed to get client version from storage: %+v", err)
}
......@@ -38,7 +40,7 @@ func Test_checkAndStoreVersions(t *testing.T) {
}
// Check WASM version
storedWasmVer, err := ls.GetItem(semverKey)
storedWasmVer, err := ls.Get(semverKey)
if err != nil {
t.Errorf("Failed to get WASM version from storage: %+v", err)
}
......@@ -53,7 +55,7 @@ func Test_checkAndStoreVersions(t *testing.T) {
}
// Check client version
storedClientVer, err = ls.GetItem(clientVerKey)
storedClientVer, err = ls.Get(clientVerKey)
if err != nil {
t.Errorf("Failed to get client version from storage: %+v", err)
}
......@@ -63,7 +65,7 @@ func Test_checkAndStoreVersions(t *testing.T) {
}
// Check WASM version
storedWasmVer, err = ls.GetItem(semverKey)
storedWasmVer, err = ls.Get(semverKey)
if err != nil {
t.Errorf("Failed to get WASM version from storage: %+v", err)
}
......@@ -76,7 +78,7 @@ func Test_checkAndStoreVersions(t *testing.T) {
// Tests that initOrLoadStoredSemver initialises the correct version on first
// run and returns the same version on subsequent runs.
func Test_initOrLoadStoredSemver(t *testing.T) {
ls := GetLocalStorage()
ls := storage.GetLocalStorage()
key := "testKey"
oldVersion := "0.1"
......
......@@ -503,7 +503,7 @@
},
// func throw(exception string, message string)
'gitlab.com/elixxir/xxdk-wasm/utils.throw': (sp) => {
'gitlab.com/elixxir/wasm-utils/utils.throw': (sp) => {
const exception = loadString(sp + 8)
const message = loadString(sp + 24)
throw globalThis[exception](message)
......
////////////////////////////////////////////////////////////////////////////////
// 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 utils
import (
"bytes"
"encoding/base64"
"syscall/js"
)
// Uint8ArrayToBase64 encodes an uint8 array to a base 64 string.
//
// Parameters:
// - args[0] - Javascript 8-bit unsigned integer array (Uint8Array).
//
// Returns:
// - Base 64 encoded string (string).
func Uint8ArrayToBase64(_ js.Value, args []js.Value) any {
return base64.StdEncoding.EncodeToString(CopyBytesToGo(args[0]))
}
// Base64ToUint8Array decodes a base 64 encoded string to a Uint8Array.
//
// Parameters:
// - args[0] - Base 64 encoded string (string).
//
// Returns:
// - Javascript 8-bit unsigned integer array (Uint8Array).
// - Throws TypeError if decoding the string fails.
func Base64ToUint8Array(_ js.Value, args []js.Value) any {
b, err := base64ToUint8Array(args[0])
if err != nil {
Throw(TypeError, err)
}
return b
}
// base64ToUint8Array is a helper function that returns an error instead of
// throwing it.
func base64ToUint8Array(base64String js.Value) (js.Value, error) {
b, err := base64.StdEncoding.DecodeString(base64String.String())
if err != nil {
return js.Value{}, err
}
return CopyBytesToJS(b), nil
}
// Uint8ArrayEquals returns true if the two Uint8Array are equal and false
// otherwise.
//
// Parameters:
// - args[0] - Array A (Uint8Array).
// - args[1] - Array B (Uint8Array).
//
// Returns:
// - If the two arrays are equal (boolean).
func Uint8ArrayEquals(_ js.Value, args []js.Value) any {
a := CopyBytesToGo(args[0])
b := CopyBytesToGo(args[1])
return bytes.Equal(a, b)
}
////////////////////////////////////////////////////////////////////////////////
// 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 utils
import (
"encoding/base64"
"fmt"
"strings"
"syscall/js"
"testing"
)
var testBytes = [][]byte{
nil,
{},
{0},
{0, 1, 2, 3},
{214, 108, 207, 78, 229, 11, 42, 219, 42, 87, 205, 104, 252, 73, 223,
229, 145, 209, 79, 111, 34, 96, 238, 127, 11, 105, 114, 62, 239,
130, 145, 82, 3},
}
// Tests that a series of Uint8Array Javascript objects are correctly converted
// to base 64 strings with Uint8ArrayToBase64.
func TestUint8ArrayToBase64(t *testing.T) {
for i, val := range testBytes {
// Create Uint8Array and set each element individually
jsBytes := Uint8Array.New(len(val))
for j, v := range val {
jsBytes.SetIndex(j, v)
}
jsB64 := Uint8ArrayToBase64(js.Value{}, []js.Value{jsBytes})
expected := base64.StdEncoding.EncodeToString(val)
if expected != jsB64 {
t.Errorf("Did not receive expected base64 encoded string (%d)."+
"\nexpected: %s\nreceived: %s", i, expected, jsB64)
}
}
}
// Tests that Base64ToUint8Array correctly decodes a series of base 64 encoded
// strings into Uint8Array.
func TestBase64ToUint8Array(t *testing.T) {
for i, val := range testBytes {
b64 := base64.StdEncoding.EncodeToString(val)
jsArr, err := base64ToUint8Array(js.ValueOf(b64))
if err != nil {
t.Errorf("Failed to convert js.Value to base 64: %+v", err)
}
// Generate the expected string to match the output of toString() on a
// Uint8Array
expected := strings.ReplaceAll(fmt.Sprintf("%d", val), " ", ",")[1:]
expected = expected[:len(expected)-1]
// Get the string value of the Uint8Array
jsString := jsArr.Call("toString").String()
if expected != jsString {
t.Errorf("Failed to recevie expected string representation of "+
"the Uint8Array (%d).\nexpected: %s\nreceived: %s",
i, expected, jsString)
}
}
}
// Tests that a base 64 encoded string decoded to Uint8Array via
// Base64ToUint8Array and back to a base 64 encoded string via
// Uint8ArrayToBase64 matches the original.
func TestBase64ToUint8ArrayUint8ArrayToBase64(t *testing.T) {
for i, val := range testBytes {
b64 := base64.StdEncoding.EncodeToString(val)
jsArr, err := base64ToUint8Array(js.ValueOf(b64))
if err != nil {
t.Errorf("Failed to convert js.Value to base 64: %+v", err)
}
jsB64 := Uint8ArrayToBase64(js.Value{}, []js.Value{jsArr})
if b64 != jsB64 {
t.Errorf("JSON from Uint8Array does not match original (%d)."+
"\nexpected: %s\nreceived: %s", i, b64, jsB64)
}
}
}
func TestUint8ArrayEquals(t *testing.T) {
for i, val := range testBytes {
// Create Uint8Array and set each element individually
jsBytesA := Uint8Array.New(len(val))
for j, v := range val {
jsBytesA.SetIndex(j, v)
}
jsBytesB := CopyBytesToJS(val)
if !Uint8ArrayEquals(js.Value{}, []js.Value{jsBytesA, jsBytesB}).(bool) {
t.Errorf("Two equal byte slices were found to be different (%d)."+
"\nexpected: %s\nreceived: %s", i,
jsBytesA.Call("toString").String(),
jsBytesB.Call("toString").String())
}
}
}
////////////////////////////////////////////////////////////////////////////////
// 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 utils
import (
"encoding/base64"
"encoding/json"
"sort"
"syscall/js"
"testing"
)
import (
"bytes"
"fmt"
"strings"
)
// Tests that CopyBytesToGo returns a byte slice that matches the Uint8Array.
func TestCopyBytesToGo(t *testing.T) {
for i, val := range testBytes {
// Create Uint8Array and set each element individually
jsBytes := Uint8Array.New(len(val))
for j, v := range val {
jsBytes.SetIndex(j, v)
}
goBytes := CopyBytesToGo(jsBytes)
if !bytes.Equal(val, goBytes) {
t.Errorf("Failed to recevie expected bytes from Uint8Array (%d)."+
"\nexpected: %d\nreceived: %d",
i, val, goBytes)
}
}
}
// Tests that CopyBytesToJS returns a Javascript Uint8Array with values matching
// the original byte slice.
func TestCopyBytesToJS(t *testing.T) {
for i, val := range testBytes {
jsBytes := CopyBytesToJS(val)
// Generate the expected string to match the output of toString() on a
// Uint8Array
expected := strings.ReplaceAll(fmt.Sprintf("%d", val), " ", ",")[1:]
expected = expected[:len(expected)-1]
// Get the string value of the Uint8Array
jsString := jsBytes.Call("toString").String()
if expected != jsString {
t.Errorf("Failed to recevie expected string representation of "+
"the Uint8Array (%d).\nexpected: %s\nreceived: %s",
i, expected, jsString)
}
}
}
// Tests that a byte slice converted to Javascript via CopyBytesToJS and
// converted back to Go via CopyBytesToGo matches the original.
func TestCopyBytesToJSCopyBytesToGo(t *testing.T) {
for i, val := range testBytes {
jsBytes := CopyBytesToJS(val)
goBytes := CopyBytesToGo(jsBytes)
if !bytes.Equal(val, goBytes) {
t.Errorf("Failed to recevie expected bytes from Uint8Array (%d)."+
"\nexpected: %d\nreceived: %d",
i, val, goBytes)
}
}
}
// Tests that JsToJson can convert a Javascript object to JSON that matches the
// output of json.Marshal on the Go version of the same object.
func TestJsToJson(t *testing.T) {
testObj := map[string]any{
"nil": nil,
"bool": true,
"int": 1,
"float": 1.5,
"string": "I am string",
"array": []any{1, 2, 3},
"object": map[string]any{"int": 5},
}
expected, err := json.Marshal(testObj)
if err != nil {
t.Errorf("Failed to JSON marshal test object: %+v", err)
}
jsJson := JsToJson(js.ValueOf(testObj))
// Javascript does not return the JSON object fields sorted so the letters
// of each Javascript string are sorted and compared
er := []rune(string(expected))
sort.SliceStable(er, func(i, j int) bool { return er[i] < er[j] })
jj := []rune(jsJson)
sort.SliceStable(jj, func(i, j int) bool { return jj[i] < jj[j] })
if string(er) != string(jj) {
t.Errorf("Recieved incorrect JSON from Javascript object."+
"\nexpected: %s\nreceived: %s", expected, jsJson)
}
}
// Tests that JsToJson return a null object when the Javascript object is
// undefined.
func TestJsToJson_Undefined(t *testing.T) {
expected, err := json.Marshal(nil)
if err != nil {
t.Errorf("Failed to JSON marshal test object: %+v", err)
}
jsJson := JsToJson(js.Undefined())
if string(expected) != jsJson {
t.Errorf("Recieved incorrect JSON from Javascript object."+
"\nexpected: %s\nreceived: %s", expected, jsJson)
}
}
// Tests that JsonToJS can convert a JSON object with multiple types to a
// Javascript object and that all values match.
func TestJsonToJS(t *testing.T) {
testObj := map[string]any{
"nil": nil,
"bool": true,
"int": 1,
"float": 1.5,
"string": "I am string",
"bytes": []byte{1, 2, 3},
"array": []any{1, 2, 3},
"object": map[string]any{"int": 5},
}
jsonData, err := json.Marshal(testObj)
if err != nil {
t.Errorf("Failed to JSON marshal test object: %+v", err)
}
jsObj, err := JsonToJS(jsonData)
if err != nil {
t.Errorf("Failed to convert JSON to Javascript object: %+v", err)
}
for key, val := range testObj {
jsVal := jsObj.Get(key)
switch key {
case "nil":
if !jsVal.IsNull() {
t.Errorf("Key %s is not null.", key)
}
case "bool":
if jsVal.Bool() != val {
t.Errorf("Incorrect value for key %s."+
"\nexpected: %t\nreceived: %t", key, val, jsVal.Bool())
}
case "int":
if jsVal.Int() != val {
t.Errorf("Incorrect value for key %s."+
"\nexpected: %d\nreceived: %d", key, val, jsVal.Int())
}
case "float":
if jsVal.Float() != val {
t.Errorf("Incorrect value for key %s."+
"\nexpected: %f\nreceived: %f", key, val, jsVal.Float())
}
case "string":
if jsVal.String() != val {
t.Errorf("Incorrect value for key %s."+
"\nexpected: %s\nreceived: %s", key, val, jsVal.String())
}
case "bytes":
if jsVal.String() != base64.StdEncoding.EncodeToString(val.([]byte)) {
t.Errorf("Incorrect value for key %s."+
"\nexpected: %s\nreceived: %s", key,
base64.StdEncoding.EncodeToString(val.([]byte)),
jsVal.String())
}
case "array":
for i, v := range val.([]any) {
if jsVal.Index(i).Int() != v {
t.Errorf("Incorrect value for key %s index %d."+
"\nexpected: %d\nreceived: %d",
key, i, v, jsVal.Index(i).Int())
}
}
case "object":
if jsVal.Get("int").Int() != val.(map[string]any)["int"] {
t.Errorf("Incorrect value for key %s."+
"\nexpected: %d\nreceived: %d", key,
val.(map[string]any)["int"], jsVal.Get("int").Int())
}
}
}
}
// Tests that JSON can be converted to a Javascript object via JsonToJS and back
// to JSON using JsToJson and matches the original.
func TestJsonToJSJsToJson(t *testing.T) {
testObj := map[string]any{
"nil": nil,
"bool": true,
"int": 1,
"float": 1.5,
"string": "I am string",
"bytes": []byte{1, 2, 3},
"array": []any{1, 2, 3},
"object": map[string]any{"int": 5},
}
jsonData, err := json.Marshal(testObj)
if err != nil {
t.Errorf("Failed to JSON marshal test object: %+v", err)
}
jsObj, err := JsonToJS(jsonData)
if err != nil {
t.Errorf("Failed to convert the Javascript object to JSON: %+v", err)
}
jsJson := JsToJson(jsObj)
// Javascript does not return the JSON object fields sorted so the letters
// of each Javascript string are sorted and compared
er := []rune(string(jsonData))
sort.SliceStable(er, func(i, j int) bool { return er[i] < er[j] })
jj := []rune(jsJson)
sort.SliceStable(jj, func(i, j int) bool { return jj[i] < jj[j] })
if string(er) != string(jj) {
t.Errorf("JSON from Javascript does not match original."+
"\nexpected: %s\nreceived: %s", jsonData, jsJson)
}
}
// Tests that JsErrorToJson can convert a Javascript object to JSON that matches
// the output of json.Marshal on the Go version of the same object.
func TestJsErrorToJson(t *testing.T) {
testObj := map[string]any{
"nil": nil,
"bool": true,
"int": 1,
"float": 1.5,
"string": "I am string",
"array": []any{1, 2, 3},
"object": map[string]any{"int": 5},
}
expected, err := json.Marshal(testObj)
if err != nil {
t.Errorf("Failed to JSON marshal test object: %+v", err)
}
jsJson := JsErrorToJson(js.ValueOf(testObj))
// Javascript does not return the JSON object fields sorted so the letters
// of each Javascript string are sorted and compared
er := []rune(string(expected))
sort.SliceStable(er, func(i, j int) bool { return er[i] < er[j] })
jj := []rune(jsJson)
sort.SliceStable(jj, func(i, j int) bool { return jj[i] < jj[j] })
if string(er) != string(jj) {
t.Errorf("Recieved incorrect JSON from Javascript object."+
"\nexpected: %s\nreceived: %s", expected, jsJson)
}
}
// Tests that JsErrorToJson return a null object when the Javascript object is
// undefined.
func TestJsErrorToJson_Undefined(t *testing.T) {
expected, err := json.Marshal(nil)
if err != nil {
t.Errorf("Failed to JSON marshal test object: %+v", err)
}
jsJson := JsErrorToJson(js.Undefined())
if string(expected) != jsJson {
t.Errorf("Recieved incorrect JSON from Javascript object."+
"\nexpected: %s\nreceived: %s", expected, jsJson)
}
}
// Tests that JsErrorToJson returns a JSON object containing the original error
// string.
func TestJsErrorToJson_ErrorObject(t *testing.T) {
expected := "An error"
jsErr := Error.New(expected)
jsJson := JsErrorToJson(jsErr)
if !strings.Contains(jsJson, expected) {
t.Errorf("Recieved incorrect JSON from Javascript error."+
"\nexpected: %s\nreceived: %s", expected, jsJson)
}
}
////////////////////////////////////////////////////////////////////////////////
// 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 utils
import (
"fmt"
"syscall/js"
)
// JsError converts the error to a Javascript Error.
func JsError(err error) js.Value {
return Error.New(err.Error())
}
// JsTrace converts the error to a Javascript Error that includes the error's
// stack trace.
func JsTrace(err error) js.Value {
return Error.New(fmt.Sprintf("%+v", err))
}
// Throw function stub to throws Javascript exceptions. The exception must be
// one of the defined Exception below. Any other error types will result in an
// error.
func Throw(exception Exception, err error) {
throw(exception, fmt.Sprintf("%+v", err))
}
func throw(exception Exception, message string)
// Exception are the possible Javascript error types that can be thrown.
type Exception string
const (
// EvalError occurs when error has occurred in the eval() function.
//
// Deprecated: This exception is not thrown by JavaScript anymore, however
// the EvalError object remains for compatibility.
EvalError Exception = "EvalError"
// RangeError occurs when a numeric variable or parameter is outside its
// valid range.
RangeError Exception = "RangeError"
// ReferenceError occurs when a variable that does not exist (or hasn't yet
// been initialized) in the current scope is referenced.
ReferenceError Exception = "ReferenceError"
// SyntaxError occurs when trying to interpret syntactically invalid code.
SyntaxError Exception = "SyntaxError"
// TypeError occurs when an operation could not be performed, typically (but
// not exclusively) when a value is not of the expected type.
//
// A TypeError may be thrown when:
// - an operand or argument passed to a function is incompatible with the
// type expected by that operator or function; or
// - when attempting to modify a value that cannot be changed; or
// - when attempting to use a value in an inappropriate way.
TypeError Exception = "TypeError"
// URIError occurs when a global URI handling function was used in a wrong
// way.
URIError Exception = "URIError"
)
////////////////////////////////////////////////////////////////////////////////
// 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 utils
import (
"github.com/pkg/errors"
jww "github.com/spf13/jwalterweatherman"
"syscall/js"
)
var (
// Error is the Javascript Error type. It used to create new Javascript
// errors.
Error = js.Global().Get("Error")
// JSON is the Javascript JSON type. It is used to perform JSON operations
// on the Javascript layer.
JSON = js.Global().Get("JSON")
// 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 type. It is used to create new
// Uint8Array.
Uint8Array = js.Global().Get("Uint8Array")
)
// WrapCB wraps a Javascript function in an object so that it can be called
// later with only the arguments and without specifying the function name.
//
// Panics if m is not a function.
func WrapCB(parent js.Value, m string) func(args ...any) js.Value {
if parent.Get(m).Type() != js.TypeFunction {
// Create the error separate from the print so stack trace is printed
err := errors.Errorf("Function %q is not of type %s", m, js.TypeFunction)
jww.FATAL.Panicf("%+v", err)
}
return func(args ...any) js.Value { return parent.Call(m, args...) }
}
// PromiseFn converts the Javascript Promise construct into Go.
//
// Call resolve with the return of the function on success. Call reject with an
// error on failure.
type PromiseFn func(resolve, reject func(args ...any) js.Value)
// CreatePromise creates a Javascript promise to return the value of a blocking
// Go function to Javascript.
func CreatePromise(f PromiseFn) any {
// Create handler for promise (this will be a Javascript function)
handler := js.FuncOf(func(this js.Value, args []js.Value) any {
// Spawn a new go routine to perform the blocking function
go func(resolve, reject js.Value) {
f(resolve.Invoke, reject.Invoke)
}(args[0], args[1])
return nil
})
// Create and return the Promise object
return Promise.New(handler)
}
// Await waits on a Javascript value. It blocks until the awaitable successfully
// resolves to the result or rejects to err.
//
// If there is a result, err will be nil and vice versa.
func Await(awaitable js.Value) (result []js.Value, err []js.Value) {
then := make(chan []js.Value)
defer close(then)
thenFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
then <- args
return nil
})
defer thenFunc.Release()
catch := make(chan []js.Value)
defer close(catch)
catchFunc := js.FuncOf(func(this js.Value, args []js.Value) any {
catch <- args
return nil
})
defer catchFunc.Release()
awaitable.Call("then", thenFunc).Call("catch", catchFunc)
select {
case result = <-then:
return result, nil
case err = <-catch:
return nil, err
}
}
#include "textflag.h"
// Throw enables throwing of Javascript exceptions.
TEXT ·throw(SB), NOSPLIT, $0
CallImport
RET
......@@ -11,7 +11,8 @@ package wasm
import (
"gitlab.com/elixxir/client/v4/bindings"
"gitlab.com/elixxir/xxdk-wasm/utils"
"gitlab.com/elixxir/wasm-utils/exception"
"gitlab.com/elixxir/wasm-utils/utils"
"syscall/js"
)
......@@ -73,7 +74,7 @@ func (ac *AuthenticatedConnection) SendE2E(_ js.Value, args []js.Value) any {
promiseFn := func(resolve, reject func(args ...any) js.Value) {
sendReport, err := ac.api.SendE2E(mt, payload)
if err != nil {
reject(utils.JsTrace(err))
reject(exception.NewTrace(err))
} else {
resolve(utils.CopyBytesToJS(sendReport))
}
......@@ -86,7 +87,7 @@ func (ac *AuthenticatedConnection) SendE2E(_ js.Value, args []js.Value) any {
// resources.
//
// Returns:
// - Throws a TypeError if closing fails.
// - Throws an error if closing fails.
func (ac *AuthenticatedConnection) Close(js.Value, []js.Value) any {
return ac.api.Close()
}
......@@ -108,13 +109,13 @@ func (ac *AuthenticatedConnection) GetPartner(js.Value, []js.Value) any {
// [bindings.Listener] interface.
//
// Returns:
// - Throws a TypeError is registering the listener fails.
// - Throws an error is registering the listener fails.
func (ac *AuthenticatedConnection) RegisterListener(
_ js.Value, args []js.Value) any {
err := ac.api.RegisterListener(args[0].Int(),
&listener{utils.WrapCB(args[1], "Hear"), utils.WrapCB(args[1], "Name")})
if err != nil {
utils.Throw(utils.TypeError, err)
exception.ThrowTrace(err)
return nil
}
......@@ -143,7 +144,7 @@ func (c *Cmix) ConnectWithAuthentication(_ js.Value, args []js.Value) any {
ac, err := c.api.ConnectWithAuthentication(
e2eID, recipientContact, e2eParamsJSON)
if err != nil {
reject(utils.JsTrace(err))
reject(exception.NewTrace(err))
} else {
resolve(newAuthenticatedConnectionJS(ac))
}
......
......@@ -11,7 +11,8 @@ package wasm
import (
"gitlab.com/elixxir/client/v4/bindings"
"gitlab.com/elixxir/xxdk-wasm/utils"
"gitlab.com/elixxir/wasm-utils/exception"
"gitlab.com/elixxir/wasm-utils/utils"
"syscall/js"
)
......@@ -69,7 +70,7 @@ func (ubf *updateBackupFunc) UpdateBackup(encryptedBackup []byte) {
//
// Returns:
// - JSON of [bindings.BackupReport] (Uint8Array).
// - Throws a TypeError if creating [Cmix] from backup fails.
// - Throws an error if creating [Cmix] from backup fails.
func NewCmixFromBackup(_ js.Value, args []js.Value) any {
ndfJSON := args[0].String()
storageDir := args[1].String()
......@@ -80,7 +81,7 @@ func NewCmixFromBackup(_ js.Value, args []js.Value) any {
report, err := bindings.NewCmixFromBackup(ndfJSON, storageDir,
backupPassphrase, sessionPassword, backupFileContents)
if err != nil {
utils.Throw(utils.TypeError, err)
exception.ThrowTrace(err)
return nil
}
......@@ -104,13 +105,13 @@ func NewCmixFromBackup(_ js.Value, args []js.Value) any {
//
// Returns:
// - Javascript representation of the [Backup] object.
// - Throws a TypeError if initializing the [Backup] fails.
// - Throws an error if initializing the [Backup] fails.
func InitializeBackup(_ js.Value, args []js.Value) any {
cb := &updateBackupFunc{utils.WrapCB(args[3], "UpdateBackup")}
api, err := bindings.InitializeBackup(
args[0].Int(), args[1].Int(), args[2].String(), cb)
if err != nil {
utils.Throw(utils.TypeError, err)
exception.ThrowTrace(err)
return nil
}
......@@ -133,12 +134,12 @@ func InitializeBackup(_ js.Value, args []js.Value) any {
//
// Returns:
// - Javascript representation of the [Backup] object.
// - Throws a TypeError if initializing the [Backup] fails.
// - Throws an error if initializing the [Backup] fails.
func ResumeBackup(_ js.Value, args []js.Value) any {
cb := &updateBackupFunc{utils.WrapCB(args[2], "UpdateBackup")}
api, err := bindings.ResumeBackup(args[0].Int(), args[1].Int(), cb)
if err != nil {
utils.Throw(utils.TypeError, err)
exception.ThrowTrace(err)
return nil
}
......@@ -149,11 +150,11 @@ func ResumeBackup(_ js.Value, args []js.Value) any {
// storage. To enable backups again, call [InitializeBackup].
//
// Returns:
// - Throws a TypeError if stopping the backup fails.
// - Throws an error if stopping the backup fails.
func (b *Backup) StopBackup(js.Value, []js.Value) any {
err := b.api.StopBackup()
if err != nil {
utils.Throw(utils.TypeError, err)
exception.ThrowTrace(err)
return nil
}
......