243 lines
9.1 KiB
JavaScript
243 lines
9.1 KiB
JavaScript
import { readShineUserPda, updateServerOnSolana } from '../../js/services/shine-user-pda-service.js';
|
||
import {
|
||
$,
|
||
buildKeyBundleFromForm,
|
||
clearGenMessage,
|
||
clearStatus,
|
||
compareExpectedPublicKeys,
|
||
deriveKeyBundleFromPassword,
|
||
fillKeyFields,
|
||
formatBigInt,
|
||
formatTimestamp,
|
||
openDevnetTopup,
|
||
parseLoginList,
|
||
publicKeyBytesToBase58,
|
||
refreshDeviceBalance,
|
||
setGenMessage,
|
||
setStatus,
|
||
setText,
|
||
setupPasswordEye,
|
||
summarizeKeyComparison,
|
||
updateSolAddress,
|
||
validateLoginOrThrow,
|
||
wireDeviceAddressPreview,
|
||
} from './server-ui-shared.js';
|
||
|
||
const fieldMap = {
|
||
masterSecret: 'masterSecret',
|
||
recoveryPub: 'recoveryPub',
|
||
recoveryPriv: 'recoveryPriv',
|
||
rootPub: 'rootPub',
|
||
rootPriv: 'rootPriv',
|
||
bchPub: 'bchPub',
|
||
bchPriv: 'bchPriv',
|
||
devPub: 'devPub',
|
||
devPriv: 'devPriv',
|
||
solBox: 'solBox',
|
||
solAdr: 'solAdr',
|
||
};
|
||
|
||
let currentPda = null;
|
||
|
||
function resetExpectedKeysUi() {
|
||
$('expectedKeysBox').style.display = 'none';
|
||
setText('expectedRecoveryPub', '');
|
||
setText('expectedRootPub', '');
|
||
setText('expectedBchPub', '');
|
||
setText('expectedDevPub', '');
|
||
clearGenMessage($('expectedKeysStatus'));
|
||
setText('deviceBalance', 'Баланс device ещё не запрашивался.');
|
||
}
|
||
|
||
function renderExpectedKeys(parsed) {
|
||
$('expectedKeysBox').style.display = 'block';
|
||
setText('expectedRecoveryPub', publicKeyBytesToBase58(parsed.recoveryKey));
|
||
setText('expectedRootPub', publicKeyBytesToBase58(parsed.rootKey));
|
||
setText('expectedBchPub', publicKeyBytesToBase58(parsed.blockchain.blockchainPublicKey));
|
||
setText('expectedDevPub', publicKeyBytesToBase58(parsed.clientKey));
|
||
setGenMessage($('expectedKeysStatus'), 'После генерации ключей этот блок покажет, совпадают ли они с уже записанной PDA.', 'warn');
|
||
}
|
||
|
||
function compareCurrentFormKeysWithPda() {
|
||
if (!currentPda) throw new Error('Сначала загрузите PDA');
|
||
const blockchainActual = String($('bchPub').value || '').trim();
|
||
return {
|
||
resultMap: {
|
||
recovery: compareExpectedPublicKeys(publicKeyBytesToBase58(currentPda.recoveryKey), $('recoveryPub').value),
|
||
root: compareExpectedPublicKeys(publicKeyBytesToBase58(currentPda.rootKey), $('rootPub').value),
|
||
blockchain: blockchainActual
|
||
? compareExpectedPublicKeys(publicKeyBytesToBase58(currentPda.blockchain.blockchainPublicKey), blockchainActual)
|
||
: { matches: true, expected: publicKeyBytesToBase58(currentPda.blockchain.blockchainPublicKey), actual: '' },
|
||
device: compareExpectedPublicKeys(publicKeyBytesToBase58(currentPda.clientKey), $('devPub').value),
|
||
},
|
||
};
|
||
}
|
||
|
||
function renderComparisonStatus() {
|
||
if (!currentPda) return false;
|
||
try {
|
||
const { resultMap } = compareCurrentFormKeysWithPda();
|
||
const summary = summarizeKeyComparison(resultMap);
|
||
if (summary.allMatch) {
|
||
setGenMessage($('expectedKeysStatus'), 'Ключи совпадают с загруженной PDA. Похоже, пароль верный.', 'ok');
|
||
return true;
|
||
}
|
||
setGenMessage(
|
||
$('expectedKeysStatus'),
|
||
`Не совпали ключи: ${summary.mismatches.join(', ')}. Похоже, пароль неверный или введены не те ключи.`,
|
||
'err',
|
||
);
|
||
return false;
|
||
} catch (error) {
|
||
setGenMessage($('expectedKeysStatus'), error?.message || String(error), 'err');
|
||
return false;
|
||
}
|
||
}
|
||
|
||
setupPasswordEye($('btnEye'), $('password'));
|
||
wireDeviceAddressPreview(fieldMap);
|
||
$('password').value = '';
|
||
resetExpectedKeysUi();
|
||
|
||
$('btnTopupDevnet').addEventListener('click', () => {
|
||
try {
|
||
openDevnetTopup($('devPub').value);
|
||
} catch (error) {
|
||
setStatus($('status'), error?.message || String(error), 'error');
|
||
}
|
||
});
|
||
|
||
$('btnRefreshBalance').addEventListener('click', async () => {
|
||
try {
|
||
$('btnRefreshBalance').disabled = true;
|
||
await refreshDeviceBalance({
|
||
endpoint: $('endpoint').value,
|
||
deviceAddress: $('devPub').value,
|
||
targetNode: $('deviceBalance'),
|
||
});
|
||
} catch (error) {
|
||
setStatus($('status'), error?.message || String(error), 'error');
|
||
} finally {
|
||
$('btnRefreshBalance').disabled = false;
|
||
}
|
||
});
|
||
|
||
$('btnGen').addEventListener('click', async () => {
|
||
clearGenMessage($('genMsg'));
|
||
clearStatus($('status'));
|
||
$('btnGen').disabled = true;
|
||
try {
|
||
const login = validateLoginOrThrow($('login').value);
|
||
const password = $('password').value;
|
||
const { masterSecret32, keyBundle } = await deriveKeyBundleFromPassword({ login, password });
|
||
fillKeyFields(fieldMap, keyBundle, masterSecret32);
|
||
updateSolAddress(fieldMap);
|
||
$('deviceBalance').textContent = 'Теперь можно проверить баланс device-аккаунта.';
|
||
const keysOk = renderComparisonStatus();
|
||
setGenMessage(
|
||
$('genMsg'),
|
||
keysOk
|
||
? 'Ключи и master secret сгенерированы из логина и пароля. Device-ключ оплатит транзакцию.'
|
||
: 'Ключи сгенерированы, но не совпали с уже загруженной PDA. Скорее всего, пароль неверный.',
|
||
keysOk ? 'ok' : 'err',
|
||
);
|
||
} catch (error) {
|
||
setGenMessage($('genMsg'), error?.message || String(error), 'err');
|
||
} finally {
|
||
$('btnGen').disabled = false;
|
||
}
|
||
});
|
||
|
||
$('btnLoad').addEventListener('click', async () => {
|
||
clearStatus($('status'));
|
||
clearGenMessage($('genMsg'));
|
||
$('btnLoad').disabled = true;
|
||
currentPda = null;
|
||
$('pdaInfo').style.display = 'none';
|
||
$('updateForm').style.display = 'none';
|
||
resetExpectedKeysUi();
|
||
try {
|
||
const login = validateLoginOrThrow($('login').value);
|
||
const endpoint = String($('endpoint').value || '').trim();
|
||
if (!endpoint) throw new Error('Укажите Solana endpoint');
|
||
|
||
setStatus($('status'), 'Загрузка PDA из Solana...', 'info');
|
||
const parsed = await readShineUserPda({ login, solanaEndpoint: endpoint });
|
||
if (!parsed.isServer) throw new Error('Эта PDA не является серверной');
|
||
currentPda = parsed;
|
||
|
||
$('iAddr').textContent = parsed.pdaAddress;
|
||
$('iVer').textContent = `#${parsed.recordNumber}`;
|
||
$('iCreated').textContent = formatTimestamp(parsed.createdAtMs);
|
||
$('iUpdated').textContent = formatTimestamp(parsed.updatedAtMs);
|
||
$('iSrvAddr').textContent = parsed.serverAddress || '—';
|
||
$('iSync').textContent = parsed.syncServers.length ? parsed.syncServers.join(', ') : '—';
|
||
$('iBch').textContent = parsed.blockchain.blockchainName;
|
||
$('iLimit').textContent = formatBigInt(parsed.blockchain.paidLimitBytes);
|
||
|
||
$('password').value = '';
|
||
$('serverAddress').value = parsed.serverAddress || '';
|
||
$('syncServers').value = parsed.syncServers.join('\n');
|
||
$('pdaInfo').style.display = 'block';
|
||
$('updateForm').style.display = 'block';
|
||
renderExpectedKeys(parsed);
|
||
setStatus($('status'), 'PDA загружена. Можно менять адрес или sync_servers.', 'success');
|
||
} catch (error) {
|
||
setStatus($('status'), error?.message || String(error), 'error');
|
||
} finally {
|
||
$('btnLoad').disabled = false;
|
||
}
|
||
});
|
||
|
||
$('btnUpdate').addEventListener('click', async () => {
|
||
clearStatus($('status'));
|
||
clearGenMessage($('genMsg'));
|
||
$('btnUpdate').disabled = true;
|
||
try {
|
||
if (!currentPda) throw new Error('Сначала загрузите PDA');
|
||
const endpoint = String($('endpoint').value || '').trim();
|
||
if (!endpoint) throw new Error('Укажите Solana endpoint');
|
||
const serverAddress = String($('serverAddress').value || '').trim();
|
||
if (!serverAddress) throw new Error('Укажите адрес сервера');
|
||
|
||
setStatus($('status'), 'Проверка и сборка keyBundle...', 'info');
|
||
const { keyBundle, normalized } = await buildKeyBundleFromForm(fieldMap, { requireBlockchain: false });
|
||
$('rootPub').value = normalized.rootPubB58;
|
||
$('rootPriv').value = normalized.rootPrivB58;
|
||
$('bchPub').value = normalized.bchPubB58;
|
||
$('bchPriv').value = normalized.bchPrivB58;
|
||
$('devPub').value = normalized.devPubB58;
|
||
$('devPriv').value = normalized.devPrivB58;
|
||
updateSolAddress(fieldMap);
|
||
if (!renderComparisonStatus()) {
|
||
throw new Error('Ключи не совпадают с загруженной PDA. Проверьте пароль или введённые ключи.');
|
||
}
|
||
|
||
setStatus($('status'), 'Отправка update_user_pda в Solana...', 'info');
|
||
const result = await updateServerOnSolana({
|
||
login: currentPda.login,
|
||
keyBundle,
|
||
serverAddress,
|
||
addressFormatType: currentPda.addressFormatType ?? 1,
|
||
addressFormatVersion: currentPda.addressFormatVersion ?? 0,
|
||
syncServers: parseLoginList($('syncServers').value),
|
||
solanaEndpoint: endpoint,
|
||
});
|
||
|
||
setStatus(
|
||
$('status'),
|
||
`✓ PDA обновлена!\n\nЛогин: ${currentPda.login}\nPDA: ${result.pdaAddress}\nТранзакция: ${result.signature}`,
|
||
'success',
|
||
);
|
||
currentPda = null;
|
||
$('pdaInfo').style.display = 'none';
|
||
$('updateForm').style.display = 'none';
|
||
} catch (error) {
|
||
setStatus($('status'), error?.message || String(error), 'error');
|
||
} finally {
|
||
$('btnUpdate').disabled = false;
|
||
}
|
||
});
|
||
|
||
document.body.dataset.ready = '1';
|