From 77f5759d6057fbef62bbc9e4cabb436ecd6b288f6200020fdc191646c7684301 Mon Sep 17 00:00:00 2001 From: AidarKC Date: Wed, 24 Jun 2026 13:48:07 +0400 Subject: [PATCH] =?UTF-8?q?=D0=9E=D0=B1=D0=BD=D0=BE=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20UI=20=D0=BA=D0=BE=D1=88=D0=B5=D0=BB=D1=8C=D0=BA=D0=B0?= =?UTF-8?q?=20=D0=B8=20=D1=80=D0=B5=D0=B3=D0=B8=D1=81=D1=82=D1=80=D0=B0?= =?UTF-8?q?=D1=86=D0=B8=D1=8E?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- SHiNE-browser-plugin-wallet/background.js | 184 +++++++++++------- SHiNE-browser-plugin-wallet/popup.js | 8 +- .../provider-bridge.js | 15 ++ VERSION.properties | 4 +- shine-UI/index.html | 4 +- shine-UI/js/pages/register-view.js | 75 +++---- .../js/pages/registration-payment-view.js | 153 ++++++++++----- shine-UI/manifest.webmanifest | 4 +- 8 files changed, 275 insertions(+), 172 deletions(-) diff --git a/SHiNE-browser-plugin-wallet/background.js b/SHiNE-browser-plugin-wallet/background.js index dd23775..e54cb75 100644 --- a/SHiNE-browser-plugin-wallet/background.js +++ b/SHiNE-browser-plugin-wallet/background.js @@ -30,7 +30,9 @@ const state = { devicesResolvedAtMs: 0, }, currentWallet: null, - pendingApproval: null, + pendingApprovals: [], + siteApprovalChain: Promise.resolve(), + sessionAttachInProgress: false, statusText: '', statusKind: 'info', }; @@ -71,23 +73,37 @@ function setStatus(message = '', kind = 'info') { } function makePendingApprovalSnapshot(payload = {}) { + const pendingId = String(payload?.id || '').trim(); + const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals : []; + const queueIndex = pendingApprovals.findIndex((item) => String(item?.id || '').trim() === pendingId); + const queueLength = pendingApprovals.length; return { - id: String(payload?.id || '').trim(), + id: pendingId, kind: String(payload?.kind || 'sign_transaction').trim() || 'sign_transaction', origin: String(payload?.origin || '').trim(), publicKeyBase58: String(payload?.publicKeyBase58 || '').trim(), comment: String(payload?.comment || '').trim(), createdAtMs: Number(payload?.createdAtMs || Date.now()), + status: String(payload?.status || 'queued').trim() || 'queued', + queuePosition: queueIndex >= 0 ? queueIndex + 1 : 1, + queueLength: queueLength || 1, transactionSummary: payload?.transactionSummary && typeof payload.transactionSummary === 'object' ? { ...payload.transactionSummary } : null, }; } -function clearPendingApproval({ rejectError = null } = {}) { - if (!state.pendingApproval) return; - const pending = state.pendingApproval; - state.pendingApproval = null; +function getCurrentPendingApproval() { + return Array.isArray(state.pendingApprovals) && state.pendingApprovals.length + ? state.pendingApprovals[0] + : null; +} + +function removePendingApproval(pendingId, { rejectError = null } = {}) { + const pendingApprovals = Array.isArray(state.pendingApprovals) ? state.pendingApprovals : []; + const index = pendingApprovals.findIndex((item) => String(item?.id || '').trim() === String(pendingId || '').trim()); + if (index < 0) return; + const [pending] = pendingApprovals.splice(index, 1); if (pending.timeoutId) { clearTimeout(pending.timeoutId); } @@ -139,7 +155,10 @@ async function loadStateFromStorage() { login: String(settings?.login || '').trim(), connectedOrigins: Array.isArray(settings?.connectedOrigins) ? settings.connectedOrigins.map((item) => normalizeOrigin(item)).filter(Boolean) : [], }; - state.activeSession = await loadSessionMaterial(); + const storedSession = await loadSessionMaterial(); + if (storedSession || !state.sessionAttachInProgress) { + state.activeSession = storedSession; + } state.walletProfile = state.activeSession?.walletProfile || null; state.signing = { selectedDeviceName: String(state.activeSession?.selectedDeviceName || ''), @@ -353,18 +372,30 @@ async function attachApprovedSession(payload) { throw new Error('Получен неполный session-only payload'); } - await clearSessionMaterial(); - state.activeSession = sessionRecord; - await hydrateWalletProfile(login); - await saveActiveSessionRecord(); - await persistSettings({ - login: sessionRecord.login, - serverLogin: sessionRecord.serverLogin, - serverHttp: sessionRecord.serverHttp, - serverUrl: sessionRecord.serverUrl, - }); - state.connectionOnline = false; - state.currentWallet = null; + state.sessionAttachInProgress = true; + try { + state.activeSession = sessionRecord; + state.walletProfile = null; + state.currentWallet = null; + state.signing = { + ...state.signing, + selectedDeviceName: '', + devicesResolvedAtMs: 0, + }; + await saveActiveSessionRecord(); + await hydrateWalletProfile(login); + await saveActiveSessionRecord(); + await persistSettings({ + login: sessionRecord.login, + serverLogin: sessionRecord.serverLogin, + serverHttp: sessionRecord.serverHttp, + serverUrl: sessionRecord.serverUrl, + }); + state.connectionOnline = false; + state.currentWallet = null; + } finally { + state.sessionAttachInProgress = false; + } } async function pollPairingStatus() { @@ -666,41 +697,56 @@ async function requestCurrentWallet() { } async function cancelPendingSiteApproval() { - clearPendingApproval({ + const pending = getCurrentPendingApproval(); + if (!pending) { + setStatus('Сейчас нет активного ожидания подписи.', 'info'); + return { ok: true }; + } + removePendingApproval(pending.id, { rejectError: makeCodeError('User canceled request in extension.', 'USER_REJECTED'), }); setStatus('Ожидание подписи отменено в расширении.', 'info'); return { ok: true }; } -async function markPendingSiteApprovalResolved() { - clearPendingApproval(); +async function markPendingSiteApprovalResolved(pendingId) { + removePendingApproval(pendingId); } -async function beginSiteTransactionFlow(payload = {}, sender = null) { - if (state.pendingApproval) { - throw makeCodeError('Another signing request is already pending.', 'APPROVAL_ALREADY_PENDING'); - } +function enqueueSiteApproval(work) { + const run = state.siteApprovalChain.then(work, work); + state.siteApprovalChain = run.catch(() => {}); + return run; +} + +async function activatePendingApproval(pending, sender = null) { const abortController = new AbortController(); + pending.status = 'active'; + pending.abortController = abortController; + pending.timeoutId = setTimeout(() => { + removePendingApproval(pending.id, { + rejectError: makeCodeError('Signing request timed out in extension.', 'USER_REJECTED'), + }); + setStatus('Ожидание подписи истекло в расширении.', 'error'); + }, 120000); + setStatus(`Сайт ${pending.origin} запросил подпись. Подтвердите или отмените на доверенном устройстве.`, 'info'); + await openSidePanelForSender(sender); + return pending; +} + +function beginSiteTransactionFlow(payload = {}) { const pending = makePendingApprovalSnapshot({ ...payload, kind: 'sign_transaction', id: `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`, createdAtMs: Date.now(), + status: 'queued', }); - const timeoutId = setTimeout(() => { - clearPendingApproval({ - rejectError: makeCodeError('Signing request timed out in extension.', 'USER_REJECTED'), - }); - setStatus('Ожидание подписи истекло в расширении.', 'error'); - }, 120000); - state.pendingApproval = { + state.pendingApprovals.push({ ...pending, - timeoutId, - abortController, - }; - setStatus(`Сайт ${pending.origin} запросил подпись. Подтвердите или отмените на доверенном устройстве.`, 'info'); - await openSidePanelForSender(sender); + timeoutId: 0, + abortController: null, + }); return pending; } @@ -747,40 +793,44 @@ async function siteSignTransaction({ origin, publicKeyBase58, transactionBase64, if (!cleanPub || !cleanTx) { throw makeCodeError('Transaction payload is incomplete.', 'BAD_REQUEST'); } - const pending = await beginSiteTransactionFlow({ + const pending = beginSiteTransactionFlow({ origin: normalizedOrigin, publicKeyBase58: cleanPub, comment: String(comment || '').trim(), transactionSummary: transactionSummary || null, - }, sender); - const requestId = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; - const signComment = String(comment || '').trim() || `Site ${normalizedOrigin} requested transaction signature`; - try { - const { response } = await callWalletRpc({ - v: 1, - operation: 'sign_transaction', - requestId, - publicKeyBase58: cleanPub, - transactionBase64: cleanTx, - comment: signComment, - }, 120000, state.pendingApproval?.id === pending.id ? state.pendingApproval.abortController.signal : null); - if (!response?.ok) { - const errorCode = String(response?.error || 'unknown_error').trim().toUpperCase(); - if (errorCode === 'REJECTED_BY_USER') { - throw makeCodeError('User rejected transaction signature on ESP32.', 'USER_REJECTED'); + }); + return enqueueSiteApproval(async () => { + await activatePendingApproval(getCurrentPendingApproval() || pending, sender); + const activePending = getCurrentPendingApproval() || pending; + const requestId = `${Date.now()}-${Math.random().toString(16).slice(2, 8)}`; + const signComment = String(comment || '').trim() || `Site ${normalizedOrigin} requested transaction signature`; + try { + const { response } = await callWalletRpc({ + v: 1, + operation: 'sign_transaction', + requestId, + publicKeyBase58: cleanPub, + transactionBase64: cleanTx, + comment: signComment, + }, 120000, activePending.abortController?.signal || null); + if (!response?.ok) { + const errorCode = String(response?.error || 'unknown_error').trim().toUpperCase(); + if (errorCode === 'REJECTED_BY_USER') { + throw makeCodeError('User rejected transaction signature on ESP32.', 'USER_REJECTED'); + } + throw makeCodeError(`ESP32 rejected transaction signature: ${String(response?.error || 'unknown_error')}`, errorCode || 'RPC_REJECTED'); } - throw makeCodeError(`ESP32 rejected transaction signature: ${String(response?.error || 'unknown_error')}`, errorCode || 'RPC_REJECTED'); + setStatus(`Подпись для ${normalizedOrigin} завершена.`, 'info'); + return { + ok: true, + publicKeyBase58: String(response?.publicKeyBase58 || cleanPub).trim(), + signedTransactionBase64: String(response?.signedTransactionBase64 || '').trim(), + signatureBase58: String(response?.signatureBase58 || '').trim(), + }; + } finally { + await markPendingSiteApprovalResolved(activePending.id); } - setStatus(`Подпись для ${normalizedOrigin} завершена.`, 'info'); - return { - ok: true, - publicKeyBase58: String(response?.publicKeyBase58 || cleanPub).trim(), - signedTransactionBase64: String(response?.signedTransactionBase64 || '').trim(), - signatureBase58: String(response?.signatureBase58 || '').trim(), - }; - } finally { - await markPendingSiteApprovalResolved(); - } + }); } function snapshot() { @@ -797,7 +847,7 @@ function snapshot() { connectionOnline: !!state.activeSession, walletProfile: state.walletProfile ? { ...state.walletProfile } : null, currentWallet: state.currentWallet ? { ...state.currentWallet } : null, - pendingApproval: state.pendingApproval ? makePendingApprovalSnapshot(state.pendingApproval) : null, + pendingApproval: getCurrentPendingApproval() ? makePendingApprovalSnapshot(getCurrentPendingApproval()) : null, signing: { ...state.signing }, status: { text: state.statusText, diff --git a/SHiNE-browser-plugin-wallet/popup.js b/SHiNE-browser-plugin-wallet/popup.js index e7f7d60..10b7572 100644 --- a/SHiNE-browser-plugin-wallet/popup.js +++ b/SHiNE-browser-plugin-wallet/popup.js @@ -118,6 +118,7 @@ function renderPendingApproval(pendingApproval) { const details = [ { label: 'Сайт', value: pendingApproval.origin || '—', mono: true }, { label: 'Кошелёк', value: pendingApproval.publicKeyBase58 || '—', mono: true }, + { label: 'Очередь', value: `${pendingApproval.queuePosition || 1} из ${pendingApproval.queueLength || 1}` }, { label: 'Комментарий', value: pendingApproval.comment || 'Транзакция запрошена сайтом' }, { label: 'Тип', value: summary.kind || 'legacy' }, { label: 'Инструкций', value: String(summary.instructionCount ?? 0) }, @@ -186,9 +187,12 @@ function applyState(nextState) { els.requestWalletBtn.disabled = !session || !signing.selectedDeviceName; if (pendingApproval) { + const queueSuffix = (pendingApproval.queueLength || 1) > 1 + ? ` В очереди ${pendingApproval.queueLength} транзакции.` + : ''; els.pendingApprovalSubtitle.textContent = pendingApproval.origin - ? `Сайт ${pendingApproval.origin} запросил подписание транзакции.` - : 'Сайт запросил подписание транзакции.'; + ? `Сайт ${pendingApproval.origin} запросил подписание транзакции.${queueSuffix}` + : `Сайт запросил подписание транзакции.${queueSuffix}`; renderPendingApproval(pendingApproval); } else { els.pendingApprovalSubtitle.textContent = 'Сайт запросил подписание транзакции.'; diff --git a/SHiNE-browser-plugin-wallet/provider-bridge.js b/SHiNE-browser-plugin-wallet/provider-bridge.js index d6ad649..d632234 100644 --- a/SHiNE-browser-plugin-wallet/provider-bridge.js +++ b/SHiNE-browser-plugin-wallet/provider-bridge.js @@ -308,6 +308,15 @@ class ShineSolanaProvider { return this.core.signTransaction(transaction); } + async signAllTransactions(transactions = []) { + const list = Array.isArray(transactions) ? transactions : []; + const outputs = []; + for (const transaction of list) { + outputs.push(await this.core.signTransaction(transaction)); + } + return outputs; + } + async request(args = {}) { const method = String(args?.method || ''); const params = args?.params; @@ -321,6 +330,12 @@ class ShineSolanaProvider { const tx = Array.isArray(params) ? params[0] : params?.transaction || params; return this.signTransaction(tx); } + if (method === 'signAllTransactions') { + const transactions = Array.isArray(params) + ? params + : Array.isArray(params?.transactions) ? params.transactions : []; + return this.signAllTransactions(transactions); + } throw createProviderError(`Unsupported request method: ${method}`, 'UNSUPPORTED_METHOD'); } } diff --git a/VERSION.properties b/VERSION.properties index be130be..893c286 100644 --- a/VERSION.properties +++ b/VERSION.properties @@ -1,2 +1,2 @@ -client.version=1.2.259 -server.version=1.2.244 +client.version=1.2.260 +server.version=1.2.245 diff --git a/shine-UI/index.html b/shine-UI/index.html index 7fd4f01..635e6bb 100644 --- a/shine-UI/index.html +++ b/shine-UI/index.html @@ -5,7 +5,9 @@ - Shine UI Demo + + + СИЯНИЕ