Skip to content

Commit 9b81945

Browse files
committed
Better item integrity with isLastChunk property
1 parent 62e75ab commit 9b81945

File tree

2 files changed

+53
-4
lines changed

2 files changed

+53
-4
lines changed

packages/idb-cache/src/index.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -395,6 +395,9 @@ export class IDBCache implements AsyncStorage {
395395

396396
const chunks: { index: number; data: EncryptedChunk }[] = [];
397397

398+
let maxIndex = -1;
399+
let lastChunkFound = false;
400+
398401
for (const chunkKey of chunkKeys) {
399402
const encryptedData = await db.get(this.storeName, chunkKey);
400403
if (!encryptedData) continue;
@@ -406,6 +409,15 @@ export class IDBCache implements AsyncStorage {
406409
continue;
407410
}
408411
const chunkIndex = parseChunkIndexFromKey(chunkKey);
412+
if (chunkIndex > maxIndex) {
413+
maxIndex = chunkIndex;
414+
}
415+
416+
const isLastChunk = encryptedData.isLastChunk ?? false;
417+
if (isLastChunk) {
418+
lastChunkFound = true;
419+
}
420+
409421
chunks.push({
410422
index: chunkIndex,
411423
data: encryptedData,
@@ -414,6 +426,31 @@ export class IDBCache implements AsyncStorage {
414426

415427
if (chunks.length === 0) return null;
416428

429+
// Integrity check: Ensure that the last chunk is present and all preceding chunks are present
430+
if (!lastChunkFound) {
431+
throw new IDBCacheError(
432+
`Integrity check failed for key ${itemKey}: Last chunk is missing.`
433+
);
434+
}
435+
436+
// Ensure all chunk indices from 0 to maxIndex are present
437+
if (chunks.length !== maxIndex + 1) {
438+
throw new IDBCacheError(
439+
`Integrity check failed for key ${itemKey}: Expected ${
440+
maxIndex + 1
441+
} chunks, but found ${chunks.length}.`
442+
);
443+
}
444+
445+
const indexSet = new Set(chunks.map((chunk) => chunk.index));
446+
for (let i = 0; i <= maxIndex; i++) {
447+
if (!indexSet.has(i)) {
448+
throw new IDBCacheError(
449+
`Integrity check failed for key ${itemKey}: Missing chunk at index ${i}.`
450+
);
451+
}
452+
}
453+
417454
chunks.sort((a, b) => a.index - b.index);
418455

419456
const decryptedChunks = await Promise.all(
@@ -429,6 +466,10 @@ export class IDBCache implements AsyncStorage {
429466

430467
return decryptedChunks.join("");
431468
} catch (error) {
469+
if (error instanceof IDBCacheError) {
470+
console.error(`Integrity check failed for key ${itemKey}:`, error);
471+
throw error;
472+
}
432473
if (error instanceof DecryptionError) {
433474
console.error(`Decryption failed for key ${itemKey}:`, error);
434475
throw error;
@@ -487,6 +528,8 @@ export class IDBCache implements AsyncStorage {
487528
}> = [];
488529
const chunksToUpdate: Array<EncryptedChunk> = [];
489530

531+
const totalChunks = Math.ceil(value.length / this.chunkSize);
532+
490533
for (let i = 0; i < value.length; i += this.chunkSize) {
491534
const chunk = value.slice(i, i + this.chunkSize);
492535
const chunkIndex = Math.floor(i / this.chunkSize);
@@ -497,6 +540,8 @@ export class IDBCache implements AsyncStorage {
497540
const chunkKey = generateChunkKey(baseKey, chunkIndex, chunkHash);
498541
newChunkKeys.add(chunkKey);
499542

543+
const isLastChunk = chunkIndex === totalChunks - 1;
544+
500545
if (existingChunkKeysSet.has(chunkKey)) {
501546
const existingChunk = await db.get(this.storeName, chunkKey);
502547
if (
@@ -507,6 +552,7 @@ export class IDBCache implements AsyncStorage {
507552
...existingChunk,
508553
timestamp: expirationTimestamp,
509554
cacheBuster: this.cacheBuster,
555+
isLastChunk, // Update the flag in case it's the last chunk
510556
});
511557
}
512558
} else {
@@ -520,6 +566,7 @@ export class IDBCache implements AsyncStorage {
520566
encryptedChunk: {
521567
...encryptedChunk,
522568
cacheBuster: this.cacheBuster,
569+
isLastChunk,
523570
},
524571
});
525572
}

packages/idb-cache/src/types.d.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,12 @@ export interface IDBCacheSchema extends DBSchema {
44
cache: {
55
key: string;
66
value: {
7-
key: string;
8-
iv: ArrayBuffer;
7+
cacheBuster: string;
98
ciphertext: ArrayBuffer;
9+
isLastChunk?: boolean;
10+
iv: ArrayBuffer;
11+
key: string;
1012
timestamp: number;
11-
cacheBuster: string;
1213
};
1314
indexes: {
1415
byTimestamp: number;
@@ -19,12 +20,13 @@ export interface IDBCacheSchema extends DBSchema {
1920

2021
export type STORE = "cache";
2122

22-
export interface EncryptedChunk {
23+
interface EncryptedChunk {
2324
key: string;
2425
iv: ArrayBuffer;
2526
ciphertext: ArrayBuffer;
2627
timestamp: number;
2728
cacheBuster: string;
29+
isLastChunk?: boolean;
2830
}
2931

3032
export type WorkerMessage =

0 commit comments

Comments
 (0)