Версия 1.2.2: JSON-ошибки как на бэкенде, лимиты 100 МБ, овал-подсказка, безопасный парсинг ответов
- med_diag_json_error() — ошибки в формате {'error': '...'} (WP_REST_Response)
- 4 проверки лимита 100 МБ (CONTENT_LENGTH, UPLOAD_ERR_INI_SIZE, UPLOAD_ERR_FORM_SIZE, size)
- Проверка расширения файла (mp4/avi/mov/mkv/webm/m4v)
- Имя файла только ASCII (med-diag-upload.ext) — защита multipart
- filename скачивания из Content-Disposition бэкенда
- Овал .med-scan-oval (inset 12%, затемнение вне зоны)
- escapeHtml() + parseJson() — безопасная обработка ответов (XSS, HTML-ошибки)
- README: nginx лимиты, таблица ошибок
This commit is contained in:
1 parent
867fe7bc66
commit
90d9576425
4 files changed
+180
-40
No files matched your search
+17
-2
@@ -161,8 +161,12 @@
|
||||
.med-scan-modal.open { display: flex; }
|
||||
|
||||
.med-scan-video-wrap {
|
||||
width: 280px;
|
||||
height: 280px;
|
||||
/* width: 280px; */
|
||||
/* height: 280px; */
|
||||
width: 80vw;
|
||||
height: 80vw;
|
||||
max-width: 500px;
|
||||
max-height: 500px;
|
||||
border-radius: 50%;
|
||||
overflow: hidden;
|
||||
border: 4px solid #0014FC;
|
||||
@@ -178,6 +182,17 @@
|
||||
transform: scaleX(-1);
|
||||
}
|
||||
|
||||
/* Овал — безопасная зона: лицо должно помещаться в него целиком */
|
||||
.med-scan-oval {
|
||||
position: absolute;
|
||||
inset: 12%;
|
||||
border: 3px solid rgba(255, 255, 255, 0.9);
|
||||
border-radius: 50%;
|
||||
box-shadow: 0 0 0 9999px rgba(0, 0, 0, 0.4);
|
||||
pointer-events: none;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.med-scan-progress { color: #fff; margin-top: 20px; font-size: 16px; text-align: center; }
|
||||
|
||||
.med-scan-progress-bar {
|
||||
|
||||
+36
-11
@@ -20,6 +20,29 @@
|
||||
return USE_PROXY ? PROXY_URL + path : API_URL + path;
|
||||
}
|
||||
|
||||
// Экранирование текста перед вставкой в innerHTML: сообщение об ошибке
|
||||
// может прийти из ответа сервера (HTML-страница nginx/PHP и т.п.).
|
||||
function escapeHtml(text) {
|
||||
return String(text === undefined || text === null ? '' : text)
|
||||
.replace(/&/g, '&')
|
||||
.replace(/</g, '<')
|
||||
.replace(/>/g, '>')
|
||||
.replace(/"/g, '"');
|
||||
}
|
||||
|
||||
// Безопасный разбор ответа: если сервер вернул не JSON (например,
|
||||
// HTML-страницу ошибки), не падаем с "Unexpected token '<'".
|
||||
async function parseJson(response) {
|
||||
var ct = (response.headers.get('content-type') || '').toLowerCase();
|
||||
if (ct.indexOf('application/json') !== -1) {
|
||||
return await response.json();
|
||||
}
|
||||
// HTML-ответ превращаем в короткое текстовое сообщение
|
||||
var text = (await response.text()).replace(/<[^>]*>/g, ' ').replace(/\s+/g, ' ').trim();
|
||||
if (text.length > 300) text = text.slice(0, 300) + '…';
|
||||
return { error: text || ('Ошибка сервера (HTTP ' + response.status + ')') };
|
||||
}
|
||||
|
||||
var uploadArea = document.getElementById('uploadArea');
|
||||
var fileInput = document.getElementById('fileInput');
|
||||
var analyzeBtn = document.getElementById('analyzeBtn');
|
||||
@@ -85,14 +108,14 @@
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
var err = await response.json();
|
||||
var err = await parseJson(response);
|
||||
var e = new Error(err.error || 'Ошибка анализа');
|
||||
e.videoUrl = err.video_url;
|
||||
throw e;
|
||||
}
|
||||
|
||||
setProgress(90, 'Формирование результата...');
|
||||
var data = await response.json();
|
||||
var data = await parseJson(response);
|
||||
|
||||
if (data.error) {
|
||||
showError(data.error);
|
||||
@@ -130,7 +153,7 @@
|
||||
function showError(msg, videoUrl) {
|
||||
progressContainer.style.display = 'none';
|
||||
resultCard.style.display = 'block';
|
||||
var html = '<div class="med-error-box"><strong>Ошибка:</strong> ' + msg + '</div>';
|
||||
var html = '<div class="med-error-box"><strong>Ошибка:</strong> ' + escapeHtml(msg) + '</div>';
|
||||
if (videoUrl) {
|
||||
html += '<div style="text-align:center; margin-top:15px">' +
|
||||
'<a class="med-btn-download" href="' + downloadUrl(videoUrl) + '">🎬 Скачать записанное видео</a>' +
|
||||
@@ -257,7 +280,7 @@
|
||||
video: {
|
||||
facingMode: 'user',
|
||||
width: { ideal: 1280 },
|
||||
height: { ideal: 720 }
|
||||
height: { ideal: 1280 }
|
||||
},
|
||||
audio: false
|
||||
});
|
||||
@@ -277,11 +300,11 @@
|
||||
scanLastFrameTime = 0;
|
||||
updateScanProgress(0);
|
||||
|
||||
// Canvas 720×720 — квадрат, лицо целиком влезает в portrait и landscape.
|
||||
// Canvas 1080×1080 — квадрат, лицо целиком влезает в portrait и landscape.
|
||||
// Серверное extract_frames масштабирует до 1280×720 для SDK.
|
||||
scanCanvas = document.createElement('canvas');
|
||||
scanCanvas.width = 720;
|
||||
scanCanvas.height = 720;
|
||||
scanCanvas.width = 1080;
|
||||
scanCanvas.height = 1080;
|
||||
scanCtx = scanCanvas.getContext('2d');
|
||||
|
||||
var captureStream;
|
||||
@@ -320,16 +343,18 @@
|
||||
return;
|
||||
}
|
||||
if (ts - scanLastFrameTime >= FRAME_INTERVAL) {
|
||||
// Квадратный center-crop: берём центральную квадратную область из видео.
|
||||
// Квадратный center-crop с запасом 10%: берём чуть меньший
|
||||
// квадрат, чтобы голова гарантированно помещалась целиком
|
||||
// (иначе SDK не находит лицо из-за обрезанной головы).
|
||||
var vw = scanVideo.videoWidth;
|
||||
var vh = scanVideo.videoHeight;
|
||||
if (vw && vh) {
|
||||
var side = Math.min(vw, vh);
|
||||
var side = Math.min(vw, vh) * 0.9;
|
||||
var sx = (vw - side) / 2;
|
||||
var sy = (vh - side) / 2;
|
||||
scanCtx.drawImage(scanVideo, sx, sy, side, side, 0, 0, 720, 720);
|
||||
scanCtx.drawImage(scanVideo, sx, sy, side, side, 0, 0, 1080, 1080);
|
||||
} else {
|
||||
scanCtx.drawImage(scanVideo, 0, 0, 720, 720);
|
||||
scanCtx.drawImage(scanVideo, 0, 0, 1080, 1080);
|
||||
}
|
||||
scanFrameCount++;
|
||||
scanLastFrameTime = ts;
|
||||
|
||||
Reference in new issue
Block a user