export const MAX_CAD_BYTES = 50 * 1024 * 1024; export const CAD_FETCH_TIMEOUT_MS = 15_000; export function assertCadByteLength(byteLength: number): void { if (!Number.isFinite(byteLength) || byteLength < 0) { throw new Error('CAD 파일 크기를 확인할 수 없습니다.'); } if (byteLength > MAX_CAD_BYTES) { throw new Error('CAD 파일은 50 MiB 이하여야 합니다.'); } } export function resolveCadUrl(input: string, base: URL): URL { const url = new URL(input, base); if (!['http:', 'https:'].includes(url.protocol)) { throw new Error('CAD URL은 HTTP(S)만 허용합니다.'); } if (url.origin !== base.origin) { throw new Error('CAD URL은 viewer와 같은 origin만 허용합니다.'); } return url; } export async function fetchCadBuffer(url: URL): Promise { const controller = new AbortController(); const timeout = window.setTimeout(() => controller.abort(), CAD_FETCH_TIMEOUT_MS); try { const response = await fetch(url, { credentials: 'same-origin', signal: controller.signal, }); if (!response.ok) throw new Error(`HTTP ${response.status}`); const declaredLength = Number(response.headers.get('content-length')); if (Number.isFinite(declaredLength) && declaredLength > 0) { assertCadByteLength(declaredLength); } if (!response.body) { const buffer = await response.arrayBuffer(); assertCadByteLength(buffer.byteLength); return buffer; } const reader = response.body.getReader(); const chunks: Uint8Array[] = []; let total = 0; while (true) { const { done, value } = await reader.read(); if (done) break; total += value.byteLength; assertCadByteLength(total); chunks.push(value); } const data = new Uint8Array(total); let offset = 0; for (const chunk of chunks) { data.set(chunk, offset); offset += chunk.byteLength; } return data.buffer; } catch (error) { if (controller.signal.aborted) { throw new Error('CAD 파일 요청 시간이 초과되었습니다.'); } throw error; } finally { window.clearTimeout(timeout); } }