Files
SubMiner/vendor/hachidori/extension/backup-downloads.js
T
sudacode d9fdc7ef6d feat(dictionary): add Hachidori backend support
- Add backend selection, setup gating, Anki integration, and external host support
- Add launcher flags, documentation, packaging, and focused tests
- Open on-demand overlay modals on the first attempt
2026-09-22 00:21:19 -07:00

55 lines
2.2 KiB
JavaScript

// Keep engine-owned backup URLs alive until Chrome finishes saving the file.
// Session storage survives service-worker restarts, but is not backup content.
// SPDX-License-Identifier: GPL-3.0-or-later
const KEY = "backupDownloads";
const TARGET = "hoshidicts-offscreen";
export function createBackupDownloads(chrome, relay) {
let tail = Promise.resolve();
const serialise = job => {
const result = tail.then(job, job);
tail = result.catch(() => {});
return result;
};
const release = blobUrl => relay({ target: TARGET, type: "hd_backup_release", blobUrl });
const read = async () => (await chrome.storage.session.get(KEY))[KEY] ?? {};
async function finish(id) {
const tracked = await read();
if (!Object.hasOwn(tracked, id)) return;
const [download] = await chrome.downloads.search({ id: Number(id) });
if (download?.state === "in_progress") return;
const reply = await release(tracked[id]);
if (!reply?.ok) throw new Error(reply?.error || "Could not release the saved backup archive.");
delete tracked[id];
await chrome.storage.session.set({ [KEY]: tracked });
}
return {
async download() {
const exported = await relay({ target: TARGET, type: "hd_backup_export" });
if (!exported?.ok) throw new Error(exported?.error || "Could not create a backup archive.");
let id;
try {
id = await chrome.downloads.download({ url: exported.blobUrl, saveAs: true,
filename: `hachidori-backup-${new Date().toISOString().slice(0, 10)}.zip`, conflictAction: "uniquify" });
} catch (error) {
await serialise(() => release(exported.blobUrl));
throw error;
}
let warning = null;
try {
await serialise(async () => {
await chrome.storage.session.set({ [KEY]: { ...await read(), [id]: exported.blobUrl } });
// A small download can complete before its ownership record is written.
await finish(id);
});
} catch (error) {
warning = `Download started, but its temporary archive could not be tracked: ${error.message}`;
}
return { downloadId: id, warning };
},
changed(id) { return serialise(() => finish(id)); },
};
}