diff --git a/README.md b/README.md index b080e856a7fdfe0f22328e5cd777a1d84450542d..d1f59cd4392fce9f8b2b541862e69fa684400039 100644 --- a/README.md +++ b/README.md @@ -4,9 +4,16 @@ This repository contains the WebAssembly bindings for xxDK. It also includes examples and a test server to serve the compiled WebAssembly module. **Note:** If you are updating the version of Go that this repository uses, you -need to ensure that you update the wasm_exec.js file as described +need to ensure that you update the `wasm_exec.js` file as described [below](#wasm_execjs). +## Updates + +The current semantic version of this repository is stored in `SEMVER` in +`version.go`. When making major updates or updates that create an +incompatibility in the storage or databases, the semantic version needs to be +updated and an upgrade path needs to be provided. + ## Building The repository can only be compiled to a WebAssembly binary using `GOOS=js` and diff --git a/main.go b/main.go index 0e067b2e46c0193c0f05907fa23fbd6a6e3c34d5..a7484593ddf3cef3fdb831ca1dd06da04d538548 100644 --- a/main.go +++ b/main.go @@ -26,6 +26,12 @@ func init() { ll := wasm.NewJsConsoleLogListener(jww.LevelInfo) jww.SetLogListeners(ll.Listen) jww.SetStdoutThreshold(jww.LevelFatal + 1) + + // Check that the WASM binary version is correct + err := utils.CheckAndStoreVersions() + if err != nil { + jww.FATAL.Panicf("WASM binary version error: %+v", err) + } } func main() { diff --git a/utils/version.go b/utils/version.go new file mode 100644 index 0000000000000000000000000000000000000000..ab26b463c72f958700840212795c6df63258d6c9 --- /dev/null +++ b/utils/version.go @@ -0,0 +1,97 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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" + "gitlab.com/elixxir/client/bindings" + "os" +) + +// SEMVER is the current semantic version of xxDK WASM. +const SEMVER = "0.0.0" + +// Storage keys. +const ( + semverKey = "xxdkWasmSemanticVersion" + clientVerKey = "xxdkClientSemanticVersion" +) + +// CheckAndStoreVersions checks that the stored xxDK WASM version matches the +// current version and if not, upgrades it. It also stored the current xxDK +// client to storage. +// +// On first load, only the xxDK WASM and xxDK client versions are stored. +func CheckAndStoreVersions() error { + return checkAndStoreVersions( + SEMVER, bindings.GetVersion(), GetLocalStorage()) +} + +func checkAndStoreVersions( + currentWasmVer, currentClientVer string, ls *LocalStorage) error { + // Get the stored client and WASM versions, if they exists + storedClientVer, err := initOrLoadStoredSemver( + clientVerKey, currentClientVer, ls) + if err != nil { + return err + } + storedWasmVer, err := initOrLoadStoredSemver(semverKey, currentWasmVer, ls) + if err != nil { + return err + } + + // Check if client needs an update + if storedClientVer != currentClientVer { + jww.INFO.Printf("xxDK client out of date; upgrading version: v%s → v%s", + storedClientVer, currentClientVer) + } else { + jww.INFO.Printf("xxDK client version is current: v%s", storedClientVer) + } + + // Check if WASM needs an update + if storedWasmVer != currentWasmVer { + jww.INFO.Printf("xxDK WASM out of date; upgrading version: v%s → v%s", + storedWasmVer, currentWasmVer) + } else { + jww.INFO.Printf("xxDK WASM version is current: v%s", storedWasmVer) + } + + // Upgrade path code goes here + + // Save current versions + ls.SetItem(clientVerKey, []byte(currentClientVer)) + ls.SetItem(semverKey, []byte(currentWasmVer)) + + return nil +} + +// initOrLoadStoredSemver returns the semantic version stored at the key in +// 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) + 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)) + return currentVersion, nil + } else { + // If the item exists, but cannot be loaded, return an error + return "", errors.Errorf( + "could not load %s from storage: %+v", key, err) + } + } + + // Return the stored version + return string(storedVersion), nil +} diff --git a/utils/version_test.go b/utils/version_test.go new file mode 100644 index 0000000000000000000000000000000000000000..f54465033749f094b03a705a552e464933bb04d5 --- /dev/null +++ b/utils/version_test.go @@ -0,0 +1,102 @@ +//////////////////////////////////////////////////////////////////////////////// +// 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 ( + "testing" +) + +// 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.Clear() + oldWasmVer := "0.1" + newWasmVer := "1.0" + oldClientVer := "2.5" + newClientVer := "2.6" + err := checkAndStoreVersions(oldWasmVer, oldClientVer, ls) + if err != nil { + t.Errorf("CheckAndStoreVersions error: %+v", err) + } + + // Check client version + storedClientVer, err := ls.GetItem(clientVerKey) + if err != nil { + t.Errorf("Failed to get client version from storage: %+v", err) + } + if string(storedClientVer) != oldClientVer { + t.Errorf("Loaded client version does not match expected."+ + "\nexpected: %s\nreceived: %s", oldClientVer, storedClientVer) + } + + // Check WASM version + storedWasmVer, err := ls.GetItem(semverKey) + if err != nil { + t.Errorf("Failed to get WASM version from storage: %+v", err) + } + if string(storedWasmVer) != oldWasmVer { + t.Errorf("Loaded WASM version does not match expected."+ + "\nexpected: %s\nreceived: %s", oldWasmVer, storedWasmVer) + } + + err = checkAndStoreVersions(newWasmVer, newClientVer, ls) + if err != nil { + t.Errorf("CheckAndStoreVersions error: %+v", err) + } + + // Check client version + storedClientVer, err = ls.GetItem(clientVerKey) + if err != nil { + t.Errorf("Failed to get client version from storage: %+v", err) + } + if string(storedClientVer) != newClientVer { + t.Errorf("Loaded client version does not match expected."+ + "\nexpected: %s\nreceived: %s", newClientVer, storedClientVer) + } + + // Check WASM version + storedWasmVer, err = ls.GetItem(semverKey) + if err != nil { + t.Errorf("Failed to get WASM version from storage: %+v", err) + } + if string(storedWasmVer) != newWasmVer { + t.Errorf("Loaded WASM version does not match expected."+ + "\nexpected: %s\nreceived: %s", newWasmVer, storedWasmVer) + } +} + +// 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() + key := "testKey" + oldVersion := "0.1" + + loadedVersion, err := initOrLoadStoredSemver(key, oldVersion, ls) + if err != nil { + t.Errorf("Failed to intilaise version: %+v", err) + } + + if loadedVersion != oldVersion { + t.Errorf("Loaded version does not match expected."+ + "\nexpected: %s\nreceived: %s", oldVersion, loadedVersion) + } + + loadedVersion, err = initOrLoadStoredSemver(key, "something", ls) + if err != nil { + t.Errorf("Failed to load version: %+v", err) + } + + if loadedVersion != oldVersion { + t.Errorf("Loaded version does not match expected."+ + "\nexpected: %s\nreceived: %s", oldVersion, loadedVersion) + } +}