diff --git a/blue_modules/analytics.ts b/blue_modules/analytics.ts index 562a6eb76..819ccfab9 100644 --- a/blue_modules/analytics.ts +++ b/blue_modules/analytics.ts @@ -1,6 +1,8 @@ import { getUniqueId } from 'react-native-device-info'; import Bugsnag from '@bugsnag/react-native'; -import BlueApp from '../BlueApp'; +import { BlueApp as BlueAppClass } from '../class'; + +const BlueApp = BlueAppClass.getInstance(); /** * in case Bugsnag was started, but user decided to opt out while using the app, we have this diff --git a/BlueApp.ts b/blue_modules/start-and-decrypt.ts similarity index 88% rename from BlueApp.ts rename to blue_modules/start-and-decrypt.ts index 2fb4c00e9..4616d4fb5 100644 --- a/BlueApp.ts +++ b/blue_modules/start-and-decrypt.ts @@ -1,11 +1,11 @@ import { Platform } from 'react-native'; -import { initCurrencyDaemon } from './blue_modules/currency'; -import Biometric from './class/biometrics'; -import prompt from './helpers/prompt'; -import loc from './loc'; -import { AppStorage } from './class/'; +import { initCurrencyDaemon } from '../blue_modules/currency'; +import Biometric from '../class/biometrics'; +import prompt from '../helpers/prompt'; +import loc from '../loc'; +import { BlueApp as BlueAppClass } from '../class/'; -const BlueApp = new AppStorage(); +const BlueApp = BlueAppClass.getInstance(); // If attempt reaches 10, a wipe keychain option will be provided to the user. let unlockAttempt = 0; diff --git a/blue_modules/storage-context.tsx b/blue_modules/storage-context.tsx index acbae6658..d9edbf73b 100644 --- a/blue_modules/storage-context.tsx +++ b/blue_modules/storage-context.tsx @@ -1,9 +1,9 @@ import React, { createContext, useEffect, useState } from 'react'; import { useAsyncStorage } from '@react-native-async-storage/async-storage'; -import BlueApp, { startAndDecrypt } from '../BlueApp'; +import { startAndDecrypt } from './start-and-decrypt'; import Notifications from '../blue_modules/notifications'; -import { LegacyWallet, TTXMetadata, WatchOnlyWallet } from '../class'; +import { LegacyWallet, TTXMetadata, WatchOnlyWallet, BlueApp as BlueAppClass } from '../class'; import type { TWallet } from '../class/wallets/types'; import presentAlert from '../components/Alert'; import loc, { STORAGE_KEY as LOC_STORAGE_KEY } from '../loc'; @@ -13,6 +13,8 @@ import { PREFERRED_CURRENCY_STORAGE_KEY } from './currency'; import triggerHapticFeedback, { HapticFeedbackTypes } from './hapticFeedback'; import A from '../blue_modules/analytics'; +const BlueApp = BlueAppClass.getInstance(); + // hashmap of timestamps we _started_ refetching some wallet const _lastTimeTriedToRefetchWallet: { [walletID: string]: number } = {}; diff --git a/class/app-storage.ts b/class/blue-app.ts similarity index 95% rename from class/app-storage.ts rename to class/blue-app.ts index 83b05146e..da9067d85 100644 --- a/class/app-storage.ts +++ b/class/blue-app.ts @@ -43,14 +43,16 @@ type TRealmTransaction = { const isReactNative = typeof navigator !== 'undefined' && navigator?.product === 'ReactNative'; -export class AppStorage { +export class BlueApp { static FLAG_ENCRYPTED = 'data_encrypted'; static LNDHUB = 'lndhub'; static ADVANCED_MODE_ENABLED = 'advancedmodeenabled'; static DO_NOT_TRACK = 'donottrack'; static HANDOFF_STORAGE_KEY = 'HandOff'; - static keys2migrate = [AppStorage.HANDOFF_STORAGE_KEY, AppStorage.DO_NOT_TRACK, AppStorage.ADVANCED_MODE_ENABLED]; + private static _instance: BlueApp | null = null; + + static keys2migrate = [BlueApp.HANDOFF_STORAGE_KEY, BlueApp.DO_NOT_TRACK, BlueApp.ADVANCED_MODE_ENABLED]; public cachedPassword?: false | string; public tx_metadata: TTXMetadata; @@ -62,13 +64,21 @@ export class AppStorage { this.cachedPassword = false; } + static getInstance(): BlueApp { + if (!BlueApp._instance) { + BlueApp._instance = new BlueApp(); + } + + return BlueApp._instance; + } + async migrateKeys() { // do not migrate keys if we are not in RN env if (!isReactNative) { return; } - for (const key of AppStorage.keys2migrate) { + for (const key of BlueApp.keys2migrate) { try { const value = await RNSecureKeyStore.get(key); if (value) { @@ -126,9 +136,9 @@ export class AppStorage { storageIsEncrypted = async (): Promise<boolean> => { let data; try { - data = await this.getItemWithFallbackToRealm(AppStorage.FLAG_ENCRYPTED); + data = await this.getItemWithFallbackToRealm(BlueApp.FLAG_ENCRYPTED); } catch (error: any) { - console.warn('error reading `' + AppStorage.FLAG_ENCRYPTED + '` key:', error.message); + console.warn('error reading `' + BlueApp.FLAG_ENCRYPTED + '` key:', error.message); return false; } @@ -190,7 +200,7 @@ export class AppStorage { data = JSON.stringify(data); this.cachedPassword = password; await this.setItem('data', data); - await this.setItem(AppStorage.FLAG_ENCRYPTED, '1'); + await this.setItem(BlueApp.FLAG_ENCRYPTED, '1'); }; /** @@ -405,7 +415,7 @@ export class AppStorage { unserializedWallet = LightningCustodianWallet.fromJson(key) as unknown as LightningCustodianWallet; let lndhub: false | any = false; try { - lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB); + lndhub = await AsyncStorage.getItem(BlueApp.LNDHUB); } catch (error) { console.warn(error); } @@ -674,12 +684,12 @@ export class AppStorage { } await this.setItem('data', JSON.stringify(data)); - await this.setItem(AppStorage.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); + await this.setItem(BlueApp.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); // now, backing up same data in realm: const realmkeyValue = await this.openRealmKeyValue(); this.saveToRealmKeyValue(realmkeyValue, 'data', JSON.stringify(data)); - this.saveToRealmKeyValue(realmkeyValue, AppStorage.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); + this.saveToRealmKeyValue(realmkeyValue, BlueApp.FLAG_ENCRYPTED, this.cachedPassword ? '1' : ''); realmkeyValue.close(); } catch (error: any) { console.error('save to disk exception:', error.message); @@ -834,50 +844,50 @@ export class AppStorage { isAdvancedModeEnabled = async (): Promise<boolean> => { try { - return !!(await AsyncStorage.getItem(AppStorage.ADVANCED_MODE_ENABLED)); + return !!(await AsyncStorage.getItem(BlueApp.ADVANCED_MODE_ENABLED)); } catch (_) {} return false; }; setIsAdvancedModeEnabled = async (value: boolean) => { - await AsyncStorage.setItem(AppStorage.ADVANCED_MODE_ENABLED, value ? '1' : ''); + await AsyncStorage.setItem(BlueApp.ADVANCED_MODE_ENABLED, value ? '1' : ''); }; isHandoffEnabled = async (): Promise<boolean> => { try { - return !!(await AsyncStorage.getItem(AppStorage.HANDOFF_STORAGE_KEY)); + return !!(await AsyncStorage.getItem(BlueApp.HANDOFF_STORAGE_KEY)); } catch (_) {} return false; }; setIsHandoffEnabled = async (value: boolean): Promise<void> => { - await AsyncStorage.setItem(AppStorage.HANDOFF_STORAGE_KEY, value ? '1' : ''); + await AsyncStorage.setItem(BlueApp.HANDOFF_STORAGE_KEY, value ? '1' : ''); }; isDoNotTrackEnabled = async (): Promise<boolean> => { try { - const keyExists = await AsyncStorage.getItem(AppStorage.DO_NOT_TRACK); + const keyExists = await AsyncStorage.getItem(BlueApp.DO_NOT_TRACK); if (keyExists !== null) { const doNotTrackValue = !!keyExists; if (doNotTrackValue) { await DefaultPreference.setName('group.io.bluewallet.bluewallet'); - await DefaultPreference.set(AppStorage.DO_NOT_TRACK, '1'); - AsyncStorage.removeItem(AppStorage.DO_NOT_TRACK); + await DefaultPreference.set(BlueApp.DO_NOT_TRACK, '1'); + AsyncStorage.removeItem(BlueApp.DO_NOT_TRACK); } else { - return Boolean(await DefaultPreference.get(AppStorage.DO_NOT_TRACK)); + return Boolean(await DefaultPreference.get(BlueApp.DO_NOT_TRACK)); } } } catch (_) {} - const doNotTrackValue = await DefaultPreference.get(AppStorage.DO_NOT_TRACK); + const doNotTrackValue = await DefaultPreference.get(BlueApp.DO_NOT_TRACK); return doNotTrackValue === '1' || false; }; setDoNotTrack = async (value: boolean) => { await DefaultPreference.setName('group.io.bluewallet.bluewallet'); if (value) { - await DefaultPreference.set(AppStorage.DO_NOT_TRACK, '1'); + await DefaultPreference.set(BlueApp.DO_NOT_TRACK, '1'); } else { - await DefaultPreference.clear(AppStorage.DO_NOT_TRACK); + await DefaultPreference.clear(BlueApp.DO_NOT_TRACK); } }; diff --git a/class/deeplink-schema-match.ts b/class/deeplink-schema-match.ts index 758a1a96c..3fdd1b77f 100644 --- a/class/deeplink-schema-match.ts +++ b/class/deeplink-schema-match.ts @@ -5,7 +5,7 @@ import URL from 'url'; import { readFileOutsideSandbox } from '../blue_modules/fs'; import { Chain } from '../models/bitcoinUnits'; -import { AppStorage, LightningCustodianWallet, WatchOnlyWallet } from './'; +import { BlueApp, LightningCustodianWallet, WatchOnlyWallet } from './'; import Azteco from './azteco'; import Lnurl from './lnurl'; import type { TWallet } from './wallets/types'; @@ -231,7 +231,7 @@ class DeeplinkSchemaMatch { w.setLabel(w.typeReadable); try { - const lndhub = await AsyncStorage.getItem(AppStorage.LNDHUB); + const lndhub = await AsyncStorage.getItem(BlueApp.LNDHUB); if (lndhub) { w.setBaseURI(lndhub); w.init(); diff --git a/class/index.ts b/class/index.ts index 34c077982..e5fd684d6 100644 --- a/class/index.ts +++ b/class/index.ts @@ -18,4 +18,4 @@ export * from './wallets/multisig-hd-wallet'; export * from './wallets/slip39-wallets'; export * from './hd-segwit-bech32-transaction'; export * from './multisig-cosigner'; -export * from './app-storage'; +export * from './blue-app'; diff --git a/screen/settings/lightningSettings.tsx b/screen/settings/lightningSettings.tsx index 2b4ed79a5..80e728d52 100644 --- a/screen/settings/lightningSettings.tsx +++ b/screen/settings/lightningSettings.tsx @@ -12,7 +12,7 @@ import DeeplinkSchemaMatch from '../../class/deeplink-schema-match'; import presentAlert from '../../components/Alert'; import { requestCameraAuthorization } from '../../helpers/scan-qr'; import { Button } from '../../components/Button'; -import { AppStorage } from '../../class'; +import { BlueApp } from '../../class'; const styles = StyleSheet.create({ uri: { @@ -62,7 +62,7 @@ const LightningSettings: React.FC & { navigationOptions: NavigationOptionsGetter }); useEffect(() => { - AsyncStorage.getItem(AppStorage.LNDHUB) + AsyncStorage.getItem(BlueApp.LNDHUB) .then(value => setURI(value ?? undefined)) .then(() => setIsLoading(false)) .catch(() => setIsLoading(false)); @@ -101,9 +101,9 @@ const LightningSettings: React.FC & { navigationOptions: NavigationOptionsGetter // validating only if its not empty. empty means use default } if (URI) { - await AsyncStorage.setItem(AppStorage.LNDHUB, URI); + await AsyncStorage.setItem(BlueApp.LNDHUB, URI); } else { - await AsyncStorage.removeItem(AppStorage.LNDHUB); + await AsyncStorage.removeItem(BlueApp.LNDHUB); } presentAlert({ message: loc.settings.lightning_saved }); } catch (error) { diff --git a/screen/wallets/add.tsx b/screen/wallets/add.tsx index 5497a001f..e9ba682e5 100644 --- a/screen/wallets/add.tsx +++ b/screen/wallets/add.tsx @@ -19,7 +19,7 @@ import { BlueButtonLink, BlueFormLabel, BlueSpacing20, BlueSpacing40, BlueText } import triggerHapticFeedback, { HapticFeedbackTypes } from '../../blue_modules/hapticFeedback'; import { BlueStorageContext } from '../../blue_modules/storage-context'; import { - AppStorage, + BlueApp, HDSegwitBech32Wallet, HDSegwitP2SHWallet, LightningCustodianWallet, @@ -149,7 +149,7 @@ const WalletsAdd: React.FC = () => { }; useEffect(() => { - AsyncStorage.getItem(AppStorage.LNDHUB) + AsyncStorage.getItem(BlueApp.LNDHUB) .then(url => (url ? setWalletBaseURI(url) : setWalletBaseURI(''))) .catch(() => setWalletBaseURI('')) .finally(() => setIsLoading(false)); diff --git a/scripts/find-unused-loc.js b/scripts/find-unused-loc.js index 4de004f37..bf1b510cb 100644 --- a/scripts/find-unused-loc.js +++ b/scripts/find-unused-loc.js @@ -3,7 +3,7 @@ const path = require('path'); const mainLocFile = './loc/en.json'; const dirsToInterate = ['components', 'screen', 'blue_modules', 'class', 'hooks', 'helpers']; -const addFiles = ['BlueComponents.js', 'App.js', 'BlueApp.ts', 'Navigation.tsx']; +const addFiles = ['BlueComponents.js', 'App.js', 'Navigation.tsx']; const allowedLocPrefixes = ['loc.lnurl_auth', 'loc.units']; const allLocKeysHashmap = {}; // loc key -> used or not diff --git a/tests/unit/storage.test.js b/tests/unit/storage.test.js index e8e762eae..a14b5098e 100644 --- a/tests/unit/storage.test.js +++ b/tests/unit/storage.test.js @@ -1,6 +1,6 @@ import assert from 'assert'; import AsyncStorage from '@react-native-async-storage/async-storage'; -import { SegwitP2SHWallet, AppStorage } from '../../class'; +import { SegwitP2SHWallet, BlueApp } from '../../class'; jest.mock('../../blue_modules/BlueElectrum', () => { return { @@ -9,8 +9,8 @@ jest.mock('../../blue_modules/BlueElectrum', () => { }); it('Appstorage - loadFromDisk works', async () => { - /** @type {AppStorage} */ - const Storage = new AppStorage(); + /** @type {BlueApp} */ + const Storage = new BlueApp(); const w = new SegwitP2SHWallet(); w.setLabel('testlabel'); await w.generate(); @@ -19,7 +19,7 @@ it('Appstorage - loadFromDisk works', async () => { // saved, now trying to load - const Storage2 = new AppStorage(); + const Storage2 = new BlueApp(); await Storage2.loadFromDisk(); assert.strictEqual(Storage2.wallets.length, 1); assert.strictEqual(Storage2.wallets[0].getLabel(), 'testlabel'); @@ -29,15 +29,15 @@ it('Appstorage - loadFromDisk works', async () => { // emulating encrypted storage (and testing flag) await AsyncStorage.setItem('data', false); - await AsyncStorage.setItem(AppStorage.FLAG_ENCRYPTED, '1'); - const Storage3 = new AppStorage(); + await AsyncStorage.setItem(BlueApp.FLAG_ENCRYPTED, '1'); + const Storage3 = new BlueApp(); isEncrypted = await Storage3.storageIsEncrypted(); assert.ok(isEncrypted); }); it('Appstorage - encryptStorage & load encrypted storage works', async () => { - /** @type {AppStorage} */ - const Storage = new AppStorage(); + /** @type {BlueApp} */ + const Storage = new BlueApp(); let w = new SegwitP2SHWallet(); w.setLabel('testlabel'); await w.generate(); @@ -52,7 +52,7 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => { // saved, now trying to load, using good password - let Storage2 = new AppStorage(); + let Storage2 = new BlueApp(); isEncrypted = await Storage2.storageIsEncrypted(); assert.ok(isEncrypted); let loadResult = await Storage2.loadFromDisk('password'); @@ -62,7 +62,7 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => { // now trying to load, using bad password - Storage2 = new AppStorage(); + Storage2 = new BlueApp(); isEncrypted = await Storage2.storageIsEncrypted(); assert.ok(isEncrypted); loadResult = await Storage2.loadFromDisk('passwordBAD'); @@ -72,7 +72,7 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => { // now, trying case with adding data after decrypt. // saveToDisk should be handled correctly - Storage2 = new AppStorage(); + Storage2 = new BlueApp(); isEncrypted = await Storage2.storageIsEncrypted(); assert.ok(isEncrypted); loadResult = await Storage2.loadFromDisk('password'); @@ -87,7 +87,7 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => { assert.strictEqual(Storage2.wallets[1].getLabel(), 'testlabel2'); await Storage2.saveToDisk(); // saved to encrypted storage after load. next load should be successfull - Storage2 = new AppStorage(); + Storage2 = new BlueApp(); isEncrypted = await Storage2.storageIsEncrypted(); assert.ok(isEncrypted); loadResult = await Storage2.loadFromDisk('password'); @@ -108,13 +108,13 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => { await Storage2.saveToDisk(); // now, will try to load & decrypt with real password and with fake password // real: - let Storage3 = new AppStorage(); + let Storage3 = new BlueApp(); loadResult = await Storage3.loadFromDisk('password'); assert.ok(loadResult); assert.strictEqual(Storage3.wallets.length, 2); assert.strictEqual(Storage3.wallets[0].getLabel(), 'testlabel'); // fake: - Storage3 = new AppStorage(); + Storage3 = new BlueApp(); loadResult = await Storage3.loadFromDisk('fakePassword'); assert.ok(loadResult); assert.strictEqual(Storage3.wallets.length, 1); @@ -122,8 +122,8 @@ it('Appstorage - encryptStorage & load encrypted storage works', async () => { }); it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load storage works', async () => { - /** @type {AppStorage} */ - const Storage = new AppStorage(); + /** @type {BlueApp} */ + const Storage = new BlueApp(); let w = new SegwitP2SHWallet(); w.setLabel('testlabel'); await w.generate(); @@ -138,7 +138,7 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s // saved, now trying to load, using good password - let Storage2 = new AppStorage(); + let Storage2 = new BlueApp(); isEncrypted = await Storage2.storageIsEncrypted(); assert.ok(isEncrypted); let loadResult = await Storage2.loadFromDisk('password'); @@ -148,7 +148,7 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s // now trying to load, using bad password - Storage2 = new AppStorage(); + Storage2 = new BlueApp(); isEncrypted = await Storage2.storageIsEncrypted(); assert.ok(isEncrypted); loadResult = await Storage2.loadFromDisk('passwordBAD'); @@ -158,7 +158,7 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s // now, trying case with adding data after decrypt. // saveToDisk should be handled correctly - Storage2 = new AppStorage(); + Storage2 = new BlueApp(); isEncrypted = await Storage2.storageIsEncrypted(); assert.ok(isEncrypted); loadResult = await Storage2.loadFromDisk('password'); @@ -173,7 +173,7 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s assert.strictEqual(Storage2.wallets[1].getLabel(), 'testlabel2'); await Storage2.saveToDisk(); // saved to encrypted storage after load. next load should be successfull - Storage2 = new AppStorage(); + Storage2 = new BlueApp(); isEncrypted = await Storage2.storageIsEncrypted(); assert.ok(isEncrypted); loadResult = await Storage2.loadFromDisk('password'); @@ -194,13 +194,13 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s await Storage2.saveToDisk(); // now, will try to load & decrypt with real password and with fake password // real: - let Storage3 = new AppStorage(); + let Storage3 = new BlueApp(); loadResult = await Storage3.loadFromDisk('password'); assert.ok(loadResult); assert.strictEqual(Storage3.wallets.length, 2); assert.strictEqual(Storage3.wallets[0].getLabel(), 'testlabel'); // fake: - Storage3 = new AppStorage(); + Storage3 = new BlueApp(); loadResult = await Storage3.loadFromDisk('fakePassword'); assert.ok(loadResult); assert.strictEqual(Storage3.wallets.length, 1); @@ -208,7 +208,7 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s // now will decrypt storage. label of wallet should be testlabel - const Storage4 = new AppStorage(); + const Storage4 = new BlueApp(); isEncrypted = await Storage4.storageIsEncrypted(); assert.ok(isEncrypted); loadResult = await Storage4.loadFromDisk('password'); @@ -216,7 +216,7 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s const decryptStorageResult = await Storage4.decryptStorage('password'); assert.ok(decryptStorageResult); - const Storage5 = new AppStorage(); + const Storage5 = new BlueApp(); isEncrypted = await Storage5.storageIsEncrypted(); assert.strictEqual(isEncrypted, false); const storage5loadResult = await Storage5.loadFromDisk(); @@ -227,8 +227,8 @@ it('Appstorage - encryptStorage & load encrypted, then decryptStorage and load s }); it('can decrypt storage that is second in a list of buckets; and isPasswordInUse() works', async () => { - /** @type {AppStorage} */ - const Storage = new AppStorage(); + /** @type {BlueApp} */ + const Storage = new BlueApp(); let w = new SegwitP2SHWallet(); w.setLabel('testlabel'); await w.generate(); @@ -255,7 +255,7 @@ it('can decrypt storage that is second in a list of buckets; and isPasswordInUse // now will decrypt storage. will try to decrypt FAKE storage (second in the list) while // currently decrypted is the MAIN (non-fake) storage. this should throw an exception - const Storage4 = new AppStorage(); + const Storage4 = new BlueApp(); isEncrypted = await Storage4.storageIsEncrypted(); assert.ok(isEncrypted); let loadResult = await Storage4.loadFromDisk('password'); @@ -274,7 +274,7 @@ it('can decrypt storage that is second in a list of buckets; and isPasswordInUse // storage, purging other buckets. this should be possible since if user wants to shoot himsel in the foot // he should be able to do it. - const Storage5 = new AppStorage(); + const Storage5 = new BlueApp(); isEncrypted = await Storage5.storageIsEncrypted(); assert.ok(isEncrypted); loadResult = await Storage5.loadFromDisk('fakePassword'); @@ -287,7 +287,7 @@ it('can decrypt storage that is second in a list of buckets; and isPasswordInUse // now we will decrypt storage. label of wallet should be testlabel - const Storage6 = new AppStorage(); + const Storage6 = new BlueApp(); isEncrypted = await Storage6.storageIsEncrypted(); assert.ok(isEncrypted); loadResult = await Storage6.loadFromDisk('fakePassword'); @@ -295,7 +295,7 @@ it('can decrypt storage that is second in a list of buckets; and isPasswordInUse const decryptStorageResult = await Storage6.decryptStorage('fakePassword'); assert.ok(decryptStorageResult); - const Storage7 = new AppStorage(); + const Storage7 = new BlueApp(); isEncrypted = await Storage7.storageIsEncrypted(); assert.strictEqual(isEncrypted, false); const storage5loadResult = await Storage7.loadFromDisk(); @@ -304,6 +304,6 @@ it('can decrypt storage that is second in a list of buckets; and isPasswordInUse }); it('Appstorage - hashIt() works', async () => { - const storage = new AppStorage(); + const storage = new BlueApp(); assert.strictEqual(storage.hashIt('hello'), '2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824'); });