Skip to content

Commit

Permalink
refactor: add serialized type to useStorageRef
Browse files Browse the repository at this point in the history
  • Loading branch information
davidyuk committed Feb 19, 2025
1 parent 2c797d0 commit 0300170
Show file tree
Hide file tree
Showing 7 changed files with 43 additions and 25 deletions.
2 changes: 1 addition & 1 deletion src/composables/auth.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ export const useAuth = createCustomScopedComposable(() => {
);

/** Part of the `encryptionKey` that is used to encrypt/decrypt protected data */
const encryptionSalt = useStorageRef<Uint8Array | null>(
const encryptionSalt = useStorageRef<Uint8Array | null, string>(
null,
STORAGE_KEYS.encryptionSalt,
{
Expand Down
3 changes: 2 additions & 1 deletion src/composables/balances.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ import { useStorageRef } from './storageRef';
import { useNetworks } from './networks';

type Balances = Record<AccountAddress, Balance>;
type BalancesSerialized = Record<AccountAddress, string>;

let composableInitialized = false;

Expand All @@ -24,7 +25,7 @@ const POLLING_INTERVAL = 5000;

const initPollingWatcher = createPollingBasedOnMountedComponents(POLLING_INTERVAL);

const balances = useStorageRef<Balances>({}, STORAGE_KEYS.balances, {
const balances = useStorageRef<Balances, BalancesSerialized>({}, STORAGE_KEYS.balances, {
serializer: {
read: (val) => mapValues(val, (balance: BalanceRaw) => new BigNumber(balance)),
write: (val) => mapValues(val, (balance) => balance.toFixed()),
Expand Down
13 changes: 10 additions & 3 deletions src/composables/invites.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import {
AE_AMOUNT_FORMATS,
Encoded,
} from '@aeternity/aepp-sdk';
import type { AccountAddress, IInvite } from '@/types';
import type { AccountAddress, IInvite, IInviteSerialized } from '@/types';
import { STORAGE_KEYS } from '@/constants';
import { tg } from '@/popup/plugins/i18n';
import { getAccountFromSecret } from '@/protocols/aeternity/helpers';
Expand All @@ -11,13 +11,20 @@ import { useStorageRef } from './storageRef';
import { useModals } from './modals';
import { useAeSdk } from './aeSdk';

const invites = useStorageRef<IInvite[]>(
const invites = useStorageRef<IInvite[], IInviteSerialized[]>(
[],
STORAGE_KEYS.invites,
{
migrations: [
migrateInvitesVuexToComposable,
],
serializer: {
read: (arr) => arr
// TODO: remove `as any` after updating `Buffer.from` type
.map(({ secretKey, ...item }) => ({ ...item, secretKey: Buffer.from(secretKey as any) })),
write: (arr) => arr
.map(({ secretKey, ...item }) => ({ ...item, secretKey: secretKey.toJSON() })),
},
},
);

Expand All @@ -27,7 +34,7 @@ export function useInvites() {

function addInvite(secretKey: Buffer) {
invites.value.unshift({
secretKey: secretKey.toJSON(),
secretKey,
createdAt: Date.now(),
});
}
Expand Down
31 changes: 17 additions & 14 deletions src/composables/storageRef.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { asyncPipe } from '@/utils';
import { SecureMobileStorage } from '@/lib/SecureMobileStorage';
import { IS_MOBILE_APP } from '@/constants';

export interface ICreateStorageRefOptions<T> {
export interface ICreateStorageRefOptions<T, TSerialized = unknown> {
/**
* Enable secure storage for the data.
* Mobile app only.
Expand All @@ -19,10 +19,10 @@ export interface ICreateStorageRefOptions<T> {
* Callbacks run on the data that will be saved and read from the browser storage.
*/
serializer?: {
read: (val: T) => any;
write: (val: T) => any;
read: (val: TSerialized) => T;
write: (val: T) => TSerialized;
};
migrations?: Migration<T>[];
migrations?: Migration<TSerialized>[];
/**
* Allows to ensure the state is already synced with browser storage and migrated.
*/
Expand All @@ -38,14 +38,17 @@ export interface ICreateStorageRefOptions<T> {
* Also allows to sync the state between the app and the extension background.
* Inspired by `useStorage`: https://vueuse.org/core/useStorage/
*/
export function useStorageRef<T = string | object | any[]>(
export function useStorageRef<T = string | object | any[], TSerialized = unknown>(
initialState: T,
storageKey: StorageKey,
options: ICreateStorageRefOptions<T> = {},
options: ICreateStorageRefOptions<T, TSerialized> = {},
) {
const {
enableSecureStorage = false,
serializer,
serializer = {
read: (a: unknown) => a,
write: (a: unknown) => a,
} as unknown as NonNullable<typeof options['serializer']>,
backgroundSync = false,
migrations,
onRestored,
Expand All @@ -56,28 +59,28 @@ export function useStorageRef<T = string | object | any[]>(
const state = ref(initialState) as Ref<T>; // https://github.com/vuejs/core/issues/2136/
const storage = (enableSecureStorage && IS_MOBILE_APP) ? SecureMobileStorage : WalletStorage;

async function setLocalState(val: T | null) {
async function setLocalState(val: TSerialized | null) {
if (val !== null) {
watcherDisabled = true;
state.value = (serializer?.read) ? await serializer.read(val) : val;
state.value = await serializer.read(val);
setTimeout(() => { watcherDisabled = false; }, 0);
}
}

async function setStorageState(val: T | null) {
storage.set(storageKey, (val && serializer?.write) ? await serializer.write(val) : val);
storage.set(storageKey, val ? await serializer.write(val) : val);
}

// Restore state and run watchers
(async () => {
let restoredValue = await storage.get<T | null>(storageKey);
let restoredValue = await storage.get<TSerialized | null>(storageKey);
if (migrations?.length) {
restoredValue = await asyncPipe<T | null>(migrations)(restoredValue!);
restoredValue = await asyncPipe<TSerialized | null>(migrations)(restoredValue!);
if (restoredValue !== null) {
await setStorageState(restoredValue);
storage.set(storageKey, restoredValue);
}
}
onRestored?.(restoredValue);
onRestored?.(restoredValue == null ? restoredValue as T : await serializer.read(restoredValue));
await setLocalState(restoredValue);

/**
Expand Down
4 changes: 2 additions & 2 deletions src/migrations/006-invites-vuex-to-composable.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { IInvite, Migration } from '@/types';
import type { IInviteSerialized, Migration } from '@/types';
import { collectVuexState } from './migrationHelpers';

const migration: Migration<IInvite[]> = async (restoredValue: IInvite[]) => {
const migration: Migration<IInviteSerialized[]> = async (restoredValue: IInviteSerialized[]) => {
if (!restoredValue?.length) {
const invites = (await collectVuexState())?.invites?.invites;
if (invites?.length) {
Expand Down
6 changes: 3 additions & 3 deletions src/popup/components/InviteItem.vue
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ export default defineComponent({
const link = computed(() => {
// nm_ prefix was chosen as a dummy to decode from base58Check
const secretKey = (encode(Buffer.from(props.secretKey), Encoding.Name)).slice(3);
const secretKey = (encode(props.secretKey, Encoding.Name)).slice(3);
return new URL(
`${router
.resolve({ name: ROUTE_INVITE_CLAIM })
Expand All @@ -171,7 +171,7 @@ export default defineComponent({
);
});
const address = computed(() => getAccountFromSecret(Buffer.from(props.secretKey)).address);
const address = computed(() => getAccountFromSecret(props.secretKey).address);
function deleteItem() {
removeInvite(props.secretKey);
Expand All @@ -192,7 +192,7 @@ export default defineComponent({
emit('loading', true);
try {
await claimInvite({
secretKey: Buffer.from(props.secretKey),
secretKey: props.secretKey,
recipientId: getLastActiveProtocolAccount(PROTOCOLS.aeternity)?.address!,
isMax: true,
});
Expand Down
9 changes: 8 additions & 1 deletion src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -899,10 +899,17 @@ export interface IFormSelectOption {
export type Migration<T = any> = (restoredValue: T | any) => Promise<T>;

export interface IInvite {
secretKey: object;
secretKey: Buffer;
createdAt: number;
}

export interface IInviteSerialized extends Omit<IInvite, 'secretKey'> {
secretKey: {
type: 'Buffer';
data: number[];
};
}

export interface IHistoryItem {
url: string;
cleanPath?: string;
Expand Down

0 comments on commit 0300170

Please sign in to comment.