ESP32 wallet RPC, browser wallet provider, and side panel

This commit is contained in:
AidarKC
2026-06-22 01:30:08 +04:00
parent 475db28095
commit ce2d310e8c
23 changed files with 2407 additions and 300 deletions
+61 -57
View File
@@ -8,6 +8,7 @@ const els = {
passwordField: document.querySelector('#password-field'),
passwordInput: document.querySelector('#password-input'),
startBtn: document.querySelector('#start-btn'),
connectCard: document.querySelector('#connect-card'),
pairingCard: document.querySelector('#pairing-card'),
shortCode: document.querySelector('#short-code'),
pairingHint: document.querySelector('#pairing-hint'),
@@ -22,11 +23,15 @@ const els = {
resumeBtn: document.querySelector('#resume-btn'),
refreshDevicesBtn: document.querySelector('#refresh-devices-btn'),
disconnectBtn: document.querySelector('#disconnect-btn'),
signingCard: document.querySelector('#signing-card'),
signKeySelect: document.querySelector('#sign-key-select'),
walletCard: document.querySelector('#wallet-card'),
deviceSelect: document.querySelector('#device-select'),
homeserverList: document.querySelector('#homeserver-list'),
prepareSignBtn: document.querySelector('#prepare-sign-btn'),
requestWalletBtn: document.querySelector('#request-wallet-btn'),
walletResultCard: document.querySelector('#wallet-result-card'),
walletType: document.querySelector('#wallet-type'),
walletPubkey: document.querySelector('#wallet-pubkey'),
walletVerify: document.querySelector('#wallet-verify'),
copyWalletBtn: document.querySelector('#copy-wallet-btn'),
connectionPill: document.querySelector('#connection-pill'),
};
@@ -34,16 +39,19 @@ let state = {
settings: {
serverLogin: 'shineupme',
serverHttp: 'https://shineup.me',
serverUrl: 'wss://shineup.me/ws',
login: '',
},
pairing: {
active: false,
pairingId: '',
expiresAtMs: 0,
shortCode: '',
},
session: null,
connectionOnline: false,
walletProfile: null,
signing: {
selectedDeviceName: '',
},
currentWallet: null,
status: {
text: '',
kind: 'info',
@@ -60,7 +68,7 @@ function setStatus(message, kind = 'info') {
}
function setConnectedPill(connected) {
els.connectionPill.textContent = connected ? 'online' : 'offline';
els.connectionPill.textContent = connected ? 'подключено' : 'не подключено';
els.connectionPill.className = connected ? 'pill pill-online' : 'pill pill-offline';
}
@@ -104,76 +112,65 @@ function applyState(nextState) {
const loginValue = String(state?.settings?.login || '');
const resolvedServerLogin = String(state?.settings?.serverLogin || '').trim();
const resolvedServerAddress = String(state?.settings?.serverHttp || '').trim();
if (loginValue && resolvedServerLogin && resolvedServerAddress) {
els.serverLoginInfo.textContent = `Сервер SHiNE: ${resolvedServerLogin}`;
els.serverAddress.textContent = `Адрес: ${resolvedServerAddress}`;
} else {
els.serverLoginInfo.textContent = 'Сервер SHiNE: —';
els.serverAddress.textContent = 'Адрес: —';
}
els.serverLoginInfo.textContent = resolvedServerLogin ? `Сервер SHiNE: ${resolvedServerLogin}` : 'Сервер SHiNE: —';
els.serverAddress.textContent = resolvedServerAddress ? `Адрес: ${resolvedServerAddress}` : 'Адрес: —';
if (document.activeElement !== els.loginInput) {
els.loginInput.value = loginValue;
}
setConnectedPill(!!state?.connectionOnline);
setConnectedPill(!!state?.session);
setStatus(state?.status?.text || '', state?.status?.kind || 'info');
const session = state?.session;
const walletProfile = state?.walletProfile;
const signing = state?.signing || {};
const currentWallet = state?.currentWallet || null;
els.connectCard.classList.toggle('hidden', !!session);
els.sessionCard.classList.toggle('hidden', !session);
els.walletCard.classList.toggle('hidden', !session);
if (session) {
els.sessionCard.classList.remove('hidden');
els.sessionLogin.textContent = session.login || '—';
els.sessionId.textContent = session.sessionId || '—';
els.sessionType.textContent = String(session.sessionType || 50) === '50' ? 'wallet' : String(session.sessionType || '—');
els.deviceKeyShort.textContent = shortKey(walletProfile?.publicKeys?.deviceKeyBase58 || '');
els.signingCard.classList.remove('hidden');
} else {
els.sessionCard.classList.add('hidden');
els.sessionLogin.textContent = '—';
els.sessionId.textContent = '—';
els.sessionType.textContent = 'wallet';
els.deviceKeyShort.textContent = '—';
els.signingCard.classList.add('hidden');
}
const signKeyOptions = Array.isArray(walletProfile?.signingKeyOptions) ? walletProfile.signingKeyOptions : [];
els.signKeySelect.innerHTML = '';
signKeyOptions.forEach((item) => {
const option = document.createElement('option');
option.value = item.id;
option.textContent = item.label;
option.selected = item.id === signing.selectedKeyId;
els.signKeySelect.append(option);
});
const homeservers = Array.isArray(walletProfile?.homeserverSessions) ? walletProfile.homeserverSessions : [];
els.deviceSelect.innerHTML = '';
homeservers.forEach((item) => {
const option = document.createElement('option');
option.value = item.sessionName;
option.textContent = `${item.sessionName} [${item.onlineState || 'unknown'}]`;
option.textContent = `${item.sessionName} [${item.onlineState || 'offline'}]`;
option.selected = item.sessionName === signing.selectedDeviceName;
els.deviceSelect.append(option);
});
renderHomeserverList(homeservers);
els.prepareSignBtn.disabled = !session || !signing.selectedKeyId || !signing.selectedDeviceName;
els.requestWalletBtn.disabled = !session || !signing.selectedDeviceName;
if (currentWallet?.publicKeyBase58) {
els.walletResultCard.classList.remove('hidden');
els.walletType.textContent = currentWallet.type || '—';
els.walletPubkey.textContent = currentWallet.publicKeyBase58 || '—';
els.walletVerify.textContent = currentWallet.verificationText || '—';
} else {
els.walletResultCard.classList.add('hidden');
els.walletType.textContent = '—';
els.walletPubkey.textContent = '—';
els.walletVerify.textContent = '—';
}
const pairing = state?.pairing || {};
if (pairing.active) {
els.pairingCard.classList.remove('hidden');
const shortCode = String(pairing.shortCode || els.shortCode.dataset.shortCode || els.shortCode.textContent || '');
els.shortCode.dataset.shortCode = shortCode;
els.shortCode.textContent = formatPairingShortCode(shortCode);
els.pairingHint.textContent = pairing.trustedSessionOnline
? 'Покажите код на доверенном устройстве и подтвердите выпуск wallet-session.'
: 'Сейчас нет онлайн доверенной сессии. Откройте другое устройство и подтвердите заявку.';
els.shortCode.textContent = formatPairingShortCode(String(pairing.shortCode || ''));
const leftMs = Number(pairing.expiresAtMs || 0) - Date.now();
els.pairingExpire.textContent = leftMs > 0 ? `Код действителен ещё ${formatRemaining(leftMs)}.` : 'Время ожидания истекло.';
els.startBtn.disabled = true;
} else {
els.pairingCard.classList.add('hidden');
els.shortCode.textContent = formatPairingShortCode('');
delete els.shortCode.dataset.shortCode;
els.pairingExpire.textContent = '';
els.startBtn.disabled = false;
}
@@ -241,16 +238,13 @@ async function startPairing() {
return;
}
setStatus('Создаём wallet-session заявку...', 'info');
els.startBtn.disabled = true;
try {
const response = await sendMessage('wallet:startPairing', {
await sendMessage('wallet:startPairing', {
login,
usePassword: !!els.usePassword.checked,
password: String(els.passwordInput.value || ''),
});
applyState(response.state);
} catch (error) {
els.startBtn.disabled = false;
setStatus(error.message || 'Не удалось начать pairing.', 'error');
}
}
@@ -289,23 +283,33 @@ async function refreshDevices() {
}
}
async function updateSigningSelection() {
async function updateDeviceSelection() {
try {
await sendMessage('wallet:updateSigningSelection', {
selectedKeyId: String(els.signKeySelect.value || '').trim(),
selectedDeviceName: String(els.deviceSelect.value || '').trim(),
});
} catch (error) {
setStatus(error.message || 'Не удалось обновить выбор для подписи.', 'error');
setStatus(error.message || 'Не удалось обновить выбор homeserver.', 'error');
}
}
async function prepareSignSignal() {
setStatus('Готовим каркас запроса подписи...', 'info');
async function requestCurrentWallet() {
setStatus('Запрашиваем текущий кошелёк с ESP32...', 'info');
try {
await sendMessage('wallet:prepareSignSignal');
await sendMessage('wallet:requestCurrentWallet');
} catch (error) {
setStatus(error.message || 'Не удалось подготовить запрос подписи.', 'error');
setStatus(error.message || 'Не удалось получить кошелёк с ESP32.', 'error');
}
}
async function copyWalletKey() {
const value = String(els.walletPubkey.textContent || '').trim();
if (!value || value === '—') return;
try {
await navigator.clipboard.writeText(value);
setStatus('Публичный ключ скопирован.', 'info');
} catch (error) {
setStatus(error.message || 'Не удалось скопировать ключ.', 'error');
}
}
@@ -340,9 +344,9 @@ function bindUi() {
els.resumeBtn.addEventListener('click', () => { void resumeSession(); });
els.refreshDevicesBtn.addEventListener('click', () => { void refreshDevices(); });
els.disconnectBtn.addEventListener('click', () => { void disconnectSession(); });
els.signKeySelect.addEventListener('change', () => { void updateSigningSelection(); });
els.deviceSelect.addEventListener('change', () => { void updateSigningSelection(); });
els.prepareSignBtn.addEventListener('click', () => { void prepareSignSignal(); });
els.deviceSelect.addEventListener('change', () => { void updateDeviceSelection(); });
els.requestWalletBtn.addEventListener('click', () => { void requestCurrentWallet(); });
els.copyWalletBtn.addEventListener('click', () => { void copyWalletKey(); });
}
async function init() {