ваше сообщение коммита

This commit is contained in:
2025-04-03 17:25:38 +03:00
parent e05f06c588
commit 808f2594c6
8 changed files with 1743 additions and 548 deletions

View File

@@ -216,6 +216,7 @@ input, textarea {
padding: 10px 15px;
border-radius: 8px;
max-width: 80%;
position: relative;
}
.user-message {
@@ -229,18 +230,58 @@ input, textarea {
align-self: flex-start;
}
.system-message {
background-color: #FFF3E0;
align-self: center;
margin-left: auto;
margin-right: auto;
font-style: italic;
color: #FF5722;
text-align: center;
max-width: 90%;
}
.message-content {
margin-bottom: 5px;
white-space: pre-wrap;
word-break: break-word;
}
.message-meta {
display: flex;
justify-content: space-between;
align-items: center;
}
.message-time {
font-size: 12px;
color: #777;
text-align: right;
}
.message-status {
font-size: 12px;
color: #777;
}
.sending-indicator {
color: #2196F3;
font-style: italic;
}
.error-indicator {
color: #F44336;
font-weight: bold;
}
.is-local {
opacity: 0.7;
}
.has-error {
border: 1px solid #F44336;
}
.chat-input {
display: flex;
background-color: white;
@@ -1015,3 +1056,121 @@ input, textarea {
color: #666;
font-size: 14px;
}
/* Стили для отображения подключенного кошелька */
.wallet-connected .wallet-button {
background-color: #4CAF50 !important;
color: white !important;
}
.wallet-connected #auth-display {
display: inline-block;
padding: 8px 12px;
background-color: rgba(76, 175, 80, 0.1);
border: 1px solid #4CAF50;
border-radius: 4px;
margin-right: 10px;
color: #4CAF50;
font-weight: 500;
}
/* Индикатор подключения */
.connection-indicator {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
margin-right: 8px;
background-color: #ccc;
}
.wallet-connected .connection-indicator {
background-color: #4CAF50;
}
/* Стили для кнопок авторизации */
#auth-buttons {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
#logout-button {
display: none;
background-color: #f44336;
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
}
#logout-button:hover {
background-color: #d32f2f;
}
/* Анимация для индикации подключения */
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(76, 175, 80, 0.7);
}
70% {
box-shadow: 0 0 0 10px rgba(76, 175, 80, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(76, 175, 80, 0);
}
}
.wallet-connected .connection-indicator {
animation: pulse 2s infinite;
}
/* Стили для отладочной информации */
.debug-info {
margin-top: 20px;
padding: 10px;
background-color: #f5f5f5;
border-radius: 8px;
font-size: 0.9em;
}
.debug-info h4 {
margin-top: 0;
margin-bottom: 8px;
color: #666;
}
.debug-item {
margin-bottom: 8px;
word-break: break-all;
}
.debug-item code {
background-color: #e0e0e0;
padding: 2px 4px;
border-radius: 4px;
font-family: monospace;
color: #333;
}
.debug-buttons {
display: flex;
gap: 8px;
margin-top: 8px;
}
.small-button {
padding: 5px 10px;
background-color: #5e5e5e;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 0.8em;
}
.small-button:hover {
background-color: #444;
}

View File

@@ -74,9 +74,23 @@ export function useAuth() {
if (isAuthenticated.value) {
console.log('Linking messages after authentication');
// Проверка, есть ли гостевой ID для обработки
const localGuestId = localStorage.getItem('guestId');
// Если гостевого ID нет или он уже был обработан, пропускаем запрос
if (!localGuestId || processedGuestIds.value.includes(localGuestId)) {
console.log('No new guest IDs to process or already processed');
return {
success: true,
message: 'No new guest IDs to process',
processedIds: processedGuestIds.value
};
}
// Создаем объект с идентификаторами для передачи на сервер
const identifiersData = {
userId: userId.value
userId: userId.value,
guestId: localGuestId
};
// Добавляем все доступные идентификаторы
@@ -84,17 +98,6 @@ export function useAuth() {
if (email.value) identifiersData.email = email.value;
if (telegramId.value) identifiersData.telegramId = telegramId.value;
// Сохраняем предыдущий guestId из localStorage, если есть
const localGuestId = localStorage.getItem('guestId');
if (localGuestId && !processedGuestIds.value.includes(localGuestId)) {
console.log('Found local guestId:', localGuestId);
// Добавляем guestId в идентификаторы
identifiersData.guestId = localGuestId;
// Добавляем guestId в список обработанных
processedGuestIds.value.push(localGuestId);
}
// Логируем попытку связывания сообщений
console.log('Sending link-guest-messages request with data:', identifiersData);
try {
@@ -104,15 +107,20 @@ export function useAuth() {
if (response.data.success) {
console.log('Messages linked successfully:', response.data);
// Если в ответе есть обработанные guestIds, добавляем их в список
if (response.data.results && Array.isArray(response.data.results)) {
// Обновляем список обработанных guestIds из ответа сервера
if (response.data.processedIds && Array.isArray(response.data.processedIds)) {
processedGuestIds.value = [...response.data.processedIds];
console.log('Updated processed guest IDs from server:', processedGuestIds.value);
}
// В качестве запасного варианта также обрабатываем старый формат ответа
else if (response.data.results && Array.isArray(response.data.results)) {
const newProcessedIds = response.data.results
.filter(result => result.guestId)
.map(result => result.guestId);
if (newProcessedIds.length > 0) {
processedGuestIds.value = [...new Set([...processedGuestIds.value, ...newProcessedIds])];
console.log('Updated processed guest IDs:', processedGuestIds.value);
console.log('Updated processed guest IDs from results:', processedGuestIds.value);
}
}
@@ -148,6 +156,7 @@ export function useAuth() {
const wasAuthenticated = isAuthenticated.value;
const previousUserId = userId.value;
const previousAuthType = authType.value;
// Обновляем данные авторизации через updateAuth вместо прямого изменения
updateAuth({
@@ -165,18 +174,34 @@ export function useAuth() {
// Сначала обновляем идентификаторы, чтобы иметь актуальные данные
await updateIdentities();
// Если пользователь только что аутентифицировался или сменил аккаунт,
// Если пользователь только что аутентифицировался или сменил аккаунт,
// связываем гостевые сообщения с его аккаунтом
if (!wasAuthenticated || (previousUserId && previousUserId !== response.data.userId)) {
// Немедленно связываем сообщения
const linkResult = await linkMessages();
console.log('Link messages result on auth change:', linkResult);
// Если пользователь только что аутентифицировался через Telegram,
// обновляем историю чата без перезагрузки страницы
if (response.data.authType === 'telegram' && previousAuthType !== 'telegram') {
console.log('Telegram auth detected, loading message history');
// Отправляем событие для загрузки истории чата
window.dispatchEvent(new CustomEvent('load-chat-history'));
}
}
// Обновляем отображение подключенного состояния в UI
updateConnectionDisplay(true, response.data.authType, response.data);
} else {
// Обновляем отображение отключенного состояния
updateConnectionDisplay(false);
}
return response.data;
} catch (error) {
console.error('Error checking auth:', error);
// В случае ошибки сбрасываем состояние аутентификации
updateConnectionDisplay(false);
return { authenticated: false };
}
};
@@ -189,6 +214,8 @@ export function useAuth() {
console.log('Created new guestId for future session:', newGuestId);
await axios.post('/api/auth/logout');
// Обновляем состояние в памяти
updateAuth({
authenticated: false,
authType: null,
@@ -199,6 +226,9 @@ export function useAuth() {
isAdmin: false
});
// Обновляем отображение отключенного состояния
updateConnectionDisplay(false);
// Очищаем списки идентификаторов
identities.value = [];
@@ -208,6 +238,11 @@ export function useAuth() {
localStorage.removeItem('address');
localStorage.removeItem('isAdmin');
// Удаляем класс подключенного кошелька
document.body.classList.remove('wallet-connected');
console.log('User disconnected successfully');
return { success: true };
} catch (error) {
console.error('Error disconnecting:', error);
@@ -222,6 +257,58 @@ export function useAuth() {
}
};
// Функция для обновления отображения подключения в UI
const updateConnectionDisplay = (isConnected, authType, authData = {}) => {
try {
console.log('Updating connection display:', { isConnected, authType, authData });
if (isConnected) {
document.body.classList.add('wallet-connected');
const authDisplayEl = document.getElementById('auth-display');
if (authDisplayEl) {
let displayText = 'Подключено';
if (authType === 'wallet' && authData.address) {
const shortAddress = `${authData.address.substring(0, 6)}...${authData.address.substring(authData.address.length - 4)}`;
displayText = `Кошелек: <strong>${shortAddress}</strong>`;
} else if (authType === 'email' && authData.email) {
displayText = `Email: <strong>${authData.email}</strong>`;
} else if (authType === 'telegram' && authData.telegramId) {
displayText = `Telegram: <strong>${authData.telegramUsername || authData.telegramId}</strong>`;
}
authDisplayEl.innerHTML = displayText;
authDisplayEl.style.display = 'inline-block';
}
// Скрываем кнопки авторизации и показываем кнопку выхода
const authButtonsEl = document.getElementById('auth-buttons');
const logoutButtonEl = document.getElementById('logout-button');
if (authButtonsEl) authButtonsEl.style.display = 'none';
if (logoutButtonEl) logoutButtonEl.style.display = 'inline-block';
} else {
document.body.classList.remove('wallet-connected');
// Скрываем отображение аутентификации
const authDisplayEl = document.getElementById('auth-display');
if (authDisplayEl) {
authDisplayEl.style.display = 'none';
}
// Показываем кнопки авторизации и скрываем кнопку выхода
const authButtonsEl = document.getElementById('auth-buttons');
const logoutButtonEl = document.getElementById('logout-button');
if (authButtonsEl) authButtonsEl.style.display = 'flex';
if (logoutButtonEl) logoutButtonEl.style.display = 'none';
}
} catch (error) {
console.error('Error updating connection display:', error);
}
};
onMounted(async () => {
await checkAuth();
});
@@ -241,6 +328,7 @@ export function useAuth() {
disconnect,
linkMessages,
updateIdentities,
updateProcessedGuestIds
updateProcessedGuestIds,
updateConnectionDisplay
};
}

View File

@@ -0,0 +1,148 @@
import axios from '../api/axios';
import { ethers } from 'ethers';
import { SiweMessage } from 'siwe';
export const connectWallet = async () => {
try {
console.log('Starting wallet connection...');
// Проверяем наличие MetaMask или другого Ethereum провайдера
if (!window.ethereum) {
console.error('No Ethereum provider (like MetaMask) detected!');
return {
success: false,
error: 'Не найден кошелек MetaMask или другой Ethereum провайдер. Пожалуйста, установите расширение MetaMask.'
};
}
console.log('MetaMask detected, requesting accounts...');
// Запрашиваем доступ к аккаунтам
const accounts = await window.ethereum.request({ method: 'eth_requestAccounts' });
console.log('Got accounts:', accounts);
if (!accounts || accounts.length === 0) {
return {
success: false,
error: 'Не удалось получить доступ к аккаунтам. Пожалуйста, разрешите доступ в MetaMask.'
};
}
// Берем первый аккаунт в списке
const address = accounts[0];
// Нормализуем адрес (приводим к нижнему регистру для последующих сравнений)
const normalizedAddress = ethers.utils.getAddress(address);
console.log('Normalized address:', normalizedAddress);
// Запрашиваем nonce с сервера
console.log('Requesting nonce...');
const nonceResponse = await axios.get(`/api/auth/nonce?address=${normalizedAddress}`);
const nonce = nonceResponse.data.nonce;
console.log('Got nonce:', nonce);
if (!nonce) {
return {
success: false,
error: 'Не удалось получить nonce от сервера.'
};
}
// Создаем провайдер Ethers
const provider = new ethers.providers.Web3Provider(window.ethereum);
const signer = provider.getSigner();
// Создаем сообщение для подписи
const domain = window.location.host;
const origin = window.location.origin;
// Создаем SIWE сообщение
const message = new SiweMessage({
domain,
address: normalizedAddress,
statement: 'Sign in with Ethereum to the app.',
uri: origin,
version: '1',
chainId: 1, // Ethereum mainnet
nonce: nonce,
issuedAt: new Date().toISOString(),
resources: [`${origin}/api/auth/verify`]
});
// Получаем строку сообщения для подписи
const messageToSign = message.prepareMessage();
console.log('SIWE message:', messageToSign);
// Запрашиваем подпись
console.log('Requesting signature...');
const signature = await signer.signMessage(messageToSign);
if (!signature) {
return {
success: false,
error: 'Подпись не была получена. Пожалуйста, подпишите сообщение в MetaMask.'
};
}
console.log('Got signature:', signature);
// Отправляем верификацию на сервер
console.log('Sending verification request...');
const verifyResponse = await axios.post('/api/auth/verify', {
address: normalizedAddress,
signature,
nonce
});
// Обновляем интерфейс для отображения подключенного состояния
document.body.classList.add('wallet-connected');
// Обновляем отображение адреса кошелька в UI
const authDisplayEl = document.getElementById('auth-display');
if (authDisplayEl) {
const shortAddress = `${normalizedAddress.substring(0, 6)}...${normalizedAddress.substring(normalizedAddress.length - 4)}`;
authDisplayEl.innerHTML = `Кошелек: <strong>${shortAddress}</strong>`;
authDisplayEl.style.display = 'inline-block';
}
// Скрываем кнопки авторизации и показываем кнопку выхода
const authButtonsEl = document.getElementById('auth-buttons');
const logoutButtonEl = document.getElementById('logout-button');
if (authButtonsEl) authButtonsEl.style.display = 'none';
if (logoutButtonEl) logoutButtonEl.style.display = 'inline-block';
console.log('Verification response:', verifyResponse.data);
if (verifyResponse.data.success) {
return {
success: true,
address: normalizedAddress,
userId: verifyResponse.data.userId,
isAdmin: verifyResponse.data.isAdmin
};
} else {
return {
success: false,
error: verifyResponse.data.error || 'Ошибка верификации на сервере.'
};
}
} catch (error) {
console.error('Error connecting wallet:', error);
// Формируем понятное сообщение об ошибке
let errorMessage = 'Произошла ошибка при подключении кошелька.';
if (error.code === 4001) {
errorMessage = 'Вы отклонили запрос на подпись в MetaMask.';
} else if (error.response && error.response.data && error.response.data.error) {
errorMessage = error.response.data.error;
} else if (error.message) {
errorMessage = error.message;
}
return {
success: false,
error: errorMessage
};
}
};

File diff suppressed because it is too large Load Diff