From bd6f92f2f3a70c44c0b80da66dad9f34fc2f492d Mon Sep 17 00:00:00 2001 From: dadaloop82 Date: Sat, 4 Apr 2026 14:44:11 +0000 Subject: [PATCH] fix: route TTS through PHP proxy to bypass mixed-content/CORS --- api/index.php | 52 +++++++++++++++++++++++++++++++++++++++++++++++ assets/js/app.js | 29 ++++++++++++++++++++------ data/dispensa.db | Bin 311296 -> 315392 bytes 3 files changed, 75 insertions(+), 6 deletions(-) diff --git a/api/index.php b/api/index.php index 12ef0bd..9605c08 100644 --- a/api/index.php +++ b/api/index.php @@ -190,6 +190,9 @@ try { case 'chat_clear': chatClear($db); break; + case 'tts_proxy': + ttsProxy(); + break; default: http_response_code(404); @@ -201,6 +204,55 @@ try { } endif; // end !CRON_MODE +// ===== TTS PROXY ===== +function ttsProxy() { + $body = json_decode(file_get_contents('php://input'), true); + $url = isset($body['url']) ? trim($body['url']) : ''; + $method = isset($body['method']) ? strtoupper(trim($body['method'])) : 'POST'; + $headers = isset($body['headers']) && is_array($body['headers']) ? $body['headers'] : []; + $payload = isset($body['payload']) ? $body['payload'] : ''; + $contentType = ''; + foreach ($headers as $k => $v) { + if (strtolower($k) === 'content-type') { $contentType = $v; break; } + } + + if (!$url || !preg_match('/^https?:\/\/.+/', $url)) { + http_response_code(400); + echo json_encode(['error' => 'URL non valido']); + return; + } + + $curlHeaders = []; + foreach ($headers as $k => $v) { + $curlHeaders[] = "$k: $v"; + } + + $ch = curl_init($url); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, $method); + if ($method !== 'GET' && $payload !== '') { + curl_setopt($ch, CURLOPT_POSTFIELDS, $payload); + } + if ($curlHeaders) { + curl_setopt($ch, CURLOPT_HTTPHEADER, $curlHeaders); + } + curl_setopt($ch, CURLOPT_TIMEOUT, 10); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); // allow self-signed certs on local network + $response = curl_exec($ch); + $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + $curlErr = curl_error($ch); + curl_close($ch); + + if ($curlErr) { + http_response_code(502); + echo json_encode(['error' => 'cURL error: ' . $curlErr]); + return; + } + + http_response_code($httpCode ?: 200); + echo json_encode(['status' => $httpCode, 'body' => $response]); +} + // ===== CLIENT LOG ===== function clientLog(): void { diff --git a/assets/js/app.js b/assets/js/app.js index 5334071..afbf792 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -6655,13 +6655,27 @@ function _buildTtsRequest(text, s) { return { url, method, headers, body }; } +async function _ttsViaProxy(req) { + // Route through server-side proxy to avoid mixed-content / CORS issues + return fetch('api/index.php?action=tts_proxy', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + url: req.url, + method: req.method, + headers: req.headers, + payload: req.body + }) + }); +} + async function speakCookingStep(text) { if (!text) return; const s = getSettings(); if (!s.tts_enabled) return; try { const req = _buildTtsRequest(text, s); - await fetch(req.url, { method: req.method, headers: req.headers, body: req.body }); + await _ttsViaProxy(req); } catch(e) { /* silent — TTS is non-critical */ } } @@ -6707,14 +6721,17 @@ async function testTTS() { if (statusEl) { statusEl.style.display = 'block'; statusEl.className = 'settings-status'; statusEl.textContent = '⏳ Invio in corso…'; } try { const req = _buildTtsRequest('Test vocale Dispensa Manager', formSettings); - const res = await fetch(req.url, { method: req.method, headers: req.headers, body: req.body }); - if (res.ok || res.status === 200) { - if (statusEl) { statusEl.className = 'settings-status success'; statusEl.textContent = `✅ Risposta ${res.status} — controlla che l'altoparlante abbia parlato.`; } + const res = await _ttsViaProxy(req); + const data = await res.json().catch(() => ({})); + const httpCode = data.status || res.status; + if (res.ok && httpCode >= 200 && httpCode < 300) { + if (statusEl) { statusEl.className = 'settings-status success'; statusEl.textContent = `✅ Risposta ${httpCode} — controlla che l'altoparlante abbia parlato.`; } } else { - if (statusEl) { statusEl.className = 'settings-status error'; statusEl.textContent = `⚠️ HTTP ${res.status}: ${res.statusText}`; } + const errDetail = data.error || data.body || res.statusText; + if (statusEl) { statusEl.className = 'settings-status error'; statusEl.textContent = `⚠️ HTTP ${httpCode}: ${errDetail}`; } } } catch(e) { - if (statusEl) { statusEl.className = 'settings-status error'; statusEl.textContent = `❌ Errore di rete: ${e.message}`; } + if (statusEl) { statusEl.className = 'settings-status error'; statusEl.textContent = `❌ Errore: ${e.message}`; } } } diff --git a/data/dispensa.db b/data/dispensa.db index ce4e9f8f40c7caebfa73a26fd9083ba765e92431..61f8bd7bd1abb28a77202abfe722394bb094c6f7 100644 GIT binary patch delta 4393 zcmb_g&2JmW6<3%Tfe%4Y80f({h~86ylJf47A}wALnxtr=mPC-0f;2Yh;qH)}dbzXN z52i99xwOT-eX5O25-@i9+_2$jJH{ad+ z{bzSB_V)Hp{!sq>gNxt1`0a}i{`R8u=TASrJKFp7?cLE|fAYog-H-pWDcyPS&(E*! z-Y;#g{&c_ekDmsiTnm?nM$2%Mz!MIBUK-z@epae{bce5)@UH&2Un<`ZSQLFLLl@^n z{9xo9o{t}N55KIBW>??rm%6FTVrH~9MiP-M{da)VcCFsDt%@-hVHBUT6^DQ>^9A>e zis7*>Z5)eLFel<1=6E8$>@cOxteci;S*B^)^(NTLCbE{f%#$*BWPh<(?R^N(fA@C3 zgx?Rpf4l$D&%R3$j6Ilt;K&uJk_+MAyjHM-rS!?PpVy9R^&_)sH|)bYu8}*F%K$9x z60ONC!!ll142TYxe@O(hVk}v7%+@j#F^`N^8ixt57%rc)#EZwV40+piLmq**F_>P` zO+WFxN>0!bB9apC4*VF(Y^%|1938?Qq-!obWsb+!2x7}7Gkk=1#UYH8;^s1JrwdMZ zH&_>1e4PLY5`N4>pLreWlHcUbLd@e14`VSG4ucOP?r^atsb4=w0QrJ>u>I?eUXoDj zH5e44p@h7sBNLLU52cRGtT&-9Q+%A|7L+yf5@<%;0(JR3B2DIXiAvq=1f?V?fHe|n z-()K?+IVDHjYiXk4gY;nBA6N^xHS`DyzC-ER0c5a;xvvIHA?~N#` z8iclvO)e-|%~U(I&1VlgIOU)h;l$JS;G#FiK9Et&JY<({d1Oqg zNvgm4sv;i$5=U6T!gvK}p5}T$T(Mtw1*EET=0Tk*2T%&3caty4X9Ot&(f}q1hN~iy zP&AleSriO%=BBwVJ9Bq>sc1|r7@37IVnYKm1RJBWayVm84cCGkX-j;Hu{n4i3k$Oq) zr7As$gcGjNTf#A4P}uXD##5#rDzzZ}+E}&S7BQ~hC_OSd^Im17X8y4Dkw509s@Ibw z*hNOct-Qj=JX{M$VE`%3;Q$zsFxrxqcaZy`Oai6WY&K08_ixaXQoSgFH$3=(*%@>O z0RB*VqWoBTub3}kZ|0~1kYMFL^VWE}E9F|j`w{RMl{5!VA?_M-YDaj}A?HWnqmy}S z^tgjmy_^UDba*TlaYBHtHxBA}N^6YCCj2vb%;_G zh$}81FASk%EHe+7{Jh%bCb?{5xmXn((6Bp$08-M1{{ zONlXr!oA(3log<4D^AsR_ZQT{S3G8Tz}3LIy?_YL(MvZ*@3j5+X>U3lot>ObpR|X= zuclAV&PUq%iZS_W2xf2GdD0sV+SAFGnWL{?Q@5ZwkAy@it;hzH)b%+rbFTvfFqO)3 z-;**Ue4Y_)OUu*5e5|pbJv3|29+Gri*v)Gm2A^{!m(0~Ur}mn2U7qU4e7DuX zK?kp&atJ82>j(`V9=zGh%oq#9RIN%{%Vx7xJ8ad+ExI(0S)4@bDk_&n8I4 z_801>{lW3%{RNDA>GAn#_qHyaP0-w??US?f(_1L-^hT3u+V7oCZgEY*uiX%*oyz z%*3iVEq{8XBvUnea(-@Feo@KvW0Fj#x38CCQel}cEze}FT#}!inx|x?RGeB=mRh7# lYh++#rfXoVYiOciXl7+(Y-M8HenXxKh?%$FkY~|Y008_NClUYv