From 6f92b2acad89fbdf1a23b7d1eef5fdbe400b1094 Mon Sep 17 00:00:00 2001 From: DIKER0K Date: Mon, 7 Jul 2025 00:21:13 +0500 Subject: [PATCH] working authirization --- package-lock.json | 74 ++- package.json | 4 +- src/main/auth-service.ts | 90 ++++ src/main/main.ts | 517 +-------------------- src/main/minecraft-launcher.ts | 739 ++++++++++++++++++++++++++++++ src/renderer/hooks/useAuth.ts | 203 +++----- src/renderer/hooks/useConfig.ts | 39 +- src/renderer/pages/LaunchPage.tsx | 34 +- 8 files changed, 997 insertions(+), 703 deletions(-) create mode 100644 src/main/auth-service.ts create mode 100644 src/main/minecraft-launcher.ts diff --git a/package-lock.json b/package-lock.json index bbde183..32ae3bd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,6 +14,7 @@ "@mui/material": "^7.2.0", "@xmcl/core": "^2.14.1", "@xmcl/installer": "^6.1.0", + "@xmcl/user": "^4.2.0", "electron-debug": "^4.1.0", "electron-log": "^5.3.2", "electron-updater": "^6.3.9", @@ -25,7 +26,8 @@ "react-router-dom": "^7.3.0", "stream-browserify": "^3.0.0", "undici": "^7.11.0", - "util": "^0.12.5" + "util": "^0.12.5", + "uuid": "^11.1.0" }, "devDependencies": { "@electron/rebuild": "^3.7.1", @@ -2806,6 +2808,15 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/@fastify/busboy": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@fastify/busboy/-/busboy-2.1.1.tgz", + "integrity": "sha512-vBZP4NlzfOlerQTnba4aqZoMhE/a9HY7HRqoOPaETQcSQuWEIyZMHGfVu6w9wGtGK5fED5qRs2DteVCjOH60sA==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, "node_modules/@gar/promisify": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", @@ -5972,6 +5983,44 @@ "yauzl": "^2.10.0" } }, + "node_modules/@xmcl/user": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/@xmcl/user/-/user-4.2.0.tgz", + "integrity": "sha512-/+AeKzb1An9OmmIKsRPKcQFYi0bIroXfA/R3VwpjcroPUc0IUXenz2vEBoxiyszu6KJkWT2t6nb/GyqjYEQQow==", + "license": "MIT", + "dependencies": { + "undici": "6.0.1", + "uuid": "^9.0.0" + }, + "engines": { + "node": ">=16.4" + } + }, + "node_modules/@xmcl/user/node_modules/undici": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.0.1.tgz", + "integrity": "sha512-eZFYQLeS9BiXpsU0cuFhCwfeda2MnC48EVmmOz/eCjsTgmyTdaHdVsPSC/kwC2GtW2e0uH0HIPbadf3/bRWSxw==", + "license": "MIT", + "dependencies": { + "@fastify/busboy": "^2.0.0" + }, + "engines": { + "node": ">=18.0" + } + }, + "node_modules/@xmcl/user/node_modules/uuid": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-9.0.1.tgz", + "integrity": "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/@xmldom/xmldom": { "version": "0.8.10", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", @@ -19640,6 +19689,16 @@ "websocket-driver": "^0.7.4" } }, + "node_modules/sockjs/node_modules/uuid": { + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", + "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", + "dev": true, + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, "node_modules/socks": { "version": "2.8.4", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", @@ -21419,13 +21478,16 @@ } }, "node_modules/uuid": { - "version": "8.3.2", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", - "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true, + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz", + "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], "license": "MIT", "bin": { - "uuid": "dist/bin/uuid" + "uuid": "dist/esm/bin/uuid" } }, "node_modules/v8-compile-cache-lib": { diff --git a/package.json b/package.json index 44822e2..c746388 100644 --- a/package.json +++ b/package.json @@ -107,6 +107,7 @@ "@mui/material": "^7.2.0", "@xmcl/core": "^2.14.1", "@xmcl/installer": "^6.1.0", + "@xmcl/user": "^4.2.0", "electron-debug": "^4.1.0", "electron-log": "^5.3.2", "electron-updater": "^6.3.9", @@ -118,7 +119,8 @@ "react-router-dom": "^7.3.0", "stream-browserify": "^3.0.0", "undici": "^7.11.0", - "util": "^0.12.5" + "util": "^0.12.5", + "uuid": "^11.1.0" }, "devDependencies": { "@electron/rebuild": "^3.7.1", diff --git a/src/main/auth-service.ts b/src/main/auth-service.ts new file mode 100644 index 0000000..1b237b7 --- /dev/null +++ b/src/main/auth-service.ts @@ -0,0 +1,90 @@ +import { YggdrasilClient, YggrasilAuthentication } from '@xmcl/user'; +import { v4 as uuidv4 } from 'uuid'; + +// Ely.by сервер +const ELY_BY_AUTH_SERVER = 'https://authserver.ely.by'; + +export class AuthService { + private client: YggdrasilClient; + + constructor() { + this.client = new YggdrasilClient(ELY_BY_AUTH_SERVER); + } + + async login( + username: string, + password: string, + ): Promise { + try { + // Генерируем уникальный clientToken + const clientToken = uuidv4(); + + // Выполняем запрос напрямую к правильному URL + const response = await fetch(`${ELY_BY_AUTH_SERVER}/auth/authenticate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username, + password, + clientToken, + requestUser: true, + }), + }); + + if (!response.ok) { + const errorText = await response.text(); + throw new Error(`Ошибка авторизации: ${response.status} ${errorText}`); + } + + const auth = await response.json(); + console.log(`Аутентификация успешна для ${auth.selectedProfile?.name}`); + return auth; + } catch (error) { + console.error('Ошибка при авторизации:', error); + throw error; + } + } + + async validate(accessToken: string, clientToken: string): Promise { + try { + const response = await fetch(`${ELY_BY_AUTH_SERVER}/auth/validate`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ accessToken, clientToken }), + }); + + return response.ok; + } catch (error) { + console.error('Ошибка при валидации токена:', error); + return false; + } + } + + async refresh( + accessToken: string, + clientToken: string, + ): Promise { + try { + const response = await fetch(`${ELY_BY_AUTH_SERVER}/auth/refresh`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ accessToken, clientToken }), + }); + + if (!response.ok) { + return null; + } + + return await response.json(); + } catch (error) { + console.error('Ошибка при обновлении токена:', error); + return null; + } + } +} diff --git a/src/main/main.ts b/src/main/main.ts index 4e954b6..e99fdd5 100644 --- a/src/main/main.ts +++ b/src/main/main.ts @@ -14,121 +14,7 @@ import { autoUpdater } from 'electron-updater'; import log from 'electron-log'; import MenuBuilder from './menu'; import { resolveHtmlPath } from './util'; -import fs from 'fs'; -import https from 'https'; -import extract from 'extract-zip'; -import { launch, Version, diagnose } from '@xmcl/core'; -import { execSync } from 'child_process'; -import { - installDependencies, - installFabric, - getFabricLoaders, - getVersionList, - install, - installTask, - installDependenciesTask, -} from '@xmcl/installer'; -import { Task } from '@xmcl/task'; -// import findJavaHome from 'find-java-home'; - -// Функция для поиска Java -async function findJava(): Promise { - return new Promise((resolve, reject) => { - try { - // 1. Сначала проверяем переменную JAVA_HOME - if (process.env.JAVA_HOME) { - const javaPath = path.join( - process.env.JAVA_HOME, - 'bin', - 'java' + (process.platform === 'win32' ? '.exe' : ''), - ); - if (fs.existsSync(javaPath)) { - return resolve(javaPath); - } - } - - // 2. Проверяем стандартные пути установки в зависимости от платформы - const checkPaths: string[] = []; - - if (process.platform === 'win32') { - // Windows - const programFiles = process.env['ProgramFiles'] || 'C:\\Program Files'; - const programFilesX86 = - process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)'; - - // JDK пути - [ - 'Java', - 'AdoptOpenJDK', - 'Eclipse Adoptium', - 'BellSoft', - 'Zulu', - 'Amazon Corretto', - ].forEach((vendor) => { - checkPaths.push(path.join(programFiles, vendor)); - checkPaths.push(path.join(programFilesX86, vendor)); - }); - } else if (process.platform === 'darwin') { - // macOS - checkPaths.push('/Library/Java/JavaVirtualMachines'); - checkPaths.push('/System/Library/Java/JavaVirtualMachines'); - checkPaths.push('/usr/libexec/java_home'); - } else { - // Linux - checkPaths.push('/usr/lib/jvm'); - checkPaths.push('/usr/java'); - checkPaths.push('/opt/java'); - } - - // Проверяем каждый путь - for (const basePath of checkPaths) { - if (fs.existsSync(basePath)) { - try { - // Находим подпапки с JDK/JRE - const entries = fs.readdirSync(basePath, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory()) { - // Проверяем наличие исполняемого файла java в bin - const javaPath = path.join( - basePath, - entry.name, - 'bin', - 'java' + (process.platform === 'win32' ? '.exe' : ''), - ); - - if (fs.existsSync(javaPath)) { - return resolve(javaPath); - } - } - } - } catch (err) { - console.error(`Ошибка при сканировании ${basePath}:`, err); - } - } - } - - // 3. Пробуем найти java в PATH через команду which/where - try { - const command = - process.platform === 'win32' ? 'where java' : 'which java'; - const javaPath = execSync(command).toString().trim(); - - if (javaPath && fs.existsSync(javaPath)) { - return resolve(javaPath); - } - } catch (err) { - console.error('Ошибка при поиске java через PATH:', err); - } - - // Если Java не найдена, выдаем ошибку - reject( - new Error('Java не найдена. Установите Java и повторите попытку.'), - ); - } catch (error) { - reject(error); - } - }); -} +import { initMinecraftHandlers, initAuthHandlers } from './minecraft-launcher'; class AppUpdater { constructor() { @@ -158,402 +44,8 @@ if (isDebug) { require('electron-debug').default(); } -// Minecraft - -const COMFORT_DOWNLOAD_URL = - 'https://github.com/DIKER0K/Comfort/releases/latest/download/Comfort.zip'; -const GITHUB_API_RELEASE_URL = - 'https://api.github.com/repos/DIKER0K/Comfort/releases/latest'; -const COMFORT_VERSION_FILE = 'comfort_version.txt'; - -async function getLatestReleaseVersion(): Promise { - try { - const response = await fetch(GITHUB_API_RELEASE_URL); - const data = await response.json(); - return data.tag_name || '0.0.0'; - } catch (error) { - console.error('Failed to fetch latest version:', error); - return '0.0.0'; - } -} - -// Модифицируем обработчик IPC -ipcMain.handle('download-and-extract', async (event) => { - try { - const appPath = path.dirname(app.getPath('exe')); - const minecraftDir = path.join(appPath, '.minecraft'); - const versionsDir = path.join(minecraftDir, 'versions'); - const versionFilePath = path.join(minecraftDir, COMFORT_VERSION_FILE); - - // Получаем текущую и последнюю версии - const latestVersion = await getLatestReleaseVersion(); - let currentVersion = ''; - - // Проверяем текущую версию, если файл существует - if (fs.existsSync(versionFilePath)) { - currentVersion = fs.readFileSync(versionFilePath, 'utf-8').trim(); - } - - // Проверяем, нужно ли обновление - if (currentVersion === latestVersion) { - return { success: true, updated: false, version: currentVersion }; - } - - const tempDir = path.join(appPath, 'temp'); - - // Создаем/очищаем временную директорию - if (fs.existsSync(tempDir)) { - fs.rmSync(tempDir, { recursive: true }); - } - fs.mkdirSync(tempDir, { recursive: true }); - - const zipPath = path.join(tempDir, 'Comfort.zip'); - - // Скачиваем файл - await downloadFile(COMFORT_DOWNLOAD_URL, zipPath, (progress) => { - event.sender.send('download-progress', progress); - }); - - // Проверяем архив - const fileStats = fs.statSync(zipPath); - if (fileStats.size < 1024) { - throw new Error('Downloaded file is too small, likely corrupted'); - } - - // Создаем папку versions если её нет - if (!fs.existsSync(versionsDir)) { - fs.mkdirSync(versionsDir, { recursive: true }); - } - - // Распаковываем архив напрямую в папку versions - await extract(zipPath, { dir: versionsDir }); - fs.unlinkSync(zipPath); - - // Сохраняем новую версию - fs.writeFileSync(versionFilePath, latestVersion); - - // Удаляем временную директорию - fs.rmSync(tempDir, { recursive: true }); - - // После распаковки архива и перед запуском - const versionsContents = fs.readdirSync(versionsDir); - console.log('Доступные версии:', versionsContents); - - return { success: true, updated: true, version: latestVersion }; - } catch (error) { - console.error('Error in download-and-extract:', error); - throw error; - } -}); - -async function downloadFile( - url: string, - dest: string, - progressCallback: (progress: number) => void, -): Promise { - return new Promise((resolve, reject) => { - const file = fs.createWriteStream(dest); - let downloadedSize = 0; - let totalSize = 0; - let redirectCount = 0; - - const makeRequest = (requestUrl: string) => { - https - .get(requestUrl, (response) => { - // Обрабатываем редиректы - if (response.statusCode === 301 || response.statusCode === 302) { - if (redirectCount++ > 5) { - reject(new Error('Too many redirects')); - return; - } - makeRequest(response.headers.location!); - return; - } - - if (response.statusCode !== 200) { - reject(new Error(`Server returned ${response.statusCode}`)); - return; - } - - totalSize = parseInt(response.headers['content-length'] || '0', 10); - - response.on('data', (chunk) => { - downloadedSize += chunk.length; - const progress = Math.round((downloadedSize / totalSize) * 100); - progressCallback(progress); - }); - - response.pipe(file); - - file.on('finish', () => { - file.close(); - // Проверяем, что файл скачан полностью - if (downloadedSize !== totalSize && totalSize > 0) { - fs.unlink(dest, () => { - reject(new Error('File download incomplete')); - }); - return; - } - resolve(); - }); - }) - .on('error', (err) => { - fs.unlink(dest, () => reject(err)); - }); - }; - - makeRequest(url); - }); -} - -ipcMain.handle('launch-minecraft', async (event) => { - try { - const baseVersion = '1.21.4'; - const appPath = path.dirname(app.getPath('exe')); - const minecraftDir = path.join(appPath, '.minecraft'); - const versionsDir = path.join(minecraftDir, 'versions'); - - // Определяем версию для запуска - const versionsContents = fs.existsSync(versionsDir) - ? fs.readdirSync(versionsDir) - : []; - console.log('Доступные версии:', versionsContents); - - // Найти версию Comfort или версию с Fabric - let versionToLaunch = ''; - if (versionsContents.includes('1.21.4-fabric0.16.14')) { - versionToLaunch = '1.21.4-fabric0.16.14'; - } else if (versionsContents.includes('Comfort')) { - versionToLaunch = 'Comfort'; - } else { - versionToLaunch = '1.21.4'; - } - - console.log('Запускаем версию:', versionToLaunch); - - // Находим путь к Java - event.sender.send('installation-status', { - step: 'java', - message: 'Поиск Java...', - }); - - let javaPath; - try { - javaPath = await findJava(); - } catch (error) { - console.warn('Ошибка при поиске Java:', error); - event.sender.send('installation-status', { - step: 'java-error', - message: 'Не удалось найти Java. Используем системную Java.', - }); - javaPath = 'java'; // Пробуем использовать системную Java - } - - // Далее пробуем установить Minecraft, но продолжаем даже при ошибках - let resolvedVersion; - try { - // 1. Получаем список версий и устанавливаем ванильный Minecraft - event.sender.send('installation-status', { - step: 'minecraft-list', - message: 'Получение списка версий Minecraft...', - }); - - const versionList = await getVersionList(); - const minecraftVersion = versionList.versions.find( - (v) => v.id === baseVersion, - ); - - if (minecraftVersion) { - // Устанавливаем базовую версию Minecraft - event.sender.send('installation-status', { - step: 'minecraft-install', - message: `Установка Minecraft ${baseVersion}...`, - }); - - try { - const installMcTask = installTask(minecraftVersion, minecraftDir, { - skipRevalidate: true, - }); - - await installMcTask.startAndWait({ - onStart(task) { - event.sender.send('installation-status', { - step: `minecraft-install.${task.path}`, - message: `Начало: ${task.name || task.path}`, - }); - }, - onUpdate(task) { - const percentage = - Math.round( - (installMcTask.progress / installMcTask.total) * 100, - ) || 0; - - event.sender.send('download-progress', percentage); - event.sender.send('installation-status', { - step: `minecraft-install.${task.path}`, - message: `Прогресс ${task.name || task.path}: ${percentage}% (${installMcTask.progress}/${installMcTask.total})`, - }); - }, - onFailed(task, error) { - console.warn( - `Ошибка при установке ${task.path}, продолжаем:`, - error, - ); - event.sender.send('installation-status', { - step: `minecraft-install.${task.path}`, - message: `Ошибка: ${error.message}`, - }); - }, - onSucceed(task) { - event.sender.send('installation-status', { - step: `minecraft-install.${task.path}`, - message: `Завершено: ${task.name || task.path}`, - }); - }, - }); - } catch (error) { - console.warn('Ошибка при установке Minecraft, продолжаем:', error); - } - - // 2. Устанавливаем Fabric - try { - event.sender.send('installation-status', { - step: 'fabric-list', - message: 'Получение списка версий Fabric...', - }); - - const fabricVersions = await getFabricLoaders(); - const fabricVersion = fabricVersions[0]; // Последняя версия - - if (fabricVersion) { - event.sender.send('installation-status', { - step: 'fabric-install', - message: `Установка Fabric ${fabricVersion.version}...`, - }); - - await installFabric({ - minecraftVersion: baseVersion, - version: fabricVersion.version, - minecraft: minecraftDir, - }); - } - } catch (error) { - console.warn('Ошибка при установке Fabric, продолжаем:', error); - } - - // 3. Подготовка версии и установка зависимостей - try { - // Используем идентификатор Fabric-версии - const fabricVersionId = `${baseVersion}-fabric0.16.14`; - - event.sender.send('installation-status', { - step: 'version-parse', - message: 'Подготовка версии...', - }); - - resolvedVersion = await Version.parse(minecraftDir, fabricVersionId); - - event.sender.send('installation-status', { - step: 'dependencies', - message: 'Установка библиотек и ресурсов...', - }); - - const depsTask = installDependenciesTask(resolvedVersion, { - assetsDownloadConcurrency: 4, - skipRevalidate: true, - prevalidSizeOnly: true, - checksumValidatorResolver: (checksum) => ({ - validate: async () => { - /* void */ - }, - }), - }); - - try { - await depsTask.startAndWait({ - onStart(task) { - event.sender.send('installation-status', { - step: `dependencies.${task.path}`, - message: `Начало: ${task.name || task.path}`, - }); - }, - onUpdate(task) { - const percentage = - Math.round((depsTask.progress / depsTask.total) * 100) || 0; - - event.sender.send('download-progress', percentage); - event.sender.send('installation-status', { - step: `dependencies.${task.path}`, - message: `Установка ${task.name || task.path}: ${percentage}%`, - }); - }, - onFailed(task, error) { - console.warn( - `Ошибка при установке ${task.path}, продолжаем:`, - error, - ); - event.sender.send('installation-status', { - step: `dependencies.${task.path}`, - message: `Ошибка: ${error.message}`, - }); - }, - onSucceed(task) { - event.sender.send('installation-status', { - step: `dependencies.${task.path}`, - message: `Завершено: ${task.name || task.path}`, - }); - }, - }); - } catch (error) { - console.warn( - 'Ошибка при загрузке ресурсов, продолжаем запуск:', - error, - ); - } - } catch (error) { - console.warn('Ошибка при подготовке версии, продолжаем:', error); - } - } - } catch (error) { - console.warn('Произошла ошибка при подготовке Minecraft:', error); - } - - // 5. Запускаем Minecraft - НЕЗАВИСИМО ОТ ПРЕДЫДУЩИХ ОШИБОК - event.sender.send('installation-status', { - step: 'launch', - message: 'Запуск игры...', - }); - - const comfortDir = path.join(versionsDir, 'Comfort'); - const proc = await launch({ - gamePath: comfortDir, - resourcePath: minecraftDir, - javaPath, - version: '1.21.4-fabric0.16.14', - extraJVMArgs: ['-Dlog4j2.formatMsgNoLookups=true', '-Xmx2G'], // Добавляем больше памяти - }); - - // Логирование - proc.stdout?.on('data', (data) => { - console.log(`Minecraft stdout: ${data}`); - }); - proc.stderr?.on('data', (data) => { - console.error(`Minecraft stderr: ${data}`); - }); - - return { success: true, pid: proc.pid }; - } catch (error) { - // Даже если произошла ошибка при запуске, возвращаем успех - // чтобы интерфейс считал, что запуск выполнен - console.error('Ошибка при запуске Minecraft:', error); - event.sender.send('installation-status', { - step: 'error', - message: `Ошибка запуска: ${error.message}`, - }); - - return { success: false, error: error.message }; - } -}); +// Инициализация обработчиков Minecraft +initMinecraftHandlers(); const installExtensions = async () => { const installer = require('electron-devtools-installer'); @@ -623,6 +115,9 @@ const createWindow = async () => { // Remove this if your app does not use auto updates // eslint-disable-next-line new AppUpdater(); + + initAuthHandlers(); + initMinecraftHandlers(); }; /** diff --git a/src/main/minecraft-launcher.ts b/src/main/minecraft-launcher.ts new file mode 100644 index 0000000..091265b --- /dev/null +++ b/src/main/minecraft-launcher.ts @@ -0,0 +1,739 @@ +import path from 'path'; +import { app, ipcMain } from 'electron'; +import fs from 'fs'; +import https from 'https'; +import extract from 'extract-zip'; +import { launch, Version, diagnose } from '@xmcl/core'; +import { execSync } from 'child_process'; +import { + installDependencies, + installFabric, + getFabricLoaders, + getVersionList, + install, + installTask, + installDependenciesTask, +} from '@xmcl/installer'; +import { spawn } from 'child_process'; +import { AuthService } from './auth-service'; + +// Константы +const COMFORT_DOWNLOAD_URL = + 'https://github.com/DIKER0K/Comfort/releases/latest/download/Comfort.zip'; +const GITHUB_API_RELEASE_URL = + 'https://api.github.com/repos/DIKER0K/Comfort/releases/latest'; +const COMFORT_VERSION_FILE = 'comfort_version.txt'; +const AUTHLIB_INJECTOR_FILENAME = 'authlib-injector-1.2.5.jar'; + +// Создаем экземпляр сервиса аутентификации +const authService = new AuthService(); + +// Функция для получения последней версии релиза +export async function getLatestReleaseVersion(): Promise { + try { + const response = await fetch(GITHUB_API_RELEASE_URL); + const data = await response.json(); + return data.tag_name || '0.0.0'; + } catch (error) { + console.error('Failed to fetch latest version:', error); + return '0.0.0'; + } +} + +// Функция для загрузки файла +export async function downloadFile( + url: string, + dest: string, + progressCallback: (progress: number) => void, +): Promise { + return new Promise((resolve, reject) => { + const file = fs.createWriteStream(dest); + let downloadedSize = 0; + let totalSize = 0; + let redirectCount = 0; + + const makeRequest = (requestUrl: string) => { + https + .get(requestUrl, (response) => { + // Обрабатываем редиректы + if (response.statusCode === 301 || response.statusCode === 302) { + if (redirectCount++ > 5) { + reject(new Error('Too many redirects')); + return; + } + makeRequest(response.headers.location!); + return; + } + + if (response.statusCode !== 200) { + reject(new Error(`Server returned ${response.statusCode}`)); + return; + } + + totalSize = parseInt(response.headers['content-length'] || '0', 10); + + response.on('data', (chunk) => { + downloadedSize += chunk.length; + const progress = Math.round((downloadedSize / totalSize) * 100); + progressCallback(progress); + }); + + response.pipe(file); + + file.on('finish', () => { + file.close(); + // Проверяем, что файл скачан полностью + if (downloadedSize !== totalSize && totalSize > 0) { + fs.unlink(dest, () => { + reject(new Error('File download incomplete')); + }); + return; + } + resolve(); + }); + }) + .on('error', (err) => { + fs.unlink(dest, () => reject(err)); + }); + }; + + makeRequest(url); + }); +} + +// Добавим функцию для определения версии Java +export async function getJavaVersion( + javaPath: string, +): Promise<{ version: string; majorVersion: number }> { + return new Promise((resolve, reject) => { + const process = spawn(javaPath, ['-version']); + let output = ''; + + process.stderr.on('data', (data) => { + output += data.toString(); + }); + + process.on('close', (code) => { + if (code === 0) { + // Извлекаем версию из вывода (например, "java version "1.8.0_291"") + const versionMatch = output.match(/version "([^"]+)"/); + if (versionMatch) { + const version = versionMatch[1]; + // Определяем major версию + let majorVersion = 8; // По умолчанию предполагаем Java 8 + + if (version.startsWith('1.8')) { + majorVersion = 8; + } else if (version.match(/^(9|10|11|12|13|14|15|16)/)) { + majorVersion = parseInt(version.split('.')[0], 10); + } else if (version.match(/^1\.(9|10|11|12|13|14|15|16)/)) { + majorVersion = parseInt(version.split('.')[1], 10); + } else if (version.match(/^([0-9]+)/)) { + majorVersion = parseInt(version.match(/^([0-9]+)/)?.[1] || '0', 10); + } + + resolve({ version, majorVersion }); + } else { + reject(new Error('Unable to parse Java version')); + } + } else { + reject(new Error(`Java process exited with code ${code}`)); + } + }); + }); +} + +// Модифицируем функцию findJava, чтобы она находила все версии Java и определяла их версии +export async function findJavaVersions(): Promise< + Array<{ path: string; version: string; majorVersion: number }> +> { + const javaPaths: string[] = []; + const results: Array<{ + path: string; + version: string; + majorVersion: number; + }> = []; + + try { + // 1. Сначала проверяем переменную JAVA_HOME + if (process.env.JAVA_HOME) { + const javaPath = path.join( + process.env.JAVA_HOME, + 'bin', + 'java' + (process.platform === 'win32' ? '.exe' : ''), + ); + if (fs.existsSync(javaPath)) { + javaPaths.push(javaPath); + } + } + + // 2. Проверяем стандартные пути установки в зависимости от платформы + const checkPaths: string[] = []; + + if (process.platform === 'win32') { + // Windows + const programFiles = process.env['ProgramFiles'] || 'C:\\Program Files'; + const programFilesX86 = + process.env['ProgramFiles(x86)'] || 'C:\\Program Files (x86)'; + + // JDK пути + [ + 'Java', + 'AdoptOpenJDK', + 'Eclipse Adoptium', + 'BellSoft', + 'Zulu', + 'Amazon Corretto', + 'Microsoft', + ].forEach((vendor) => { + checkPaths.push(path.join(programFiles, vendor)); + checkPaths.push(path.join(programFilesX86, vendor)); + }); + } else if (process.platform === 'darwin') { + // macOS + checkPaths.push('/Library/Java/JavaVirtualMachines'); + checkPaths.push('/System/Library/Java/JavaVirtualMachines'); + checkPaths.push('/usr/libexec/java_home'); + } else { + // Linux + checkPaths.push('/usr/lib/jvm'); + checkPaths.push('/usr/java'); + checkPaths.push('/opt/java'); + } + + // Проверяем каждый путь + for (const basePath of checkPaths) { + if (fs.existsSync(basePath)) { + try { + // Находим подпапки с JDK/JRE + const entries = fs.readdirSync(basePath, { withFileTypes: true }); + for (const entry of entries) { + if (entry.isDirectory()) { + // Проверяем наличие исполняемого файла java в bin + const javaPath = path.join( + basePath, + entry.name, + 'bin', + 'java' + (process.platform === 'win32' ? '.exe' : ''), + ); + + if (fs.existsSync(javaPath)) { + javaPaths.push(javaPath); + } + } + } + } catch (err) { + console.error(`Ошибка при сканировании ${basePath}:`, err); + } + } + } + + // 3. Пробуем найти java в PATH через команду which/where + try { + const command = + process.platform === 'win32' ? 'where java' : 'which java'; + const javaPathFromCmd = execSync(command).toString().trim().split('\n'); + + javaPathFromCmd.forEach((path) => { + if (path && fs.existsSync(path) && !javaPaths.includes(path)) { + javaPaths.push(path); + } + }); + } catch (err) { + console.error('Ошибка при поиске java через PATH:', err); + } + + // Получаем информацию о версиях найденных Java + for (const javaPath of javaPaths) { + try { + const versionInfo = await getJavaVersion(javaPath); + results.push({ + path: javaPath, + version: versionInfo.version, + majorVersion: versionInfo.majorVersion, + }); + } catch (err) { + console.error(`Ошибка при определении версии для ${javaPath}:`, err); + } + } + + // Сортируем результаты по majorVersion (от меньшей к большей) + return results.sort((a, b) => a.majorVersion - b.majorVersion); + } catch (error) { + console.error('Ошибка при поиске версий Java:', error); + return results; + } +} + +// Обновим функцию findJava для использования Java 8 +export async function findJava(): Promise { + try { + console.log('Поиск доступных версий Java...'); + + const javaVersions = await findJavaVersions(); + + if (javaVersions.length === 0) { + throw new Error('Java не найдена. Установите Java и повторите попытку.'); + } + + console.log('Найденные версии Java:'); + javaVersions.forEach((java) => { + console.log( + `- Java ${java.majorVersion} (${java.version}) по пути: ${java.path}`, + ); + }); + + // Предпочитаем Java 21 или 17 для совместимости с authlib-injector + const preferredVersions = [21, 17, 11]; + + for (const preferredVersion of preferredVersions) { + const preferred = javaVersions.find( + (java) => java.majorVersion === preferredVersion, + ); + if (preferred) { + console.log( + `Выбрана предпочтительная версия Java ${preferredVersion}: ${preferred.path}`, + ); + return preferred.path; + } + } + + // Если не нашли предпочтительные версии, берем самую старую версию + console.log(`Выбрана доступная версия Java: ${javaVersions[0].path}`); + return javaVersions[0].path; + } catch (error) { + console.error('Ошибка при поиске Java:', error); + throw error; + } +} + +// Добавим функцию для проверки/копирования authlib-injector (как в C#) +async function ensureAuthlibInjectorExists(appPath: string): Promise { + const authlibPath = path.join(appPath, AUTHLIB_INJECTOR_FILENAME); + + // Проверяем, существует ли файл + if (fs.existsSync(authlibPath)) { + console.log( + `Файл ${AUTHLIB_INJECTOR_FILENAME} уже существует: ${authlibPath}`, + ); + return authlibPath; + } + + // Ищем authlib в ресурсах приложения + const resourcePath = path.join(app.getAppPath(), AUTHLIB_INJECTOR_FILENAME); + + if (fs.existsSync(resourcePath)) { + console.log(`Копирование ${AUTHLIB_INJECTOR_FILENAME} из ресурсов...`); + fs.copyFileSync(resourcePath, authlibPath); + console.log(`Файл успешно скопирован: ${authlibPath}`); + return authlibPath; + } + + // Если не нашли локальный файл - скачиваем его + console.log(`Скачивание ${AUTHLIB_INJECTOR_FILENAME}...`); + await downloadFile( + `https://github.com/yushijinhun/authlib-injector/releases/download/v1.2.5/${AUTHLIB_INJECTOR_FILENAME}`, + authlibPath, + (progress) => { + console.log(`Прогресс скачивания: ${progress}%`); + }, + ); + + return authlibPath; +} + +// Инициализация IPC обработчиков +export function initMinecraftHandlers() { + // Обработчик для скачивания и распаковки + ipcMain.handle('download-and-extract', async (event) => { + try { + const appPath = path.dirname(app.getPath('exe')); + const minecraftDir = path.join(appPath, '.minecraft'); + const versionsDir = path.join(minecraftDir, 'versions'); + const versionFilePath = path.join(minecraftDir, COMFORT_VERSION_FILE); + + // Получаем текущую и последнюю версии + const latestVersion = await getLatestReleaseVersion(); + let currentVersion = ''; + + // Проверяем текущую версию, если файл существует + if (fs.existsSync(versionFilePath)) { + currentVersion = fs.readFileSync(versionFilePath, 'utf-8').trim(); + } + + // Проверяем, нужно ли обновление + if (currentVersion === latestVersion) { + return { success: true, updated: false, version: currentVersion }; + } + + const tempDir = path.join(appPath, 'temp'); + + // Создаем/очищаем временную директорию + if (fs.existsSync(tempDir)) { + fs.rmSync(tempDir, { recursive: true }); + } + fs.mkdirSync(tempDir, { recursive: true }); + + const zipPath = path.join(tempDir, 'Comfort.zip'); + + // Скачиваем файл + await downloadFile(COMFORT_DOWNLOAD_URL, zipPath, (progress) => { + event.sender.send('download-progress', progress); + }); + + // Проверяем архив + const fileStats = fs.statSync(zipPath); + if (fileStats.size < 1024) { + throw new Error('Downloaded file is too small, likely corrupted'); + } + + // Создаем папку versions если её нет + if (!fs.existsSync(versionsDir)) { + fs.mkdirSync(versionsDir, { recursive: true }); + } + + // Распаковываем архив напрямую в папку versions + await extract(zipPath, { dir: versionsDir }); + fs.unlinkSync(zipPath); + + // Сохраняем новую версию + fs.writeFileSync(versionFilePath, latestVersion); + + // Удаляем временную директорию + fs.rmSync(tempDir, { recursive: true }); + + // После распаковки архива и перед запуском + const versionsContents = fs.readdirSync(versionsDir); + console.log('Доступные версии:', versionsContents); + + return { success: true, updated: true, version: latestVersion }; + } catch (error) { + console.error('Error in download-and-extract:', error); + throw error; + } + }); + + // Обработчик для запуска Minecraft + ipcMain.handle('launch-minecraft', async (event, gameConfig) => { + try { + const baseVersion = '1.21.4'; + const appPath = path.dirname(app.getPath('exe')); + const minecraftDir = path.join(appPath, '.minecraft'); + const versionsDir = path.join(minecraftDir, 'versions'); + + // Определяем версию для запуска + const versionsContents = fs.existsSync(versionsDir) + ? fs.readdirSync(versionsDir) + : []; + console.log('Доступные версии:', versionsContents); + + // Найти версию Comfort или версию с Fabric + let versionToLaunch = ''; + if (versionsContents.includes('1.21.4-fabric0.16.14')) { + versionToLaunch = '1.21.4-fabric0.16.14'; + } else if (versionsContents.includes('Comfort')) { + versionToLaunch = 'Comfort'; + } else { + versionToLaunch = '1.21.4'; + } + + console.log('Запускаем версию:', versionToLaunch); + + // Находим путь к Java + event.sender.send('installation-status', { + step: 'java', + message: 'Поиск Java...', + }); + + let javaPath; + try { + javaPath = await findJava(); + } catch (error) { + console.warn('Ошибка при поиске Java:', error); + event.sender.send('installation-status', { + step: 'java-error', + message: 'Не удалось найти Java. Используем системную Java.', + }); + javaPath = 'java'; + } + + // Далее пробуем установить Minecraft, но продолжаем даже при ошибках + let resolvedVersion; + try { + // 1. Получаем список версий и устанавливаем ванильный Minecraft + event.sender.send('installation-status', { + step: 'minecraft-list', + message: 'Получение списка версий Minecraft...', + }); + + const versionList = await getVersionList(); + const minecraftVersion = versionList.versions.find( + (v) => v.id === baseVersion, + ); + + if (minecraftVersion) { + // Устанавливаем базовую версию Minecraft + event.sender.send('installation-status', { + step: 'minecraft-install', + message: `Установка Minecraft ${baseVersion}...`, + }); + + try { + const installMcTask = installTask(minecraftVersion, minecraftDir, { + skipRevalidate: true, + }); + + await installMcTask.startAndWait({ + onStart(task) { + event.sender.send('installation-status', { + step: `minecraft-install.${task.path}`, + message: `Начало: ${task.name || task.path}`, + }); + }, + onUpdate(task) { + const percentage = + Math.round( + (installMcTask.progress / installMcTask.total) * 100, + ) || 0; + + event.sender.send('download-progress', percentage); + event.sender.send('installation-status', { + step: `minecraft-install.${task.path}`, + message: `Прогресс ${task.name || task.path}: ${percentage}% (${installMcTask.progress}/${installMcTask.total})`, + }); + }, + onFailed(task, error) { + console.warn( + `Ошибка при установке ${task.path}, продолжаем:`, + error, + ); + event.sender.send('installation-status', { + step: `minecraft-install.${task.path}`, + message: `Ошибка: ${error.message}`, + }); + }, + onSucceed(task) { + event.sender.send('installation-status', { + step: `minecraft-install.${task.path}`, + message: `Завершено: ${task.name || task.path}`, + }); + }, + }); + } catch (error) { + console.warn('Ошибка при установке Minecraft, продолжаем:', error); + } + + // 2. Устанавливаем Fabric + try { + event.sender.send('installation-status', { + step: 'fabric-list', + message: 'Получение списка версий Fabric...', + }); + + const fabricVersions = await getFabricLoaders(); + const fabricVersion = fabricVersions[0]; // Последняя версия + + if (fabricVersion) { + event.sender.send('installation-status', { + step: 'fabric-install', + message: `Установка Fabric ${fabricVersion.version}...`, + }); + + await installFabric({ + minecraftVersion: baseVersion, + version: fabricVersion.version, + minecraft: minecraftDir, + }); + } + } catch (error) { + console.warn('Ошибка при установке Fabric, продолжаем:', error); + } + + // 3. Подготовка версии и установка зависимостей + try { + // Используем идентификатор Fabric-версии + const fabricVersionId = `${baseVersion}-fabric0.16.14`; + + event.sender.send('installation-status', { + step: 'version-parse', + message: 'Подготовка версии...', + }); + + resolvedVersion = await Version.parse( + minecraftDir, + fabricVersionId, + ); + + event.sender.send('installation-status', { + step: 'dependencies', + message: 'Установка библиотек и ресурсов...', + }); + + const depsTask = installDependenciesTask(resolvedVersion, { + assetsDownloadConcurrency: 4, + skipRevalidate: true, + prevalidSizeOnly: true, + checksumValidatorResolver: (checksum) => ({ + validate: async () => { + /* void */ + }, + }), + }); + + try { + await depsTask.startAndWait({ + onStart(task) { + event.sender.send('installation-status', { + step: `dependencies.${task.path}`, + message: `Начало: ${task.name || task.path}`, + }); + }, + onUpdate(task) { + const percentage = + Math.round((depsTask.progress / depsTask.total) * 100) || 0; + + event.sender.send('download-progress', percentage); + event.sender.send('installation-status', { + step: `dependencies.${task.path}`, + message: `Установка ${task.name || task.path}: ${percentage}%`, + }); + }, + onFailed(task, error) { + console.warn( + `Ошибка при установке ${task.path}, продолжаем:`, + error, + ); + event.sender.send('installation-status', { + step: `dependencies.${task.path}`, + message: `Ошибка: ${error.message}`, + }); + }, + onSucceed(task) { + event.sender.send('installation-status', { + step: `dependencies.${task.path}`, + message: `Завершено: ${task.name || task.path}`, + }); + }, + }); + } catch (error) { + console.warn( + 'Ошибка при загрузке ресурсов, продолжаем запуск:', + error, + ); + } + } catch (error) { + console.warn('Ошибка при подготовке версии, продолжаем:', error); + } + } + } catch (error) { + console.warn('Произошла ошибка при подготовке Minecraft:', error); + } + + // Загрузка и проверка authlib-injector + const authlibPath = await ensureAuthlibInjectorExists(appPath); + event.sender.send('installation-status', { + step: 'authlib-injector', + message: 'authlib-injector готов', + }); + + // Запускаем Minecraft с authlib-injector для Ely.by + event.sender.send('installation-status', { + step: 'launch', + message: 'Запуск игры...', + }); + + const comfortDir = path.join(versionsDir, 'Comfort'); + const proc = await launch({ + gamePath: comfortDir, + resourcePath: minecraftDir, + javaPath, + version: versionToLaunch, + extraJVMArgs: [ + '-Dlog4j2.formatMsgNoLookups=true', + `-javaagent:${authlibPath}=ely.by`, + `-Xmx${gameConfig.memory || 2048}M`, + ], + // Используем данные аутентификации Yggdrasil + accessToken: gameConfig.accessToken, + gameProfile: { + id: gameConfig.uuid, + name: gameConfig.username, + }, + }); + + // Логирование + proc.stdout?.on('data', (data) => { + console.log(`Minecraft stdout: ${data}`); + }); + proc.stderr?.on('data', (data) => { + console.error(`Minecraft stderr: ${data}`); + }); + + return { success: true, pid: proc.pid }; + } catch (error) { + console.error('Ошибка при запуске Minecraft:', error); + event.sender.send('installation-status', { + step: 'error', + message: `Ошибка запуска: ${error.message}`, + }); + + return { success: false, error: error.message }; + } + }); +} + +// Добавляем обработчики IPC для аутентификации +export function initAuthHandlers() { + // Аутентификация + ipcMain.handle('authenticate', async (event, { username, password }) => { + try { + const auth = await authService.login(username, password); + return { + accessToken: auth.accessToken, + clientToken: auth.clientToken, + selectedProfile: auth.selectedProfile, + }; + } catch (error) { + console.error('Ошибка аутентификации:', error); + throw error; + } + }); + + // Валидация токена + ipcMain.handle('validate-token', async (event, accessToken) => { + try { + const clientToken = JSON.parse( + fs.readFileSync( + path.join(app.getPath('userData'), 'config.json'), + 'utf8', + ), + ).clientToken; + + const valid = await authService.validate(accessToken, clientToken); + return { valid }; + } catch (error) { + console.error('Ошибка валидации токена:', error); + return { valid: false }; + } + }); + + // Обновление токена + ipcMain.handle( + 'refresh-token', + async (event, { accessToken, clientToken }) => { + try { + const auth = await authService.refresh(accessToken, clientToken); + if (!auth) return null; + + return { + accessToken: auth.accessToken, + clientToken: auth.clientToken, + selectedProfile: auth.selectedProfile, + }; + } catch (error) { + console.error('Ошибка обновления токена:', error); + return null; + } + }, + ); +} diff --git a/src/renderer/hooks/useAuth.ts b/src/renderer/hooks/useAuth.ts index f8fbfb1..503b50f 100644 --- a/src/renderer/hooks/useAuth.ts +++ b/src/renderer/hooks/useAuth.ts @@ -1,177 +1,94 @@ import { useState } from 'react'; -const useAuth = () => { - const [status, setStatus] = useState(''); +interface AuthSession { + accessToken: string; + clientToken: string; + selectedProfile: { + id: string; + name: string; + }; +} - const validateSession = async (accessToken: string) => { +export default function useAuth() { + const [status, setStatus] = useState('idle'); + + // Проверка валидности токена + const validateSession = async (accessToken: string): Promise => { try { - const response = await fetch('https://authserver.ely.by/auth/validate', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - accessToken: accessToken, - }), - }); - return response.ok; + setStatus('validating'); + const response = await window.electron.ipcRenderer.invoke( + 'validate-token', + accessToken, + ); + setStatus('idle'); + return response.valid; } catch (error) { - console.log(`Ошибка при проверке токена: ${error.message}`); + console.error('Ошибка при валидации токена:', error); + setStatus('error'); return false; } }; - const refreshSession = async (accessToken: string, clientToken: string) => { + // Обновление токена + const refreshSession = async ( + accessToken: string, + clientToken: string, + ): Promise => { try { - const refreshData = { - accessToken: accessToken, - clientToken: clientToken, - }; - - const response = await fetch('https://authserver.ely.by/auth/refresh', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(refreshData), - }); - - if (response.ok) { - const data = await response.json(); - const newAccessToken = data.accessToken; - const profile = data.selectedProfile; - const uuid = profile.id; - const name = profile.name; - - if (newAccessToken && uuid && name) { - return { - accessToken: newAccessToken, - uuid: uuid, - username: name, - clientToken: clientToken, - }; - } - } - return null; + setStatus('refreshing'); + const response = await window.electron.ipcRenderer.invoke( + 'refresh-token', + { accessToken, clientToken }, + ); + setStatus('idle'); + return response; } catch (error) { - console.log(`Ошибка при обновлении сессии: ${error.message}`); + console.error('Ошибка при обновлении токена:', error); + setStatus('error'); return null; } }; + // Аутентификация в Ely.by const authenticateWithElyBy = async ( username: string, password: string, - saveConfig: Function, - ) => { + saveConfigFunc: Function, + ): Promise => { try { - const clientToken = crypto.randomUUID(); - const authData = { - username: username, - password: password, - clientToken: clientToken, - requestUser: true, - }; - - console.log(`Аутентификация пользователя ${username} на Ely.by...`); - - const response = await fetch( - 'https://authserver.ely.by/auth/authenticate', - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(authData), - }, + setStatus('authenticating'); + const response = await window.electron.ipcRenderer.invoke( + 'authenticate', + { username, password }, ); - const responseData = await response.json(); - if (response.ok) { - const accessToken = responseData.accessToken; - const profile = responseData.selectedProfile; - const uuid = profile.id; - const name = profile.name; + if (response && response.accessToken) { + // Правильно сохраняем данные в конфигурации + saveConfigFunc({ + username: response.selectedProfile.name, // Имя игрока как строка + uuid: response.selectedProfile.id, + accessToken: response.accessToken, + clientToken: response.clientToken, + memory: 4096, // Сохраняем значение по умолчанию или из предыдущей конфигурации + }); - if (accessToken && uuid && name) { - saveConfig( - username, - 4096, // default memory - accessToken, - clientToken, - '', - password, - ); - console.log(`Аутентификация успешна: UUID=${uuid}, Username=${name}`); - - return { - accessToken: accessToken, - uuid: uuid, - username: name, - clientToken: clientToken, - }; - } - } else { - if (responseData.error === 'Account protected with two factor auth') { - const totpToken = prompt( - 'Введите код двухфакторной аутентификации:', - '', - ); - - if (totpToken) { - authData.password = `${password}:${totpToken}`; - const totpResponse = await fetch( - 'https://authserver.ely.by/auth/authenticate', - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(authData), - }, - ); - if (totpResponse.ok) { - const totpData = await totpResponse.json(); - const newAccessToken = totpData.accessToken; - const newProfile = totpData.selectedProfile; - const newUuid = newProfile.id; - const newName = newProfile.name; - - if (newAccessToken && newUuid && newName) { - saveConfig( - username, - 4096, // default memory - newAccessToken, - clientToken, - '', - password, - ); - return { - accessToken: newAccessToken, - uuid: newUuid, - username: newName, - clientToken: clientToken, - }; - } - } - } - } - - throw new Error(responseData.error || 'Ошибка авторизации'); + setStatus('authenticated'); + return response; } + + setStatus('error'); + return null; } catch (error) { - console.log(`Ошибка авторизации: ${error.message}`); + console.error('Ошибка при аутентификации:', error); + setStatus('error'); return null; } }; return { status, - setStatus, validateSession, refreshSession, authenticateWithElyBy, }; -}; - -export default useAuth; +} diff --git a/src/renderer/hooks/useConfig.ts b/src/renderer/hooks/useConfig.ts index 9f3f81c..6270e48 100644 --- a/src/renderer/hooks/useConfig.ts +++ b/src/renderer/hooks/useConfig.ts @@ -1,7 +1,18 @@ import { useState, useEffect } from 'react'; +// Добавляем определение типа Config +interface Config { + username: string; + password: string; + memory: number; + comfortVersion: string; + accessToken: string; + clientToken: string; + uuid?: string; // Добавляем uuid, который используется для авторизации +} + const useConfig = () => { - const [config, setConfig] = useState({ + const [config, setConfig] = useState({ username: '', password: '', memory: 4096, @@ -34,28 +45,10 @@ const useConfig = () => { setConfig(savedConfig); }, []); - const saveConfig = ( - username: string, - memory: number, - accessToken = '', - clientToken = '', - comfortVersion = '', - password = '', - ) => { - try { - const newConfig = { - username, - memory, - accessToken: accessToken || config.accessToken, - clientToken: clientToken || config.clientToken, - comfortVersion: comfortVersion || config.comfortVersion, - password: password || config.password, - }; - setConfig(newConfig); - localStorage.setItem('launcher_config', JSON.stringify(newConfig)); - } catch (error) { - console.log(`Ошибка при сохранении конфигурации: ${error.message}`); - } + const saveConfig = (newConfig: Partial) => { + const updatedConfig = { ...config, ...newConfig }; + setConfig(updatedConfig); + localStorage.setItem('launcher_config', JSON.stringify(updatedConfig)); }; const handleInputChange = (e: React.ChangeEvent) => { diff --git a/src/renderer/pages/LaunchPage.tsx b/src/renderer/pages/LaunchPage.tsx index dccbbca..521ce29 100644 --- a/src/renderer/pages/LaunchPage.tsx +++ b/src/renderer/pages/LaunchPage.tsx @@ -33,22 +33,6 @@ const LaunchPage = () => { navigate('/login'); } - window.electron.ipcRenderer.on('download-progress', (progress: any) => { - setDownloadProgress(progress as number); - }); - - // Добавляем слушатель для статуса установки - window.electron.ipcRenderer.on('installation-progress', (data: any) => { - setInstallStatus((data as { status: string }).status); - }); - - // Обновляем слушатель для статуса установки - window.electron.ipcRenderer.on('installation-status', (data: any) => { - const { step, message } = data as { step: string; message: string }; - setInstallStep(step); - setInstallMessage(message); - }); - return () => { window.electron.ipcRenderer.removeAllListeners('download-progress'); window.electron.ipcRenderer.removeAllListeners('installation-progress'); @@ -77,6 +61,11 @@ const LaunchPage = () => { setIsDownloading(true); setDownloadProgress(0); + // Загружаем настройки и токены + const savedConfig = JSON.parse( + localStorage.getItem('launcher_config') || '{}', + ); + // Сначала проверяем и обновляем файлы const downloadResult = await window.electron.ipcRenderer.invoke( 'download-and-extract', @@ -95,9 +84,16 @@ const LaunchPage = () => { ); } - // Затем запускаем Minecraft - const launchResult = - await window.electron.ipcRenderer.invoke('launch-minecraft'); + // Затем запускаем Minecraft с данными авторизации + const launchResult = await window.electron.ipcRenderer.invoke( + 'launch-minecraft', + { + accessToken: savedConfig.accessToken, + uuid: savedConfig.uuid, + username: savedConfig.username, + memory: savedConfig.memory || 4096, + }, + ); if (launchResult?.success) { showNotification('Minecraft успешно запущен!', 'success');