Восстанавливать загрузку30мин после Reload и удерживать Back (#9)

This commit is contained in:
Panfilov Konstantin committed 2026-10-20 18:31:43 +03:00
1 parent f2e6877013
commit d70d396670
3 files changed
+320 -19

No files matched your search

+63 -19
View File
@@ -1,5 +1,7 @@
<?php
require __DIR__ . '/browser-upload.php';
echo '<script>', require __DIR__ . '/navigation-guard.php', '</script>';
echo '<script>', require __DIR__ . '/upload-recovery.php', '</script>';
$errorMessage = Yii::t('app', 'Возникла ошибка');
$utmJson = json_encode($UTM ?? [], JSON_UNESCAPED_UNICODE);
@@ -98,6 +100,11 @@ $basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/a
}
let uploadPending = false;
let leavingUploadPage = false;
let uploadAttempt = 0;
const recovery = window.ZoomzillaUploadRecovery.create({scope: 'preland', target: createApiUrl});
const metadata = () => ({actionUrl: createApiUrl, redirectBase: redirectUrlBase, redirectUtm: redirectUrlUtm});
const clearRecovery = () => recovery.clear().catch(() => {});
let uploadDots = 1;
let uploadDotsTimer = null;
const uploadButton = formEl.querySelector('.dropzone-form-button');
@@ -111,6 +118,8 @@ $basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/a
function beginUploadPending() {
if (uploadPending) return false;
uploadPending = true;
uploadAttempt++;
window.PublicNavigationGuard.activate();
uploadDots = 1;
renderUploadButton();
if (uploadButton) {
@@ -191,51 +200,62 @@ $basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/a
: Array.from(crypto.getRandomValues(new Uint8Array(16)), (b) => b.toString(16).padStart(2, '0')).join('');
}
function redirectToTour(clientKey) {
let targetUrl = redirectUrlBase + encodeURIComponent(clientKey);
if (redirectUrlUtm) targetUrl += '&' + redirectUrlUtm;
window.location.href = targetUrl;
function redirectToTour(record) {
let targetUrl = record.meta.redirectBase + encodeURIComponent(record.key);
if (record.meta.redirectUtm) targetUrl += '&' + record.meta.redirectUtm;
window.PublicNavigationGuard.allowNavigation(() => { window.location.href = targetUrl; });
}
function rejectOversizedFile(file) {
async function rejectOversizedFile(file, saved = null) {
if (!beginUploadPending()) return;
const clientKey = createClientKey();
const attempt = uploadAttempt;
try {
const record = saved || await recovery.remember(null, {...metadata(), rejectionCode: 105, name: file.name, size: file.size});
if (attempt !== uploadAttempt || leavingUploadPage) return;
const clientKey = record.key;
const formData = new FormData();
formData.append('rejection_code', '105');
formData.append('original_name', file.name || 'upload');
formData.append('original_size', String(file.size || 0));
formData.append('client_key', clientKey);
const xhr = new XMLHttpRequest();
xhr.open('POST', createApiUrl || formEl.getAttribute('action') || '/api/images/public/create', true);
xhr.open('POST', record.meta.actionUrl, true);
xhr.withCredentials = true;
xhr.setRequestHeader('Accept', 'application/json');
xhr.onload = () => {
xhr.onload = async () => {
if (xhr.status >= 200 && xhr.status < 300) {
redirectToTour(clientKey);
try { const result = JSON.parse(xhr.responseText); if (!result?.id) throw new Error(); await recovery.accepted({id: result.id}); redirectToTour(record); }
catch (_) { showUploadError(genericErrorMsg); }
return;
}
showUploadError(genericErrorMsg);
};
xhr.onerror = () => showUploadError(genericErrorMsg);
xhr.onerror = () => showUploadError(genericErrorMsg, true);
xhr.send(formData);
} catch (_) { showUploadError(genericErrorMsg); }
}
function uploadFile(file) {
function uploadFile(file, saved = null) {
const attempt = uploadAttempt;
// Тот же обход нестабильного Android FileProvider, что на основном сайте.
window.ZoomzillaUpload.prepare(file, maxUploadBytes || 15 * 1024 * 1024)
.then(bytes => uploadPreparedFile(file, bytes))
.then(async bytes => {
if (attempt !== uploadAttempt || leavingUploadPage) return;
const record = saved || await recovery.remember(new File([bytes], file.name, {type: file.type, lastModified: file.lastModified}), metadata());
if (attempt === uploadAttempt && !leavingUploadPage) uploadPreparedFile(file, bytes, record);
})
.catch(() => showUploadError(genericErrorMsg));
}
function uploadPreparedFile(file, bytes) {
function uploadPreparedFile(file, bytes, record) {
const xhr = new XMLHttpRequest();
const formData = new FormData();
// Ключ загрузки: по нему сайт передаёт её в сессию посетителя (/api/images/public/claim, годен 30 мин).
// randomUUID есть в HTTPS-контексте; где его нет (старый Safari), есть getRandomValues.
const clientKey = createClientKey();
const clientKey = record.key;
formData.append('file', bytes, file.name || 'upload');
formData.append('client_key', clientKey);
const actionUrl = createApiUrl || formEl.getAttribute('action') || '/api/images/public/create';
const actionUrl = record.meta.actionUrl;
xhr.open('POST', actionUrl, true);
xhr.withCredentials = true;
xhr.setRequestHeader('Accept', 'application/json');
@@ -247,7 +267,7 @@ $basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/a
}
};
xhr.onload = () => {
xhr.onload = async () => {
if (xhr.status >= 200 && xhr.status < 300) {
let response;
try {
@@ -269,24 +289,48 @@ $basePickerAccept = 'image/jpeg,image/png,image/gif,image/webp,image/bmp,image/a
}
// Переход: $redirect_url_base . client_key . ($redirect_url_utm ? '&' . $redirect_url_utm : '')
redirectToTour(clientKey);
try { await recovery.accepted({id: imageId}); redirectToTour(record); }
catch (_) { showUploadError(genericErrorMsg); }
} else {
showUploadError(genericErrorMsg);
}
};
xhr.onerror = () => {
showUploadError(genericErrorMsg);
showUploadError(genericErrorMsg, true);
};
xhr.send(formData);
}
function showUploadError(msg) {
function showUploadError(msg, keepRecovery = false) {
if (leavingUploadPage) return;
uploadAttempt++;
if (!keepRecovery) clearRecovery();
resetUploadPending();
window.PublicNavigationGuard.allowNavigation();
form.show();
if (typeof preview !== 'undefined' && preview) preview.showError(msg);
fileInput.value = '';
}
window.addEventListener('pagehide', () => { leavingUploadPage = true; });
window.addEventListener('pageshow', event => {
if (!event.persisted) return;
leavingUploadPage = false;
uploadAttempt++;
const wasUploading = uploadPending;
resetUploadPending(); clearRecovery(); fileInput.value = '';
if (wasUploading) window.PublicNavigationGuard.allowNavigation();
});
(async () => {
try {
const saved = await recovery.load();
if (!saved) return;
if (performance.getEntriesByType('navigation')[0]?.type !== 'reload' || saved.sourceUrl !== location.href || saved.meta.actionUrl !== createApiUrl) { clearRecovery(); return; }
if (saved.result?.id) { beginUploadPending(); redirectToTour(saved); }
else if (saved.file) { beginUploadPending(); uploadFile(saved.file, saved); }
else if (saved.meta.rejectionCode === 105) rejectOversizedFile(saved.meta, saved);
} catch (_) { clearRecovery(); }
})();
})();
</script>