working authirization

This commit is contained in:
2025-07-07 00:21:13 +05:00
parent b65b9538bb
commit 6f92b2acad
8 changed files with 997 additions and 703 deletions

74
package-lock.json generated
View File

@ -14,6 +14,7 @@
"@mui/material": "^7.2.0", "@mui/material": "^7.2.0",
"@xmcl/core": "^2.14.1", "@xmcl/core": "^2.14.1",
"@xmcl/installer": "^6.1.0", "@xmcl/installer": "^6.1.0",
"@xmcl/user": "^4.2.0",
"electron-debug": "^4.1.0", "electron-debug": "^4.1.0",
"electron-log": "^5.3.2", "electron-log": "^5.3.2",
"electron-updater": "^6.3.9", "electron-updater": "^6.3.9",
@ -25,7 +26,8 @@
"react-router-dom": "^7.3.0", "react-router-dom": "^7.3.0",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"undici": "^7.11.0", "undici": "^7.11.0",
"util": "^0.12.5" "util": "^0.12.5",
"uuid": "^11.1.0"
}, },
"devDependencies": { "devDependencies": {
"@electron/rebuild": "^3.7.1", "@electron/rebuild": "^3.7.1",
@ -2806,6 +2808,15 @@
"node": "^12.22.0 || ^14.17.0 || >=16.0.0" "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": { "node_modules/@gar/promisify": {
"version": "1.1.3", "version": "1.1.3",
"resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
@ -5972,6 +5983,44 @@
"yauzl": "^2.10.0" "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": { "node_modules/@xmldom/xmldom": {
"version": "0.8.10", "version": "0.8.10",
"resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz", "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.8.10.tgz",
@ -19640,6 +19689,16 @@
"websocket-driver": "^0.7.4" "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": { "node_modules/socks": {
"version": "2.8.4", "version": "2.8.4",
"resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.4.tgz",
@ -21419,13 +21478,16 @@
} }
}, },
"node_modules/uuid": { "node_modules/uuid": {
"version": "8.3.2", "version": "11.1.0",
"resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.0.tgz",
"integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "integrity": "sha512-0/A9rDy9P7cJ+8w1c9WD9V//9Wj15Ce2MPz8Ri6032usz+NfePxx5AcN3bN+r6ZL6jEo066/yNYB3tn4pQEx+A==",
"dev": true, "funding": [
"https://github.com/sponsors/broofa",
"https://github.com/sponsors/ctavan"
],
"license": "MIT", "license": "MIT",
"bin": { "bin": {
"uuid": "dist/bin/uuid" "uuid": "dist/esm/bin/uuid"
} }
}, },
"node_modules/v8-compile-cache-lib": { "node_modules/v8-compile-cache-lib": {

View File

@ -107,6 +107,7 @@
"@mui/material": "^7.2.0", "@mui/material": "^7.2.0",
"@xmcl/core": "^2.14.1", "@xmcl/core": "^2.14.1",
"@xmcl/installer": "^6.1.0", "@xmcl/installer": "^6.1.0",
"@xmcl/user": "^4.2.0",
"electron-debug": "^4.1.0", "electron-debug": "^4.1.0",
"electron-log": "^5.3.2", "electron-log": "^5.3.2",
"electron-updater": "^6.3.9", "electron-updater": "^6.3.9",
@ -118,7 +119,8 @@
"react-router-dom": "^7.3.0", "react-router-dom": "^7.3.0",
"stream-browserify": "^3.0.0", "stream-browserify": "^3.0.0",
"undici": "^7.11.0", "undici": "^7.11.0",
"util": "^0.12.5" "util": "^0.12.5",
"uuid": "^11.1.0"
}, },
"devDependencies": { "devDependencies": {
"@electron/rebuild": "^3.7.1", "@electron/rebuild": "^3.7.1",

90
src/main/auth-service.ts Normal file
View File

@ -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<YggrasilAuthentication> {
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<boolean> {
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<YggrasilAuthentication | null> {
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;
}
}
}

View File

@ -14,121 +14,7 @@ import { autoUpdater } from 'electron-updater';
import log from 'electron-log'; import log from 'electron-log';
import MenuBuilder from './menu'; import MenuBuilder from './menu';
import { resolveHtmlPath } from './util'; import { resolveHtmlPath } from './util';
import fs from 'fs'; import { initMinecraftHandlers, initAuthHandlers } from './minecraft-launcher';
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<string> {
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);
}
});
}
class AppUpdater { class AppUpdater {
constructor() { constructor() {
@ -158,402 +44,8 @@ if (isDebug) {
require('electron-debug').default(); require('electron-debug').default();
} }
// Minecraft // Инициализация обработчиков Minecraft
initMinecraftHandlers();
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<string> {
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<void> {
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 };
}
});
const installExtensions = async () => { const installExtensions = async () => {
const installer = require('electron-devtools-installer'); const installer = require('electron-devtools-installer');
@ -623,6 +115,9 @@ const createWindow = async () => {
// Remove this if your app does not use auto updates // Remove this if your app does not use auto updates
// eslint-disable-next-line // eslint-disable-next-line
new AppUpdater(); new AppUpdater();
initAuthHandlers();
initMinecraftHandlers();
}; };
/** /**

View File

@ -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<string> {
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<void> {
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<string> {
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<string> {
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;
}
},
);
}

View File

@ -1,177 +1,94 @@
import { useState } from 'react'; import { useState } from 'react';
const useAuth = () => { interface AuthSession {
const [status, setStatus] = useState(''); 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<boolean> => {
try { try {
const response = await fetch('https://authserver.ely.by/auth/validate', { setStatus('validating');
method: 'POST', const response = await window.electron.ipcRenderer.invoke(
headers: { 'validate-token',
'Content-Type': 'application/json', accessToken,
}, );
body: JSON.stringify({ setStatus('idle');
accessToken: accessToken, return response.valid;
}),
});
return response.ok;
} catch (error) { } catch (error) {
console.log(`Ошибка при проверке токена: ${error.message}`); console.error('Ошибка при валидации токена:', error);
setStatus('error');
return false; return false;
} }
}; };
const refreshSession = async (accessToken: string, clientToken: string) => { // Обновление токена
const refreshSession = async (
accessToken: string,
clientToken: string,
): Promise<AuthSession | null> => {
try { try {
const refreshData = { setStatus('refreshing');
accessToken: accessToken, const response = await window.electron.ipcRenderer.invoke(
clientToken: clientToken, 'refresh-token',
}; { accessToken, clientToken },
);
const response = await fetch('https://authserver.ely.by/auth/refresh', { setStatus('idle');
method: 'POST', return response;
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;
} catch (error) { } catch (error) {
console.log(`Ошибка при обновлении сессии: ${error.message}`); console.error('Ошибка при обновлении токена:', error);
setStatus('error');
return null; return null;
} }
}; };
// Аутентификация в Ely.by
const authenticateWithElyBy = async ( const authenticateWithElyBy = async (
username: string, username: string,
password: string, password: string,
saveConfig: Function, saveConfigFunc: Function,
) => { ): Promise<AuthSession | null> => {
try { try {
const clientToken = crypto.randomUUID(); setStatus('authenticating');
const authData = { const response = await window.electron.ipcRenderer.invoke(
username: username, 'authenticate',
password: password, { username, 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),
},
); );
const responseData = await response.json(); if (response && response.accessToken) {
if (response.ok) { // Правильно сохраняем данные в конфигурации
const accessToken = responseData.accessToken; saveConfigFunc({
const profile = responseData.selectedProfile; username: response.selectedProfile.name, // Имя игрока как строка
const uuid = profile.id; uuid: response.selectedProfile.id,
const name = profile.name; accessToken: response.accessToken,
clientToken: response.clientToken,
memory: 4096, // Сохраняем значение по умолчанию или из предыдущей конфигурации
});
if (accessToken && uuid && name) { setStatus('authenticated');
saveConfig( return response;
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('error');
} return null;
} catch (error) { } catch (error) {
console.log(`Ошибка авторизации: ${error.message}`); console.error('Ошибка при аутентификации:', error);
setStatus('error');
return null; return null;
} }
}; };
return { return {
status, status,
setStatus,
validateSession, validateSession,
refreshSession, refreshSession,
authenticateWithElyBy, authenticateWithElyBy,
}; };
}; }
export default useAuth;

View File

@ -1,7 +1,18 @@
import { useState, useEffect } from 'react'; 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 useConfig = () => {
const [config, setConfig] = useState({ const [config, setConfig] = useState<Config>({
username: '', username: '',
password: '', password: '',
memory: 4096, memory: 4096,
@ -34,28 +45,10 @@ const useConfig = () => {
setConfig(savedConfig); setConfig(savedConfig);
}, []); }, []);
const saveConfig = ( const saveConfig = (newConfig: Partial<Config>) => {
username: string, const updatedConfig = { ...config, ...newConfig };
memory: number, setConfig(updatedConfig);
accessToken = '', localStorage.setItem('launcher_config', JSON.stringify(updatedConfig));
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 handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => { const handleInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {

View File

@ -33,22 +33,6 @@ const LaunchPage = () => {
navigate('/login'); 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 () => { return () => {
window.electron.ipcRenderer.removeAllListeners('download-progress'); window.electron.ipcRenderer.removeAllListeners('download-progress');
window.electron.ipcRenderer.removeAllListeners('installation-progress'); window.electron.ipcRenderer.removeAllListeners('installation-progress');
@ -77,6 +61,11 @@ const LaunchPage = () => {
setIsDownloading(true); setIsDownloading(true);
setDownloadProgress(0); setDownloadProgress(0);
// Загружаем настройки и токены
const savedConfig = JSON.parse(
localStorage.getItem('launcher_config') || '{}',
);
// Сначала проверяем и обновляем файлы // Сначала проверяем и обновляем файлы
const downloadResult = await window.electron.ipcRenderer.invoke( const downloadResult = await window.electron.ipcRenderer.invoke(
'download-and-extract', 'download-and-extract',
@ -95,9 +84,16 @@ const LaunchPage = () => {
); );
} }
// Затем запускаем Minecraft // Затем запускаем Minecraft с данными авторизации
const launchResult = const launchResult = await window.electron.ipcRenderer.invoke(
await window.electron.ipcRenderer.invoke('launch-minecraft'); 'launch-minecraft',
{
accessToken: savedConfig.accessToken,
uuid: savedConfig.uuid,
username: savedConfig.username,
memory: savedConfig.memory || 4096,
},
);
if (launchResult?.success) { if (launchResult?.success) {
showNotification('Minecraft успешно запущен!', 'success'); showNotification('Minecraft успешно запущен!', 'success');