diff --git a/src/lib/common/services/network.service.js b/src/lib/common/services/network.service.js index cfaacbc..361b600 100644 --- a/src/lib/common/services/network.service.js +++ b/src/lib/common/services/network.service.js @@ -1,88 +1,45 @@ // lib/common/services/network.service.js // -// Проверка подключения через эндпоинты бэкенда: +// Определение сети, в которой находится устройство. // -// - /api/is_server — публичный сервер unitprint.ru. -// Отвечает {"is_server":true} → интернет есть, скан доступен; -// - local.unitprint.ru/api/is_local — локальный сервер печати. -// Отвечает {"is_local":true} → устройство в Wi-Fi принтера (UnitPrintLocal), -// печать доступна. +// - внутри локальной сети принтера (Wi-Fi UnitPrintLocal) +// домен unitprint.ru резолвится на 192.168.20.1 — локальный сервер печати; +// - из интернета домен отвечает публичным адресом 95.165.135.233. // -// Сайт всегда отдаётся с unitprint.ru, поэтому проверка локалки ходит -// на отдельный хост local.unitprint.ru: вне локальной сети принтера -// он просто недоступен. -// -// Скан работает только через интернет, печать — только через локальную сеть. +// Клиент не может сам отличить эти случаи по IP (сертификаты, mixed content), +// поэтому спрашиваем сам сервер: GET /api/is_local → { "is_local": true|false }. +// Эндпоинт обязан быть на обоих инстансах: +// локальный → { "is_local": true } +// публичный → { "is_local": false } -export const LOCAL_HOST = 'local.unitprint.ru'; +export const DOMAIN = 'unitprint.ru'; +export const PUBLIC_IP = '95.165.135.233'; +export const LOCAL_IP = '192.168.20.1'; export const LOCAL_WIFI_NAME = 'UnitPrintLocal'; /** - * GET с таймаутом, читаем JSON. - * @returns {Promise} null — если хост недоступен или ошибка. + * Определяет текущий режим сети. + * @returns {Promise<'local' | 'internet' | 'offline'>} + * - 'local' → отвечающий сервер — локальный (домен → 192.168.20.1) + * - 'internet' → отвечающий сервер — публичный (домен → 95.165.135.233) + * - 'offline' → сервер вообще не ответил */ -async function getJson(url, timeoutMs = 4000) { - if (typeof fetch === 'undefined') return null; +export async function detectNetwork(timeoutMs = 6000) { + if (typeof fetch === 'undefined') return 'offline'; const controller = new AbortController(); const timer = setTimeout(() => controller.abort(), timeoutMs); try { - const res = await fetch(url, { - method: 'GET', + const res = await fetch('/api/is_local', { cache: 'no-store', signal: controller.signal, }); - if (!res.ok) return null; - return await res.json(); + if (!res.ok) return 'offline'; + const data = await res.json(); + if (typeof data?.is_local !== 'boolean') return 'offline'; + return data.is_local ? 'local' : 'internet'; } catch { - return null; + return 'offline'; } finally { clearTimeout(timer); } } - -/** - * Доступность хоста в режиме 'no-cors': CORS не мешает, важен сам факт ответа. - */ -async function canReach(url, timeoutMs = 4000) { - if (typeof fetch === 'undefined') return false; - const controller = new AbortController(); - const timer = setTimeout(() => controller.abort(), timeoutMs); - try { - await fetch(url, { - method: 'GET', - mode: 'no-cors', - cache: 'no-store', - signal: controller.signal, - }); - return true; - } catch { - return false; - } finally { - clearTimeout(timer); - } -} - -/** - * Устройство в локальной сети принтера: - * local.unitprint.ru/api/is_local отвечает {"is_local":true}. - */ -export async function isLocalNetwork() { - const ts = Date.now(); - const httpsUrl = `https://${LOCAL_HOST}/api/is_local?ping=${ts}`; - const httpUrl = `http://${LOCAL_HOST}/api/is_local?ping=${ts}`; - - const data = (await getJson(httpsUrl, 3000)) ?? (await getJson(httpUrl, 3000)); - if (data) return data.is_local === true; - - // JSON не читается (CORS / самоподписанный сертификат) —fallback: - // сам факт ответа local.unitprint.ru означает, что мы в локалке. - return canReach(httpsUrl, 3000); -} - -/** - * Есть интернет: публичный сервер отвечает на /api/is_server. - */ -export async function isServerReachable() { - const data = await getJson(`/api/is_server?ping=${Date.now()}`, 5000); - return data?.is_server === true; -} diff --git a/src/lib/common/ui/NetworkGate.svelte b/src/lib/common/ui/NetworkGate.svelte index cb419f5..e1e1b06 100644 --- a/src/lib/common/ui/NetworkGate.svelte +++ b/src/lib/common/ui/NetworkGate.svelte @@ -1,76 +1,119 @@ +
+

{isLocalMode ? 'Подключение к принтеру' : 'Нет интернета'}

- {#if success} + {#if status === 'success'}
@@ -98,29 +141,41 @@ mode:
{#if isLocalMode} -

Подключитесь к принтеру

+

Нет связи с принтером

Печать доступна только в локальной сети принтера. Подключите устройство к сети:

{:else}

Необходимо подключение к интернету

-

Сканирование доступно только онлайн

+

Убедитесь, что вы отключены от локальной сети Wi-Fi принтера.

{/if}
- {#if isLocalMode} -
+
+ {#if isLocalMode}
1 - +
Подключитесь к Wi-Fi - - - {LOCAL_WIFI_NAME} - - +
+ + + {LOCAL_WIFI_NAME} + + +
+
2 @@ -130,166 +185,254 @@ mode: 3 Отключите VPN
-
- {/if} + {:else} +
+ 1 + Отключитесь от сети {LOCAL_WIFI_NAME} +
+
+ 2 + Включите мобильный интернет или подключитесь к другому Wi-Fi +
+ {/if} +
- Ожидание подключения… + + {#if status === 'checking'} + Проверка подключения… + {:else if isLocalMode} + Ожидание подключения к принтеру… + {:else} + Ожидание интернета… + {/if} +
+

Как только подключение появится — продолжим автоматически

+ + {/if}
diff --git a/src/lib/pages/main/MainMenu.svelte b/src/lib/pages/main/MainMenu.svelte index ed99c38..182a0f0 100644 --- a/src/lib/pages/main/MainMenu.svelte +++ b/src/lib/pages/main/MainMenu.svelte @@ -205,5 +205,5 @@
v{backendVersion} - v1.0.9 + v1.1.0
diff --git a/src/lib/pages/print/PrintMenu.svelte b/src/lib/pages/print/PrintMenu.svelte index 8faf99a..d7428d3 100644 --- a/src/lib/pages/print/PrintMenu.svelte +++ b/src/lib/pages/print/PrintMenu.svelte @@ -7,7 +7,6 @@ import PaymentPage from '$lib/common/ui/PaymentPage.svelte'; import { plFiles, plCopies } from '$lib/common/utils/pricing.util.js'; import NetworkGate from '$lib/common/ui/NetworkGate.svelte'; - import { isLocalNetwork } from '$lib/common/services/network.service.js'; let { onBack = () => {} } = $props(); const store = createPrintStore(); @@ -38,32 +37,27 @@ /* ── Проверка сети перед отправкой печати ── * Печать работает только в локальной сети принтера (домен → 192.168.20.1). - * Если «Я оплатил(а)» нажата не в той сети — показываем экран подключения - * и автоматически отправляем печать, как только сеть появится. + * Гейт открывается мгновенно по нажатию «Я оплатил(а)» и сам внутри себя + * делает первую проверку: если мы уже в локалке — короткая зелёная вспышка + * и печать ушла; если нет — инструкции и авто-проход при появлении сети. */ - /** @type {null | 'checking' | 'blocked'} */ - let printNetGate = $state(null); + /** true → показываем экран подключения к принтеру */ + let printNetGate = $state(false); - async function handleConfirmPayment() { - if (printNetGate === 'checking') return; - printNetGate = 'checking'; - const net = await isLocalNetwork(); - if (net === 'local') { - printNetGate = null; - store.submitPrint(); - } else { - printNetGate = 'blocked'; - } + function handleConfirmPayment() { + // Гейт сам проверит сеть: если уже локальная — сразу пропустит и напечатает, + // если нет — покажет инструкции и будет ждать появления сети. + printNetGate = true; } function handleGateReady() { // сеть принтера появилась — пропускаем пользователя и сразу печатаем - printNetGate = null; + printNetGate = false; store.submitPrint(); } function handleGateBack() { - printNetGate = null; // возврат к странице оплаты + printNetGate = false; // возврат к странице оплаты } @@ -71,15 +65,8 @@ {:else if store.showPayment} - {#if printNetGate === 'blocked'} + {#if printNetGate} - {:else if printNetGate === 'checking'} -
-
-
-

Проверка подключения…

-
-
{:else} - .net-checking { flex: 1; display: flex; flex-direction: column; align-items: center; justify-content: center; gap: 18px; padding: 40px 20px; color: #8b93a1; font-size: 15px; font-weight: 600; text-align: center; } - .net-spinner { width: 42px; height: 42px; border-radius: 50%; border: 3px solid rgba(255,255,255,0.12); border-top-color: #6366f1; animation: net-spin 0.8s linear infinite; } - @keyframes net-spin { to { transform: rotate(360deg); } } .page-container { width: 100%; min-height: 100dvh; display: flex; flex-direction: column; background: radial-gradient(120% 60% at 50% -10%, rgba(99,102,241,0.18) 0%, transparent 60%); font-family: system-ui, -apple-system, sans-serif; color: #f5f7fa; } .header { padding: 24px 20px 8px; display: flex; align-items: center; position: relative; } .back-btn { background: none; border: none; color: #6366f1; font-size: 15px; font-weight: 600; cursor: pointer; padding: 8px 4px; margin-right: auto; min-height: 44px; }