SHA256
chore: зафиксированы все текущие изменения проекта
This commit is contained in:
@@ -59,6 +59,7 @@ import * as deviceSessionView from './pages/device-session-view.js';
|
||||
import * as languageView from './pages/language-view.js';
|
||||
import * as appLogView from './pages/app-log-view.js';
|
||||
import * as pwaDiagnosticsView from './pages/pwa-diagnostics-view.js';
|
||||
import * as solanaUsersInitView from './pages/solana-users-init-view.js';
|
||||
import * as messagesList from './pages/messages-list.js';
|
||||
import * as contactSearchView from './pages/contact-search-view.js';
|
||||
import * as chatView from './pages/chat-view.js';
|
||||
@@ -98,6 +99,7 @@ const routes = {
|
||||
'language-view': languageView,
|
||||
'app-log-view': appLogView,
|
||||
'pwa-diagnostics-view': pwaDiagnosticsView,
|
||||
'solana-users-init-view': solanaUsersInitView,
|
||||
'messages-list': messagesList,
|
||||
'contact-search-view': contactSearchView,
|
||||
'chat-view': chatView,
|
||||
|
||||
@@ -262,6 +262,7 @@ export function render({ navigate }) {
|
||||
<button class="text-btn" type="button" id="settings-force-ui-update">Принудительно обновить UI</button>
|
||||
<button class="text-btn" type="button" id="settings-force-update-help">Клиент не обновляется?</button>
|
||||
<button class="text-btn" type="button" id="settings-ui-error-reporting">Отправлять ошибки на сервер</button>
|
||||
<button class="text-btn" type="button" id="settings-solana-users-init">Solana: init регистрации</button>
|
||||
<button class="text-btn" type="button" id="settings-app-log">Лог приложения</button>
|
||||
<button class="text-btn" type="button" id="settings-pwa-diagnostics">Диагностика PWA / Push</button>
|
||||
<button class="text-btn" type="button" id="settings-pwa-install">Как установить PWA</button>
|
||||
@@ -275,8 +276,10 @@ export function render({ navigate }) {
|
||||
const forceUpdateBtn = card.querySelector('#settings-force-ui-update');
|
||||
const forceUpdateHelpBtn = card.querySelector('#settings-force-update-help');
|
||||
const uiErrorReportingBtn = card.querySelector('#settings-ui-error-reporting');
|
||||
const solanaUsersInitBtn = card.querySelector('#settings-solana-users-init');
|
||||
|
||||
appLogBtn?.addEventListener('click', () => navigate('app-log-view'));
|
||||
solanaUsersInitBtn?.addEventListener('click', () => navigate('solana-users-init-view'));
|
||||
diagnosticsBtn?.addEventListener('click', () => navigate('pwa-diagnostics-view'));
|
||||
uploadAvatarBtn?.addEventListener('click', () => {
|
||||
openDeveloperAvatarUploadModal({
|
||||
|
||||
@@ -0,0 +1,196 @@
|
||||
import { renderHeader } from '../components/header.js';
|
||||
import {
|
||||
SHINE_USERS_ECONOMY_CONFIG_SEED,
|
||||
SHINE_USERS_PROGRAM_ID,
|
||||
SOLANA_CLUSTER,
|
||||
} from '../solana-programs.js';
|
||||
import { state } from '../state.js';
|
||||
|
||||
export const pageMeta = { id: 'solana-users-init-view', title: 'Solana Init (users)' };
|
||||
|
||||
let solanaLibPromise = null;
|
||||
function loadSolanaLib() {
|
||||
if (!solanaLibPromise) {
|
||||
solanaLibPromise = import('https://esm.sh/@solana/web3.js@1.98.4?bundle');
|
||||
}
|
||||
return solanaLibPromise;
|
||||
}
|
||||
|
||||
function getProvider() {
|
||||
const p = globalThis?.solana;
|
||||
if (!p || !p.isPhantom) return null;
|
||||
return p;
|
||||
}
|
||||
|
||||
function shortAddr(value = '') {
|
||||
const v = String(value || '').trim();
|
||||
if (v.length < 12) return v;
|
||||
return `${v.slice(0, 6)}...${v.slice(-6)}`;
|
||||
}
|
||||
|
||||
export function render({ navigate }) {
|
||||
const screen = document.createElement('section');
|
||||
screen.className = 'stack';
|
||||
|
||||
const card = document.createElement('div');
|
||||
card.className = 'card stack';
|
||||
|
||||
const status = document.createElement('p');
|
||||
status.className = 'meta-muted';
|
||||
status.textContent = 'Подключите кошелёк и выполните init_users_economy_config.';
|
||||
|
||||
const programId = SHINE_USERS_PROGRAM_ID;
|
||||
const programInput = document.createElement('input');
|
||||
programInput.className = 'input';
|
||||
programInput.type = 'text';
|
||||
programInput.readOnly = true;
|
||||
programInput.value = programId;
|
||||
|
||||
const rpcInput = document.createElement('input');
|
||||
rpcInput.className = 'input';
|
||||
rpcInput.type = 'text';
|
||||
rpcInput.readOnly = true;
|
||||
rpcInput.value = String(state.entrySettings.solanaServer || '');
|
||||
|
||||
const walletLine = document.createElement('p');
|
||||
walletLine.className = 'meta-muted';
|
||||
walletLine.textContent = 'Кошелёк: не подключен';
|
||||
|
||||
const economyPdaLine = document.createElement('p');
|
||||
economyPdaLine.className = 'meta-muted';
|
||||
economyPdaLine.textContent = 'PDA economy config: —';
|
||||
|
||||
const txLine = document.createElement('p');
|
||||
txLine.className = 'meta-muted';
|
||||
txLine.textContent = 'TX: —';
|
||||
|
||||
const connectBtn = document.createElement('button');
|
||||
connectBtn.className = 'text-btn';
|
||||
connectBtn.type = 'button';
|
||||
connectBtn.textContent = 'Подключить кошелёк';
|
||||
|
||||
const initBtn = document.createElement('button');
|
||||
initBtn.className = 'primary-btn';
|
||||
initBtn.type = 'button';
|
||||
initBtn.textContent = 'Запустить init_users_economy_config';
|
||||
initBtn.disabled = true;
|
||||
|
||||
let provider = null;
|
||||
let walletPubkey = null;
|
||||
let economyPda = null;
|
||||
|
||||
async function recomputePda() {
|
||||
const solana = await loadSolanaLib();
|
||||
const pid = new solana.PublicKey(programId);
|
||||
const [pda] = solana.PublicKey.findProgramAddressSync(
|
||||
[new TextEncoder().encode(SHINE_USERS_ECONOMY_CONFIG_SEED)],
|
||||
pid,
|
||||
);
|
||||
economyPda = pda;
|
||||
economyPdaLine.textContent = `PDA economy config: ${pda.toBase58()}`;
|
||||
}
|
||||
|
||||
connectBtn.addEventListener('click', async () => {
|
||||
status.textContent = 'Подключение кошелька...';
|
||||
try {
|
||||
provider = getProvider();
|
||||
if (!provider) throw new Error('Phantom не найден');
|
||||
const result = await provider.connect();
|
||||
walletPubkey = result?.publicKey || provider.publicKey;
|
||||
if (!walletPubkey) throw new Error('Кошелёк не вернул public key');
|
||||
walletLine.textContent = `Кошелёк: ${walletPubkey.toBase58()} (${shortAddr(walletPubkey.toBase58())})`;
|
||||
initBtn.disabled = false;
|
||||
status.textContent = 'Кошелёк подключен.';
|
||||
} catch (e) {
|
||||
status.textContent = `Ошибка подключения: ${e?.message || 'unknown'}`;
|
||||
}
|
||||
});
|
||||
|
||||
initBtn.addEventListener('click', async () => {
|
||||
if (!provider || !walletPubkey || !economyPda) return;
|
||||
initBtn.disabled = true;
|
||||
status.textContent = 'Отправка транзакции...';
|
||||
txLine.textContent = 'TX: —';
|
||||
try {
|
||||
const solana = await loadSolanaLib();
|
||||
const connection = new solana.Connection(String(state.entrySettings.solanaServer || ''), 'confirmed');
|
||||
const programPubkey = new solana.PublicKey(programId);
|
||||
|
||||
const discriminator = Uint8Array.from([13, 16, 103, 175, 121, 137, 166, 222]);
|
||||
const ix = new solana.TransactionInstruction({
|
||||
programId: programPubkey,
|
||||
keys: [
|
||||
{ pubkey: walletPubkey, isSigner: true, isWritable: true },
|
||||
{ pubkey: economyPda, isSigner: false, isWritable: true },
|
||||
{ pubkey: solana.SystemProgram.programId, isSigner: false, isWritable: false },
|
||||
],
|
||||
data: discriminator,
|
||||
});
|
||||
|
||||
const { blockhash, lastValidBlockHeight } = await connection.getLatestBlockhash('confirmed');
|
||||
const tx = new solana.Transaction({
|
||||
feePayer: walletPubkey,
|
||||
blockhash,
|
||||
lastValidBlockHeight,
|
||||
}).add(ix);
|
||||
|
||||
const signed = await provider.signTransaction(tx);
|
||||
const sig = await connection.sendRawTransaction(signed.serialize(), { skipPreflight: false });
|
||||
await connection.confirmTransaction({ signature: sig, blockhash, lastValidBlockHeight }, 'confirmed');
|
||||
|
||||
txLine.textContent = `TX: ${sig}`;
|
||||
status.textContent = 'Успешно: init_users_economy_config выполнен.';
|
||||
} catch (e) {
|
||||
const message = String(e?.message || 'unknown');
|
||||
txLine.textContent = 'TX: ошибка';
|
||||
if (message.toLowerCase().includes('already') || message.includes('1000')) {
|
||||
status.textContent = 'PDA уже инициализирован. Повторный init не требуется.';
|
||||
} else {
|
||||
status.textContent = `Ошибка init: ${message}`;
|
||||
}
|
||||
} finally {
|
||||
initBtn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
void recomputePda();
|
||||
|
||||
card.append(
|
||||
(() => {
|
||||
const t = document.createElement('p');
|
||||
t.className = 'meta-muted';
|
||||
t.textContent = `Cluster: ${SOLANA_CLUSTER}`;
|
||||
return t;
|
||||
})(),
|
||||
(() => {
|
||||
const label = document.createElement('label');
|
||||
label.className = 'field-label';
|
||||
label.textContent = 'Program ID (shine_users)';
|
||||
return label;
|
||||
})(),
|
||||
programInput,
|
||||
(() => {
|
||||
const label = document.createElement('label');
|
||||
label.className = 'field-label';
|
||||
label.textContent = 'RPC endpoint';
|
||||
return label;
|
||||
})(),
|
||||
rpcInput,
|
||||
walletLine,
|
||||
economyPdaLine,
|
||||
txLine,
|
||||
connectBtn,
|
||||
initBtn,
|
||||
status,
|
||||
);
|
||||
|
||||
screen.append(
|
||||
renderHeader({
|
||||
title: 'Solana Init (users)',
|
||||
leftAction: { label: '←', onClick: () => navigate('developer-settings-view') },
|
||||
}),
|
||||
card,
|
||||
);
|
||||
|
||||
return screen;
|
||||
}
|
||||
@@ -182,7 +182,8 @@ export function resolveToolbarActive(pageId) {
|
||||
pageId === 'device-session-view' ||
|
||||
pageId === 'language-view' ||
|
||||
pageId === 'app-log-view' ||
|
||||
pageId === 'pwa-diagnostics-view'
|
||||
pageId === 'pwa-diagnostics-view' ||
|
||||
pageId === 'solana-users-init-view'
|
||||
) return 'profile-view';
|
||||
if (pageId === 'chat-view' || pageId === 'contact-search-view') return 'messages-list';
|
||||
if (pageId === 'channel-view' || pageId === 'channel-thread-view' || pageId === 'add-channel-view' || pageId === 'add-personal-public-chat-view') return 'channels-list';
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
import { deriveEd25519FromPassword } from './crypto-utils.js';
|
||||
import { extractDeviceKey32FromStoredValue } from './device-key-utils.js';
|
||||
import { loadEncryptedUserSecrets } from './key-vault.js';
|
||||
import { SOLANA_ENDPOINT_DEFAULT } from '../solana-programs.js';
|
||||
|
||||
const DEFAULT_SOLANA_ENDPOINT = 'https://api.devnet.solana.com';
|
||||
const DEFAULT_SOLANA_ENDPOINT = SOLANA_ENDPOINT_DEFAULT;
|
||||
const TOPUP_SITE_URL = 'https://shine-promo-solana-devnet.shineup.me/';
|
||||
|
||||
let solanaLibPromise = null;
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
export const SOLANA_CLUSTER = 'devnet';
|
||||
export const SOLANA_ENDPOINT_DEFAULT = 'https://api.devnet.solana.com';
|
||||
|
||||
// Программа регистрации пользователей SHiNE (shine_users), задеплоена в devnet.
|
||||
export const SHINE_USERS_PROGRAM_ID = 'FZS1YctoeEhCkZ5VTjsysUFAXR8CqxYztcLboXcg2Rpm';
|
||||
export const SHINE_USERS_ECONOMY_CONFIG_SEED = 'shine_users_economy_config';
|
||||
export const SHINE_LOGIN_GUARD_PROGRAM_ID = '3xkopA7cXagxzMFrKdv3NCBfV6BKiRJCk69kr27M2sRo';
|
||||
export const SHINE_PAYMENTS_PROGRAM_ID = 'm48pWRGWrMj3TEHjuU4zsp5Gju4e7ZaPovk8RcVt7kR';
|
||||
@@ -1,5 +1,6 @@
|
||||
import { AuthService } from './services/auth-service.js';
|
||||
import { listStoredMessages, putStoredMessage } from './services/message-store.js';
|
||||
import { SOLANA_ENDPOINT_DEFAULT } from './solana-programs.js';
|
||||
|
||||
const clone = (value) => JSON.parse(JSON.stringify(value));
|
||||
const SESSION_STORAGE_KEY = 'shine-ui-current-session-v1';
|
||||
@@ -76,7 +77,7 @@ function inferTunnelWsUrl() {
|
||||
}
|
||||
|
||||
const LOCAL_WS_OVERRIDE_URL = readLocalWsOverrideUrl() || inferTunnelWsUrl();
|
||||
const DEFAULT_SOLANA_SERVER = 'https://api.devnet.solana.com';
|
||||
const DEFAULT_SOLANA_SERVER = SOLANA_ENDPOINT_DEFAULT;
|
||||
const DEFAULT_SHINE_SERVER = 'wss://shineup.me/ws';
|
||||
const DEFAULT_ARWEAVE_SERVER = 'https://arweave.net';
|
||||
const DEFAULT_CALL_PREFLIGHT_TIMEOUT_MS = 6000;
|
||||
|
||||
Reference in New Issue
Block a user