ваше сообщение коммита
This commit is contained in:
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
};
|
||||
}
|
||||
148
frontend/src/utils/wallet.js
Normal file
148
frontend/src/utils/wallet.js
Normal 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
Reference in New Issue
Block a user