const SNAPSHOT_STORAGE_KEY = 'shine-ui-blockchain-snapshot-v1'; function normalizeLogin(login) { return String(login || '').trim().toLowerCase(); } function storageBucketKey(login) { return normalizeLogin(login) || 'anonymous'; } function decodeBase64ToBytes(base64) { const raw = String(base64 || '').trim(); if (!raw) return new Uint8Array(); const bin = atob(raw); const out = new Uint8Array(bin.length); for (let i = 0; i < bin.length; i += 1) out[i] = bin.charCodeAt(i); return out; } function readStorageMap() { try { const raw = localStorage.getItem(SNAPSHOT_STORAGE_KEY); const parsed = raw ? JSON.parse(raw) : {}; return parsed && typeof parsed === 'object' ? parsed : {}; } catch { return {}; } } function writeStorageMap(value) { try { localStorage.setItem(SNAPSHOT_STORAGE_KEY, JSON.stringify(value || {})); } catch { // ignore localStorage errors } } export function readStoredBlockchainSnapshot(login) { const key = storageBucketKey(login); const map = readStorageMap(); const value = map?.[key]; return value && typeof value === 'object' ? value : null; } export function saveStoredBlockchainSnapshot(login, snapshot) { const key = storageBucketKey(login); const map = readStorageMap(); map[key] = { ...snapshot, savedAtMs: Date.now(), }; writeStorageMap(map); return map[key]; } export function clearStoredBlockchainSnapshot(login) { const key = storageBucketKey(login); const map = readStorageMap(); delete map[key]; writeStorageMap(map); } export async function buildBlockchainSnapshotFile({ authService, login, blockchainName, lastBlockNumber, } = {}) { const cleanLogin = normalizeLogin(login); const cleanBlockchainName = String(blockchainName || '').trim(); const maxBlockNumber = Number(lastBlockNumber); if (!authService?.ws?.request) throw new Error('Сервис сервера недоступен.'); if (!cleanLogin) throw new Error('Не указан логин.'); if (!cleanBlockchainName) throw new Error('Не указано имя блокчейна.'); if (!Number.isFinite(maxBlockNumber) || maxBlockNumber < 0) { throw new Error('На сервере нет блоков для слепка.'); } const parts = []; let totalBytes = 0; for (let blockNumber = 0; blockNumber <= maxBlockNumber; blockNumber += 1) { const response = await authService.ws.request('GetBlockchainBlock', { blockchainName: cleanBlockchainName, blockNumber, }); if (response?.status !== 200) { const message = String(response?.payload?.message || response?.message || 'Не удалось получить блок.'); throw new Error(`Не удалось скачать блок ${blockNumber}: ${message}`); } const blockBytes = decodeBase64ToBytes(response?.payload?.blockBytesB64 || ''); parts.push(blockBytes); totalBytes += blockBytes.length; } const file = new File(parts, `${cleanBlockchainName}.shine-blockchain`, { type: 'application/octet-stream', lastModified: Date.now(), }); return { file, blockCount: maxBlockNumber + 1, totalBytes, lastBlockNumber: maxBlockNumber, login: cleanLogin, blockchainName: cleanBlockchainName, }; }