// Export / import of a save as a JSON or TXT file (or pasted text). // Works on every device: download via a Blob link, import via file input or paste. import { sanitizeState, serializeState } from '../engine/state.js'; const GAME_ID = 'idle-clicker'; const FORMAT = 1; export class ImportError extends Error { /** @param {'empty'|'invalid'|'unknown_theme'|'wrong_game'} code */ constructor(code) { super(code); this.code = code; } } /** The text a player downloads, copies or pastes back. */ export function buildExport(themeId, state, now = Date.now()) { return JSON.stringify( { game: GAME_ID, format: FORMAT, theme: themeId, exportedAt: new Date(now).toISOString(), state: JSON.parse(serializeState(state)), }, null, 2, ); } /** Validate imported text. Throws ImportError; never trusts the content. */ export function parseImport(text, ctx, knownThemeIds) { const trimmed = String(text ?? '').trim().replace(/^/, ''); if (!trimmed) throw new ImportError('empty'); let data; try { data = JSON.parse(trimmed); } catch { throw new ImportError('invalid'); } if (data?.game !== GAME_ID || data.format !== FORMAT) throw new ImportError('wrong_game'); if (!knownThemeIds.includes(data.theme)) throw new ImportError('unknown_theme'); const state = sanitizeState(data.state, ctx); if (!state) throw new ImportError('invalid'); return { themeId: data.theme, state }; } export function downloadText(filename, text, mime = 'application/json') { const url = URL.createObjectURL(new Blob([text], { type: `${mime};charset=utf-8` })); const link = document.createElement('a'); link.href = url; link.download = filename; document.body.append(link); link.click(); link.remove(); setTimeout(() => URL.revokeObjectURL(url), 10_000); } export function readFileText(file) { return file.text(); }