mirror of
https://git.sanhost.net/sanasol/hytale-f2p
synced 2026-03-02 15:01:52 -03:00
Compare commits
4 Commits
v2.4.7
...
feature/ma
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5e5ddbcc4 | ||
|
|
e610072fa6 | ||
|
|
a40465cac9 | ||
|
|
57056e5b7a |
123
GUI/index.html
123
GUI/index.html
@@ -24,7 +24,7 @@
|
|||||||
height="100%25" filter="url(%23noiseFilter)" opacity="0.1" /%3E%3C/svg%3E')] opacity-20"></div>
|
height="100%25" filter="url(%23noiseFilter)" opacity="0.1" /%3E%3C/svg%3E')] opacity-20"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="flex w-full h-screen relative z-10">
|
<div class="flex w-full h-screen relative z-10" style="isolation: isolate">
|
||||||
<nav class="sidebar">
|
<nav class="sidebar">
|
||||||
<div class="sidebar-logo">
|
<div class="sidebar-logo">
|
||||||
<img src="./icon.png" alt="Hytale Logo" />
|
<img src="./icon.png" alt="Hytale Logo" />
|
||||||
@@ -44,10 +44,6 @@
|
|||||||
<i class="fas fa-box"></i>
|
<i class="fas fa-box"></i>
|
||||||
<span class="nav-tooltip" data-i18n="nav.mods">Mods</span>
|
<span class="nav-tooltip" data-i18n="nav.mods">Mods</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" data-page="news">
|
|
||||||
<i class="fas fa-newspaper"></i>
|
|
||||||
<span class="nav-tooltip" data-i18n="nav.news">News</span>
|
|
||||||
</div>
|
|
||||||
<div class="nav-item" data-page="settings">
|
<div class="nav-item" data-page="settings">
|
||||||
<i class="fas fa-cog"></i>
|
<i class="fas fa-cog"></i>
|
||||||
<span class="nav-tooltip" data-i18n="nav.settings">Settings</span>
|
<span class="nav-tooltip" data-i18n="nav.settings">Settings</span>
|
||||||
@@ -56,9 +52,12 @@
|
|||||||
<i class="fas fa-terminal"></i>
|
<i class="fas fa-terminal"></i>
|
||||||
<span class="nav-tooltip">Logs</span>
|
<span class="nav-tooltip">Logs</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="nav-item" onclick="openDiscordExternal()">
|
<div class="nav-item" id="matchaNavItem">
|
||||||
<i class="fas fa-comments"></i>
|
<i class="fas fa-comments" id="matchaNavIcon"></i>
|
||||||
<span class="nav-tooltip">Community Chat</span>
|
<img id="matchaNavAvatar" class="matcha-nav-avatar" style="display:none" />
|
||||||
|
<span class="matcha-nav-status" id="matchaNavStatus" style="display:none"></span>
|
||||||
|
<span class="nav-tooltip">Matcha!</span>
|
||||||
|
<span id="matchaUnreadBadge" class="matcha-unread-badge" style="display:none"></span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -219,36 +218,38 @@
|
|||||||
<span data-i18n="play.playButton">PLAY HYTALE</span>
|
<span data-i18n="play.playButton">PLAY HYTALE</span>
|
||||||
</button>
|
</button>
|
||||||
|
|
||||||
<div style="display: flex; justify-content: center; align-items: center; gap: 8px; margin-top: 12px; font-size: 12px;">
|
<div id="game-info-bar" class="game-info-bar">
|
||||||
<span style="color: #93a3b8;">Telegram:</span>
|
<span id="game-version-info" class="game-info-item game-info-loading">loading...</span>
|
||||||
<a href="#" onclick="window.electronAPI?.openExternal('https://t.me/sanhostnet'); return false;" style="color: #93a3b8; text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.2s;" onmouseover="this.style.color='#60a5fa'" onmouseout="this.style.color='#93a3b8'">
|
<span class="game-info-sep"></span>
|
||||||
<i class="fas fa-users"></i> Group
|
<span id="game-branch-info" class="game-info-item game-info-loading">...</span>
|
||||||
|
<span id="game-last-played" class="game-info-item" style="display: none;"></span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="community-links">
|
||||||
|
<a href="#" class="community-link" onclick="openDiscordExternal(); return false;" title="Community Chat">
|
||||||
|
<i class="fas fa-comments" style="color: #22c55e;"></i>
|
||||||
|
<span>Chat</span>
|
||||||
</a>
|
</a>
|
||||||
<span style="color: #4b5563;">|</span>
|
<a href="#" class="community-link" onclick="window.electronAPI?.openExternal('https://discord.gg/8WVU24XshK'); return false;" title="Discord">
|
||||||
<a href="#" onclick="window.electronAPI?.openExternal('https://t.me/hf2p_og'); return false;" style="color: #93a3b8; text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.2s;" onmouseover="this.style.color='#60a5fa'" onmouseout="this.style.color='#93a3b8'">
|
<i class="fab fa-discord" style="color: #5865F2;"></i>
|
||||||
<i class="fab fa-telegram"></i> Channel
|
<span>Discord</span>
|
||||||
</a>
|
</a>
|
||||||
<span style="color: #4b5563;">|</span>
|
<a href="#" class="community-link" onclick="window.electronAPI?.openExternal('https://t.me/hf2p_og'); return false;" title="Telegram Channel">
|
||||||
<a href="#" onclick="openDiscordExternal(); return false;" style="color: #93a3b8; text-decoration: none; display: flex; align-items: center; gap: 4px; transition: color 0.2s;" onmouseover="this.style.color='#60a5fa'" onmouseout="this.style.color='#93a3b8'">
|
<i class="fab fa-telegram" style="color: #26A5E4;"></i>
|
||||||
<i class="fas fa-comments"></i> Community Chat
|
<span>Channel</span>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="community-link" onclick="window.electronAPI?.openExternal('https://t.me/sanhostnet'); return false;" title="Telegram Group">
|
||||||
|
<i class="fab fa-telegram" style="color: #26A5E4;"></i>
|
||||||
|
<span>Group</span>
|
||||||
|
</a>
|
||||||
|
<a href="#" class="community-link" onclick="window.electronAPI?.openExternal('https://git.sanhost.net/sanasol/hytale-f2p'); return false;" title="Source Code">
|
||||||
|
<i class="fab fa-git-alt" style="color: #e2e8f0;"></i>
|
||||||
|
<span>Source</span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="news-section">
|
|
||||||
<div class="news-header">
|
|
||||||
<h2 class="news-title">
|
|
||||||
<i class="fas fa-star mr-2"></i>
|
|
||||||
<span data-i18n="play.latestNews">LATEST NEWS</span>
|
|
||||||
</h2>
|
|
||||||
<button class="view-all-btn" onclick="navigateToPage('news')">
|
|
||||||
<span data-i18n="play.viewAll">VIEW ALL</span> <i
|
|
||||||
class="fas fa-arrow-right ml-1"></i>
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
<div id="newsGrid" class="news-grid-horizontal"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="featured-page" class="page">
|
<div id="featured-page" class="page">
|
||||||
@@ -301,15 +302,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="news-page" class="page">
|
|
||||||
<div class="news-header">
|
|
||||||
<h2 class="news-title">
|
|
||||||
<i class="fas fa-newspaper mr-2"></i>
|
|
||||||
<span data-i18n="news.title">ALL NEWS</span>
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div id="allNewsGrid" class="news-grid-full"></div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="settings-page" class="page">
|
<div id="settings-page" class="page">
|
||||||
<div class="settings-container">
|
<div class="settings-container">
|
||||||
@@ -698,6 +690,29 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Matcha! Panel -->
|
||||||
|
<div id="matchaPanelBackdrop" class="matcha-panel-backdrop"></div>
|
||||||
|
<div id="matchaPanel" class="matcha-panel">
|
||||||
|
<div class="matcha-panel-header">
|
||||||
|
<div id="matchaPanelHeaderContent" class="matcha-panel-header-content">
|
||||||
|
<span class="matcha-header-title">Matcha!</span>
|
||||||
|
</div>
|
||||||
|
<button class="matcha-panel-close" id="matchaPanelCloseBtn">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div id="matchaPanelBody" class="matcha-panel-body"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Matcha User Profile Popup -->
|
||||||
|
<div id="matchaUserProfileOverlay" class="matcha-user-profile-overlay" style="display:none">
|
||||||
|
<div class="matcha-user-profile-card" id="matchaUserProfileCard">
|
||||||
|
<div class="matcha-user-profile-body" id="matchaUserProfileBody">
|
||||||
|
<div class="matcha-loading"><i class="fas fa-spinner fa-spin"></i></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div id="myModsModal" class="mods-modal">
|
<div id="myModsModal" class="mods-modal">
|
||||||
<div class="mods-modal-content">
|
<div class="mods-modal-content">
|
||||||
<div class="mods-modal-header">
|
<div class="mods-modal-header">
|
||||||
@@ -950,36 +965,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="version-display-bottom">
|
<div class="version-display-bottom" onclick="window.electronAPI?.openExternal('https://git.sanhost.net/sanasol/hytale-f2p/releases')" style="cursor: pointer;" title="View releases">
|
||||||
<i class="fas fa-code-branch"></i>
|
<i class="fas fa-code-branch"></i>
|
||||||
<span id="launcherVersion"></span>
|
<span id="launcherVersion"></span>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<footer class="fixed bottom-0 left-0 right-0 z-50 bg-black/80 backdrop-blur-sm px-4 py-2">
|
<footer class="fixed bottom-0 left-0 right-0 z-50 bg-black/80 backdrop-blur-sm px-4 py-2">
|
||||||
<div class="flex items-center justify-center text-xs text-gray-400">
|
<div class="flex items-center justify-center text-xs text-gray-400">
|
||||||
<span>Made by <a href="https://github.com/amiayweb" target="_blank"
|
<span>Made by <a href="#" onclick="window.electronAPI?.openExternal('https://github.com/amiayweb'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@amiayweb</a> & <a
|
class="text-blue-400 hover:text-blue-300 transition-colors">@amiayweb</a> & <a
|
||||||
href="https://github.com/Relyz1993" target="_blank"
|
href="#" onclick="window.electronAPI?.openExternal('https://github.com/Relyz1993'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@Relyz</a></span>
|
class="text-blue-400 hover:text-blue-300 transition-colors">@Relyz</a></span>
|
||||||
<span class="mx-2">|</span>
|
<span class="mx-2">|</span>
|
||||||
<span>Contributors:
|
<span>Contributors:
|
||||||
<a href="https://github.com/chasem-dev" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/chasem-dev'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@chasem-dev</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@chasem-dev</a>,
|
||||||
<a href="https://github.com/crimera" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/crimera'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@crimera</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@crimera</a>,
|
||||||
<a href="https://github.com/sanasol" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/sanasol'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@sanasol</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@sanasol</a>,
|
||||||
<a href="https://github.com/Terromur" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/Terromur'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@terromur</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@terromur</a>,
|
||||||
<a href="https://github.com/ericiskoolbeans" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/ericiskoolbeans'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@ericiskoolbeans</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@ericiskoolbeans</a>,
|
||||||
<a href="https://github.com/fazrigading" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/fazrigading'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@fazrigading</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@fazrigading</a>,
|
||||||
<a href="https://github.com/Rahul-Sahani04" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/Rahul-Sahani04'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@Rahul-Sahani04</a>,
|
class="text-blue-400 hover:text-blue-300 transition-colors">@Rahul-Sahani04</a>,
|
||||||
<a href="https://github.com/xSamiVS" target="_blank"
|
<a href="#" onclick="window.electronAPI?.openExternal('https://github.com/xSamiVS'); return false;"
|
||||||
class="text-blue-400 hover:text-blue-300 transition-colors">@xSamiVS</a>
|
class="text-blue-400 hover:text-blue-300 transition-colors">@xSamiVS</a>
|
||||||
</span>
|
</span>
|
||||||
|
<span class="mx-2">|</span>
|
||||||
|
<span>Social powered by <a href="#" onclick="window.electronAPI?.openExternal('https://github.com/vZylev/Butter-Launcher'); return false;"
|
||||||
|
class="text-purple-400 hover:text-purple-300 transition-colors">Butter Launcher</a> &
|
||||||
|
<a href="#" onclick="window.electronAPI?.openExternal('https://butterlauncher.tech/'); return false;"
|
||||||
|
class="text-purple-400 hover:text-purple-300 transition-colors">Matcha!</a>
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
@@ -169,6 +169,16 @@ window.switchProfile = async (id) => {
|
|||||||
// Refresh UI
|
// Refresh UI
|
||||||
await loadProfiles();
|
await loadProfiles();
|
||||||
|
|
||||||
|
// Refresh branch radio buttons in settings
|
||||||
|
if (window.SettingsAPI?.reloadBranch) {
|
||||||
|
await window.SettingsAPI.reloadBranch();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Refresh game info bar on play page
|
||||||
|
if (window.LauncherUI?.loadGameInfoBar) {
|
||||||
|
window.LauncherUI.loadGameInfoBar();
|
||||||
|
}
|
||||||
|
|
||||||
// Refresh Mods
|
// Refresh Mods
|
||||||
if (window.modsManager) {
|
if (window.modsManager) {
|
||||||
if (window.modsManager.loadInstalledMods) await window.modsManager.loadInstalledMods();
|
if (window.modsManager.loadInstalledMods) await window.modsManager.loadInstalledMods();
|
||||||
|
|||||||
1920
GUI/js/matcha.js
Normal file
1920
GUI/js/matcha.js
Normal file
File diff suppressed because it is too large
Load Diff
@@ -1,11 +1,11 @@
|
|||||||
import './ui.js';
|
import './ui.js';
|
||||||
import './install.js';
|
import './install.js';
|
||||||
import './launcher.js';
|
import './launcher.js';
|
||||||
import './news.js';
|
|
||||||
import { initModsManager } from './mods.js';
|
import { initModsManager } from './mods.js';
|
||||||
import './players.js';
|
import './players.js';
|
||||||
import './settings.js';
|
import './settings.js';
|
||||||
import './logs.js';
|
import './logs.js';
|
||||||
|
import { initMatcha } from './matcha.js';
|
||||||
|
|
||||||
let i18nInitialized = false;
|
let i18nInitialized = false;
|
||||||
(async () => {
|
(async () => {
|
||||||
@@ -16,10 +16,12 @@ let i18nInitialized = false;
|
|||||||
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
if (document.readyState === 'complete' || document.readyState === 'interactive') {
|
||||||
updateLanguageSelector();
|
updateLanguageSelector();
|
||||||
initModsManager();
|
initModsManager();
|
||||||
|
initMatcha();
|
||||||
} else {
|
} else {
|
||||||
document.addEventListener('DOMContentLoaded', () => {
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
updateLanguageSelector();
|
updateLanguageSelector();
|
||||||
initModsManager();
|
initModsManager();
|
||||||
|
initMatcha();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
|
|||||||
@@ -1924,6 +1924,11 @@ async function switchBranch(newBranch) {
|
|||||||
await loadVersionBranch();
|
await loadVersionBranch();
|
||||||
console.log('[Settings] Radio buttons updated after branch switch');
|
console.log('[Settings] Radio buttons updated after branch switch');
|
||||||
|
|
||||||
|
// Refresh game info bar on play page
|
||||||
|
if (window.LauncherUI?.loadGameInfoBar) {
|
||||||
|
window.LauncherUI.loadGameInfoBar();
|
||||||
|
}
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
if (window.LauncherUI) {
|
if (window.LauncherUI) {
|
||||||
window.LauncherUI.hideProgress();
|
window.LauncherUI.hideProgress();
|
||||||
|
|||||||
59
GUI/js/ui.js
59
GUI/js/ui.js
@@ -138,6 +138,7 @@ function showLauncherOrInstall(isInstalled) {
|
|||||||
if (gameTitle) gameTitle.style.display = '';
|
if (gameTitle) gameTitle.style.display = '';
|
||||||
showPage('play-page');
|
showPage('play-page');
|
||||||
setActiveNav('play');
|
setActiveNav('play');
|
||||||
|
loadGameInfoBar();
|
||||||
} else {
|
} else {
|
||||||
if (launcher) launcher.style.display = 'none';
|
if (launcher) launcher.style.display = 'none';
|
||||||
if (install) {
|
if (install) {
|
||||||
@@ -738,13 +739,69 @@ async function checkGameInstallation() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function loadGameInfoBar() {
|
||||||
|
try {
|
||||||
|
const info = await window.electronAPI.getGameInfo();
|
||||||
|
const bar = document.getElementById('game-info-bar');
|
||||||
|
if (!bar) return;
|
||||||
|
|
||||||
|
const versionEl = document.getElementById('game-version-info');
|
||||||
|
const branchEl = document.getElementById('game-branch-info');
|
||||||
|
const lastPlayedEl = document.getElementById('game-last-played');
|
||||||
|
|
||||||
|
if (versionEl) {
|
||||||
|
versionEl.classList.remove('game-info-loading');
|
||||||
|
if (info.version) {
|
||||||
|
const display = info.readableVersion
|
||||||
|
? `${info.version} - ${info.readableVersion}`
|
||||||
|
: info.version;
|
||||||
|
versionEl.textContent = display;
|
||||||
|
} else {
|
||||||
|
versionEl.textContent = 'Not installed';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (branchEl) {
|
||||||
|
branchEl.classList.remove('game-info-loading');
|
||||||
|
const isPreRelease = info.branch === 'pre-release';
|
||||||
|
branchEl.textContent = isPreRelease ? 'Pre-Release' : 'Release';
|
||||||
|
branchEl.style.color = isPreRelease ? '#f59e0b' : '';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastPlayedEl && info.lastPlayed) {
|
||||||
|
lastPlayedEl.style.display = '';
|
||||||
|
if (!lastPlayedEl.previousElementSibling?.classList?.contains('game-info-sep')) {
|
||||||
|
const sep = document.createElement('span');
|
||||||
|
sep.className = 'game-info-sep';
|
||||||
|
lastPlayedEl.before(sep);
|
||||||
|
}
|
||||||
|
lastPlayedEl.textContent = formatTimeAgo(info.lastPlayed);
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.log('Could not load game info:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTimeAgo(timestamp) {
|
||||||
|
const diff = Date.now() - timestamp;
|
||||||
|
const minutes = Math.floor(diff / 60000);
|
||||||
|
const hours = Math.floor(diff / 3600000);
|
||||||
|
const days = Math.floor(diff / 86400000);
|
||||||
|
|
||||||
|
if (minutes < 1) return 'Just played';
|
||||||
|
if (minutes < 60) return `Played ${minutes}m ago`;
|
||||||
|
if (hours < 24) return `Played ${hours}h ago`;
|
||||||
|
if (days < 30) return `Played ${days}d ago`;
|
||||||
|
return `Played ${Math.floor(days / 30)}mo ago`;
|
||||||
|
}
|
||||||
|
|
||||||
window.LauncherUI = {
|
window.LauncherUI = {
|
||||||
showPage,
|
showPage,
|
||||||
setActiveNav,
|
setActiveNav,
|
||||||
showLauncherOrInstall,
|
showLauncherOrInstall,
|
||||||
showProgress,
|
showProgress,
|
||||||
hideProgress,
|
hideProgress,
|
||||||
updateProgress
|
updateProgress,
|
||||||
|
loadGameInfoBar
|
||||||
};
|
};
|
||||||
|
|
||||||
// Make installation effects globally available
|
// Make installation effects globally available
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ class ClientUpdateManager {
|
|||||||
});
|
});
|
||||||
|
|
||||||
window.electronAPI.onUpdateDownloadProgress((progress) => {
|
window.electronAPI.onUpdateDownloadProgress((progress) => {
|
||||||
|
console.log('📊 download-progress event:', progress);
|
||||||
|
this.downloadProgressReceived = true;
|
||||||
this.updateDownloadProgress(progress);
|
this.updateDownloadProgress(progress);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -49,7 +51,7 @@ class ClientUpdateManager {
|
|||||||
|
|
||||||
const popupHTML = `
|
const popupHTML = `
|
||||||
<div id="update-popup-overlay">
|
<div id="update-popup-overlay">
|
||||||
<div class="update-popup-container update-popup-pulse">
|
<div class="update-popup-container">
|
||||||
<div class="update-popup-header">
|
<div class="update-popup-header">
|
||||||
<div class="update-popup-icon">
|
<div class="update-popup-icon">
|
||||||
<i class="fas fa-download"></i>
|
<i class="fas fa-download"></i>
|
||||||
@@ -118,11 +120,29 @@ class ClientUpdateManager {
|
|||||||
this.blockInterface();
|
this.blockInterface();
|
||||||
|
|
||||||
// Show progress container immediately (auto-download is enabled)
|
// Show progress container immediately (auto-download is enabled)
|
||||||
|
this.downloadProgressReceived = false;
|
||||||
const progressContainer = document.getElementById('update-progress-container');
|
const progressContainer = document.getElementById('update-progress-container');
|
||||||
if (progressContainer) {
|
if (progressContainer) {
|
||||||
progressContainer.style.display = 'block';
|
progressContainer.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// If no progress events arrive within 2 seconds, show indeterminate animation
|
||||||
|
setTimeout(() => {
|
||||||
|
if (!this.downloadProgressReceived && progressContainer) {
|
||||||
|
const bar = document.getElementById('update-progress-bar');
|
||||||
|
const speed = document.getElementById('update-progress-speed');
|
||||||
|
const percent = document.getElementById('update-progress-percent');
|
||||||
|
if (bar) {
|
||||||
|
bar.style.width = '100%';
|
||||||
|
bar.style.animation = 'indeterminateProgress 1.5s ease-in-out infinite';
|
||||||
|
bar.style.opacity = '0.7';
|
||||||
|
}
|
||||||
|
if (speed) speed.textContent = '';
|
||||||
|
if (percent) percent.textContent = 'Downloading...';
|
||||||
|
console.log('⏳ No download-progress events received, showing indeterminate progress');
|
||||||
|
}
|
||||||
|
}, 2000);
|
||||||
|
|
||||||
const installBtn = document.getElementById('update-install-btn');
|
const installBtn = document.getElementById('update-install-btn');
|
||||||
if (installBtn) {
|
if (installBtn) {
|
||||||
installBtn.addEventListener('click', async (e) => {
|
installBtn.addEventListener('click', async (e) => {
|
||||||
@@ -248,6 +268,10 @@ class ClientUpdateManager {
|
|||||||
const progressSize = document.getElementById('update-progress-size');
|
const progressSize = document.getElementById('update-progress-size');
|
||||||
|
|
||||||
if (progressBar && progress) {
|
if (progressBar && progress) {
|
||||||
|
// Stop indeterminate animation if it was running
|
||||||
|
progressBar.style.animation = 'none';
|
||||||
|
progressBar.style.opacity = '1';
|
||||||
|
|
||||||
const percent = Math.round(progress.percent || 0);
|
const percent = Math.round(progress.percent || 0);
|
||||||
progressBar.style.width = `${percent}%`;
|
progressBar.style.width = `${percent}%`;
|
||||||
|
|
||||||
@@ -273,57 +297,35 @@ class ClientUpdateManager {
|
|||||||
showUpdateDownloaded(updateInfo) {
|
showUpdateDownloaded(updateInfo) {
|
||||||
const statusText = document.getElementById('update-status-text');
|
const statusText = document.getElementById('update-status-text');
|
||||||
const progressContainer = document.getElementById('update-progress-container');
|
const progressContainer = document.getElementById('update-progress-container');
|
||||||
|
const progressBar = document.getElementById('update-progress-bar');
|
||||||
|
const progressPercent = document.getElementById('update-progress-percent');
|
||||||
|
const progressSpeed = document.getElementById('update-progress-speed');
|
||||||
|
const progressSize = document.getElementById('update-progress-size');
|
||||||
const buttonsContainer = document.getElementById('update-buttons-container');
|
const buttonsContainer = document.getElementById('update-buttons-container');
|
||||||
const installBtn = document.getElementById('update-install-btn');
|
|
||||||
const downloadBtn = document.getElementById('update-download-btn');
|
|
||||||
const skipBtn = document.getElementById('update-skip-btn');
|
const skipBtn = document.getElementById('update-skip-btn');
|
||||||
const footerText = document.getElementById('update-footer-text');
|
const footerText = document.getElementById('update-footer-text');
|
||||||
const popupContainer = document.querySelector('.update-popup-container');
|
|
||||||
|
|
||||||
// Remove breathing/pulse animation when download is complete
|
// Stop indeterminate animation and show 100%
|
||||||
if (popupContainer) {
|
if (progressBar) {
|
||||||
popupContainer.classList.remove('update-popup-pulse');
|
progressBar.style.animation = 'none';
|
||||||
|
progressBar.style.opacity = '1';
|
||||||
|
progressBar.style.width = '100%';
|
||||||
|
}
|
||||||
|
if (progressPercent) progressPercent.textContent = '100%';
|
||||||
|
if (progressSpeed) progressSpeed.textContent = 'Complete';
|
||||||
|
if (progressContainer) progressContainer.style.display = 'block';
|
||||||
|
|
||||||
|
// Hide progress after a short delay so user sees 100%
|
||||||
|
setTimeout(() => {
|
||||||
|
if (progressContainer) progressContainer.style.display = 'none';
|
||||||
|
}, 1500);
|
||||||
|
|
||||||
|
if (statusText) {
|
||||||
|
statusText.textContent = 'Update downloaded! Ready to install.';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (progressContainer) {
|
if (footerText) {
|
||||||
progressContainer.style.display = 'none';
|
footerText.textContent = 'Click to install the update:';
|
||||||
}
|
|
||||||
|
|
||||||
// Use platform info from main process if available, fallback to browser detection
|
|
||||||
const autoInstallSupported = updateInfo.autoInstallSupported !== undefined
|
|
||||||
? updateInfo.autoInstallSupported
|
|
||||||
: navigator.platform.toUpperCase().indexOf('MAC') < 0;
|
|
||||||
|
|
||||||
if (!autoInstallSupported) {
|
|
||||||
// macOS: Show manual download as primary since auto-update doesn't work
|
|
||||||
if (statusText) {
|
|
||||||
statusText.textContent = 'Update downloaded but auto-install may not work on macOS.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (installBtn) {
|
|
||||||
// Still show install button but as secondary option
|
|
||||||
installBtn.classList.add('update-download-btn-secondary');
|
|
||||||
installBtn.innerHTML = '<i class="fas fa-check" style="margin-right: 0.5rem;"></i>Try Install & Restart';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (downloadBtn) {
|
|
||||||
// Make manual download primary
|
|
||||||
downloadBtn.classList.remove('update-download-btn-secondary');
|
|
||||||
downloadBtn.innerHTML = '<i class="fas fa-external-link-alt" style="margin-right: 0.5rem;"></i>Download Manually (Recommended)';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (footerText) {
|
|
||||||
footerText.textContent = 'Auto-install often fails on macOS:';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Windows/Linux: Auto-install should work
|
|
||||||
if (statusText) {
|
|
||||||
statusText.textContent = 'Update downloaded! Ready to install.';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (footerText) {
|
|
||||||
footerText.textContent = 'Click to install the update:';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (buttonsContainer) {
|
if (buttonsContainer) {
|
||||||
@@ -338,7 +340,7 @@ class ClientUpdateManager {
|
|||||||
console.error('❌ Skip button not found in DOM!');
|
console.error('❌ Skip button not found in DOM!');
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('✅ Update downloaded, ready to install. autoInstallSupported:', autoInstallSupported);
|
console.log('✅ Update downloaded, ready to install');
|
||||||
}
|
}
|
||||||
|
|
||||||
handleUpdateError(errorInfo) {
|
handleUpdateError(errorInfo) {
|
||||||
@@ -366,9 +368,6 @@ class ClientUpdateManager {
|
|||||||
|
|
||||||
if (errorMessage && errorText) {
|
if (errorMessage && errorText) {
|
||||||
let message = errorInfo.message || 'An error occurred during the update process.';
|
let message = errorInfo.message || 'An error occurred during the update process.';
|
||||||
if (errorInfo.isMacSigningError) {
|
|
||||||
message = 'Auto-update requires code signing. Please download manually.';
|
|
||||||
}
|
|
||||||
errorText.textContent = message;
|
errorText.textContent = message;
|
||||||
errorMessage.style.display = 'block';
|
errorMessage.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|||||||
1614
GUI/style.css
1614
GUI/style.css
File diff suppressed because it is too large
Load Diff
@@ -1087,6 +1087,48 @@ function _generateWindowsWrapper(stripFlags, alwaysArgs, serverArgs) {
|
|||||||
return lines.join('\r\n');
|
return lines.join('\r\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// MATCHA SOCIAL AUTH
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
function saveMatchaToken(token) {
|
||||||
|
saveConfig({ matchaToken: token || null });
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMatchaToken() {
|
||||||
|
const config = loadConfig();
|
||||||
|
return config.matchaToken || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveMatchaHandle(handle) {
|
||||||
|
saveConfig({ matchaHandle: handle || null });
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMatchaHandle() {
|
||||||
|
const config = loadConfig();
|
||||||
|
return config.matchaHandle || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveMatchaUserId(id) {
|
||||||
|
saveConfig({ matchaUserId: id || null });
|
||||||
|
}
|
||||||
|
|
||||||
|
function loadMatchaUserId() {
|
||||||
|
const config = loadConfig();
|
||||||
|
return config.matchaUserId || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearMatchaAuth() {
|
||||||
|
const config = loadConfig();
|
||||||
|
delete config.matchaToken;
|
||||||
|
delete config.matchaUserId;
|
||||||
|
delete config.matchaHandle;
|
||||||
|
const data = JSON.stringify(config, null, 2);
|
||||||
|
fs.writeFileSync(CONFIG_TEMP, data, 'utf8');
|
||||||
|
if (fs.existsSync(CONFIG_FILE)) fs.copyFileSync(CONFIG_FILE, CONFIG_BACKUP);
|
||||||
|
fs.renameSync(CONFIG_TEMP, CONFIG_FILE);
|
||||||
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// EXPORTS
|
// EXPORTS
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
@@ -1162,6 +1204,15 @@ module.exports = {
|
|||||||
resetWrapperConfig,
|
resetWrapperConfig,
|
||||||
generateWrapperScript,
|
generateWrapperScript,
|
||||||
|
|
||||||
|
// Matcha Social
|
||||||
|
saveMatchaToken,
|
||||||
|
loadMatchaToken,
|
||||||
|
saveMatchaHandle,
|
||||||
|
loadMatchaHandle,
|
||||||
|
saveMatchaUserId,
|
||||||
|
loadMatchaUserId,
|
||||||
|
clearMatchaAuth,
|
||||||
|
|
||||||
// Constants
|
// Constants
|
||||||
CONFIG_FILE,
|
CONFIG_FILE,
|
||||||
UUID_STORE_FILE
|
UUID_STORE_FILE
|
||||||
|
|||||||
@@ -1,10 +1,14 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const { v4: uuidv4 } = require('uuid');
|
const { v4: uuidv4 } = require('uuid');
|
||||||
const {
|
const {
|
||||||
loadConfig,
|
loadConfig,
|
||||||
saveConfig,
|
saveConfig,
|
||||||
getModsPath
|
getModsPath,
|
||||||
|
loadVersionBranch,
|
||||||
|
saveVersionBranch,
|
||||||
|
loadVersionClient,
|
||||||
|
saveVersionClient
|
||||||
} = require('../core/config');
|
} = require('../core/config');
|
||||||
|
|
||||||
// Lazy-load modManager to avoid circular deps, or keep imports structured.
|
// Lazy-load modManager to avoid circular deps, or keep imports structured.
|
||||||
@@ -39,11 +43,13 @@ class ProfileManager {
|
|||||||
name: 'Default',
|
name: 'Default',
|
||||||
created: new Date().toISOString(),
|
created: new Date().toISOString(),
|
||||||
lastUsed: new Date().toISOString(),
|
lastUsed: new Date().toISOString(),
|
||||||
|
|
||||||
// settings specific to this profile
|
// settings specific to this profile
|
||||||
// If global settings existed, we copy them here
|
// If global settings existed, we copy them here
|
||||||
mods: config.installedMods || [], // Legacy mods are now part of default profile
|
mods: config.installedMods || [], // Legacy mods are now part of default profile
|
||||||
javaPath: config.javaPath || '',
|
javaPath: config.javaPath || '',
|
||||||
|
versionBranch: config.version_branch || 'release',
|
||||||
|
versionClient: config.version_client || null,
|
||||||
gameOptions: {
|
gameOptions: {
|
||||||
minMemory: '1G',
|
minMemory: '1G',
|
||||||
maxMemory: '4G',
|
maxMemory: '4G',
|
||||||
@@ -73,13 +79,16 @@ class ProfileManager {
|
|||||||
const config = loadConfig();
|
const config = loadConfig();
|
||||||
const id = uuidv4();
|
const id = uuidv4();
|
||||||
|
|
||||||
|
// New profiles inherit the current branch/version
|
||||||
const newProfile = {
|
const newProfile = {
|
||||||
id,
|
id,
|
||||||
name: name.trim(),
|
name: name.trim(),
|
||||||
created: new Date().toISOString(),
|
created: new Date().toISOString(),
|
||||||
lastUsed: null,
|
lastUsed: null,
|
||||||
mods: [], // Start with no mods enabled
|
mods: [], // Start with no mods enabled
|
||||||
javaPath: '',
|
javaPath: '',
|
||||||
|
versionBranch: loadVersionBranch(),
|
||||||
|
versionClient: loadVersionClient(),
|
||||||
gameOptions: {
|
gameOptions: {
|
||||||
minMemory: '1G',
|
minMemory: '1G',
|
||||||
maxMemory: '4G',
|
maxMemory: '4G',
|
||||||
@@ -128,20 +137,35 @@ class ProfileManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
console.log(`[ProfileManager] Switching to profile: ${config.profiles[id].name} (${id})`);
|
console.log(`[ProfileManager] Switching to profile: ${config.profiles[id].name} (${id})`);
|
||||||
|
|
||||||
// 1. Update config first
|
// Save current branch/version to the outgoing profile
|
||||||
|
const oldId = config.activeProfileId;
|
||||||
|
if (oldId && config.profiles[oldId]) {
|
||||||
|
config.profiles[oldId].versionBranch = loadVersionBranch();
|
||||||
|
config.profiles[oldId].versionClient = loadVersionClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1. Update config
|
||||||
config.profiles[id].lastUsed = new Date().toISOString();
|
config.profiles[id].lastUsed = new Date().toISOString();
|
||||||
saveConfig({
|
saveConfig({
|
||||||
activeProfileId: id,
|
activeProfileId: id,
|
||||||
profiles: config.profiles
|
profiles: config.profiles
|
||||||
});
|
});
|
||||||
|
|
||||||
// 2. Trigger Mod Sync
|
// 2. Restore branch/version from the new profile
|
||||||
// We need to require this here to ensure it uses the *newly saved* active profile ID
|
const newProfile = config.profiles[id];
|
||||||
|
if (newProfile.versionBranch) {
|
||||||
|
saveVersionBranch(newProfile.versionBranch);
|
||||||
|
}
|
||||||
|
if (newProfile.versionClient !== undefined) {
|
||||||
|
saveVersionClient(newProfile.versionClient);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Trigger Mod Sync
|
||||||
const { syncModsForCurrentProfile } = require('./modManager');
|
const { syncModsForCurrentProfile } = require('./modManager');
|
||||||
await syncModsForCurrentProfile();
|
await syncModsForCurrentProfile();
|
||||||
|
|
||||||
return config.profiles[id];
|
return newProfile;
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteProfile(id) {
|
deleteProfile(id) {
|
||||||
@@ -177,7 +201,7 @@ class ProfileManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Safety checks on updates
|
// Safety checks on updates
|
||||||
const allowedFields = ['name', 'javaPath', 'gameOptions', 'mods'];
|
const allowedFields = ['name', 'javaPath', 'gameOptions', 'mods', 'versionBranch', 'versionClient'];
|
||||||
const sanitizedUpdates = {};
|
const sanitizedUpdates = {};
|
||||||
|
|
||||||
Object.keys(updates).forEach(key => {
|
Object.keys(updates).forEach(key => {
|
||||||
|
|||||||
529
backend/services/matchaService.js
Normal file
529
backend/services/matchaService.js
Normal file
@@ -0,0 +1,529 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
const WebSocket = require('ws');
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const { loadConfig, saveConfig } = require('../core/config');
|
||||||
|
|
||||||
|
const MATCHA_BASE = 'https://butter.lat';
|
||||||
|
const MATCHA_API = `${MATCHA_BASE}/api/matcha`;
|
||||||
|
const MATCHA_WS = 'wss://butter.lat/api/matcha/ws';
|
||||||
|
|
||||||
|
class MatchaService {
|
||||||
|
constructor() {
|
||||||
|
this.token = null;
|
||||||
|
this.user = null;
|
||||||
|
this.ws = null;
|
||||||
|
this.wsConnected = false;
|
||||||
|
this.mainWindow = null;
|
||||||
|
this.heartbeatInterval = null;
|
||||||
|
this.reconnectTimeout = null;
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
this.maxReconnectAttempts = 5;
|
||||||
|
this.lastMessageSentAt = 0;
|
||||||
|
this.gameRunning = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// LIFECYCLE
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
init(mainWindow) {
|
||||||
|
this.mainWindow = mainWindow;
|
||||||
|
this.token = this._loadToken();
|
||||||
|
if (this.token) {
|
||||||
|
this._connectWs();
|
||||||
|
this._startHeartbeat();
|
||||||
|
// Load cached user info
|
||||||
|
const config = loadConfig();
|
||||||
|
if (config.matchaUserId && config.matchaHandle) {
|
||||||
|
this.user = { id: config.matchaUserId, handle: config.matchaHandle };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
destroy() {
|
||||||
|
// Best-effort offline heartbeat
|
||||||
|
if (this.token) {
|
||||||
|
this.sendHeartbeat('offline').catch(() => {});
|
||||||
|
}
|
||||||
|
this._stopHeartbeat();
|
||||||
|
this._disconnectWs();
|
||||||
|
this.mainWindow = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// TOKEN MANAGEMENT
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
_loadToken() {
|
||||||
|
const config = loadConfig();
|
||||||
|
return config.matchaToken || null;
|
||||||
|
}
|
||||||
|
|
||||||
|
_saveToken(token) {
|
||||||
|
this.token = token;
|
||||||
|
saveConfig({ matchaToken: token });
|
||||||
|
}
|
||||||
|
|
||||||
|
_saveUser(user) {
|
||||||
|
this.user = user;
|
||||||
|
if (user) {
|
||||||
|
saveConfig({ matchaUserId: user.id, matchaHandle: user.handle });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_clearAuth() {
|
||||||
|
this.token = null;
|
||||||
|
this.user = null;
|
||||||
|
this._stopHeartbeat();
|
||||||
|
this._disconnectWs();
|
||||||
|
// Set to null — saveConfig merges, and JSON.stringify preserves null values,
|
||||||
|
// but this effectively marks them as cleared for _loadToken() checks
|
||||||
|
saveConfig({ matchaToken: null, matchaUserId: null, matchaHandle: null });
|
||||||
|
}
|
||||||
|
|
||||||
|
_authHeaders() {
|
||||||
|
return this.token ? { Authorization: `Bearer ${this.token}` } : {};
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// AUTH
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
async register(username, password, password2) {
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${MATCHA_API}/register`, {
|
||||||
|
username, password, password2, deferCreate: true
|
||||||
|
});
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async confirmRegistration(pendingId, proofId) {
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${MATCHA_API}/register/confirm`, {
|
||||||
|
pendingId, proofId
|
||||||
|
});
|
||||||
|
if (res.data.token) {
|
||||||
|
this._saveToken(res.data.token);
|
||||||
|
if (res.data.user) this._saveUser(res.data.user);
|
||||||
|
this._connectWs();
|
||||||
|
this._startHeartbeat();
|
||||||
|
}
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async login(handle, password) {
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${MATCHA_API}/login`, { handle, password });
|
||||||
|
if (res.data.token) {
|
||||||
|
this._saveToken(res.data.token);
|
||||||
|
if (res.data.user) this._saveUser(res.data.user);
|
||||||
|
this._connectWs();
|
||||||
|
this._startHeartbeat();
|
||||||
|
}
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async logout() {
|
||||||
|
await this.sendHeartbeat('offline').catch(() => {});
|
||||||
|
this._clearAuth();
|
||||||
|
return { ok: true };
|
||||||
|
}
|
||||||
|
|
||||||
|
getAuthState() {
|
||||||
|
return {
|
||||||
|
authenticated: !!this.token,
|
||||||
|
user: this.user,
|
||||||
|
wsConnected: this.wsConnected
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// PROFILE
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
async getMe() {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`${MATCHA_API}/me`, { headers: this._authHeaders() });
|
||||||
|
if (res.data?.user) this._saveUser({ id: res.data.user.id, handle: res.data.user.handle });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUser(userId) {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`${MATCHA_API}/users/${encodeURIComponent(userId)}`, { headers: this._authHeaders() });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// FRIENDS
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
async getFriends() {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`${MATCHA_API}/friends`, { headers: this._authHeaders() });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendFriendRequest(handle) {
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${MATCHA_API}/friends/request`, { toHandle: handle }, { headers: this._authHeaders() });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async acceptFriend(requestId) {
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${MATCHA_API}/friends/request/accept`, { id: requestId }, { headers: this._authHeaders() });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async rejectFriend(requestId) {
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${MATCHA_API}/friends/request/reject`, { id: requestId }, { headers: this._authHeaders() });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async cancelFriendRequest(requestId) {
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${MATCHA_API}/friends/request/cancel`, { id: requestId }, { headers: this._authHeaders() });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async removeFriend(friendId) {
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${MATCHA_API}/friends/remove`, { friendId }, { headers: this._authHeaders() });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// MESSAGES
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
async getMessages(withTarget, cursor, after) {
|
||||||
|
try {
|
||||||
|
const params = { with: withTarget };
|
||||||
|
if (cursor) params.cursor = cursor;
|
||||||
|
if (after) params.after = after;
|
||||||
|
const res = await axios.get(`${MATCHA_API}/messages`, { headers: this._authHeaders(), params });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendMessage(to, body, replyTo) {
|
||||||
|
// Enforce 800ms throttle
|
||||||
|
const now = Date.now();
|
||||||
|
const elapsed = now - this.lastMessageSentAt;
|
||||||
|
if (elapsed < 800) {
|
||||||
|
return { ok: false, error: 'Please wait before sending another message' };
|
||||||
|
}
|
||||||
|
this.lastMessageSentAt = now;
|
||||||
|
|
||||||
|
// Try WebSocket first
|
||||||
|
if (this.wsConnected && this.ws && this.ws.readyState === WebSocket.OPEN) {
|
||||||
|
const msg = { type: 'send', to, body };
|
||||||
|
if (replyTo) msg.replyTo = replyTo;
|
||||||
|
this.ws.send(JSON.stringify(msg));
|
||||||
|
return { ok: true, data: { sent: true, via: 'ws' } };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback to HTTP
|
||||||
|
try {
|
||||||
|
const payload = { to, body };
|
||||||
|
if (replyTo) payload.replyTo = replyTo;
|
||||||
|
const res = await axios.post(`${MATCHA_API}/messages/send`, payload, { headers: this._authHeaders() });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteMessage(messageId) {
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${MATCHA_API}/messages/${messageId}/delete`, {}, { headers: this._authHeaders() });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// UNREAD
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
async getUnread() {
|
||||||
|
try {
|
||||||
|
const res = await axios.get(`${MATCHA_API}/unread`, { headers: this._authHeaders() });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async clearUnread(withTarget) {
|
||||||
|
try {
|
||||||
|
const res = await axios.post(`${MATCHA_API}/unread/clear`, { with: withTarget }, { headers: this._authHeaders() });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// HEARTBEAT
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
async sendHeartbeat(state) {
|
||||||
|
if (!this.token) return;
|
||||||
|
try {
|
||||||
|
await axios.post(`${MATCHA_API}/heartbeat`, { state: state || 'online' }, { headers: this._authHeaders() });
|
||||||
|
} catch (err) {
|
||||||
|
console.log('[Matcha] Heartbeat failed:', err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_startHeartbeat() {
|
||||||
|
this._stopHeartbeat();
|
||||||
|
this.heartbeatInterval = setInterval(() => {
|
||||||
|
const state = this.gameRunning ? 'in_game' : 'online';
|
||||||
|
this.sendHeartbeat(state);
|
||||||
|
}, 30000);
|
||||||
|
// Send initial heartbeat
|
||||||
|
this.sendHeartbeat('online');
|
||||||
|
}
|
||||||
|
|
||||||
|
_stopHeartbeat() {
|
||||||
|
if (this.heartbeatInterval) {
|
||||||
|
clearInterval(this.heartbeatInterval);
|
||||||
|
this.heartbeatInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setGameRunning(running) {
|
||||||
|
this.gameRunning = running;
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// AVATAR
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
async uploadAvatar(filePath, mode) {
|
||||||
|
try {
|
||||||
|
const crypto = require('crypto');
|
||||||
|
const fileBuffer = fs.readFileSync(filePath);
|
||||||
|
if (fileBuffer.length > 1024 * 1024) {
|
||||||
|
return { ok: false, error: 'Avatar too large (max 1MB)' };
|
||||||
|
}
|
||||||
|
const hash = crypto.createHash('sha256').update(fileBuffer).digest('hex');
|
||||||
|
const endpoint = mode === 'custom' ? `${MATCHA_API}/avatar/custom` : `${MATCHA_API}/avatar`;
|
||||||
|
const res = await axios.post(endpoint, fileBuffer, {
|
||||||
|
headers: {
|
||||||
|
...this._authHeaders(),
|
||||||
|
'Content-Type': 'image/png',
|
||||||
|
'x-avatar-hash': hash,
|
||||||
|
'x-avatar-enable': '1',
|
||||||
|
'x-avatar-force': '1',
|
||||||
|
'Cache-Control': 'no-store'
|
||||||
|
},
|
||||||
|
maxContentLength: 1024 * 1024
|
||||||
|
});
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteAvatar() {
|
||||||
|
try {
|
||||||
|
const res = await axios.delete(`${MATCHA_API}/avatar`, { headers: this._authHeaders() });
|
||||||
|
return { ok: true, data: res.data };
|
||||||
|
} catch (err) {
|
||||||
|
return this._handleError(err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// WEBSOCKET
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
_connectWs() {
|
||||||
|
if (this.ws) this._disconnectWs();
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.ws = new WebSocket(MATCHA_WS);
|
||||||
|
|
||||||
|
this.ws.on('open', () => {
|
||||||
|
console.log('[Matcha] WebSocket connected');
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
// Authenticate
|
||||||
|
if (this.token) {
|
||||||
|
this.ws.send(JSON.stringify({ type: 'auth', token: this.token }));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('message', (raw) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(raw.toString());
|
||||||
|
this._handleWsMessage(data);
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Matcha] WS parse error:', err.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('close', (code) => {
|
||||||
|
console.log('[Matcha] WebSocket closed:', code);
|
||||||
|
this.wsConnected = false;
|
||||||
|
this._sendToRenderer('matcha:ws:disconnected');
|
||||||
|
|
||||||
|
// Handle ban
|
||||||
|
if (code === 4003) {
|
||||||
|
this._sendToRenderer('matcha:ws:banned', { reason: 'Account banned' });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto-reconnect if we have a token
|
||||||
|
if (this.token && code !== 4003) {
|
||||||
|
this._scheduleReconnect();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.ws.on('error', (err) => {
|
||||||
|
console.error('[Matcha] WS error:', err.message);
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Matcha] WS connect failed:', err.message);
|
||||||
|
this._scheduleReconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_disconnectWs() {
|
||||||
|
if (this.reconnectTimeout) {
|
||||||
|
clearTimeout(this.reconnectTimeout);
|
||||||
|
this.reconnectTimeout = null;
|
||||||
|
}
|
||||||
|
if (this.ws) {
|
||||||
|
try {
|
||||||
|
this.ws.close();
|
||||||
|
} catch (e) {}
|
||||||
|
this.ws = null;
|
||||||
|
}
|
||||||
|
this.wsConnected = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
_scheduleReconnect() {
|
||||||
|
if (this.reconnectTimeout) return;
|
||||||
|
// Exponential backoff capped at 30s, no hard limit (matches Butter's infinite reconnect)
|
||||||
|
const delay = Math.min(2000 * Math.pow(2, this.reconnectAttempts), 30000);
|
||||||
|
this.reconnectAttempts++;
|
||||||
|
// Notify renderer after several failures so it can show a banner
|
||||||
|
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||||
|
this._sendToRenderer('matcha:ws:max-retries');
|
||||||
|
}
|
||||||
|
console.log(`[Matcha] Reconnecting in ${delay}ms (attempt ${this.reconnectAttempts})`);
|
||||||
|
this.reconnectTimeout = setTimeout(() => {
|
||||||
|
this.reconnectTimeout = null;
|
||||||
|
if (this.token) this._connectWs();
|
||||||
|
}, delay);
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleWsMessage(data) {
|
||||||
|
switch (data.type) {
|
||||||
|
case 'authed':
|
||||||
|
this.wsConnected = true;
|
||||||
|
if (data.user) this._saveUser(data.user);
|
||||||
|
this._sendToRenderer('matcha:ws:connected', { user: this.user });
|
||||||
|
break;
|
||||||
|
case 'message':
|
||||||
|
console.log('[Matcha] WS message received:', JSON.stringify(data).substring(0, 200));
|
||||||
|
this._sendToRenderer('matcha:ws:message', data);
|
||||||
|
break;
|
||||||
|
case 'message_deleted':
|
||||||
|
this._sendToRenderer('matcha:ws:message-deleted', data);
|
||||||
|
break;
|
||||||
|
case 'avatar_updated':
|
||||||
|
this._sendToRenderer('matcha:ws:avatar-updated', data);
|
||||||
|
break;
|
||||||
|
case 'banned':
|
||||||
|
this._sendToRenderer('matcha:ws:banned', data);
|
||||||
|
break;
|
||||||
|
case 'announcement':
|
||||||
|
this._sendToRenderer('matcha:ws:announcement', data);
|
||||||
|
break;
|
||||||
|
case 'error':
|
||||||
|
console.log('[Matcha] WS error:', data.message || data.error || JSON.stringify(data));
|
||||||
|
// If auth error, treat as ban/disconnect
|
||||||
|
if (data.message === 'Not authed' || data.error === 'Not authed') {
|
||||||
|
this._clearAuth();
|
||||||
|
this._sendToRenderer('matcha:ws:disconnected');
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
console.log('[Matcha] Unknown WS message type:', data.type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
manualReconnect() {
|
||||||
|
this.reconnectAttempts = 0;
|
||||||
|
if (this.token) this._connectWs();
|
||||||
|
}
|
||||||
|
|
||||||
|
// =========================================================================
|
||||||
|
// HELPERS
|
||||||
|
// =========================================================================
|
||||||
|
|
||||||
|
_sendToRenderer(channel, data) {
|
||||||
|
if (this.mainWindow && !this.mainWindow.isDestroyed()) {
|
||||||
|
this.mainWindow.webContents.send(channel, data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
_handleError(err) {
|
||||||
|
if (err.response) {
|
||||||
|
const status = err.response.status;
|
||||||
|
const msg = err.response.data?.error || err.response.data?.message || err.message;
|
||||||
|
if (status === 401) {
|
||||||
|
// Token expired or invalid
|
||||||
|
this._clearAuth();
|
||||||
|
this._sendToRenderer('matcha:ws:disconnected');
|
||||||
|
}
|
||||||
|
return { ok: false, error: msg, status };
|
||||||
|
}
|
||||||
|
return { ok: false, error: err.message };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = new MatchaService();
|
||||||
202
main.js
202
main.js
@@ -6,6 +6,7 @@ const fs = require('fs');
|
|||||||
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, saveLauncherHardwareAcceleration, loadLauncherHardwareAcceleration, saveAllowMultiInstance, loadAllowMultiInstance, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched, loadConfig, saveConfig, checkLaunchReady } = require('./backend/launcher');
|
const { launchGame, launchGameWithVersionCheck, installGame, saveUsername, loadUsername, saveJavaPath, loadJavaPath, saveInstallPath, loadInstallPath, saveDiscordRPC, loadDiscordRPC, saveLanguage, loadLanguage, saveCloseLauncherOnStart, loadCloseLauncherOnStart, saveLauncherHardwareAcceleration, loadLauncherHardwareAcceleration, saveAllowMultiInstance, loadAllowMultiInstance, isGameInstalled, uninstallGame, repairGame, getHytaleNews, handleFirstLaunchCheck, proposeGameUpdate, markAsLaunched, loadConfig, saveConfig, checkLaunchReady } = require('./backend/launcher');
|
||||||
const { retryPWRDownload } = require('./backend/managers/gameManager');
|
const { retryPWRDownload } = require('./backend/managers/gameManager');
|
||||||
const { migrateUserDataToCentralized } = require('./backend/utils/userDataMigration');
|
const { migrateUserDataToCentralized } = require('./backend/utils/userDataMigration');
|
||||||
|
const matchaService = require('./backend/services/matchaService');
|
||||||
|
|
||||||
// Handle Hardware Acceleration
|
// Handle Hardware Acceleration
|
||||||
try {
|
try {
|
||||||
@@ -193,7 +194,7 @@ function createWindow() {
|
|||||||
preload: path.join(__dirname, 'preload.js'),
|
preload: path.join(__dirname, 'preload.js'),
|
||||||
nodeIntegration: false,
|
nodeIntegration: false,
|
||||||
contextIsolation: true,
|
contextIsolation: true,
|
||||||
devTools: false,
|
devTools: process.argv.includes('--dev'),
|
||||||
webSecurity: true
|
webSecurity: true
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -213,6 +214,9 @@ function createWindow() {
|
|||||||
// Initialize Discord Rich Presence
|
// Initialize Discord Rich Presence
|
||||||
initDiscordRPC();
|
initDiscordRPC();
|
||||||
|
|
||||||
|
// Initialize Matcha Social service
|
||||||
|
matchaService.init(mainWindow);
|
||||||
|
|
||||||
// Configure and initialize electron-updater
|
// Configure and initialize electron-updater
|
||||||
// Enable auto-download so updates start immediately when available
|
// Enable auto-download so updates start immediately when available
|
||||||
autoUpdater.autoDownload = true;
|
autoUpdater.autoDownload = true;
|
||||||
@@ -251,7 +255,7 @@ function createWindow() {
|
|||||||
mainWindow.webContents.send('update-error', {
|
mainWindow.webContents.send('update-error', {
|
||||||
message: err.message,
|
message: err.message,
|
||||||
isMacSigningError: isMacSigningError,
|
isMacSigningError: isMacSigningError,
|
||||||
requiresManualDownload: isMacSigningError || process.platform === 'darwin'
|
requiresManualDownload: isMacSigningError
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -272,9 +276,7 @@ function createWindow() {
|
|||||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
mainWindow.webContents.send('update-downloaded', {
|
mainWindow.webContents.send('update-downloaded', {
|
||||||
version: info.version,
|
version: info.version,
|
||||||
platform: process.platform,
|
platform: process.platform
|
||||||
// macOS auto-install often fails on unsigned apps
|
|
||||||
autoInstallSupported: process.platform !== 'darwin'
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -286,9 +288,12 @@ function createWindow() {
|
|||||||
});
|
});
|
||||||
}, 3000);
|
}, 3000);
|
||||||
|
|
||||||
mainWindow.webContents.on('devtools-opened', () => {
|
const isDev = process.argv.includes('--dev');
|
||||||
mainWindow.webContents.closeDevTools();
|
if (!isDev) {
|
||||||
});
|
mainWindow.webContents.on('devtools-opened', () => {
|
||||||
|
mainWindow.webContents.closeDevTools();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
mainWindow.webContents.on('before-input-event', (event, input) => {
|
mainWindow.webContents.on('before-input-event', (event, input) => {
|
||||||
// Allow standard copy/paste/cut/select-all shortcuts
|
// Allow standard copy/paste/cut/select-all shortcuts
|
||||||
@@ -301,18 +306,20 @@ function createWindow() {
|
|||||||
return; // Don't block these
|
return; // Don't block these
|
||||||
}
|
}
|
||||||
|
|
||||||
// Block devtools shortcuts
|
// Block devtools shortcuts (except in dev mode)
|
||||||
if (input.control && input.shift && input.key.toLowerCase() === 'i') {
|
if (!isDev) {
|
||||||
event.preventDefault();
|
if (input.control && input.shift && input.key.toLowerCase() === 'i') {
|
||||||
}
|
event.preventDefault();
|
||||||
if (input.control && input.shift && input.key.toLowerCase() === 'j') {
|
}
|
||||||
event.preventDefault();
|
if (input.control && input.shift && input.key.toLowerCase() === 'j') {
|
||||||
}
|
event.preventDefault();
|
||||||
if (input.control && input.shift && input.key.toLowerCase() === 'c') {
|
}
|
||||||
event.preventDefault();
|
if (input.control && input.shift && input.key.toLowerCase() === 'c') {
|
||||||
}
|
event.preventDefault();
|
||||||
if (input.key === 'F12') {
|
}
|
||||||
event.preventDefault();
|
if (input.key === 'F12') {
|
||||||
|
event.preventDefault();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if (input.key === 'F5') {
|
if (input.key === 'F5') {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
@@ -506,6 +513,7 @@ async function cleanupDiscordRPC() {
|
|||||||
app.on('before-quit', () => {
|
app.on('before-quit', () => {
|
||||||
console.log('=== LAUNCHER BEFORE QUIT ===');
|
console.log('=== LAUNCHER BEFORE QUIT ===');
|
||||||
cleanupDiscordRPC();
|
cleanupDiscordRPC();
|
||||||
|
matchaService.destroy();
|
||||||
});
|
});
|
||||||
|
|
||||||
app.on('window-all-closed', () => {
|
app.on('window-all-closed', () => {
|
||||||
@@ -552,6 +560,12 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, g
|
|||||||
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference, null, launchOptions);
|
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference, null, launchOptions);
|
||||||
|
|
||||||
if (result.success && result.launched) {
|
if (result.success && result.launched) {
|
||||||
|
// Save last played timestamp
|
||||||
|
try { saveConfig({ last_played: Date.now() }); } catch (e) { /* ignore */ }
|
||||||
|
|
||||||
|
// Notify Matcha that game is running (heartbeat will send 'in_game')
|
||||||
|
matchaService.setGameRunning(true);
|
||||||
|
|
||||||
const closeOnStart = loadCloseLauncherOnStart();
|
const closeOnStart = loadCloseLauncherOnStart();
|
||||||
if (closeOnStart) {
|
if (closeOnStart) {
|
||||||
console.log('Close Launcher on start enabled, quitting application...');
|
console.log('Close Launcher on start enabled, quitting application...');
|
||||||
@@ -608,6 +622,10 @@ ipcMain.handle('launch-game-with-password', async (event, playerName, javaPath,
|
|||||||
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference, null, { password });
|
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference, null, { password });
|
||||||
|
|
||||||
if (result.success && result.launched) {
|
if (result.success && result.launched) {
|
||||||
|
try { saveConfig({ last_played: Date.now() }); } catch (e) { /* ignore */ }
|
||||||
|
|
||||||
|
matchaService.setGameRunning(true);
|
||||||
|
|
||||||
const closeOnStart = loadCloseLauncherOnStart();
|
const closeOnStart = loadCloseLauncherOnStart();
|
||||||
if (closeOnStart) {
|
if (closeOnStart) {
|
||||||
setTimeout(() => { app.quit(); }, 1000);
|
setTimeout(() => { app.quit(); }, 1000);
|
||||||
@@ -1366,6 +1384,11 @@ ipcMain.handle('get-detected-gpu', () => {
|
|||||||
ipcMain.handle('save-version-branch', (event, branch) => {
|
ipcMain.handle('save-version-branch', (event, branch) => {
|
||||||
const { saveVersionBranch } = require('./backend/launcher');
|
const { saveVersionBranch } = require('./backend/launcher');
|
||||||
saveVersionBranch(branch);
|
saveVersionBranch(branch);
|
||||||
|
// Sync to active profile
|
||||||
|
const activeProfile = profileManager.getActiveProfile();
|
||||||
|
if (activeProfile) {
|
||||||
|
profileManager.updateProfile(activeProfile.id, { versionBranch: branch });
|
||||||
|
}
|
||||||
return { success: true };
|
return { success: true };
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1379,6 +1402,43 @@ ipcMain.handle('load-version-client', () => {
|
|||||||
return loadVersionClient();
|
return loadVersionClient();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('get-game-info', async () => {
|
||||||
|
const { loadVersionClient, loadVersionBranch } = require('./backend/launcher');
|
||||||
|
const { fetchMirrorManifest } = require('./backend/services/versionManager');
|
||||||
|
const config = loadConfig();
|
||||||
|
const branch = loadVersionBranch();
|
||||||
|
|
||||||
|
let version = null;
|
||||||
|
let readableVersion = null;
|
||||||
|
try {
|
||||||
|
const manifest = await fetchMirrorManifest();
|
||||||
|
if (manifest?.versions?.[branch]) {
|
||||||
|
const branchVersions = manifest.versions[branch];
|
||||||
|
// Get the highest version number for the current branch
|
||||||
|
const nums = Object.keys(branchVersions).map(Number).filter(n => !isNaN(n));
|
||||||
|
if (nums.length > 0) {
|
||||||
|
const latest = Math.max(...nums).toString();
|
||||||
|
version = `v${latest}`;
|
||||||
|
readableVersion = branchVersions[latest]?.version || null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
// Manifest fetch failed, fall back to stored version
|
||||||
|
version = loadVersionClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!version) {
|
||||||
|
version = loadVersionClient();
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
version,
|
||||||
|
readableVersion,
|
||||||
|
branch,
|
||||||
|
lastPlayed: config.last_played || null
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('window-close', () => {
|
ipcMain.handle('window-close', () => {
|
||||||
app.quit();
|
app.quit();
|
||||||
});
|
});
|
||||||
@@ -1759,6 +1819,108 @@ ipcMain.handle('preview-wrapper-script', (event, config, platform) => {
|
|||||||
return generateWrapperScript(config || require('./backend/launcher').loadWrapperConfig(), platform || process.platform, '/path/to/java');
|
return generateWrapperScript(config || require('./backend/launcher').loadWrapperConfig(), platform || process.platform, '/path/to/java');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// MATCHA SOCIAL IPC HANDLERS
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:log', (event, level, ...args) => {
|
||||||
|
const prefix = '[Matcha/Renderer]';
|
||||||
|
if (level === 'error') console.error(prefix, ...args);
|
||||||
|
else if (level === 'warn') console.warn(prefix, ...args);
|
||||||
|
else console.log(prefix, ...args);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:register', async (event, username, password, password2) => {
|
||||||
|
return matchaService.register(username, password, password2);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:confirm-register', async (event, pendingId, proofId) => {
|
||||||
|
return matchaService.confirmRegistration(pendingId, proofId);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:login', async (event, handle, password) => {
|
||||||
|
return matchaService.login(handle, password);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:logout', async () => {
|
||||||
|
return matchaService.logout();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:get-auth-state', () => {
|
||||||
|
return matchaService.getAuthState();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:get-me', async () => {
|
||||||
|
return matchaService.getMe();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:get-user', async (event, userId) => {
|
||||||
|
return matchaService.getUser(userId);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:get-friends', async () => {
|
||||||
|
return matchaService.getFriends();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:friend-request', async (event, handle) => {
|
||||||
|
return matchaService.sendFriendRequest(handle);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:friend-accept', async (event, requestId) => {
|
||||||
|
return matchaService.acceptFriend(requestId);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:friend-reject', async (event, requestId) => {
|
||||||
|
return matchaService.rejectFriend(requestId);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:friend-cancel', async (event, requestId) => {
|
||||||
|
return matchaService.cancelFriendRequest(requestId);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:friend-remove', async (event, friendId) => {
|
||||||
|
return matchaService.removeFriend(friendId);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:get-messages', async (event, withTarget, cursor, after) => {
|
||||||
|
return matchaService.getMessages(withTarget, cursor, after);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:send-message', async (event, to, body, replyTo) => {
|
||||||
|
return matchaService.sendMessage(to, body, replyTo);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:delete-message', async (event, messageId) => {
|
||||||
|
return matchaService.deleteMessage(messageId);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:get-unread', async () => {
|
||||||
|
return matchaService.getUnread();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:clear-unread', async (event, withTarget) => {
|
||||||
|
return matchaService.clearUnread(withTarget);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:upload-avatar', async (event, mode) => {
|
||||||
|
const result = await dialog.showOpenDialog(mainWindow, {
|
||||||
|
title: 'Select Avatar Image',
|
||||||
|
filters: [{ name: 'PNG Images', extensions: ['png'] }],
|
||||||
|
properties: ['openFile']
|
||||||
|
});
|
||||||
|
if (result.canceled || !result.filePaths[0]) return { ok: false, error: 'Cancelled' };
|
||||||
|
return matchaService.uploadAvatar(result.filePaths[0], mode);
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:delete-avatar', async () => {
|
||||||
|
return matchaService.deleteAvatar();
|
||||||
|
});
|
||||||
|
|
||||||
|
ipcMain.handle('matcha:reconnect', () => {
|
||||||
|
matchaService.manualReconnect();
|
||||||
|
return { ok: true };
|
||||||
|
});
|
||||||
|
|
||||||
ipcMain.handle('get-current-platform', () => {
|
ipcMain.handle('get-current-platform', () => {
|
||||||
return process.platform;
|
return process.platform;
|
||||||
});
|
});
|
||||||
|
|||||||
54
package-lock.json
generated
54
package-lock.json
generated
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.4.2",
|
"version": "2.4.8",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.4.2",
|
"version": "2.4.8",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"adm-zip": "^0.5.10",
|
"adm-zip": "^0.5.10",
|
||||||
@@ -17,7 +17,8 @@
|
|||||||
"encoding": "^0.1.13",
|
"encoding": "^0.1.13",
|
||||||
"fs-extra": "^11.3.3",
|
"fs-extra": "^11.3.3",
|
||||||
"tar": "^7.5.7",
|
"tar": "^7.5.7",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1",
|
||||||
|
"ws": "^8.16.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"electron": "^40.0.0",
|
"electron": "^40.0.0",
|
||||||
@@ -335,6 +336,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "BSD-2-Clause",
|
"license": "BSD-2-Clause",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"cross-dirname": "^0.1.0",
|
"cross-dirname": "^0.1.0",
|
||||||
"debug": "^4.3.4",
|
"debug": "^4.3.4",
|
||||||
@@ -789,7 +791,6 @@
|
|||||||
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
"integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fast-deep-equal": "^3.1.1",
|
"fast-deep-equal": "^3.1.1",
|
||||||
"fast-json-stable-stringify": "^2.0.0",
|
"fast-json-stable-stringify": "^2.0.0",
|
||||||
@@ -1592,7 +1593,8 @@
|
|||||||
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
"integrity": "sha512-+R08/oI0nl3vfPcqftZRpytksBXDzOUveBq/NBVx0sUp1axwzPQrKinNx5yd5sxPu8j1wIy8AfnVQ+5eFdha6Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true
|
"optional": true,
|
||||||
|
"peer": true
|
||||||
},
|
},
|
||||||
"node_modules/cross-spawn": {
|
"node_modules/cross-spawn": {
|
||||||
"version": "7.0.6",
|
"version": "7.0.6",
|
||||||
@@ -1803,6 +1805,27 @@
|
|||||||
"register-scheme": "github:devsnek/node-register-scheme"
|
"register-scheme": "github:devsnek/node-register-scheme"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/discord-rpc/node_modules/ws": {
|
||||||
|
"version": "7.5.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
||||||
|
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.3.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"bufferutil": "^4.0.1",
|
||||||
|
"utf-8-validate": "^5.0.2"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"bufferutil": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"utf-8-validate": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/dmg-builder": {
|
"node_modules/dmg-builder": {
|
||||||
"version": "26.6.0",
|
"version": "26.6.0",
|
||||||
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.6.0.tgz",
|
"resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.6.0.tgz",
|
||||||
@@ -2094,6 +2117,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@electron/asar": "^3.2.1",
|
"@electron/asar": "^3.2.1",
|
||||||
"debug": "^4.1.1",
|
"debug": "^4.1.1",
|
||||||
@@ -2114,6 +2138,7 @@
|
|||||||
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
"integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"graceful-fs": "^4.1.2",
|
"graceful-fs": "^4.1.2",
|
||||||
"jsonfile": "^4.0.0",
|
"jsonfile": "^4.0.0",
|
||||||
@@ -2129,6 +2154,7 @@
|
|||||||
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
"integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"graceful-fs": "^4.1.6"
|
"graceful-fs": "^4.1.6"
|
||||||
}
|
}
|
||||||
@@ -2139,6 +2165,7 @@
|
|||||||
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
"integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">= 4.0.0"
|
"node": ">= 4.0.0"
|
||||||
}
|
}
|
||||||
@@ -2155,7 +2182,6 @@
|
|||||||
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
"resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
|
||||||
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
"integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"iconv-lite": "^0.6.2"
|
"iconv-lite": "^0.6.2"
|
||||||
}
|
}
|
||||||
@@ -3399,6 +3425,7 @@
|
|||||||
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
"integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minimist": "^1.2.6"
|
"minimist": "^1.2.6"
|
||||||
},
|
},
|
||||||
@@ -3751,7 +3778,6 @@
|
|||||||
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"peer": true,
|
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
},
|
},
|
||||||
@@ -3781,6 +3807,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"commander": "^9.4.0"
|
"commander": "^9.4.0"
|
||||||
},
|
},
|
||||||
@@ -3798,6 +3825,7 @@
|
|||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"optional": true,
|
"optional": true,
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": "^12.20.0 || >=14"
|
"node": "^12.20.0 || >=14"
|
||||||
}
|
}
|
||||||
@@ -4006,6 +4034,7 @@
|
|||||||
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
"deprecated": "Rimraf versions prior to v4 are no longer supported",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "ISC",
|
"license": "ISC",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"glob": "^7.1.3"
|
"glob": "^7.1.3"
|
||||||
},
|
},
|
||||||
@@ -4403,6 +4432,7 @@
|
|||||||
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
"integrity": "sha512-yYrrsWnrXMcdsnu/7YMYAofM1ktpL5By7vZhf15CrXijWWrEYZks5AXBudalfSWJLlnen/QUJUB5aoB0kqZUGA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"mkdirp": "^0.5.1",
|
"mkdirp": "^0.5.1",
|
||||||
"rimraf": "~2.6.2"
|
"rimraf": "~2.6.2"
|
||||||
@@ -4712,16 +4742,16 @@
|
|||||||
"license": "ISC"
|
"license": "ISC"
|
||||||
},
|
},
|
||||||
"node_modules/ws": {
|
"node_modules/ws": {
|
||||||
"version": "7.5.10",
|
"version": "8.19.0",
|
||||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz",
|
"resolved": "https://registry.npmjs.org/ws/-/ws-8.19.0.tgz",
|
||||||
"integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==",
|
"integrity": "sha512-blAT2mjOEIi0ZzruJfIhb3nps74PRWTCz1IjglWEEpQl5XS/UNama6u2/rjFkDDouqr4L67ry+1aGIALViWjDg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.3.0"
|
"node": ">=10.0.0"
|
||||||
},
|
},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"bufferutil": "^4.0.1",
|
"bufferutil": "^4.0.1",
|
||||||
"utf-8-validate": "^5.0.2"
|
"utf-8-validate": ">=5.0.2"
|
||||||
},
|
},
|
||||||
"peerDependenciesMeta": {
|
"peerDependenciesMeta": {
|
||||||
"bufferutil": {
|
"bufferutil": {
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "hytale-f2p-launcher",
|
"name": "hytale-f2p-launcher",
|
||||||
"version": "2.4.7",
|
"version": "2.4.8",
|
||||||
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
|
"description": "A modern, cross-platform launcher for Hytale with automatic updates and multi-client support",
|
||||||
"homepage": "https://git.sanhost.net/sanasol/hytale-f2p",
|
"homepage": "https://git.sanhost.net/sanasol/hytale-f2p",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
@@ -56,7 +56,8 @@
|
|||||||
"electron-updater": "^6.7.3",
|
"electron-updater": "^6.7.3",
|
||||||
"fs-extra": "^11.3.3",
|
"fs-extra": "^11.3.3",
|
||||||
"tar": "^7.5.7",
|
"tar": "^7.5.7",
|
||||||
"uuid": "^9.0.1"
|
"uuid": "^9.0.1",
|
||||||
|
"ws": "^8.16.0"
|
||||||
},
|
},
|
||||||
"build": {
|
"build": {
|
||||||
"appId": "com.hytalef2p.launcher",
|
"appId": "com.hytalef2p.launcher",
|
||||||
|
|||||||
35
preload.js
35
preload.js
@@ -79,6 +79,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
saveVersionBranch: (branch) => ipcRenderer.invoke('save-version-branch', branch),
|
saveVersionBranch: (branch) => ipcRenderer.invoke('save-version-branch', branch),
|
||||||
loadVersionBranch: () => ipcRenderer.invoke('load-version-branch'),
|
loadVersionBranch: () => ipcRenderer.invoke('load-version-branch'),
|
||||||
loadVersionClient: () => ipcRenderer.invoke('load-version-client'),
|
loadVersionClient: () => ipcRenderer.invoke('load-version-client'),
|
||||||
|
getGameInfo: () => ipcRenderer.invoke('get-game-info'),
|
||||||
|
|
||||||
acceptFirstLaunchUpdate: (existingGame) => ipcRenderer.invoke('accept-first-launch-update', existingGame),
|
acceptFirstLaunchUpdate: (existingGame) => ipcRenderer.invoke('accept-first-launch-update', existingGame),
|
||||||
markAsLaunched: () => ipcRenderer.invoke('mark-as-launched'),
|
markAsLaunched: () => ipcRenderer.invoke('mark-as-launched'),
|
||||||
@@ -152,5 +153,39 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
},
|
},
|
||||||
onUpdateError: (callback) => {
|
onUpdateError: (callback) => {
|
||||||
ipcRenderer.on('update-error', (event, data) => callback(data));
|
ipcRenderer.on('update-error', (event, data) => callback(data));
|
||||||
|
},
|
||||||
|
|
||||||
|
// Matcha Social API
|
||||||
|
matcha: {
|
||||||
|
log: (level, ...args) => ipcRenderer.invoke('matcha:log', level, ...args),
|
||||||
|
register: (username, password, password2) => ipcRenderer.invoke('matcha:register', username, password, password2),
|
||||||
|
confirmRegister: (pendingId, proofId) => ipcRenderer.invoke('matcha:confirm-register', pendingId, proofId),
|
||||||
|
login: (handle, password) => ipcRenderer.invoke('matcha:login', handle, password),
|
||||||
|
logout: () => ipcRenderer.invoke('matcha:logout'),
|
||||||
|
getAuthState: () => ipcRenderer.invoke('matcha:get-auth-state'),
|
||||||
|
getMe: () => ipcRenderer.invoke('matcha:get-me'),
|
||||||
|
getUser: (userId) => ipcRenderer.invoke('matcha:get-user', userId),
|
||||||
|
getFriends: () => ipcRenderer.invoke('matcha:get-friends'),
|
||||||
|
friendRequest: (handle) => ipcRenderer.invoke('matcha:friend-request', handle),
|
||||||
|
friendAccept: (requestId) => ipcRenderer.invoke('matcha:friend-accept', requestId),
|
||||||
|
friendReject: (requestId) => ipcRenderer.invoke('matcha:friend-reject', requestId),
|
||||||
|
friendCancel: (requestId) => ipcRenderer.invoke('matcha:friend-cancel', requestId),
|
||||||
|
friendRemove: (friendId) => ipcRenderer.invoke('matcha:friend-remove', friendId),
|
||||||
|
getMessages: (withTarget, cursor, after) => ipcRenderer.invoke('matcha:get-messages', withTarget, cursor, after),
|
||||||
|
sendMessage: (to, body, replyTo) => ipcRenderer.invoke('matcha:send-message', to, body, replyTo),
|
||||||
|
deleteMessage: (messageId) => ipcRenderer.invoke('matcha:delete-message', messageId),
|
||||||
|
getUnread: () => ipcRenderer.invoke('matcha:get-unread'),
|
||||||
|
clearUnread: (withTarget) => ipcRenderer.invoke('matcha:clear-unread', withTarget),
|
||||||
|
uploadAvatar: (mode) => ipcRenderer.invoke('matcha:upload-avatar', mode),
|
||||||
|
deleteAvatar: () => ipcRenderer.invoke('matcha:delete-avatar'),
|
||||||
|
reconnect: () => ipcRenderer.invoke('matcha:reconnect'),
|
||||||
|
onWsMessage: (callback) => ipcRenderer.on('matcha:ws:message', (event, data) => callback(data)),
|
||||||
|
onWsConnected: (callback) => ipcRenderer.on('matcha:ws:connected', (event, data) => callback(data)),
|
||||||
|
onWsDisconnected: (callback) => ipcRenderer.on('matcha:ws:disconnected', () => callback()),
|
||||||
|
onMessageDeleted: (callback) => ipcRenderer.on('matcha:ws:message-deleted', (event, data) => callback(data)),
|
||||||
|
onAvatarUpdated: (callback) => ipcRenderer.on('matcha:ws:avatar-updated', (event, data) => callback(data)),
|
||||||
|
onBanned: (callback) => ipcRenderer.on('matcha:ws:banned', (event, data) => callback(data)),
|
||||||
|
onAnnouncement: (callback) => ipcRenderer.on('matcha:ws:announcement', (event, data) => callback(data)),
|
||||||
|
onMaxRetries: (callback) => ipcRenderer.on('matcha:ws:max-retries', () => callback())
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user