mirror of
https://github.com/kremalicious/metamask-extension.git
synced 2024-11-29 15:50:28 +01:00
128 lines
3.3 KiB
TypeScript
128 lines
3.3 KiB
TypeScript
|
import { StorageBackend } from '@metamask/ppom-validator';
|
||
|
|
||
|
type StorageKey = {
|
||
|
name: string;
|
||
|
chainId: string;
|
||
|
};
|
||
|
|
||
|
const validateChecksum = async (
|
||
|
key: StorageKey,
|
||
|
data: ArrayBuffer,
|
||
|
checksum: string,
|
||
|
) => {
|
||
|
const hash = await crypto.subtle.digest('SHA-256', data);
|
||
|
const hashString = Array.from(new Uint8Array(hash))
|
||
|
.map((b) => b.toString(16).padStart(2, '0'))
|
||
|
.join('');
|
||
|
|
||
|
if (hashString !== checksum) {
|
||
|
throw new Error(`Checksum mismatch for key ${key}`);
|
||
|
}
|
||
|
};
|
||
|
|
||
|
export class IndexedDBPPOMStorage implements StorageBackend {
|
||
|
private storeName: string;
|
||
|
|
||
|
private dbVersion: number;
|
||
|
|
||
|
constructor(storeName: string, dbVersion: number) {
|
||
|
this.storeName = storeName;
|
||
|
this.dbVersion = dbVersion;
|
||
|
}
|
||
|
|
||
|
#getObjectStore(mode: IDBTransactionMode): Promise<IDBObjectStore> {
|
||
|
return new Promise((resolve, reject) => {
|
||
|
const request = indexedDB.open(this.storeName, this.dbVersion);
|
||
|
|
||
|
request.onerror = (event: Event) => {
|
||
|
reject(
|
||
|
new Error(
|
||
|
`Failed to open database ${this.storeName}: ${
|
||
|
(event.target as any)?.error
|
||
|
}`,
|
||
|
),
|
||
|
);
|
||
|
};
|
||
|
|
||
|
request.onupgradeneeded = (event) => {
|
||
|
const db = (event.target as IDBOpenDBRequest).result;
|
||
|
|
||
|
if (!db.objectStoreNames.contains(this.storeName)) {
|
||
|
db.createObjectStore(this.storeName, {
|
||
|
keyPath: ['name', 'chainId'],
|
||
|
});
|
||
|
}
|
||
|
};
|
||
|
|
||
|
request.onsuccess = (event) => {
|
||
|
const db = (event.target as IDBOpenDBRequest).result;
|
||
|
const transaction = db.transaction([this.storeName], mode);
|
||
|
const objectStore = transaction.objectStore(this.storeName);
|
||
|
resolve(objectStore);
|
||
|
};
|
||
|
});
|
||
|
}
|
||
|
|
||
|
private async objectStoreAction(
|
||
|
method: 'get' | 'delete' | 'put' | 'getAllKeys',
|
||
|
args?: any,
|
||
|
mode: IDBTransactionMode = 'readonly',
|
||
|
): Promise<any> {
|
||
|
return new Promise<Event>((resolve, reject) => {
|
||
|
this.#getObjectStore(mode)
|
||
|
.then((objectStore) => {
|
||
|
const request = objectStore[method](args);
|
||
|
|
||
|
request.onsuccess = async (event) => {
|
||
|
resolve(event);
|
||
|
};
|
||
|
|
||
|
request.onerror = (event) => {
|
||
|
reject(
|
||
|
new Error(
|
||
|
`Error in indexDB operation ${method}: ${
|
||
|
(event.target as any)?.error
|
||
|
}`,
|
||
|
),
|
||
|
);
|
||
|
};
|
||
|
})
|
||
|
.catch((error) => {
|
||
|
reject(error);
|
||
|
});
|
||
|
});
|
||
|
}
|
||
|
|
||
|
async read(key: StorageKey, checksum: string): Promise<ArrayBuffer> {
|
||
|
const event = await this.objectStoreAction('get', [key.name, key.chainId]);
|
||
|
const data = (event.target as any)?.result?.data;
|
||
|
await validateChecksum(key, data, checksum);
|
||
|
return data;
|
||
|
}
|
||
|
|
||
|
async write(
|
||
|
key: StorageKey,
|
||
|
data: ArrayBuffer,
|
||
|
checksum: string,
|
||
|
): Promise<void> {
|
||
|
await validateChecksum(key, data, checksum);
|
||
|
await this.objectStoreAction('put', { ...key, data }, 'readwrite');
|
||
|
}
|
||
|
|
||
|
async delete(key: StorageKey): Promise<void> {
|
||
|
await this.objectStoreAction(
|
||
|
'delete',
|
||
|
[key.name, key.chainId],
|
||
|
'readwrite',
|
||
|
);
|
||
|
}
|
||
|
|
||
|
async dir(): Promise<StorageKey[]> {
|
||
|
const event = await this.objectStoreAction('getAllKeys');
|
||
|
return (event.target as any)?.result.map(([name, chainId]: string[]) => ({
|
||
|
name,
|
||
|
chainId,
|
||
|
}));
|
||
|
}
|
||
|
}
|