-
Notifications
You must be signed in to change notification settings - Fork 47
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Refactor WalletPersistence to use Platform Agnostic Storage Provider (#…
…337) * PersistentWallet context to use SecureStore * mock native secure storage (no web variant) * remove platform.select in code, create different platform extension for secure store * updated README.md * implemented platform agnostic storage provider * refactor WalletPersistence to just set/get * WalletPersistence to store wallet separately to work with 2kb constraint * added global mocking * playground:start will kill all container before starting * fixed ../api Co-authored-by: ivan <[email protected]>
- Loading branch information
Showing
18 changed files
with
501 additions
and
189 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export default { | ||
getItemAsync: jest.fn(), | ||
setItemAsync: jest.fn(), | ||
deleteItemAsync: jest.fn() | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
export * from './wallet/persistence' | ||
export * from './storage' | ||
export * from './logging' |
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,138 @@ | ||
import ExpoSecureStore from "expo-secure-store"; | ||
import { EnvironmentNetwork } from "../../environment"; | ||
import { StorageAPI } from "./index"; | ||
|
||
// TODO(fuxingloh): 'jest-expo' only test native (provider.native.ts) by default, need to improve testing capability | ||
|
||
const getItem = jest.spyOn(ExpoSecureStore, 'getItemAsync') | ||
const setItem = jest.spyOn(ExpoSecureStore, 'setItemAsync') | ||
const removeItem = jest.spyOn(ExpoSecureStore, 'deleteItemAsync') | ||
|
||
beforeEach(() => { | ||
jest.clearAllMocks() | ||
}) | ||
|
||
describe('network', () => { | ||
it('should default to Local Playground', async () => { | ||
expect(await StorageAPI.getNetwork()).toBe(EnvironmentNetwork.LocalPlayground) | ||
expect(getItem).toBeCalled() | ||
}); | ||
|
||
it('should call setItem', async () => { | ||
await StorageAPI.setNetwork(EnvironmentNetwork.RemotePlayground) | ||
expect(setItem).toBeCalled() | ||
}); | ||
|
||
it('should get Local Playground', async () => { | ||
getItem.mockResolvedValue(EnvironmentNetwork.LocalPlayground) | ||
expect(await StorageAPI.getNetwork()).toBe(EnvironmentNetwork.LocalPlayground) | ||
}); | ||
|
||
it('should get Local Playground', async () => { | ||
getItem.mockResolvedValue(EnvironmentNetwork.RemotePlayground) | ||
expect(await StorageAPI.getNetwork()).toBe(EnvironmentNetwork.RemotePlayground) | ||
}); | ||
|
||
it('should errored as network is not part of environment', async () => { | ||
await expect(StorageAPI.setNetwork(EnvironmentNetwork.MainNet)) | ||
.rejects.toThrow('network is not part of environment') | ||
}); | ||
}) | ||
|
||
describe('item', () => { | ||
beforeEach(() => { | ||
getItem.mockResolvedValue(EnvironmentNetwork.RemotePlayground) | ||
}) | ||
|
||
it('should getItem with environment and network prefixed key', async () => { | ||
await StorageAPI.getItem('get') | ||
expect(getItem).toBeCalledTimes(2) | ||
expect(getItem).toBeCalledWith('Development.NETWORK') | ||
expect(getItem).toBeCalledWith('Development.Remote Playground.get') | ||
}) | ||
|
||
it('should setItem with environment and network prefixed key', async () => { | ||
await StorageAPI.setItem('set', 'value') | ||
expect(setItem).toBeCalledWith('Development.Remote Playground.set', 'value') | ||
}) | ||
|
||
it('should removeItem with environment and network prefixed key', async () => { | ||
await StorageAPI.removeItem('remove') | ||
expect(removeItem).toBeCalledWith('Development.Remote Playground.remove') | ||
}) | ||
}) | ||
|
||
describe('byte length validation', () => { | ||
beforeEach(() => { | ||
getItem.mockResolvedValue(EnvironmentNetwork.LocalPlayground) | ||
}) | ||
|
||
it('should set if 1 byte length', async () => { | ||
await StorageAPI.setItem('key', '1') | ||
expect(setItem).toBeCalledWith('Development.Local Playground.key', '1') | ||
}) | ||
|
||
it('should set if 100 byte length', async () => { | ||
await StorageAPI.setItem('key', '0000000000100000000020000000003000000000400000000050000000006000000000700000000080000000009000000000') | ||
expect(setItem).toBeCalledWith('Development.Local Playground.key', '0000000000100000000020000000003000000000400000000050000000006000000000700000000080000000009000000000') | ||
}) | ||
|
||
function generateText (length: number, sequence: string) { | ||
let text = '' | ||
for (let i = 0; i < length; i++) { | ||
text += sequence | ||
} | ||
return text | ||
} | ||
|
||
it('should set if 2047 byte length', async () => { | ||
const text = generateText(2047, '0') | ||
|
||
await StorageAPI.setItem('key', text) | ||
expect(setItem).toBeCalledWith('Development.Local Playground.key', text) | ||
}) | ||
|
||
it('should error if 2048 byte length', async () => { | ||
const text = generateText(2048, '0') | ||
const promise = StorageAPI.setItem('key', text) | ||
await expect(promise).rejects.toThrow('value exceed 2048 bytes, unable to setItem') | ||
}) | ||
|
||
it('should error if 2049 byte length', async () => { | ||
const text = generateText(2049, '0') | ||
|
||
const promise = StorageAPI.setItem('key', text) | ||
await expect(promise).rejects.toThrow('value exceed 2048 bytes, unable to setItem') | ||
}) | ||
|
||
describe('utf-8 1-4 char', () => { | ||
it('should set if 3 byte length utf-8', async () => { | ||
const text = generateText(1, '好') | ||
|
||
await StorageAPI.setItem('key', text) | ||
expect(setItem).toBeCalledWith('Development.Local Playground.key', text) | ||
}) | ||
|
||
it('should set if 2046 byte length utf-8', async () => { | ||
const text = generateText(682, '好') | ||
|
||
await StorageAPI.setItem('key', text) | ||
expect(setItem).toBeCalledWith('Development.Local Playground.key', text) | ||
}) | ||
|
||
|
||
it('should error if 2049 byte length', async () => { | ||
const text = generateText(683, '好') | ||
|
||
const promise = StorageAPI.setItem('key', text) | ||
await expect(promise).rejects.toThrow('value exceed 2048 bytes, unable to setItem') | ||
}) | ||
|
||
it('should error if 3072 byte length', async () => { | ||
const text = generateText(1024, '好') | ||
|
||
const promise = StorageAPI.setItem('key', text) | ||
await expect(promise).rejects.toThrow('value exceed 2048 bytes, unable to setItem') | ||
}) | ||
}) | ||
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
import { EnvironmentNetwork, getEnvironment } from '../../environment' | ||
import { StorageProvider } from './provider' | ||
|
||
/** | ||
* @return EnvironmentNetwork if invalid, will be set to `networks[0]` | ||
*/ | ||
async function getNetwork (): Promise<EnvironmentNetwork> { | ||
const env = getEnvironment() | ||
const network = await StorageProvider.getItem(`${env.name}.NETWORK`) | ||
|
||
if ((env.networks as any[]).includes(network)) { | ||
return network as EnvironmentNetwork | ||
} | ||
|
||
await setNetwork(env.networks[0]) | ||
return env.networks[0] | ||
} | ||
|
||
/** | ||
* @param network {EnvironmentNetwork} with set with 'environment' prefixed | ||
*/ | ||
async function setNetwork (network: EnvironmentNetwork): Promise<void> { | ||
const env = getEnvironment() | ||
|
||
if (!env.networks.includes(network)) { | ||
throw new Error('network is not part of environment') | ||
} | ||
|
||
await StorageProvider.setItem(`${env.name}.NETWORK`, network) | ||
} | ||
|
||
async function getKey (key: string): Promise<string> { | ||
const env = getEnvironment() | ||
const network = await getNetwork() | ||
return `${env.name}.${network}.${key}` | ||
} | ||
|
||
/** | ||
* @param key {string} of item with 'environment' and 'network' prefixed | ||
* @return {string | null} | ||
*/ | ||
async function getItem (key: string): Promise<string | null> { | ||
return await StorageProvider.getItem(await getKey(key)) | ||
} | ||
|
||
/** | ||
* @param key {string} of item with 'environment' and 'network' prefixed | ||
* @param value {string} to set | ||
* @throws Error when byte length exceed 2048 bytes | ||
*/ | ||
async function setItem (key: string, value: string): Promise<void> { | ||
if (Buffer.byteLength(value, 'utf-8') >= 2048) { | ||
throw new Error('value exceed 2048 bytes, unable to setItem') | ||
} | ||
return await StorageProvider.setItem(await getKey(key), value) | ||
} | ||
|
||
/** | ||
* @param key {string} of item with 'environment' and 'network' prefixed | ||
*/ | ||
async function removeItem (key: string): Promise<void> { | ||
await StorageProvider.removeItem(await getKey(key)) | ||
} | ||
|
||
export const StorageAPI = { | ||
getNetwork, | ||
setNetwork, | ||
getItem, | ||
setItem, | ||
removeItem | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
import { Provider } from './provider' | ||
|
||
/** | ||
* Provider storage interface for platform agnostic storage provider | ||
*/ | ||
export interface IStorage { | ||
getItem: (key: string) => Promise<string | null> | ||
setItem: (key: string, value: string) => Promise<void> | ||
removeItem: (key: string) => Promise<void> | ||
} | ||
|
||
/** | ||
* Platform agnostic storage provider | ||
*/ | ||
export const StorageProvider = Provider |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import ExpoSecureStore from 'expo-secure-store' | ||
import { IStorage } from './index' | ||
|
||
export const Provider: IStorage = { | ||
getItem: ExpoSecureStore.getItemAsync, | ||
setItem: ExpoSecureStore.setItemAsync, | ||
removeItem: ExpoSecureStore.deleteItemAsync | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import AsyncStorage from '@react-native-async-storage/async-storage' | ||
import { IStorage } from './index' | ||
|
||
/** | ||
* NOTE: We don't officially support web platform yet. | ||
*/ | ||
export const Provider: IStorage = { | ||
getItem: AsyncStorage.getItem, | ||
setItem: AsyncStorage.setItem, | ||
removeItem: AsyncStorage.removeItem | ||
} |
Oops, something went wrong.