feat: add item limit for ciphers import and streamline response handling

This commit is contained in:
shuaiplus
2026-03-18 00:56:32 +08:00
parent 3f7ca52983
commit 9280f6916e
+8 -65
View File
@@ -110,6 +110,8 @@ export interface ImportedCipherMapEntry {
id: string;
}
const IMPORT_ITEM_LIMIT = 5000;
export async function importCiphers(
authedFetch: AuthedFetch,
payload: CiphersImportPayload,
@@ -118,10 +120,9 @@ export async function importCiphers(
const returnCipherMap = !!options?.returnCipherMap;
const url = returnCipherMap ? '/api/ciphers/import?returnCipherMap=1' : '/api/ciphers/import';
const totalItems = (payload.folders?.length || 0) + (payload.ciphers?.length || 0);
const responses: ImportedCipherMapEntry[] = [];
const folderChunkSize = Math.min(BULK_API_CHUNK_SIZE, Math.max(0, BULK_API_CHUNK_SIZE - 1));
if (totalItems <= BULK_API_CHUNK_SIZE || payload.folders.length > folderChunkSize) {
if (totalItems > IMPORT_ITEM_LIMIT) {
throw new Error(`Import exceeds maximum of ${IMPORT_ITEM_LIMIT} items`);
}
const resp = await authedFetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
@@ -129,11 +130,14 @@ export async function importCiphers(
});
if (!resp.ok) throw new Error(await parseErrorMessage(resp, 'Import failed'));
if (!returnCipherMap) return null;
const body =
(await parseJson<{
cipherMap?: Array<{ index?: number; sourceId?: string | null; id?: string }>;
}>(resp)) || {};
if (!Array.isArray(body.cipherMap)) return [];
const responses: ImportedCipherMapEntry[] = [];
for (const row of body.cipherMap) {
const index = Number(row?.index);
const id = String(row?.id || '').trim();
@@ -148,67 +152,6 @@ export async function importCiphers(
return responses;
}
const folders = payload.folders || [];
const relationshipsByCipher = new Map<number, number | null>();
for (const relation of payload.folderRelationships || []) {
relationshipsByCipher.set(Number(relation.key), Number(relation.value));
}
for (const cipherChunkStart of Array.from({ length: Math.ceil(payload.ciphers.length / BULK_API_CHUNK_SIZE) }, (_, i) => i * BULK_API_CHUNK_SIZE)) {
const cipherChunk = payload.ciphers.slice(cipherChunkStart, cipherChunkStart + BULK_API_CHUNK_SIZE);
const usedFolderIndices = Array.from(
new Set(
cipherChunk
.map((_, localIndex) => relationshipsByCipher.get(cipherChunkStart + localIndex))
.filter((value): value is number => Number.isFinite(value as number) && (value as number) >= 0)
)
);
const folderIndexMap = new Map<number, number>();
const chunkFolders = usedFolderIndices.map((folderIndex, localIndex) => {
folderIndexMap.set(folderIndex, localIndex);
return folders[folderIndex];
});
const chunkRelationships = cipherChunk
.map((_, localIndex) => {
const originalCipherIndex = cipherChunkStart + localIndex;
const originalFolderIndex = relationshipsByCipher.get(originalCipherIndex);
if (!Number.isFinite(originalFolderIndex as number)) return null;
const localFolderIndex = folderIndexMap.get(Number(originalFolderIndex));
if (!Number.isFinite(localFolderIndex as number)) return null;
return { key: localIndex, value: Number(localFolderIndex) };
})
.filter((value): value is { key: number; value: number } => !!value);
const resp = await authedFetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
ciphers: cipherChunk,
folders: chunkFolders,
folderRelationships: chunkRelationships,
}),
});
if (!resp.ok) throw new Error(await parseErrorMessage(resp, 'Import failed'));
if (!returnCipherMap) continue;
const body =
(await parseJson<{
cipherMap?: Array<{ index?: number; sourceId?: string | null; id?: string }>;
}>(resp)) || {};
for (const row of body.cipherMap || []) {
const localIndex = Number(row?.index);
const id = String(row?.id || '').trim();
if (!Number.isFinite(localIndex) || !id) continue;
const sourceRaw = String(row?.sourceId || '').trim();
responses.push({
index: cipherChunkStart + localIndex,
id,
sourceId: sourceRaw || null,
});
}
}
return returnCipherMap ? responses : null;
}
export interface AttachmentDownloadInfo {
id: string;
url: string;