Обновить pairing устройств и доработать ESP32 UI

This commit is contained in:
AidarKC
2026-06-19 20:47:56 +04:00
parent cc074a941f
commit a788d8bcf5
18 changed files with 1020 additions and 126 deletions
+19 -30
View File
@@ -14,6 +14,7 @@ import {
buildSessionAttachPayload,
deriveEspPairingPasswordHash,
encryptPairingPayloadForRequester,
formatPairingShortCode,
} from '../services/device-pairing-service.js';
import { loadEncryptedUserSecrets } from '../services/key-vault.js';
import { toUserMessage } from '../services/ui-error-texts.js';
@@ -29,8 +30,10 @@ function setStatus(statusEl, message, kind = 'info') {
statusEl.style.display = message ? '' : 'none';
}
function normalizeCode(value) {
return String(value || '').replace(/\D+/g, '').slice(0, 7);
const SESSION_TYPE_WALLET = 50;
function pairingSessionKindLabel(sessionType) {
return Number(sessionType || 0) === SESSION_TYPE_WALLET ? 'Wallet session' : 'Client session';
}
function buildTransferKeys(savedKeys, { withExtras = false }) {
@@ -54,19 +57,19 @@ function buildTransferKeys(savedKeys, { withExtras = false }) {
}
function requestCardHtml(request) {
const shortCode = String(request?.shortCode || '').trim() || '0000000';
const shortCode = formatPairingShortCode(request?.shortCode || '');
const client = String(request?.requesterClientPlatform || 'unknown');
const requesterSessionType = Number(request?.requesterSessionType || 0);
const expiresText = request?.expiresAtMs ? formatRelativeTime(request.expiresAtMs) : '—';
const sessionOnly = requesterSessionType === 50;
const sessionOnly = requesterSessionType === SESSION_TYPE_WALLET;
const sessionKind = pairingSessionKindLabel(requesterSessionType);
return `
<div class="card stack" data-pairing-id="${String(request?.pairingId || '')}">
<div class="row" style="align-items:flex-start;">
<div class="stack" style="gap:4px;">
<div style="font-size:28px; font-weight:700; letter-spacing:0.14em;">${shortCode}</div>
<span class="meta-muted">Платформа: ${client}</span>
<span class="meta-muted">Тип сессии: ${requesterSessionType || '—'}</span>
<span class="meta-muted">Тип payload: ${Number(request?.payloadType || 0)}</span>
<span class="meta-muted">Session name: ${client}</span>
<span class="meta-muted">Session kind: ${sessionKind}</span>
<span class="meta-muted">Истекает: ${expiresText}</span>
</div>
</div>
@@ -198,14 +201,11 @@ export function render({ navigate }) {
const requestsCard = document.createElement('div');
requestsCard.className = 'card stack';
requestsCard.innerHTML = `
<div class="row" style="align-items:flex-end; gap:10px; flex-wrap:wrap;">
<label class="stack" style="flex:1 1 180px;">
<span class="field-label">Код нового устройства</span>
<input class="input" id="pairing-code-filter" inputmode="numeric" maxlength="7" placeholder="7 цифр" />
</label>
<div class="row" style="justify-content:space-between; align-items:center; gap:10px; flex-wrap:wrap;">
<span class="field-label">Активные заявки</span>
<button class="ghost-btn" type="button" id="refresh-pairing-requests">Обновить заявки</button>
</div>
<p class="meta-muted">Если код не введён, показываются все активные заявки. Для wallet-заявки можно выпустить отдельную session-only сессию без передачи постоянных ключей.</p>
<p class="meta-muted">Показываются все активные заявки. Для wallet-заявки можно выпустить отдельную session-only сессию без передачи постоянных ключей.</p>
<div class="stack" id="pairing-requests-list"></div>
`;
@@ -213,7 +213,6 @@ export function render({ navigate }) {
status.className = 'status-line is-unavailable';
status.style.display = 'none';
const keySummaryEl = keySummaryCard.querySelector('#pairing-key-summary');
const codeFilterInput = requestsCard.querySelector('#pairing-code-filter');
const refreshBtn = requestsCard.querySelector('#refresh-pairing-requests');
const requestsListEl = requestsCard.querySelector('#pairing-requests-list');
@@ -413,23 +412,17 @@ export function render({ navigate }) {
};
const renderRequests = () => {
const filterCode = normalizeCode(codeFilterInput.value);
const filtered = filterCode
? requests.filter((item) => normalizeCode(item?.shortCode) === filterCode)
: requests;
requestsListEl.innerHTML = '';
if (!filtered.length) {
if (!requests.length) {
const empty = document.createElement('p');
empty.className = 'meta-muted';
empty.textContent = filterCode
? 'Заявка с таким кодом пока не найдена.'
: 'Активных заявок сейчас нет.';
empty.textContent = 'Активных заявок сейчас нет.';
requestsListEl.append(empty);
return;
}
filtered.forEach((request) => {
requests.forEach((request) => {
const wrapper = document.createElement('div');
wrapper.innerHTML = requestCardHtml(request);
requestsListEl.append(wrapper.firstElementChild);
@@ -470,12 +463,12 @@ export function render({ navigate }) {
const approveRequest = async (request, mode) => {
const withExtras = mode === 'with-extras';
let payload;
if (!withExtras && Number(request?.requesterSessionType || 0) === 50) {
if (!withExtras && Number(request?.requesterSessionType || 0) === SESSION_TYPE_WALLET) {
const delegatedSession = await authService.createDelegatedSessionWithDeviceKey({
login: state.session.login,
devicePrivPkcs8: String(savedKeys?.deviceKey || '').trim(),
sessionKey: String(request?.requesterSessionKey || '').trim(),
sessionType: Number(request?.requesterSessionType || 50) || 50,
sessionType: Number(request?.requesterSessionType || SESSION_TYPE_WALLET) || SESSION_TYPE_WALLET,
clientPlatform: String(request?.requesterClientPlatform || '').trim() || 'Wallet plugin',
clientInfo: 'Wallet session approved via device pairing',
});
@@ -493,7 +486,7 @@ export function render({ navigate }) {
}
const encryptedPayload = await encryptPairingPayloadForRequester(request?.requesterSessionKey, payload);
await runPairingOpWithSessionRestore(() => authService.approveTrustedDeviceLogin(request?.pairingId, encryptedPayload));
const sessionOnly = !withExtras && Number(request?.requesterSessionType || 0) === 50;
const sessionOnly = !withExtras && Number(request?.requesterSessionType || 0) === SESSION_TYPE_WALLET;
showToast(
withExtras
? 'Ключи переданы на новое устройство'
@@ -600,10 +593,6 @@ export function render({ navigate }) {
refreshBtn.addEventListener('click', () => {
void reloadRequests();
});
codeFilterInput.addEventListener('input', () => {
codeFilterInput.value = normalizeCode(codeFilterInput.value);
renderRequests();
});
requestsListEl.addEventListener('click', async (event) => {
const target = event.target;
+9 -4
View File
@@ -12,7 +12,12 @@ import {
terminateCurrentSession,
} from '../state.js';
import { showToast } from '../services/channels-ux.js';
import { decryptPairingPayloadFromEnvelope, deriveEspPairingPasswordHash, createRequesterPairingMaterial } from '../services/device-pairing-service.js';
import {
decryptPairingPayloadFromEnvelope,
deriveEspPairingPasswordHash,
createRequesterPairingMaterial,
formatPairingShortCode,
} from '../services/device-pairing-service.js';
import { clearClientAuthData, saveEncryptedUserSecrets } from '../services/key-vault.js';
import { clearStoredMessages } from '../services/message-store.js';
import { toUserMessage } from '../services/ui-error-texts.js';
@@ -30,7 +35,7 @@ function codeCardHtml() {
return `
<div class="card stack">
<p class="field-label">Код подключения</p>
<div id="pairing-short-code" style="font-size:34px; font-weight:700; letter-spacing:0.18em;">0000000</div>
<div id="pairing-short-code" style="font-size:34px; font-weight:700; letter-spacing:0.12em;">00 00 00 00 00</div>
<p class="meta-muted" id="pairing-status-hint">Покажите этот код на уже подключённом устройстве в разделе «Подключить по коду».</p>
<p class="meta-muted" id="pairing-online-hint"></p>
<p class="meta-muted" id="pairing-expire-hint"></p>
@@ -47,7 +52,7 @@ function formatRemaining(ms) {
function resetCodeCard(resultWrap, shortCodeEl, statusHintEl, onlineHintEl, expireHintEl) {
resultWrap.style.display = 'none';
shortCodeEl.textContent = '0000000';
shortCodeEl.textContent = formatPairingShortCode('');
statusHintEl.textContent = 'Покажите этот код на уже подключённом устройстве в разделе «Подключить по коду».';
onlineHintEl.textContent = '';
expireHintEl.textContent = '';
@@ -327,7 +332,7 @@ export function render({ navigate }) {
throw new Error('Сервер не вернул pairingId.');
}
shortCodeEl.textContent = String(payload?.shortCode || '0000000');
shortCodeEl.textContent = formatPairingShortCode(payload?.shortCode || '');
statusHintEl.textContent = 'Откройте на доверенном устройстве: Настройки -> Устройства -> Подключить устройство -> Подключить по коду.';
onlineHintEl.textContent = payload?.trustedSessionOnline
? 'Сейчас есть хотя бы одна онлайн доверенная сессия, которая может принять заявку.'