SHA256
Обновить UI кошелька и регистрацию
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user