SHA256
Синхронизировать изменения проекта
This commit is contained in:
@@ -13,10 +13,39 @@ import {
|
||||
PASSWORD_MAX_LENGTH,
|
||||
PASSWORD_WORDS_COUNT,
|
||||
} from '../services/password-words.js';
|
||||
import { sha256Text } from '../services/crypto-utils.js';
|
||||
import { openRegistrationFaq } from './registration-faq-view.js';
|
||||
import { defaultServerHttp } from '../deploy-config.js';
|
||||
|
||||
export const pageMeta = { id: 'register-view', title: 'Зарегистрироваться', showAppChrome: false };
|
||||
// ВРЕМЕННАЯ UI-ЗАГЛУШКА:
|
||||
// пока полноценная логика продажи/выдачи коротких имён не внедрена on-chain,
|
||||
// UI пускает логины 5..7 символов только по временному коду.
|
||||
const TEMP_MIN_LOGIN_WITHOUT_PROMO = 8;
|
||||
const TEMP_ABSOLUTE_MIN_LOGIN_LEN = 5;
|
||||
const TEMP_PROMO_HASH_HEX = 'a665a45920422f9d417e4867efdc4fb8a04a1f3fff1fa07e998e86f7f7a27ae3';
|
||||
|
||||
function bytesToHex(bytes) {
|
||||
return Array.from(bytes || [], (b) => b.toString(16).padStart(2, '0')).join('');
|
||||
}
|
||||
|
||||
async function isTemporaryPromoAccepted(value) {
|
||||
const digest = await sha256Text(String(value || ''));
|
||||
return bytesToHex(digest) === TEMP_PROMO_HASH_HEX;
|
||||
}
|
||||
|
||||
function normalizeLoginForTemporaryUiGuard(login) {
|
||||
const source = String(login || '').trim();
|
||||
if (!source || source.length > 20) return null;
|
||||
let normalized = '';
|
||||
for (const ch of source) {
|
||||
if (ch === '_') continue;
|
||||
if (!/[0-9A-Za-z]/.test(ch)) return null;
|
||||
normalized += ch.toLowerCase();
|
||||
}
|
||||
if (!normalized || normalized.length > 20) return null;
|
||||
return normalized;
|
||||
}
|
||||
|
||||
function createWordsLayout({ words, onInput }) {
|
||||
const section = document.createElement('div');
|
||||
@@ -148,7 +177,7 @@ export function render({ navigate }) {
|
||||
|
||||
const promoHint = document.createElement('p');
|
||||
promoHint.className = 'meta-muted';
|
||||
promoHint.textContent = 'Если промокод включён, обычная проверка premium/trademark логина пропускается.';
|
||||
promoHint.textContent = 'Временный режим: логины длиной 5-7 символов доступны только по специальному коду. Любое другое значение считается неверным промокодом.';
|
||||
|
||||
promoField.append(promoFieldLabel, promoInput, promoHint);
|
||||
|
||||
@@ -247,26 +276,58 @@ export function render({ navigate }) {
|
||||
|
||||
async function runAvailabilityCheck() {
|
||||
const login = loginInput.value.trim();
|
||||
const promoCode = String(promoInput.value || '').trim();
|
||||
if (!login) {
|
||||
statusText.textContent = 'Введите логин';
|
||||
formError.style.display = 'none';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (usePromoCode) {
|
||||
lastCheckedLogin = login;
|
||||
lastCheckedFree = true;
|
||||
lastCheckedClassName = 'promo';
|
||||
statusText.textContent = 'Промокод включён: обычная проверка premium/trademark логина пропущена';
|
||||
statusText.className = 'status-line is-available';
|
||||
const normalizedLogin = normalizeLoginForTemporaryUiGuard(login);
|
||||
if (!normalizedLogin) {
|
||||
statusText.textContent = 'Логин содержит недопустимые символы или имеет неверную длину ❌';
|
||||
statusText.className = 'status-line is-unavailable';
|
||||
formError.style.display = 'none';
|
||||
return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (normalizedLogin.length < TEMP_ABSOLUTE_MIN_LOGIN_LEN) {
|
||||
statusText.textContent = `Логин должен быть не короче ${TEMP_ABSOLUTE_MIN_LOGIN_LEN} символов ❌`;
|
||||
statusText.className = 'status-line is-unavailable';
|
||||
formError.style.display = 'none';
|
||||
return false;
|
||||
}
|
||||
|
||||
const wantsShortLogin = normalizedLogin.length < TEMP_MIN_LOGIN_WITHOUT_PROMO;
|
||||
let promoAccepted = false;
|
||||
if (usePromoCode) {
|
||||
if (!promoCode) {
|
||||
statusText.textContent = 'Введите промокод ❌';
|
||||
statusText.className = 'status-line is-unavailable';
|
||||
formError.style.display = 'none';
|
||||
return false;
|
||||
}
|
||||
promoAccepted = await isTemporaryPromoAccepted(promoCode);
|
||||
if (!promoAccepted) {
|
||||
statusText.textContent = 'Неверный промокод ❌';
|
||||
statusText.className = 'status-line is-unavailable';
|
||||
formError.style.display = 'none';
|
||||
return false;
|
||||
}
|
||||
} else if (wantsShortLogin) {
|
||||
statusText.textContent = `Логины короче ${TEMP_MIN_LOGIN_WITHOUT_PROMO} символов временно доступны только по специальному коду ❌`;
|
||||
statusText.className = 'status-line is-unavailable';
|
||||
formError.style.display = 'none';
|
||||
return false;
|
||||
}
|
||||
|
||||
if (login === lastCheckedLogin) {
|
||||
if (!lastCheckedFree) {
|
||||
statusText.textContent = 'Логин уже занят ❌';
|
||||
statusText.className = 'status-line is-unavailable';
|
||||
} else if (lastCheckedClassName === 'promo') {
|
||||
statusText.textContent = 'Логин свободен ✅ Временный код принят';
|
||||
statusText.className = 'status-line is-available';
|
||||
} else if (lastCheckedClassName === 'free') {
|
||||
statusText.textContent = 'Логин свободен ✅';
|
||||
statusText.className = 'status-line is-available';
|
||||
@@ -281,7 +342,7 @@ export function render({ navigate }) {
|
||||
statusText.className = 'status-line is-unavailable';
|
||||
}
|
||||
formError.style.display = 'none';
|
||||
return lastCheckedFree && lastCheckedClassName === 'free';
|
||||
return lastCheckedFree && (lastCheckedClassName === 'free' || lastCheckedClassName === 'promo');
|
||||
}
|
||||
|
||||
checkButton.disabled = true;
|
||||
@@ -295,15 +356,19 @@ export function render({ navigate }) {
|
||||
let className = '';
|
||||
let precheckWarning = '';
|
||||
if (isFree) {
|
||||
try {
|
||||
const precheck = await precheckLoginClassOnSolana({
|
||||
login,
|
||||
solanaEndpoint: state.entrySettings.solanaServer,
|
||||
});
|
||||
className = precheck.className;
|
||||
} catch (precheckError) {
|
||||
className = 'free';
|
||||
precheckWarning = formatSolanaErrorDetails(precheckError);
|
||||
if (promoAccepted) {
|
||||
className = 'promo';
|
||||
} else {
|
||||
try {
|
||||
const precheck = await precheckLoginClassOnSolana({
|
||||
login,
|
||||
solanaEndpoint: state.entrySettings.solanaServer,
|
||||
});
|
||||
className = precheck.className;
|
||||
} catch (precheckError) {
|
||||
className = 'free';
|
||||
precheckWarning = formatSolanaErrorDetails(precheckError);
|
||||
}
|
||||
}
|
||||
}
|
||||
lastCheckedLogin = login;
|
||||
@@ -312,6 +377,9 @@ export function render({ navigate }) {
|
||||
if (!isFree) {
|
||||
statusText.textContent = 'Логин уже занят ❌';
|
||||
statusText.className = 'status-line is-unavailable';
|
||||
} else if (className === 'promo') {
|
||||
statusText.textContent = 'Логин свободен ✅ Временный код принят';
|
||||
statusText.className = 'status-line is-available';
|
||||
} else if (className === 'free') {
|
||||
statusText.textContent = precheckWarning
|
||||
? `Логин свободен ✅ (предпроверка Solana недоступна: ${precheckWarning})`
|
||||
@@ -328,7 +396,7 @@ export function render({ navigate }) {
|
||||
statusText.className = 'status-line is-unavailable';
|
||||
}
|
||||
formError.style.display = 'none';
|
||||
return isFree && className === 'free';
|
||||
return isFree && (className === 'free' || className === 'promo');
|
||||
} catch (error) {
|
||||
const base = toUserMessage(error, 'Не удалось проверить логин');
|
||||
const details = formatSolanaErrorDetails(error);
|
||||
@@ -379,7 +447,7 @@ export function render({ navigate }) {
|
||||
updatePromoVisibility();
|
||||
syncDraftState();
|
||||
if (usePromoCode) {
|
||||
statusText.textContent = 'Промокод включён: проверка логина будет пропущена';
|
||||
statusText.textContent = 'Временный код включён: для логинов 5-7 символов будет локальная проверка';
|
||||
statusText.className = 'status-line is-available';
|
||||
} else {
|
||||
statusText.textContent = 'Проверка логина: не выполнена';
|
||||
@@ -388,6 +456,9 @@ export function render({ navigate }) {
|
||||
});
|
||||
|
||||
promoInput.addEventListener('input', () => {
|
||||
lastCheckedLogin = '';
|
||||
lastCheckedFree = false;
|
||||
lastCheckedClassName = '';
|
||||
syncDraftState();
|
||||
});
|
||||
|
||||
@@ -395,7 +466,7 @@ export function render({ navigate }) {
|
||||
formError.style.display = 'none';
|
||||
const promoCode = String(promoInput.value || '').trim();
|
||||
if (usePromoCode && !promoCode) {
|
||||
formError.textContent = 'Если включён промокод, поле промокода должно быть заполнено.';
|
||||
formError.textContent = 'Если включён временный код, поле должно быть заполнено.';
|
||||
formError.style.display = '';
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -266,7 +266,7 @@ export function render({ navigate }) {
|
||||
keyBundle,
|
||||
solanaEndpoint: state.entrySettings.solanaServer,
|
||||
accessServers: [state.entrySettings.shineServerLogin || defaultServerLogin],
|
||||
promoCode: state.registrationDraft.usePromoCode ? state.registrationDraft.promoCode : '',
|
||||
promoCode: '',
|
||||
});
|
||||
} catch (solanaError) {
|
||||
const solanaMsg = formatSolanaErrorDetails(solanaError);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
export const SOLANA_CLUSTER = 'mainnet-beta';
|
||||
export const SOLANA_ENDPOINT_DEFAULT = 'https://api.mainnet-beta.solana.com';
|
||||
// Для браузерного UI используем endpoint, который не режет JSON-RPC по Origin.
|
||||
export const SOLANA_ENDPOINT_DEFAULT = 'https://solana-rpc.publicnode.com';
|
||||
|
||||
// Единый файл актуальных Solana Program ID для UI-части SHiNE.
|
||||
// При смене адресов обновлять этот файл и все его потребители внутри shine-UI.
|
||||
|
||||
+18
-3
@@ -92,6 +92,21 @@ const DEFAULT_ARWEAVE_SERVER = 'https://arweave.net';
|
||||
const DEFAULT_CALL_PREFLIGHT_TIMEOUT_MS = 6000;
|
||||
const DEFAULT_OPENAI_BASE_URL = 'https://api.openai.com/v1';
|
||||
|
||||
function normalizeStoredSolanaServer(value) {
|
||||
const raw = String(value || '').trim();
|
||||
if (!raw) return DEFAULT_SOLANA_SERVER;
|
||||
const normalized = raw.replace(/\/+$/u, '').toLowerCase();
|
||||
if (
|
||||
normalized === 'https://api.devnet.solana.com'
|
||||
|| normalized === 'http://api.devnet.solana.com'
|
||||
|| normalized === 'https://api.mainnet-beta.solana.com'
|
||||
|| normalized === 'http://api.mainnet-beta.solana.com'
|
||||
) {
|
||||
return DEFAULT_SOLANA_SERVER;
|
||||
}
|
||||
return raw;
|
||||
}
|
||||
|
||||
export function normalizeDmChatId(value) {
|
||||
return String(value || '').trim().toLowerCase();
|
||||
}
|
||||
@@ -182,7 +197,7 @@ function persistEntrySettings(settings) {
|
||||
try {
|
||||
const payload = {
|
||||
language: String(settings?.language || 'ru'),
|
||||
solanaServer: String(settings?.solanaServer || DEFAULT_SOLANA_SERVER),
|
||||
solanaServer: normalizeStoredSolanaServer(settings?.solanaServer),
|
||||
shineServer: String(settings?.shineServer || DEFAULT_SHINE_SERVER),
|
||||
shineServerLogin: String(settings?.shineServerLogin || DEFAULT_SHINE_SERVER_LOGIN_VALUE),
|
||||
shineServerHttp: String(settings?.shineServerHttp || DEFAULT_SHINE_SERVER_HTTP_VALUE),
|
||||
@@ -249,7 +264,7 @@ function createInitialState({ withStoredSession = true } = {}) {
|
||||
startHint: '',
|
||||
entrySettings: {
|
||||
language: String(storedEntrySettings?.language || 'ru'),
|
||||
solanaServer: String(storedEntrySettings?.solanaServer || DEFAULT_SOLANA_SERVER),
|
||||
solanaServer: normalizeStoredSolanaServer(storedEntrySettings?.solanaServer),
|
||||
shineServer: String(LOCAL_WS_OVERRIDE_URL || storedEntrySettings?.shineServer || initialShineServer),
|
||||
shineServerLogin: String(storedEntrySettings?.shineServerLogin || DEFAULT_SHINE_SERVER_LOGIN_VALUE),
|
||||
shineServerHttp: String(storedEntrySettings?.shineServerHttp || DEFAULT_SHINE_SERVER_HTTP_VALUE),
|
||||
@@ -718,7 +733,7 @@ export function checkServerAvailability(address) {
|
||||
}
|
||||
|
||||
export async function saveEntrySettings(nextSettings) {
|
||||
const nextSolanaServer = String(nextSettings?.solanaServer || state.entrySettings.solanaServer || DEFAULT_SOLANA_SERVER);
|
||||
const nextSolanaServer = normalizeStoredSolanaServer(nextSettings?.solanaServer || state.entrySettings.solanaServer || DEFAULT_SOLANA_SERVER);
|
||||
const nextShineServerLogin = String(nextSettings?.shineServerLogin || state.entrySettings.shineServerLogin || DEFAULT_SHINE_SERVER_LOGIN_VALUE).trim().toLowerCase()
|
||||
|| DEFAULT_SHINE_SERVER_LOGIN_VALUE;
|
||||
let forcedShineServer = LOCAL_WS_OVERRIDE_URL || '';
|
||||
|
||||
@@ -48,8 +48,8 @@
|
||||
<h2>Параметры Solana</h2>
|
||||
<div class="field">
|
||||
<label>Solana Endpoint</label>
|
||||
<input type="text" id="endpoint" value="https://api.mainnet-beta.solana.com" />
|
||||
<div class="hint">mainnet: https://api.mainnet-beta.solana.com · devnet: https://api.devnet.solana.com</div>
|
||||
<input type="text" id="endpoint" value="https://solana-rpc.publicnode.com" />
|
||||
<div class="hint">mainnet: https://solana-rpc.publicnode.com · devnet: https://api.devnet.solana.com</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -52,7 +52,7 @@
|
||||
<h2>Параметры Solana</h2>
|
||||
<div class="field">
|
||||
<label>Solana Endpoint</label>
|
||||
<input type="text" id="endpoint" value="https://api.mainnet-beta.solana.com" />
|
||||
<input type="text" id="endpoint" value="https://solana-rpc.publicnode.com" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -56,7 +56,7 @@
|
||||
<h2>Параметры Solana</h2>
|
||||
<div class="field">
|
||||
<label>Solana Endpoint</label>
|
||||
<input type="text" id="endpoint" value="https://api.mainnet-beta.solana.com" />
|
||||
<input type="text" id="endpoint" value="https://solana-rpc.publicnode.com" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user