v1.0.9 upd connections instruct and upd backend realisation

This commit is contained in:
2026-09-19 15:54:26 +03:00
parent 7de197fa81
commit db9be1a90a
4 changed files with 276 additions and 406 deletions

View File

@ -1,22 +1,47 @@
// lib/common/services/network.service.js // lib/common/services/network.service.js
// //
// Определение сети, в которой находится устройство. // Проверка подключения через эндпоинты бэкенда:
// //
// - внутри локальной сети принтера (Wi-Fi UnitPrintLocal) // - /api/is_server — публичный сервер unitprint.ru.
// домен unitprint.ru резолвится на 192.168.20.1 — локальный сервер печати; // Отвечает {"is_server":true} → интернет есть, скан доступен;
// - из интернета домен отвечает публичным адресом 95.165.135.233. // - local.unitprint.ru/api/is_local — локальный сервер печати.
// Отвечает {"is_local":true} → устройство в Wi-Fi принтера (UnitPrintLocal),
// печать доступна.
//
// Сайт всегда отдаётся с unitprint.ru, поэтому проверка локалки ходит
// на отдельный хост local.unitprint.ru: вне локальной сети принтера
// он просто недоступен.
// //
// Скан работает только через интернет, печать — только через локальную сеть. // Скан работает только через интернет, печать — только через локальную сеть.
export const DOMAIN = 'unitprint.ru'; export const LOCAL_HOST = 'local.unitprint.ru';
export const PUBLIC_IP = '95.165.135.233';
export const LOCAL_IP = '192.168.20.1';
export const LOCAL_WIFI_NAME = 'UnitPrintLocal'; export const LOCAL_WIFI_NAME = 'UnitPrintLocal';
/** /**
* Сетевая доступность URL. * GET с таймаутом, читаем JSON.
* mode: 'no-cors' — чтобы CORS не мешал: важен сам факт ответа сервера, * @returns {Promise<any|null>} null — если хост недоступен или ошибка.
* а не содержимое ответа. Любой HTTP-ответ = хост доступен. */
async function getJson(url, timeoutMs = 4000) {
if (typeof fetch === 'undefined') return null;
const controller = new AbortController();
const timer = setTimeout(() => controller.abort(), timeoutMs);
try {
const res = await fetch(url, {
method: 'GET',
cache: 'no-store',
signal: controller.signal,
});
if (!res.ok) return null;
return await res.json();
} catch {
return null;
} finally {
clearTimeout(timer);
}
}
/**
* Доступность хоста в режиме 'no-cors': CORS не мешает, важен сам факт ответа.
*/ */
async function canReach(url, timeoutMs = 4000) { async function canReach(url, timeoutMs = 4000) {
if (typeof fetch === 'undefined') return false; if (typeof fetch === 'undefined') return false;
@ -37,33 +62,27 @@ async function canReach(url, timeoutMs = 4000) {
} }
} }
/** Доступен ли локальный сервер печати (192.168.20.1) */ /**
export async function isLocalServerReachable() { * Устройство в локальной сети принтера:
* local.unitprint.ru/api/is_local отвечает {"is_local":true}.
*/
export async function isLocalNetwork() {
const ts = Date.now(); const ts = Date.now();
if (await canReach(`http://${LOCAL_IP}/api/version?ping=${ts}`, 3000)) return true; const httpsUrl = `https://${LOCAL_HOST}/api/is_local?ping=${ts}`;
return canReach(`https://${LOCAL_IP}/api/version?ping=${ts}`, 3000); const httpUrl = `http://${LOCAL_HOST}/api/is_local?ping=${ts}`;
}
/** Есть ли интернет (домен отвечает публичным сервером) */ const data = (await getJson(httpsUrl, 3000)) ?? (await getJson(httpUrl, 3000));
export function isInternetReachable() { if (data) return data.is_local === true;
return canReach(`https://${DOMAIN}/api/version?ping=${Date.now()}`, 5000);
// JSON не читается (CORS / самоподписанный сертификат) —fallback:
// сам факт ответа local.unitprint.ru означает, что мы в локалке.
return canReach(httpsUrl, 3000);
} }
/** /**
* Определяет текущий режим сети. * Есть интернет: публичный сервер отвечает на /api/is_server.
* @returns {Promise<'local' | 'internet' | 'offline'>}
* - 'local' → устройство в сети принтера (домен → 192.168.20.1)
* - 'internet' → устройство в интернете (домен → 95.165.135.233)
* - 'offline' → нет ни того, ни другого
*/ */
export async function detectNetwork() { export async function isServerReachable() {
const [local, internet] = await Promise.all([ const data = await getJson(`/api/is_server?ping=${Date.now()}`, 5000);
isLocalServerReachable(), return data?.is_server === true;
isInternetReachable(),
]);
// Локальная сеть в приоритете: если 192.168.20.1 доступен,
// значит домен сейчас указывает на локальный сервер печати.
if (local) return 'local';
if (internet) return 'internet';
return 'offline';
} }

View File

@ -1,127 +1,76 @@
<!-- <!--
lib/common/ui/NetworkGate.svelte lib/common/ui/NetworkGate.svelte
Экран-блокиратор: не пускает дальше, пока устройство не окажется в нужной сети.
Экран-блокиратор: не пускает дальше, пока устройство не окажется в нужной сети. mode:
- 'need-internet' — нужен интернет (/api/is_server отвечает true). Используется в скане.
mode: - 'need-local' — нужна локальная сеть принтера (local.unitprint.ru/api/is_local отвечает true). Перед печатью.
- 'need-internet' — нужен интернет (домен → 95.165.135.233). Используется в скане. Сам опрашивает подключение; как только оно появилось — показывает «Подключено» и вызывает onReady().
- 'need-local' — нужна локальная сеть принтера (домен → 192.168.20.1). Перед печатью.
Как только сеть появилась — показывает «Подключено» и вызывает onReady().
--> -->
<script> <script>
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { detectNetwork, LOCAL_WIFI_NAME } from '$lib/common/services/network.service.js'; import { isLocalNetwork, isServerReachable, LOCAL_WIFI_NAME } from '$lib/common/services/network.service.js';
let { let {
/** 'need-internet' | 'need-local' */ /** 'need-internet' | 'need-local' */
mode, mode,
onReady = () => {}, onReady = () => {},
onBack = () => {}, onBack = () => {},
pollMs = 3000, pollMs = 3000,
} = $props(); } = $props();
/** 'checking' — первая проверка, 'waiting' — ждём сеть, 'success' — сеть появилась */ let success = $state(false);
let status = $state('checking'); let busy = false;
let copySuccess = $state(false); let finished = false;
let timer = null;
let busy = false; const isLocalMode = $derived(mode === 'need-local');
let finished = false;
let timer = null;
const isLocalMode = $derived(mode === 'need-local'); async function check() {
if (busy || finished) return;
async function check() { busy = true;
if (busy || finished) return; try {
busy = true; const ok = isLocalMode ? await isLocalNetwork() : await isServerReachable();
try { if (ok) {
const net = await detectNetwork(); finished = true;
const ok = isLocalMode ? net === 'local' : net === 'internet'; success = true;
if (ok) { stopPoll();
finished = true; setTimeout(() => onReady?.(), 900);
status = 'success'; }
stopPoll(); } finally {
setTimeout(() => onReady?.(), 900); busy = false;
} else if (status === 'checking') {
status = 'waiting';
startPoll();
} }
} finally {
busy = false;
} }
}
function startPoll() { function startPoll() {
stopPoll();
timer = setInterval(check, pollMs);
}
function stopPoll() {
if (timer) clearInterval(timer);
timer = null;
}
onMount(() => {
check();
const onVis = () => {
if (document.visibilityState === 'visible') check();
};
document.addEventListener('visibilitychange', onVis);
return () => {
document.removeEventListener('visibilitychange', onVis);
stopPoll(); stopPoll();
}; timer = setInterval(check, pollMs);
}); }
function stopPoll() {
if (timer) clearInterval(timer);
timer = null;
}
/* ── копирование имени сети ── */ onMount(() => {
async function copyWifiName() { check();
let success = false; startPoll();
if (navigator.clipboard && window.isSecureContext) { const onVis = () => {
try { if (document.visibilityState === 'visible') check();
await navigator.clipboard.writeText(LOCAL_WIFI_NAME); };
success = true; document.addEventListener('visibilitychange', onVis);
} catch (err) { return () => {
console.warn('Clipboard API failed:', err); document.removeEventListener('visibilitychange', onVis);
} stopPoll();
} };
if (!success) { });
try {
const textArea = document.createElement('textarea');
textArea.value = LOCAL_WIFI_NAME;
textArea.style.position = 'fixed';
textArea.style.left = '-9999px';
textArea.style.opacity = '0';
document.body.appendChild(textArea);
textArea.focus();
textArea.select();
const deprecated = /** @type {any} */ (document).execCommand;
if (deprecated && deprecated.call(document, 'copy')) success = true;
document.body.removeChild(textArea);
} catch (err) {
console.error('Fallback copy failed:', err);
}
}
if (success) {
copySuccess = true;
setTimeout(() => (copySuccess = false), 2000);
}
}
</script> </script>
<div class="gate-container"> <div class="gate-container">
<header class="gate-header"> <header class="gate-header">
<button type="button" class="back-btn" onclick={() => onBack?.()}>←</button> <button type="button" class="back-btn" onclick={() => onBack?.()}>←</button>
<h1>{isLocalMode ? 'Подключение к принтеру' : 'Нет интернета'}</h1>
</header> </header>
<main class="gate-content"> <main class="gate-content">
{#if status === 'checking'} {#if success}
<div class="state-block">
<div class="spinner big"></div>
<p class="state-title">Проверка подключения…</p>
<p class="state-sub">Определяем, в какой вы сети</p>
</div>
{:else if status === 'success'}
<div class="state-block"> <div class="state-block">
<div class="success-icon"> <div class="success-icon">
<svg width="42" height="42" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"></polyline></svg> <svg width="42" height="42" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><polyline points="20 6 9 17 4 12"></polyline></svg>
@ -149,41 +98,29 @@ async function copyWifiName() {
<div class="gate-info"> <div class="gate-info">
{#if isLocalMode} {#if isLocalMode}
<h2>Нет связи с принтером</h2> <h2>Подключитесь к принтеру</h2>
<p>Печать доступна только в локальной сети принтера. Подключите устройство к сети:</p> <p>Печать доступна только в локальной сети принтера. Подключите устройство к сети:</p>
{:else} {:else}
<h2>Необходимо подключение к интернету</h2> <h2>Необходимо подключение к интернету</h2>
<p>Убедитесь, что вы отключены от локальной сети Wi-Fi принтера.</p> <p>Сканирование доступно только онлайн</p>
{/if} {/if}
</div> </div>
<div class="steps"> {#if isLocalMode}
{#if isLocalMode} <div class="steps-card">
<div class="step"> <div class="step">
<span class="step-num">1</span> <span class="step-num">1</span>
<div class="step-body"> <span class="step-body">
<span class="step-text">Подключитесь к Wi-Fi</span> <span class="step-text">Подключитесь к Wi-Fi</span>
<div class="wifi-chip-row"> <span class="wifi-name">
<span class="wifi-chip"> <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"> <path d="M5 12.55a11 11 0 0 1 14.08 0"></path>
<path d="M5 12.55a11 11 0 0 1 14.08 0"></path> <path d="M8.53 16.11a6 6 0 0 1 6.95 0"></path>
<path d="M8.53 16.11a6 6 0 0 1 6.95 0"></path> <line x1="12" y1="20" x2="12.01" y2="20"></line>
<line x1="12" y1="20" x2="12.01" y2="20"></line> </svg>
</svg> {LOCAL_WIFI_NAME}
{LOCAL_WIFI_NAME} </span>
</span> </span>
<button
type="button"
class="copy-btn"
class:success={copySuccess}
onclick={copyWifiName}
title={copySuccess ? 'Скопировано' : 'Скопировать название сети'}
aria-label={copySuccess ? 'Скопировано' : 'Скопировать название сети'}
>
{copySuccess ? '✓' : '📋'}
</button>
</div>
</div>
</div> </div>
<div class="step"> <div class="step">
<span class="step-num">2</span> <span class="step-num">2</span>
@ -193,252 +130,166 @@ async function copyWifiName() {
<span class="step-num">3</span> <span class="step-num">3</span>
<span class="step-text">Отключите VPN</span> <span class="step-text">Отключите VPN</span>
</div> </div>
{:else} </div>
<div class="step"> {/if}
<span class="step-num">1</span>
<span class="step-text">Отключитесь от сети <strong>{LOCAL_WIFI_NAME}</strong></span>
</div>
<div class="step">
<span class="step-num">2</span>
<span class="step-text">Включите мобильный интернет или подключитесь к другому Wi-Fi</span>
</div>
{/if}
</div>
<div class="waiting-row"> <div class="waiting-row">
<div class="spinner small"></div> <div class="spinner small"></div>
<span>{isLocalMode ? 'Ожидание подключения к принтеру…' : 'Ожидание интернета…'}</span> <span>Ожидание подключения…</span>
</div> </div>
<p class="auto-hint">Как только подключение появится — продолжим автоматически</p>
<button type="button" class="retry-btn" onclick={check}>Проверить снова</button>
{/if} {/if}
</main> </main>
</div> </div>
<style> <style>
.gate-container { .gate-container {
width: 100%; width: 100%;
min-height: 100dvh; min-height: 100dvh;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
background: radial-gradient(120% 60% at 50% -10%, rgba(99, 102, 241, 0.18) 0%, transparent 60%); 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; font-family: system-ui, -apple-system, sans-serif;
color: #f5f7fa; color: #f5f7fa;
} }
.gate-header { .gate-header {
padding: 24px 20px 8px; padding: 24px 20px 8px;
display: flex; display: flex;
align-items: center; align-items: center;
position: relative; }
} .back-btn {
.back-btn { background: none;
background: none; border: none;
border: none; color: #6366f1;
color: #6366f1; font-size: 15px;
font-size: 15px; font-weight: 600;
font-weight: 600; cursor: pointer;
cursor: pointer; padding: 8px 4px;
padding: 8px 4px; min-height: 44px;
margin-right: auto; touch-action: manipulation;
min-height: 44px; -webkit-tap-highlight-color: transparent;
touch-action: manipulation; }
-webkit-tap-highlight-color: transparent; .gate-content {
} flex: 1;
.gate-header h1 { width: 100%;
margin: 0; max-width: 520px;
font-size: 28px; margin: 0 auto;
font-weight: 700; box-sizing: border-box;
position: absolute; padding: 28px 20px 40px;
left: 50%; display: flex;
transform: translateX(-50%); flex-direction: column;
white-space: nowrap; align-items: center;
} gap: 22px;
.gate-content { }
flex: 1; /* ── состояние success ── */
width: 100%; .state-block {
max-width: 520px; flex: 1;
margin: 0 auto; display: flex;
box-sizing: border-box; flex-direction: column;
padding: 28px 20px 40px; align-items: center;
display: flex; justify-content: center;
flex-direction: column; gap: 14px;
align-items: center; text-align: center;
gap: 22px; padding: 40px 0;
} }
.state-title { margin: 0; font-size: 19px; font-weight: 700; color: #f5f7fa; }
/* ── состояния checking / success ── */ .state-title.ok { color: #4ade80; }
.state-block { .state-sub { margin: 0; font-size: 14px; color: #8b93a1; }
flex: 1; .success-icon {
display: flex; width: 88px;
flex-direction: column; height: 88px;
align-items: center; border-radius: 50%;
justify-content: center; display: grid;
gap: 14px; place-items: center;
text-align: center; background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%);
padding: 40px 0; box-shadow: 0 16px 38px -12px rgba(34, 197, 94, 0.55);
} color: #fff;
.state-title { margin: 0; font-size: 19px; font-weight: 700; color: #f5f7fa; } animation: pop-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1);
.state-title.ok { color: #4ade80; } }
.state-sub { margin: 0; font-size: 14px; color: #8b93a1; } @keyframes pop-in {
.success-icon { from { transform: scale(0.5); opacity: 0; }
width: 88px; to { transform: scale(1); opacity: 1; }
height: 88px; }
border-radius: 50%; /* ── состояние ожидания ── */
display: grid; .icon-wrap {
place-items: center; width: 92px;
background: linear-gradient(135deg, #22c55e 0%, #16a34a 100%); height: 92px;
box-shadow: 0 16px 38px -12px rgba(34, 197, 94, 0.55); border-radius: 28px;
color: #fff; display: grid;
animation: pop-in 0.4s cubic-bezier(0.34, 1.56, 0.64, 1); place-items: center;
} color: #fff;
@keyframes pop-in { margin-top: 6px;
from { transform: scale(0.5); opacity: 0; } }
to { transform: scale(1); opacity: 1; } .icon-wrap.local {
} background: linear-gradient(135deg, #5b8cff 0%, #38c6ff 100%);
box-shadow: 0 16px 38px -12px rgba(72, 150, 255, 0.55);
/* ── состояние waiting ── */ }
.icon-wrap { .icon-wrap.internet {
width: 92px; background: linear-gradient(135deg, #34d399 0%, #2dd4bf 100%);
height: 92px; box-shadow: 0 16px 38px -12px rgba(52, 211, 153, 0.55);
border-radius: 28px; }
display: grid; .gate-info { text-align: center; display: flex; flex-direction: column; gap: 8px; }
place-items: center; .gate-info h2 { margin: 0; font-size: 21px; font-weight: 700; letter-spacing: -0.01em; }
color: #fff; .gate-info p { margin: 0; font-size: 14px; line-height: 1.5; color: #9ca3af; }
margin-top: 6px; /* ── единый блок шагов ── */
} .steps-card {
.icon-wrap.local { width: 100%;
background: linear-gradient(135deg, #5b8cff 0%, #38c6ff 100%); display: flex;
box-shadow: 0 16px 38px -12px rgba(72, 150, 255, 0.55); flex-direction: column;
} gap: 12px;
.icon-wrap.internet { padding: 14px 16px;
background: linear-gradient(135deg, #34d399 0%, #2dd4bf 100%); border-radius: 16px;
box-shadow: 0 16px 38px -12px rgba(52, 211, 153, 0.55); background: rgba(255, 255, 255, 0.05);
} border: 1px solid rgba(255, 255, 255, 0.08);
.gate-info { text-align: center; display: flex; flex-direction: column; gap: 8px; } text-align: left;
.gate-info h2 { margin: 0; font-size: 21px; font-weight: 700; letter-spacing: -0.01em; } }
.gate-info p { margin: 0; font-size: 14px; line-height: 1.5; color: #9ca3af; } .step { display: flex; align-items: center; gap: 14px; }
.step-num {
/* ── шаги ── */ width: 26px;
.steps { width: 100%; display: flex; flex-direction: column; gap: 10px; } height: 26px;
.step { border-radius: 50%;
display: flex; flex-shrink: 0;
align-items: center; display: flex;
gap: 14px; align-items: center;
padding: 14px 16px; justify-content: center;
border-radius: 16px; background: rgba(99, 102, 241, 0.18);
background: rgba(255, 255, 255, 0.05); border: 1px solid rgba(99, 102, 241, 0.4);
border: 1px solid rgba(255, 255, 255, 0.08); color: #a5b4fc;
text-align: left; font-size: 13px;
} font-weight: 700;
.step-num { }
width: 26px; .step-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 6px; }
height: 26px; .step-text { font-size: 15px; line-height: 1.45; color: #e5e9f0; }
border-radius: 50%; /* имя Wi-Fi без рамки, просто иконка + текст */
flex-shrink: 0; .wifi-name {
display: flex; display: inline-flex;
align-items: center; align-items: center;
justify-content: center; gap: 8px;
background: rgba(99, 102, 241, 0.18); color: #7db4ff;
border: 1px solid rgba(99, 102, 241, 0.4); font-size: 15px;
color: #a5b4fc; font-weight: 700;
font-size: 13px; letter-spacing: 0.01em;
font-weight: 700; }
} .wifi-name svg { flex-shrink: 0; }
.step-text { font-size: 15px; line-height: 1.45; color: #e5e9f0; } /* ── ожидание ── */
.step-text strong { color: #fff; } .waiting-row {
.step-body { flex: 1; min-width: 0; display: flex; flex-direction: column; gap: 10px; } display: flex;
align-items: center;
/* ── плашка Wi-Fi + копирование ── */ gap: 10px;
.wifi-chip-row { display: flex; align-items: center; gap: 10px; } margin-top: 4px;
.wifi-chip { color: #8b93a1;
flex: 1; font-size: 14px;
min-width: 0; font-weight: 600;
display: flex; }
align-items: center; /* ── спиннер ── */
justify-content: center; .spinner {
gap: 8px; border-radius: 50%;
padding: 12px 14px; border: 3px solid rgba(255, 255, 255, 0.12);
border-radius: 12px; border-top-color: #6366f1;
background: rgba(91, 140, 255, 0.14); animation: gate-spin 0.8s linear infinite;
border: 1px solid rgba(91, 140, 255, 0.45); }
color: #fff; .spinner.small { width: 18px; height: 18px; border-width: 2px; }
font-size: 16px; @keyframes gate-spin { to { transform: rotate(360deg); } }
font-weight: 700; @media (max-width: 380px) {
letter-spacing: 0.01em; .gate-info h2 { font-size: 19px; }
overflow: hidden; }
text-overflow: ellipsis;
white-space: nowrap;
}
.wifi-chip svg { flex-shrink: 0; color: #7db4ff; }
.copy-btn {
width: 46px;
height: 46px;
flex-shrink: 0;
display: flex;
align-items: center;
justify-content: center;
background: rgba(99, 102, 241, 0.2);
border: 1px solid rgba(99, 102, 241, 0.4);
border-radius: 12px;
color: #fff;
font-size: 20px;
cursor: pointer;
transition: all 0.2s ease;
padding: 0;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
.copy-btn:hover { background: rgba(99, 102, 241, 0.35); border-color: #6366f1; }
.copy-btn:active { transform: scale(0.95); }
.copy-btn.success { background: rgba(34, 197, 94, 0.25); border-color: rgba(34, 197, 94, 0.5); }
/* ── ожидание ── */
.waiting-row {
display: flex;
align-items: center;
gap: 10px;
margin-top: 4px;
color: #8b93a1;
font-size: 14px;
font-weight: 600;
}
.auto-hint {
margin: -12px 0 0;
text-align: center;
color: rgba(139, 147, 161, 0.6);
font-size: 12px;
line-height: 1.4;
}
.retry-btn {
width: 100%;
padding: 16px 24px;
border-radius: 16px;
border: 1px solid rgba(255, 255, 255, 0.14);
background: rgba(255, 255, 255, 0.06);
color: #f5f7fa;
font-size: 16px;
font-weight: 700;
cursor: pointer;
transition: background 0.2s ease, border-color 0.2s ease;
touch-action: manipulation;
-webkit-tap-highlight-color: transparent;
}
.retry-btn:hover { background: rgba(255, 255, 255, 0.1); border-color: rgba(255, 255, 255, 0.24); }
/* ── спиннер ── */
.spinner {
border-radius: 50%;
border: 3px solid rgba(255, 255, 255, 0.12);
border-top-color: #6366f1;
animation: gate-spin 0.8s linear infinite;
}
.spinner.big { width: 46px; height: 46px; }
.spinner.small { width: 18px; height: 18px; border-width: 2px; }
@keyframes gate-spin { to { transform: rotate(360deg); } }
@media (max-width: 380px) {
.gate-header h1 { font-size: 22px; }
.gate-info h2 { font-size: 19px; }
}
</style> </style>

View File

@ -205,5 +205,5 @@
<div class="version"> <div class="version">
<span>v{backendVersion}</span> <span>v{backendVersion}</span>
<span>v1.0.8</span> <span>v1.0.9</span>
</div> </div>

View File

@ -7,7 +7,7 @@
import PaymentPage from '$lib/common/ui/PaymentPage.svelte'; import PaymentPage from '$lib/common/ui/PaymentPage.svelte';
import { plFiles, plCopies } from '$lib/common/utils/pricing.util.js'; import { plFiles, plCopies } from '$lib/common/utils/pricing.util.js';
import NetworkGate from '$lib/common/ui/NetworkGate.svelte'; import NetworkGate from '$lib/common/ui/NetworkGate.svelte';
import { detectNetwork } from '$lib/common/services/network.service.js'; import { isLocalNetwork } from '$lib/common/services/network.service.js';
let { onBack = () => {} } = $props(); let { onBack = () => {} } = $props();
const store = createPrintStore(); const store = createPrintStore();
@ -47,7 +47,7 @@
async function handleConfirmPayment() { async function handleConfirmPayment() {
if (printNetGate === 'checking') return; if (printNetGate === 'checking') return;
printNetGate = 'checking'; printNetGate = 'checking';
const net = await detectNetwork(); const net = await isLocalNetwork();
if (net === 'local') { if (net === 'local') {
printNetGate = null; printNetGate = null;
store.submitPrint(); store.submitPrint();
@ -77,7 +77,7 @@
<div class="page-container"> <div class="page-container">
<div class="net-checking"> <div class="net-checking">
<div class="net-spinner"></div> <div class="net-spinner"></div>
<p>Проверка подключения к принтеру</p> <p>Проверка подключения…</p>
</div> </div>
</div> </div>
{:else} {:else}