Skip to content

Commit b1b252d

Browse files
committed
SD-92: Fixes
1 parent 225fb60 commit b1b252d

File tree

3 files changed

+166
-40
lines changed

3 files changed

+166
-40
lines changed

src/domains/shielder/components/Activity/Activity.tsx

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import dayjs from 'dayjs';
22
import { AnimatePresence } from 'framer-motion';
33
import styled from 'styled-components';
44
import { objectEntries } from 'tsafe';
5+
import { Address } from 'viem';
56

67
import Badge from 'src/domains/misc/components/Badge';
78
import ScrollShadow from 'src/domains/misc/components/ScrollShadow';
@@ -16,7 +17,10 @@ const Activity = () => {
1617
const { data, isLoading } = useTransactionsHistory();
1718

1819
const grouped = data?.reduce<Record<string, typeof data>>((acc, tx) => {
19-
const newTx = tx.type === 'NewAccount' ? [ { ...tx, type: 'Deposit' as const }, { ...tx }] : [tx];
20+
const newTx = tx.type === 'NewAccount' ? [
21+
{ ...tx, type: 'Deposit' as const },
22+
{ ...tx, txHash: `${tx.txHash}-new-account` as Address }] :
23+
[tx];
2024
const date = tx.submitTimestamp ?? tx.completedTimestamp;
2125

2226
if(!date) return acc;

src/domains/shielder/components/Activity/ActivityItem.tsx

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ const getTransactionLabel = (
2626
status === 'pending' ? 'Shielding' : 'Shielded';
2727
return (
2828
<AccountLabel>
29-
<p><ActivityType $status={status}>{depositText}</ActivityType> from</p>
29+
<div><ActivityType $status={status}>{depositText}</ActivityType> from</div>
3030
<AccountTypeIcon type="public" size={16} />
3131
<p>Public</p>
3232
</AccountLabel>
@@ -101,7 +101,7 @@ const ActivityItem = ({ transaction }: Props) => {
101101
{ txHash: transaction.txHash } :
102102
{ localId: transaction.localId };
103103

104-
openTransactionModal(identifier);
104+
void openTransactionModal(identifier);
105105
};
106106

107107
return (
@@ -150,12 +150,12 @@ const Info = styled.div`
150150
flex-direction: column;
151151
`;
152152

153-
const Label = styled.p`
153+
const Label = styled.div`
154154
color: ${vars('--color-neutral-foreground-3-rest')};
155155
${typography.web.caption2}
156156
`;
157157

158-
const Title = styled.p`
158+
const Title = styled.div`
159159
${typography.decorative.subtitle2}
160160
`;
161161

src/domains/shielder/stores/getShielderIndexedDB.ts

Lines changed: 157 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -109,19 +109,72 @@ const createDB = (): Promise<IDBPDatabase<DBSchema>> => {
109109
});
110110
};
111111

112-
const initDB = async (): Promise<IDBPDatabase<DBSchema>> => {
113-
try {
114-
return await createDB();
115-
} catch (error) {
116-
if ((error as Error).name === 'VersionError') {
117-
console.warn('IndexedDB version mismatch. Clearing database and retrying...');
112+
// Database instance management using a closure to avoid mutations
113+
const createDBManager = () => {
114+
const cache = new Map<string, Promise<IDBPDatabase<DBSchema>>>();
115+
116+
const handleDatabaseError = async (error: unknown): Promise<IDBPDatabase<DBSchema>> => {
117+
const errorMessage = (error as Error).message || '';
118+
119+
// Check if it's an object store not found error
120+
if (errorMessage.includes('object stores was not found') ||
121+
errorMessage.includes('object store') ||
122+
(error as Error).name === 'NotFoundError') {
123+
console.warn('IndexedDB object stores not found. Recreating database...');
118124
await deleteDatabase();
119-
return await createDB();
125+
cache.clear();
126+
return await initDB();
120127
}
128+
121129
throw error;
122-
}
130+
};
131+
132+
const initDB = async (): Promise<IDBPDatabase<DBSchema>> => {
133+
const cacheKey = 'db';
134+
const cached = cache.get(cacheKey);
135+
if (cached) {
136+
return cached;
137+
}
138+
139+
const dbPromise = (async () => {
140+
try {
141+
return await createDB();
142+
} catch (error) {
143+
// Remove from cache on error so next call will retry
144+
cache.delete(cacheKey);
145+
146+
if ((error as Error).name === 'VersionError') {
147+
console.warn('IndexedDB version mismatch. Clearing database and retrying...');
148+
await deleteDatabase();
149+
return await createDB();
150+
}
151+
throw error;
152+
}
153+
})();
154+
155+
cache.set(cacheKey, dbPromise);
156+
157+
try {
158+
return await dbPromise;
159+
} catch (error) {
160+
// Remove from cache if promise rejects
161+
cache.delete(cacheKey);
162+
throw error;
163+
}
164+
};
165+
166+
const resetCache = () => {
167+
cache.clear();
168+
};
169+
170+
return { initDB, handleDatabaseError, resetCache };
123171
};
124172

173+
const dbManager = createDBManager();
174+
const initDB = dbManager.initDB;
175+
const handleDatabaseError = dbManager.handleDatabaseError;
176+
const resetDBInstance = dbManager.resetCache;
177+
125178
export const getShielderIndexedDB = (chainId: number, privateKey: Address) => {
126179
const chainKey = chainId.toString();
127180
const hashedKey = sha256(privateKey);
@@ -135,7 +188,14 @@ export const getShielderIndexedDB = (chainId: number, privateKey: Address) => {
135188
return addrData?.[chainKey]?.[itemKey] ?? null;
136189
} catch (error) {
137190
console.error('Failed to get item from IndexedDB:', error);
138-
return null;
191+
try {
192+
const db = await handleDatabaseError(error);
193+
const addrData = await db.get(STORE_CLIENTS, hashedKey);
194+
return addrData?.[chainKey]?.[itemKey] ?? null;
195+
} catch (retryError) {
196+
console.error('Failed to get item from IndexedDB after retry:', retryError);
197+
return null;
198+
}
139199
}
140200
},
141201
setItem: async (itemKey: string, value: string): Promise<void> => {
@@ -150,6 +210,18 @@ export const getShielderIndexedDB = (chainId: number, privateKey: Address) => {
150210
);
151211
} catch (error) {
152212
console.error('Failed to set item in IndexedDB:', error);
213+
try {
214+
const db = await handleDatabaseError(error);
215+
const addrData = (await db.get(STORE_CLIENTS, hashedKey)) ?? {};
216+
const chainData = addrData[chainKey] ?? {};
217+
await db.put(
218+
STORE_CLIENTS,
219+
{ ...addrData, [chainKey]: { ...chainData, [itemKey]: value }},
220+
hashedKey,
221+
);
222+
} catch (retryError) {
223+
console.error('Failed to set item in IndexedDB after retry:', retryError);
224+
}
153225
}
154226
},
155227
};
@@ -167,51 +239,101 @@ export const getLocalShielderActivityHistoryIndexedDB = (accountAddress: string)
167239
return raw ? fromActivityHistoryStorageFormat(raw) : null;
168240
} catch (error) {
169241
console.error('Failed to get activity history from IndexedDB:', error);
170-
return null;
242+
try {
243+
const db = await handleDatabaseError(error);
244+
const allChains = await db.get(STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY, accountAddress);
245+
const raw = allChains?.[chainId.toString()];
246+
return raw ? fromActivityHistoryStorageFormat(raw) : null;
247+
} catch (retryError) {
248+
console.error('Failed to get activity history from IndexedDB after retry:', retryError);
249+
return null;
250+
}
171251
}
172252
},
173253
upsertItem: async (chainId: number, activity: PartialLocalShielderActivityHistory) => {
174-
const db = await dbp;
175-
const chainKey = chainId.toString();
176-
const allChains = (await db.get(STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY, accountAddress)) ?? {};
177-
const existingRaw = allChains[chainKey];
178-
const existing = existingRaw ? fromActivityHistoryStorageFormat(existingRaw) : [];
254+
try {
255+
const db = await dbp;
256+
const chainKey = chainId.toString();
257+
const allChains = (await db.get(STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY, accountAddress)) ?? {};
258+
const existingRaw = allChains[chainKey];
259+
const existing = existingRaw ? fromActivityHistoryStorageFormat(existingRaw) : [];
179260

180-
const isSame = (a: PartialLocalShielderActivityHistory, b: PartialLocalShielderActivityHistory) =>
181-
(isPresent(a.localId) && a.localId === b.localId) ||
182-
(isPresent(a.txHash) && a.txHash === b.txHash);
261+
const isSame = (a: PartialLocalShielderActivityHistory, b: PartialLocalShielderActivityHistory) =>
262+
(isPresent(a.localId) && a.localId === b.localId) ||
263+
(isPresent(a.txHash) && a.txHash === b.txHash);
183264

184-
const matches = existing.filter(item => isSame(item, activity));
185-
const rest = existing.filter(item => !isSame(item, activity));
265+
const matches = existing.filter(item => isSame(item, activity));
266+
const rest = existing.filter(item => !isSame(item, activity));
186267

187-
const localOnly = matches.find(i => isPresent(i.localId) && !isPresent(i.txHash)) ?? {};
188-
const withTxHash = matches.find(i => isPresent(i.txHash)) ?? {};
268+
const localOnly = matches.find(i => isPresent(i.localId) && !isPresent(i.txHash)) ?? {};
269+
const withTxHash = matches.find(i => isPresent(i.txHash)) ?? {};
189270

190-
const merged = {
191-
...localOnly,
192-
...withTxHash,
193-
...activity,
194-
};
271+
const merged = {
272+
...localOnly,
273+
...withTxHash,
274+
...activity,
275+
};
195276

196-
const updated: LocalShielderActivityHistoryArray = [...rest, merged];
277+
const updated: LocalShielderActivityHistoryArray = [...rest, merged];
197278

198-
await db.put(
199-
STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY,
200-
{ ...allChains, [chainKey]: toActivityHistoryStorageFormat(updated) },
201-
accountAddress,
202-
);
279+
await db.put(
280+
STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY,
281+
{ ...allChains, [chainKey]: toActivityHistoryStorageFormat(updated) },
282+
accountAddress,
283+
);
203284

204-
return merged;
285+
return merged;
286+
} catch (error) {
287+
console.error('Failed to upsert activity history in IndexedDB:', error);
288+
try {
289+
const db = await handleDatabaseError(error);
290+
const chainKey = chainId.toString();
291+
const allChains = (await db.get(STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY, accountAddress)) ?? {};
292+
const existingRaw = allChains[chainKey];
293+
const existing = existingRaw ? fromActivityHistoryStorageFormat(existingRaw) : [];
294+
295+
const isSame = (a: PartialLocalShielderActivityHistory, b: PartialLocalShielderActivityHistory) =>
296+
(isPresent(a.localId) && a.localId === b.localId) ||
297+
(isPresent(a.txHash) && a.txHash === b.txHash);
298+
299+
const matches = existing.filter(item => isSame(item, activity));
300+
const rest = existing.filter(item => !isSame(item, activity));
301+
302+
const localOnly = matches.find(i => isPresent(i.localId) && !isPresent(i.txHash)) ?? {};
303+
const withTxHash = matches.find(i => isPresent(i.txHash)) ?? {};
304+
305+
const merged = {
306+
...localOnly,
307+
...withTxHash,
308+
...activity,
309+
};
310+
311+
const updated: LocalShielderActivityHistoryArray = [...rest, merged];
312+
313+
await db.put(
314+
STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY,
315+
{ ...allChains, [chainKey]: toActivityHistoryStorageFormat(updated) },
316+
accountAddress,
317+
);
318+
319+
return merged;
320+
} catch (retryError) {
321+
console.error('Failed to upsert activity history in IndexedDB after retry:', retryError);
322+
throw retryError;
323+
}
324+
}
205325
},
206326
};
207327
};
208328

209329
export const clearShielderIndexedDB = async (): Promise<void> => {
210330
try {
211-
const db = await openDB(DB_NAME, DB_VERSION);
331+
const db = await initDB();
212332
await Promise.all([db.clear(STORE_CLIENTS), db.clear(STORE_LOCAL_SHIELDER_ACTIVITY_HISTORY)]);
333+
resetDBInstance(); // Reset instance after clearing to ensure fresh state
213334
} catch (error) {
214335
console.error('Failed to clear IndexedDB:', error);
336+
resetDBInstance(); // Reset instance on error as well
215337
throw error;
216338
}
217339
};

0 commit comments

Comments
 (0)