Обработать browser storage ошибки и показывать восстановленную форму (#9)
This commit is contained in:
1 parent
4c5d3de7bc
commit
6a2532cb87
2 files changed
+42
-24
No files matched your search
@@ -11,9 +11,9 @@ return <<<'JS'
|
||||
let storageInitialized = false;
|
||||
const initializeStorage = () => {
|
||||
if (storageInitialized) return;
|
||||
// window.open копирует sessionStorage родителя. Новый документ с opener
|
||||
// начинает собственный слот; настоящий Reload этой вкладки сохраняет его.
|
||||
if (window.opener && performance.getEntriesByType('navigation')[0]?.type === 'navigate') {
|
||||
// Дублирование вкладки копирует sessionStorage даже без opener. Новый
|
||||
// заход отвязывает только указатели: IndexedDB исходной вкладки не трогаем.
|
||||
if (performance.getEntriesByType('navigation')[0]?.type !== 'reload') {
|
||||
for (const name of Object.keys(sessionStorage)) {
|
||||
if (name.startsWith(prefix)) sessionStorage.removeItem(name);
|
||||
}
|
||||
@@ -36,37 +36,54 @@ return <<<'JS'
|
||||
&& record.expiresAt === record.createdAt + ttl && record.expiresAt > Date.now()
|
||||
&& typeof record.sourceUrl === 'string' && record.sourceUrl.length <= 16384
|
||||
&& typeof record.slot === 'string' && json(record.meta) !== undefined
|
||||
&& (record.result === null ? (record.blob === null || (record.blob instanceof Blob && record.fileInfo && typeof record.fileInfo.name === 'string' && typeof record.fileInfo.type === 'string' && Number.isFinite(record.fileInfo.lastModified))) : (record.blob === null && record.fileInfo === null && json(record.result) !== undefined));
|
||||
&& (record.result === null ? (record.blob === null || ((record.blob instanceof ArrayBuffer || record.blob instanceof Blob) && record.fileInfo && typeof record.fileInfo.name === 'string' && typeof record.fileInfo.type === 'string' && Number.isFinite(record.fileInfo.lastModified))) : (record.blob === null && record.fileInfo === null && json(record.result) !== undefined));
|
||||
} catch (_) { return false; }
|
||||
};
|
||||
const open = () => new Promise((resolve, reject) => {
|
||||
let request;
|
||||
try { request = indexedDB.open('zoomzilla-upload-recovery', 1); } catch (error) { reject(error); return; }
|
||||
let abandoned = false;
|
||||
let request, settled = false;
|
||||
const finish = (error, db) => {
|
||||
if (settled) { if (db) db.close(); return; }
|
||||
settled = true;
|
||||
window.clearTimeout(timer);
|
||||
if (error) reject(error); else resolve(db);
|
||||
};
|
||||
const timer = window.setTimeout(() => finish(failure('open timeout')), 10000);
|
||||
try { request = indexedDB.open('zoomzilla-upload-recovery', 1); } catch (error) { finish(error); return; }
|
||||
request.onupgradeneeded = () => request.result.createObjectStore('uploads');
|
||||
request.onerror = () => reject(request.error || failure('open failed'));
|
||||
request.onblocked = () => { abandoned = true; reject(failure('database blocked')); };
|
||||
request.onerror = () => finish(request.error || failure('open failed'));
|
||||
request.onblocked = () => finish(failure('database blocked'));
|
||||
request.onsuccess = () => {
|
||||
if (abandoned) { request.result.close(); return; }
|
||||
request.result.onversionchange = () => request.result.close();
|
||||
resolve(request.result);
|
||||
finish(null, request.result);
|
||||
};
|
||||
});
|
||||
const transaction = async callback => {
|
||||
const db = await open();
|
||||
try {
|
||||
return await new Promise((resolve, reject) => {
|
||||
let tx, result, cause;
|
||||
let tx, result, settled = false;
|
||||
try { tx = db.transaction('uploads', 'readwrite'); } catch (error) { reject(error); return; }
|
||||
tx.oncomplete = () => resolve(result);
|
||||
tx.onabort = () => reject(cause || tx.error || failure('transaction aborted'));
|
||||
tx.onerror = () => { /* onabort даёт окончательный результат всей транзакции. */ };
|
||||
const fail = error => { cause = error; try { tx.abort(); } catch (_) { reject(error); } };
|
||||
const finish = error => {
|
||||
if (settled) return;
|
||||
settled = true;
|
||||
window.clearTimeout(timer);
|
||||
if (error) reject(error); else resolve(result);
|
||||
};
|
||||
const fail = error => {
|
||||
finish(error);
|
||||
try { tx.abort(); } catch (_) { /* Транзакция могла уже завершиться. */ }
|
||||
};
|
||||
const timer = window.setTimeout(() => fail(failure('transaction timeout')), 10000);
|
||||
tx.oncomplete = () => finish();
|
||||
tx.onabort = () => finish(tx.error || failure('transaction aborted'));
|
||||
// WebKit может дать request/transaction error без последующего abort.
|
||||
tx.onerror = event => fail(event.target.error || tx.error || failure('transaction failed'));
|
||||
try {
|
||||
const store = tx.objectStore('uploads');
|
||||
// Общая уборка сирот/протухших записей, включая давно закрытые вкладки.
|
||||
const cursor = store.openCursor();
|
||||
cursor.onsuccess = () => {
|
||||
if (settled) return;
|
||||
try {
|
||||
const entry = cursor.result;
|
||||
if (!entry) return;
|
||||
@@ -113,6 +130,10 @@ return <<<'JS'
|
||||
sessionStorage.removeItem(slot);
|
||||
const metadata = json(meta);
|
||||
if (file !== null && !(file instanceof File)) throw failure('prepared File required');
|
||||
// ArrayBuffer не зависит от поддержки Blob-файлов в IndexedDB
|
||||
// (в WebKit её отсутствие проявляется асинхронным UnknownError).
|
||||
const bytes = file === null ? null : await file.arrayBuffer();
|
||||
if (generation !== state.generation) throw failure('cancelled');
|
||||
if (key !== undefined && (typeof key !== 'string' || !key || key.length > 200)) throw failure('invalid key');
|
||||
let tab = sessionStorage.getItem(prefix + 'tab');
|
||||
if (!tab) { tab = randomKey(); sessionStorage.setItem(prefix + 'tab', tab); }
|
||||
@@ -124,7 +145,7 @@ return <<<'JS'
|
||||
const createdAt = resumed ? old.createdAt : Date.now();
|
||||
record = {slot, key: key || randomKey(), createdAt, expiresAt: createdAt + ttl,
|
||||
sourceUrl: resumed ? old.sourceUrl : location.href, meta: metadata,
|
||||
blob: file, fileInfo: file === null ? null : {name: file.name, type: file.type, lastModified: file.lastModified}, result: null};
|
||||
blob: bytes, fileInfo: file === null ? null : {name: file.name, type: file.type, lastModified: file.lastModified}, result: null};
|
||||
if (previous) store.delete(previous);
|
||||
store.put(record, id);
|
||||
done(null);
|
||||
|
||||
Reference in new issue
Block a user