SHA256
Улучшить финал регистрации и автовход
This commit is contained in:
+2
-2
@@ -1,2 +1,2 @@
|
|||||||
client.version=1.2.322
|
client.version=1.2.323
|
||||||
server.version=1.2.294
|
server.version=1.2.295
|
||||||
|
|||||||
@@ -0,0 +1,20 @@
|
|||||||
|
# Исправление payer для Solana login guard предпроверки
|
||||||
|
|
||||||
|
- статус: `pending`
|
||||||
|
|
||||||
|
## Кратко
|
||||||
|
|
||||||
|
В браузерной предпроверке логина через `shine_login_guard` убран несуществующий test payer-аккаунт.
|
||||||
|
Для `simulateTransaction` теперь используется существующий `program id` самой `shine_login_guard`.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
|
||||||
|
- на странице регистрации в devnet логин длиной от `5` символов больше не показывает предупреждение:
|
||||||
|
`предпроверка Solana недоступна: ... AccountNotFound`;
|
||||||
|
- свободный логин показывает обычный статус `Логин свободен ✅`;
|
||||||
|
- короткий логин всё ещё корректно классифицируется как недоступный для обычной регистрации.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
- `simulateTransaction` для `shine_login_guard` не падает из-за `AccountNotFound`;
|
||||||
|
- UI показывает результат on-chain предпроверки без ложного warning.
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
# Торжественный финал регистрации и фоновый автовход
|
||||||
|
|
||||||
|
- статус: `pending`
|
||||||
|
|
||||||
|
## Кратко
|
||||||
|
|
||||||
|
После успешной регистрации экран завершения сделан более торжественным.
|
||||||
|
Убран countdown и кнопка ручного запуска автовхода.
|
||||||
|
Теперь UI ждёт 5 секунд, затем раз в секунду пробует автоматически войти в аккаунт, пока запись не станет доступна на сервере.
|
||||||
|
|
||||||
|
## Что проверять
|
||||||
|
|
||||||
|
- после регистрации появляется экран с текстом `Поздравляем, регистрация завершена`;
|
||||||
|
- ниже есть пояснение про ожидание обновления записи в блокчейне;
|
||||||
|
- отображается строка `Или вы можете` и кнопка `Вернуться в главное меню`;
|
||||||
|
- если ничего не нажимать, через несколько секунд начинается фоновый автовход без мигающего countdown;
|
||||||
|
- после появления записи пользователь автоматически попадает в свой аккаунт;
|
||||||
|
- если нажать `Вернуться в главное меню`, автоматические попытки прекращаются и открывается стартовый экран.
|
||||||
|
|
||||||
|
## Ожидаемый результат
|
||||||
|
|
||||||
|
- финальный экран регистрации выглядит спокойнее и понятнее;
|
||||||
|
- пользователь не видит лишний таймер и ручную кнопку входа;
|
||||||
|
- автовход происходит сам, когда сервер уже готов принять логин.
|
||||||
@@ -400,7 +400,6 @@ export function render({ navigate }) {
|
|||||||
if (runId !== loginCheckRunId) return false;
|
if (runId !== loginCheckRunId) return false;
|
||||||
const isFree = !check.exists;
|
const isFree = !check.exists;
|
||||||
let className = '';
|
let className = '';
|
||||||
let precheckWarning = '';
|
|
||||||
if (isFree) {
|
if (isFree) {
|
||||||
if (promoAccepted) {
|
if (promoAccepted) {
|
||||||
className = 'promo';
|
className = 'promo';
|
||||||
@@ -413,7 +412,7 @@ export function render({ navigate }) {
|
|||||||
className = precheck.className;
|
className = precheck.className;
|
||||||
} catch (precheckError) {
|
} catch (precheckError) {
|
||||||
className = 'free';
|
className = 'free';
|
||||||
precheckWarning = formatSolanaErrorDetails(precheckError);
|
console.warn('Solana login precheck fallback to free', formatSolanaErrorDetails(precheckError));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -425,12 +424,7 @@ export function render({ navigate }) {
|
|||||||
} else if (className === 'promo') {
|
} else if (className === 'promo') {
|
||||||
setStatusMessage('Логин свободен ✅ Временный код принят', 'is-available');
|
setStatusMessage('Логин свободен ✅ Временный код принят', 'is-available');
|
||||||
} else if (className === 'free') {
|
} else if (className === 'free') {
|
||||||
setStatusMessage(
|
setStatusMessage('Логин свободен ✅', 'is-available');
|
||||||
precheckWarning
|
|
||||||
? `Логин свободен ✅ (предпроверка Solana недоступна: ${precheckWarning})`
|
|
||||||
: 'Логин свободен ✅',
|
|
||||||
'is-available',
|
|
||||||
);
|
|
||||||
} else if (className === 'premium') {
|
} else if (className === 'premium') {
|
||||||
setStatusMessage('Логин свободен, но это премиум-логин (покупка через DAO) ❌', 'is-unavailable');
|
setStatusMessage('Логин свободен, но это премиум-логин (покупка через DAO) ❌', 'is-unavailable');
|
||||||
} else if (className === 'company') {
|
} else if (className === 'company') {
|
||||||
|
|||||||
@@ -24,7 +24,8 @@ import { defaultServerLogin } from '../deploy-config.js';
|
|||||||
|
|
||||||
export const pageMeta = { id: 'registration-payment-view', title: 'Оплата регистрации', showAppChrome: false };
|
export const pageMeta = { id: 'registration-payment-view', title: 'Оплата регистрации', showAppChrome: false };
|
||||||
const MIN_REQUIRED_SOL = 0.01;
|
const MIN_REQUIRED_SOL = 0.01;
|
||||||
const SOLANA_SYNC_WAIT_SEC = 12;
|
const AUTO_LOGIN_INITIAL_DELAY_MS = 5000;
|
||||||
|
const AUTO_LOGIN_RETRY_MS = 1000;
|
||||||
const EMPTY_PASSWORD_WORDS = Array.from({ length: 12 }, () => '');
|
const EMPTY_PASSWORD_WORDS = Array.from({ length: 12 }, () => '');
|
||||||
|
|
||||||
function parseBalanceSol(value) {
|
function parseBalanceSol(value) {
|
||||||
@@ -329,82 +330,76 @@ function renderSolanaDoneStage({ navigate, status, keyBundle }) {
|
|||||||
const card = screen.querySelector('.card.stack');
|
const card = screen.querySelector('.card.stack');
|
||||||
if (!card) return;
|
if (!card) return;
|
||||||
|
|
||||||
let remainingSec = SOLANA_SYNC_WAIT_SEC;
|
let initialDelayId = null;
|
||||||
let timerId = null;
|
let retryTimerId = null;
|
||||||
let loginStarted = false;
|
let loginInFlight = false;
|
||||||
|
let loginCompleted = false;
|
||||||
|
|
||||||
const info = document.createElement('p');
|
const title = document.createElement('h2');
|
||||||
info.className = 'auth-copy';
|
title.textContent = 'Поздравляем, регистрация завершена';
|
||||||
info.textContent = 'Регистрация завершена успешно.';
|
title.style.margin = '0';
|
||||||
|
|
||||||
const hint = document.createElement('p');
|
const hint = document.createElement('p');
|
||||||
hint.className = 'meta-muted';
|
hint.className = 'auth-copy';
|
||||||
hint.textContent = 'Нужно подождать 10–15 секунд, пока в блокчейне SHiNE обновится ваша запись о регистрации. После этого мы автоматически войдём в ваш аккаунт.';
|
hint.textContent = 'Осталось подождать несколько секунд, пока обновится запись в блокчейне. После этого приложение автоматически войдёт в ваш аккаунт.';
|
||||||
|
|
||||||
const timer = document.createElement('p');
|
const progress = document.createElement('p');
|
||||||
timer.className = 'meta-muted';
|
progress.className = 'meta-muted';
|
||||||
timer.textContent = `Готовим автоматический вход: ${remainingSec} сек`;
|
progress.textContent = 'Подготавливаем автоматический вход...';
|
||||||
|
|
||||||
const tryLoginBtn = document.createElement('button');
|
const altHint = document.createElement('p');
|
||||||
tryLoginBtn.className = 'primary-btn';
|
altHint.className = 'meta-muted';
|
||||||
tryLoginBtn.type = 'button';
|
altHint.textContent = 'Или вы можете';
|
||||||
tryLoginBtn.textContent = 'Входим автоматически...';
|
|
||||||
tryLoginBtn.disabled = true;
|
|
||||||
|
|
||||||
const statusHint = document.createElement('p');
|
const backBtn = document.createElement('button');
|
||||||
statusHint.className = 'meta-muted';
|
backBtn.className = 'ghost-btn';
|
||||||
statusHint.textContent = 'Ничего нажимать не нужно. Если сервер ответит раньше, войдём сразу.';
|
backBtn.type = 'button';
|
||||||
|
backBtn.textContent = 'Вернуться в главное меню';
|
||||||
|
|
||||||
const stopTimer = () => {
|
const stopAutoLogin = () => {
|
||||||
if (timerId) {
|
if (initialDelayId) {
|
||||||
window.clearInterval(timerId);
|
window.clearTimeout(initialDelayId);
|
||||||
timerId = null;
|
initialDelayId = null;
|
||||||
|
}
|
||||||
|
if (retryTimerId) {
|
||||||
|
window.clearInterval(retryTimerId);
|
||||||
|
retryTimerId = null;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const startAutoLogin = async () => {
|
const tryAutoLogin = async () => {
|
||||||
if (loginStarted) return;
|
if (loginCompleted || loginInFlight) return;
|
||||||
loginStarted = true;
|
loginInFlight = true;
|
||||||
stopTimer();
|
progress.textContent = 'Пробуем автоматически войти в аккаунт...';
|
||||||
status.style.display = 'none';
|
status.style.display = 'none';
|
||||||
timer.textContent = 'Пробуем автоматически войти...';
|
|
||||||
tryLoginBtn.textContent = 'Входим...';
|
|
||||||
try {
|
try {
|
||||||
await completeRegistrationLogin({ navigate, keyBundle });
|
await completeRegistrationLogin({ navigate, keyBundle });
|
||||||
|
loginCompleted = true;
|
||||||
|
stopAutoLogin();
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
loginStarted = false;
|
if (!loginCompleted) {
|
||||||
status.className = 'status-line is-unavailable';
|
progress.textContent = 'Ждём обновления записи и скоро попробуем снова...';
|
||||||
status.textContent = toUserMessage(error, 'Пока не удалось автоматически войти. Попробуйте ещё раз через несколько секунд.');
|
console.warn('Auto login after registration is not ready yet', toUserMessage(error, 'auto-login-wait'));
|
||||||
status.style.display = '';
|
}
|
||||||
timer.textContent = 'Автовход пока не удался.';
|
} finally {
|
||||||
tryLoginBtn.disabled = false;
|
loginInFlight = false;
|
||||||
tryLoginBtn.textContent = 'Попробовать войти сейчас';
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const updateTimerUi = () => {
|
backBtn.addEventListener('click', () => {
|
||||||
if (remainingSec > 0) {
|
loginCompleted = true;
|
||||||
timer.textContent = `Готовим автоматический вход: ${remainingSec} сек`;
|
stopAutoLogin();
|
||||||
tryLoginBtn.textContent = `Автовход через ${remainingSec} сек`;
|
navigate('start-view');
|
||||||
tryLoginBtn.disabled = true;
|
|
||||||
} else {
|
|
||||||
timer.textContent = 'Запускаем автоматический вход...';
|
|
||||||
tryLoginBtn.textContent = 'Входим...';
|
|
||||||
tryLoginBtn.disabled = true;
|
|
||||||
void startAutoLogin();
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
tryLoginBtn.addEventListener('click', async () => {
|
|
||||||
if (loginStarted) return;
|
|
||||||
await startAutoLogin();
|
|
||||||
});
|
});
|
||||||
|
|
||||||
card.innerHTML = '';
|
card.innerHTML = '';
|
||||||
card.append(info, hint, statusHint, timer, tryLoginBtn, status);
|
card.append(title, hint, progress, altHint, backBtn, status);
|
||||||
updateTimerUi();
|
|
||||||
timerId = window.setInterval(() => {
|
initialDelayId = window.setTimeout(() => {
|
||||||
remainingSec -= 1;
|
if (loginCompleted) return;
|
||||||
updateTimerUi();
|
void tryAutoLogin();
|
||||||
}, 1000);
|
retryTimerId = window.setInterval(() => {
|
||||||
|
void tryAutoLogin();
|
||||||
|
}, AUTO_LOGIN_RETRY_MS);
|
||||||
|
}, AUTO_LOGIN_INITIAL_DELAY_MS);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,6 @@ import {
|
|||||||
import { loadSolanaWeb3 } from '../vendor/solana-web3-loader.js';
|
import { loadSolanaWeb3 } from '../vendor/solana-web3-loader.js';
|
||||||
|
|
||||||
const CLASSIFY_LOGIN_INSTRUCTION_TAG = 1;
|
const CLASSIFY_LOGIN_INSTRUCTION_TAG = 1;
|
||||||
const PRECHECK_SIM_PAYER = 'SHiJ58FvbJQJnPEUa5mZNVpWd1ym9CkjjPgeHYzUJmh';
|
|
||||||
const SHINE_USERS_USER_PDA_SEED_PREFIX = 'user_login=';
|
const SHINE_USERS_USER_PDA_SEED_PREFIX = 'user_login=';
|
||||||
|
|
||||||
let solanaLibPromise = null;
|
let solanaLibPromise = null;
|
||||||
@@ -86,7 +85,6 @@ export async function precheckLoginClassOnSolana({ login, solanaEndpoint }) {
|
|||||||
const solana = await loadSolanaLib();
|
const solana = await loadSolanaLib();
|
||||||
const connection = new solana.Connection(String(solanaEndpoint || ''), 'confirmed');
|
const connection = new solana.Connection(String(solanaEndpoint || ''), 'confirmed');
|
||||||
const loginGuardProgram = new solana.PublicKey(SHINE_LOGIN_GUARD_PROGRAM_ID);
|
const loginGuardProgram = new solana.PublicKey(SHINE_LOGIN_GUARD_PROGRAM_ID);
|
||||||
const payer = new solana.PublicKey(PRECHECK_SIM_PAYER);
|
|
||||||
const ix = new solana.TransactionInstruction({
|
const ix = new solana.TransactionInstruction({
|
||||||
programId: loginGuardProgram,
|
programId: loginGuardProgram,
|
||||||
keys: [],
|
keys: [],
|
||||||
@@ -94,7 +92,9 @@ export async function precheckLoginClassOnSolana({ login, solanaEndpoint }) {
|
|||||||
});
|
});
|
||||||
const { blockhash } = await connection.getLatestBlockhash('confirmed');
|
const { blockhash } = await connection.getLatestBlockhash('confirmed');
|
||||||
const v0Message = new solana.TransactionMessage({
|
const v0Message = new solana.TransactionMessage({
|
||||||
payerKey: payer,
|
// Для simulateTransaction нужен существующий аккаунт payer.
|
||||||
|
// Используем сам program id, чтобы не зависеть от внешнего тестового адреса.
|
||||||
|
payerKey: loginGuardProgram,
|
||||||
recentBlockhash: blockhash,
|
recentBlockhash: blockhash,
|
||||||
instructions: [ix],
|
instructions: [ix],
|
||||||
}).compileToV0Message();
|
}).compileToV0Message();
|
||||||
|
|||||||
Reference in New Issue
Block a user