Восстанавливать загрузку30мин после Reload и удерживать Back (#9)
This commit is contained in:
1 parent
f2e6877013
commit
d70d396670
3 files changed
+320
-19
No files matched your search
+63
-19
@@ -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>
|
||||
Reference in new issue
Block a user