"use strict"; (function registerStoragePlugin() { const storageKey = "tdkpin.save.v1"; const encoder = new TextEncoder(); const decoder = new TextDecoder(); const highScoreApi = "api/highscores"; let lastRevision = 0; let lastHighScoreRevision = 0; let highScoreRequestInFlight = false; let highScoreGeneration = 0; let highScoreSubmissionWarningShown = false; function browserStorage() { try { return window.localStorage; } catch (_error) { return null; } } function sendSavedDataToRust() { wasm_exports.tdkpin_browser_storage_clear(); const storage = browserStorage(); let saved = null; try { saved = storage?.getItem(storageKey); } catch (_error) { saved = null; } if (saved !== null && saved !== undefined) { for (const byte of encoder.encode(saved)) { wasm_exports.tdkpin_browser_storage_push(byte); } } wasm_exports.tdkpin_browser_storage_finish(); } function flushRustSave() { const revision = wasm_exports.tdkpin_browser_storage_save_revision(); if (revision === lastRevision) { return; } const bytes = new Uint8Array( wasm_exports.tdkpin_browser_storage_save_length(), ); for (let index = 0; index < bytes.length; index += 1) { bytes[index] = wasm_exports.tdkpin_browser_storage_save_byte(index); } const storage = browserStorage(); try { storage?.setItem(storageKey, decoder.decode(bytes)); } catch (_error) { // Private browsing or a full quota should not stop the game loop. } wasm_exports.tdkpin_browser_storage_save_ack(); lastRevision = revision; } function deliverHighScores(json) { wasm_exports.tdkpin_browser_high_scores_clear(); for (const byte of encoder.encode(json)) { wasm_exports.tdkpin_browser_high_scores_push(byte); } wasm_exports.tdkpin_browser_high_scores_finish(); } async function fetchHighScores() { const generation = ++highScoreGeneration; try { const response = await fetch(highScoreApi, { cache: "no-store", headers: { Accept: "application/json" }, }); if (!response.ok) { throw new Error(`high-score request failed (${response.status})`); } const json = await response.text(); if (generation === highScoreGeneration) { deliverHighScores(json); } } catch (error) { console.warn("shared high scores unavailable; using local scores", error); } } async function flushHighScoreSubmission() { if (highScoreRequestInFlight) { return; } const revision = wasm_exports.tdkpin_browser_high_score_revision(); if (revision === lastHighScoreRevision) { return; } const bytes = new Uint8Array( wasm_exports.tdkpin_browser_high_score_length(), ); for (let index = 0; index < bytes.length; index += 1) { bytes[index] = wasm_exports.tdkpin_browser_high_score_byte(index); } highScoreRequestInFlight = true; try { const response = await fetch(highScoreApi, { method: "POST", headers: { "Content-Type": "application/json" }, body: decoder.decode(bytes), keepalive: true, }); if (!response.ok) { throw new Error(`high-score submission failed (${response.status})`); } const json = await response.text(); highScoreGeneration += 1; deliverHighScores(json); wasm_exports.tdkpin_browser_high_score_ack(revision); lastHighScoreRevision = revision; highScoreSubmissionWarningShown = false; } catch (error) { if (!highScoreSubmissionWarningShown) { console.warn("shared high-score submission failed; will retry", error); highScoreSubmissionWarningShown = true; } } finally { highScoreRequestInFlight = false; } } function onInit() { sendSavedDataToRust(); lastRevision = wasm_exports.tdkpin_browser_storage_save_revision(); void fetchHighScores(); window.setInterval(flushRustSave, 50); window.setInterval(() => void flushHighScoreSubmission(), 50); window.addEventListener("beforeunload", flushRustSave); } miniquad_add_plugin({ name: "tdkpin_storage", on_init: onInit, version: 1, }); })();