mirror of
https://github.com/shuaiplus/nodewarden.git
synced 2026-06-20 13:00:39 +00:00
feat: add item limit for ciphers import and streamline response handling
This commit is contained in:
+29
-86
@@ -110,6 +110,8 @@ export interface ImportedCipherMapEntry {
|
|||||||
id: string;
|
id: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const IMPORT_ITEM_LIMIT = 5000;
|
||||||
|
|
||||||
export async function importCiphers(
|
export async function importCiphers(
|
||||||
authedFetch: AuthedFetch,
|
authedFetch: AuthedFetch,
|
||||||
payload: CiphersImportPayload,
|
payload: CiphersImportPayload,
|
||||||
@@ -118,95 +120,36 @@ export async function importCiphers(
|
|||||||
const returnCipherMap = !!options?.returnCipherMap;
|
const returnCipherMap = !!options?.returnCipherMap;
|
||||||
const url = returnCipherMap ? '/api/ciphers/import?returnCipherMap=1' : '/api/ciphers/import';
|
const url = returnCipherMap ? '/api/ciphers/import?returnCipherMap=1' : '/api/ciphers/import';
|
||||||
const totalItems = (payload.folders?.length || 0) + (payload.ciphers?.length || 0);
|
const totalItems = (payload.folders?.length || 0) + (payload.ciphers?.length || 0);
|
||||||
|
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' },
|
||||||
|
body: JSON.stringify(payload),
|
||||||
|
});
|
||||||
|
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[] = [];
|
const responses: ImportedCipherMapEntry[] = [];
|
||||||
const folderChunkSize = Math.min(BULK_API_CHUNK_SIZE, Math.max(0, BULK_API_CHUNK_SIZE - 1));
|
for (const row of body.cipherMap) {
|
||||||
|
const index = Number(row?.index);
|
||||||
if (totalItems <= BULK_API_CHUNK_SIZE || payload.folders.length > folderChunkSize) {
|
const id = String(row?.id || '').trim();
|
||||||
const resp = await authedFetch(url, {
|
if (!Number.isFinite(index) || !id) continue;
|
||||||
method: 'POST',
|
const sourceRaw = String(row?.sourceId || '').trim();
|
||||||
headers: { 'Content-Type': 'application/json' },
|
responses.push({
|
||||||
body: JSON.stringify(payload),
|
index,
|
||||||
|
id,
|
||||||
|
sourceId: sourceRaw || null,
|
||||||
});
|
});
|
||||||
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 [];
|
|
||||||
for (const row of body.cipherMap) {
|
|
||||||
const index = Number(row?.index);
|
|
||||||
const id = String(row?.id || '').trim();
|
|
||||||
if (!Number.isFinite(index) || !id) continue;
|
|
||||||
const sourceRaw = String(row?.sourceId || '').trim();
|
|
||||||
responses.push({
|
|
||||||
index,
|
|
||||||
id,
|
|
||||||
sourceId: sourceRaw || null,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return responses;
|
|
||||||
}
|
}
|
||||||
|
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 {
|
export interface AttachmentDownloadInfo {
|
||||||
|
|||||||
Reference in New Issue
Block a user