Skip to content

Commit 1f44b88

Browse files
authored
fix:add indexdb storage (#1339)
* fix: add new indexdb storage replacement for localstorage * fix: add indexdb storage * fix: test improvement * fix: test should validate when we don't use indexDB or localStorage * fix: mock native implementation in fixtures * fix: web mocks platform should be web * fix: remove unused import * fix: catching transaction exceptions, prevent unhandled exceptions * fix: no longer needed * fix: remove fakeIndexDB from fixtures
1 parent 26005b4 commit 1f44b88

File tree

5 files changed

+109
-9
lines changed

5 files changed

+109
-9
lines changed

packages/sdk-multichain/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
"@types/ws": "^8.18.1",
3636
"@vitest/coverage-v8": "^3.2.4",
3737
"esbuild-plugin-umd-wrapper": "^3.0.0",
38+
"fake-indexeddb": "^6.0.1",
3839
"jsdom": "^26.1.0",
3940
"nock": "^14.0.4",
4041
"prettier": "^3.3.3",

packages/sdk-multichain/src/fixtures.test.ts

Lines changed: 20 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
/** biome-ignore-all lint/suspicious/noExplicitAny: Tests require it */
2+
/** biome-ignore-all lint/suspicious/noAsyncPromiseExecutor: ok for tests */
23
/** biome-ignore-all lint/style/noNonNullAssertion: Tests require it */
34
/**
45
* Test fixtures and utilities for the Multichain SDK tests
56
* This file is excluded from test discovery via vitest.config.ts
67
*/
7-
88
// Additional imports for standardized setup functions
99
import AsyncStorage from '@react-native-async-storage/async-storage';
1010
import { JSDOM as Page } from 'jsdom';
@@ -19,6 +19,7 @@ import { createMetamaskSDK as createMetamaskSDKWeb } from './index.browser';
1919
import { createMetamaskSDK as createMetamaskSDKRN } from './index.native';
2020
import { createMetamaskSDK as createMetamaskSDKNode } from './index.node';
2121
import * as nodeStorage from './store/adapters/node';
22+
import * as webStorage from './store/adapters/web';
2223
import type { DappClient } from '@metamask/mobile-wallet-protocol-dapp-client';
2324

2425
// Mock logger at the top level
@@ -341,7 +342,6 @@ export const setupWebMocks = (nativeStorageStub: NativeStorageStub, dappUrl = 'h
341342
...dom.window,
342343
addEventListener: t.vi.fn(),
343344
removeEventListener: t.vi.fn(),
344-
localStorage: nativeStorageStub,
345345
ethereum: {
346346
isMetaMask: true,
347347
},
@@ -356,6 +356,24 @@ export const setupWebMocks = (nativeStorageStub: NativeStorageStub, dappUrl = 'h
356356
t.vi.stubGlobal('document', dom.window.document);
357357
t.vi.stubGlobal('HTMLElement', dom.window.HTMLElement);
358358
t.vi.stubGlobal('requestAnimationFrame', t.vi.fn());
359+
t.vi.spyOn(webStorage, 'StoreAdapterWeb').mockImplementation(() => {
360+
const __storage = {
361+
get: t.vi.fn((key: string) => {
362+
return nativeStorageStub.getItem(key);
363+
}),
364+
set: t.vi.fn((key: string, value: string) => {
365+
return nativeStorageStub.setItem(key, value);
366+
}),
367+
delete: t.vi.fn((key: string) => {
368+
return nativeStorageStub.removeItem(key);
369+
}),
370+
platform: 'web' as const,
371+
get storage() {
372+
return __storage;
373+
},
374+
} as any;
375+
return __storage;
376+
});
359377
};
360378

361379
// Helper functions to create standardized test configurations
Lines changed: 75 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,92 @@
11
import { StoreAdapter } from '../../domain';
22

3+
type kvStores = 'sdk-kv-store' | 'key-value-pairs';
4+
35
export class StoreAdapterWeb extends StoreAdapter {
6+
static readonly stores: kvStores[] = ['sdk-kv-store', 'key-value-pairs'];
7+
static readonly DB_NAME = 'mmsdk';
8+
49
readonly platform = 'web';
10+
readonly dbPromise: Promise<IDBDatabase>;
511

612
private get internal() {
7-
if (typeof window === 'undefined' || !window.localStorage) {
8-
throw new Error('localStorage is not available in this environment');
13+
if (typeof window === 'undefined' || !window.indexedDB) {
14+
throw new Error('indexedDB is not available in this environment');
915
}
10-
return window.localStorage;
16+
return window.indexedDB;
17+
}
18+
19+
constructor(
20+
dbNameSuffix: `-${string}` = '-kv-store',
21+
private storeName: kvStores = StoreAdapterWeb.stores[0],
22+
) {
23+
super();
24+
25+
const dbName = `${StoreAdapterWeb.DB_NAME}${dbNameSuffix}`;
26+
this.dbPromise = new Promise((resolve, reject) => {
27+
try {
28+
const request = this.internal.open(dbName, 1);
29+
request.onerror = () => reject(new Error('Failed to open IndexedDB.'));
30+
request.onsuccess = () => resolve(request.result);
31+
request.onupgradeneeded = () => {
32+
const db = request.result;
33+
for (const name of StoreAdapterWeb.stores) {
34+
if (!db.objectStoreNames.contains(name)) {
35+
db.createObjectStore(name);
36+
}
37+
}
38+
};
39+
} catch (error) {
40+
reject(error);
41+
}
42+
});
1143
}
44+
1245
async get(key: string): Promise<string | null> {
13-
return this.internal.getItem(key);
46+
const { storeName } = this;
47+
const db = await this.dbPromise;
48+
return new Promise((resolve, reject) => {
49+
try {
50+
const tx = db.transaction(storeName, 'readonly');
51+
const store = tx.objectStore(storeName);
52+
const request = store.get(key);
53+
request.onerror = () => reject(new Error('Failed to get value from IndexedDB.'));
54+
request.onsuccess = () => resolve((request.result as string) ?? null);
55+
} catch (error) {
56+
reject(error);
57+
}
58+
});
1459
}
1560

1661
async set(key: string, value: string): Promise<void> {
17-
return this.internal.setItem(key, value);
62+
const { storeName } = this;
63+
const db = await this.dbPromise;
64+
return new Promise((resolve, reject) => {
65+
try {
66+
const tx = db.transaction(storeName, 'readwrite');
67+
const store = tx.objectStore(storeName);
68+
const request = store.put(value, key);
69+
request.onerror = () => reject(new Error('Failed to set value in IndexedDB.'));
70+
request.onsuccess = () => resolve();
71+
} catch (error) {
72+
reject(error);
73+
}
74+
});
1875
}
1976

2077
async delete(key: string): Promise<void> {
21-
return this.internal.removeItem(key);
78+
const { storeName } = this;
79+
const db = await this.dbPromise;
80+
return new Promise((resolve, reject) => {
81+
try {
82+
const tx = db.transaction(storeName, 'readwrite');
83+
const store = tx.objectStore(storeName);
84+
const request = store.delete(key);
85+
request.onerror = () => reject(new Error('Failed to delete value from IndexedDB.'));
86+
request.onsuccess = () => resolve();
87+
} catch (error) {
88+
reject(error);
89+
}
90+
});
2291
}
2392
}

packages/sdk-multichain/src/store/index.test.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
/** biome-ignore-all lint/suspicious/noExplicitAny: Tests require it */
22
/** biome-ignore-all lint/style/noNonNullAssertion: Tests require it */
3+
import 'fake-indexeddb/auto';
4+
import { IDBFactory } from 'fake-indexeddb';
35
import AsyncStorage from '@react-native-async-storage/async-storage';
46
import * as t from 'vitest';
57
import type { StoreAdapter } from '../domain';
@@ -227,13 +229,15 @@ t.describe(`Store with WebAdapter`, () => {
227229
() => {
228230
t.vi.stubGlobal('window', {
229231
localStorage: nativeStorageStub,
232+
indexedDB: new IDBFactory(),
230233
});
231234
},
232235
);
233236

234237
t.it("Should throw an exception if we try using the store with a browser that doesn't support localStorage", async () => {
235238
t.vi.stubGlobal('window', {
236-
localStorage: null,
239+
localStorage: undefined,
240+
indexedDB: undefined,
237241
});
238242
const store = new Store(new StoreAdapterWeb());
239243
await t.expect(() => store.getAnonId()).rejects.toThrow();

yarn.lock

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11279,6 +11279,7 @@ __metadata:
1127911279
eciesjs: ^0.4.15
1128011280
esbuild-plugin-umd-wrapper: ^3.0.0
1128111281
eventemitter3: ^5.0.1
11282+
fake-indexeddb: ^6.0.1
1128211283
jsdom: ^26.1.0
1128311284
nock: ^14.0.4
1128411285
prettier: ^3.3.3
@@ -32148,6 +32149,13 @@ __metadata:
3214832149
languageName: node
3214932150
linkType: hard
3215032151

32152+
"fake-indexeddb@npm:^6.0.1":
32153+
version: 6.0.1
32154+
resolution: "fake-indexeddb@npm:6.0.1"
32155+
checksum: c4b8a0576cf3165238494b67641539d4ff36194e038b36e6992449eb882923dfaadba78a62cfc7d5ae9a5c0ac2fa1e70af5cb6c2228dc764ac79b65f0e68e942
32156+
languageName: node
32157+
linkType: hard
32158+
3215132159
"fast-deep-equal@npm:^2.0.1":
3215232160
version: 2.0.1
3215332161
resolution: "fast-deep-equal@npm:2.0.1"

0 commit comments

Comments
 (0)