mirror of
https://git.sanhost.net/sanasol/hytale-f2p
synced 2026-02-28 16:11:46 -03:00
feat: add password protection UI and fix launch flow
- Password management UI in settings (set/change/remove password) - Shield icon on play button for protected identities - Interactive password popup on launch with inline error display - Fix: re-throw password errors instead of falling to local tokens - Fix: password popup properly cleans up on success/cancel - Fix: expose updatePasswordShieldIcon for cross-module access Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -77,6 +77,7 @@
|
||||
<button class="identity-btn" onclick="toggleIdentityDropdown()">
|
||||
<i class="fas fa-id-badge"></i>
|
||||
<span id="currentIdentityName">Player</span>
|
||||
<i id="passwordShieldIcon" class="fas fa-unlock password-shield unprotected" data-tooltip="Click to protect identity" onclick="event.stopPropagation(); openPasswordModal()"></i>
|
||||
<i class="fas fa-chevron-down"></i>
|
||||
</button>
|
||||
<div class="identity-dropdown" id="identityDropdown">
|
||||
@@ -830,6 +831,41 @@
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="uuid-advanced-section" style="margin-top: 12px;">
|
||||
<button id="passwordSectionToggle" class="uuid-advanced-toggle">
|
||||
<i class="fas fa-chevron-right uuid-advanced-chevron"></i>
|
||||
<span>Password Protection</span>
|
||||
</button>
|
||||
<div id="passwordSectionContent" class="uuid-advanced-content" style="display: none;">
|
||||
<h3 class="uuid-section-title">Protect Your Identity</h3>
|
||||
<p class="uuid-custom-hint" style="margin-bottom: 12px;">
|
||||
<i class="fas fa-shield-alt"></i>
|
||||
<span>Set a password to prevent others from using your UUID</span>
|
||||
</p>
|
||||
<div id="passwordStatusMsg" class="uuid-custom-hint" style="margin-bottom: 8px; color: #93a3b8;"></div>
|
||||
<div id="passwordSetForm">
|
||||
<div class="uuid-custom-form" style="margin-bottom: 8px;">
|
||||
<input type="password" id="currentPasswordInput" class="uuid-input"
|
||||
placeholder="Current password" style="display: none;" />
|
||||
</div>
|
||||
<div class="uuid-custom-form" style="margin-bottom: 8px;">
|
||||
<input type="password" id="newPasswordInput" class="uuid-input"
|
||||
placeholder="New password (min 6 chars)" />
|
||||
</div>
|
||||
<div class="uuid-custom-form">
|
||||
<button id="setPasswordBtn" class="uuid-set-btn" onclick="handleSetPassword()">
|
||||
<i class="fas fa-lock"></i>
|
||||
<span>Set Password</span>
|
||||
</button>
|
||||
<button id="removePasswordBtn" class="uuid-cancel-btn" onclick="handleRemovePassword()" style="display: none;">
|
||||
<i class="fas fa-unlock"></i>
|
||||
<span>Remove Password</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -861,6 +897,59 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Password Protection Modal -->
|
||||
<div id="passwordModal" class="uuid-modal" style="display: none;">
|
||||
<div class="uuid-modal-content" style="max-width: 420px;">
|
||||
<div class="uuid-modal-header">
|
||||
<h2 class="uuid-modal-title">
|
||||
<i class="fas fa-shield-alt mr-2"></i>
|
||||
<span>Identity Protection</span>
|
||||
</h2>
|
||||
<button class="modal-close-btn" onclick="closePasswordModal()">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="uuid-modal-body" style="padding: 16px 20px;">
|
||||
<div style="display: flex; align-items: center; gap: 12px; margin-bottom: 16px; padding: 12px; background: rgba(255,255,255,0.03); border-radius: 8px; border: 1px solid rgba(255,255,255,0.06);">
|
||||
<i class="fas fa-user" style="color: #22c55e; font-size: 1.2em;"></i>
|
||||
<div style="flex:1; min-width:0;">
|
||||
<div id="pwModalName" style="font-weight: 600; font-size: 1em; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;">Player</div>
|
||||
<div id="pwModalUuid" style="font-size: 0.7em; color: #6b7280; font-family: monospace; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"></div>
|
||||
</div>
|
||||
<div id="pwModalStatusBadge" style="flex-shrink:0;"></div>
|
||||
</div>
|
||||
|
||||
<div id="pwModalStatusText" style="margin-bottom: 14px; font-size: 0.85em; color: #93a3b8; text-align: center;"></div>
|
||||
|
||||
<div id="pwModalSetForm">
|
||||
<div style="margin-bottom: 10px;">
|
||||
<input type="password" id="pwModalCurrentPassword" class="uuid-input"
|
||||
placeholder="Current password" style="display: none; width: 100%;" />
|
||||
</div>
|
||||
<div style="margin-bottom: 10px;">
|
||||
<input type="password" id="pwModalNewPassword" class="uuid-input"
|
||||
placeholder="New password (min 6 chars)" style="width: 100%;" />
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button id="pwModalSetBtn" class="uuid-set-btn" style="flex:1;" onclick="handlePasswordModalSet()">
|
||||
<i class="fas fa-lock"></i>
|
||||
<span>Set Password</span>
|
||||
</button>
|
||||
<button id="pwModalRemoveBtn" class="uuid-cancel-btn" style="display: none;" onclick="handlePasswordModalRemove()">
|
||||
<i class="fas fa-unlock"></i>
|
||||
<span>Remove</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="pwModalUsernameInfo" style="margin-top: 14px; padding: 10px; background: rgba(34,197,94,0.06); border-radius: 6px; border: 1px solid rgba(34,197,94,0.15); font-size: 0.8em; color: #93a3b8; display: none;">
|
||||
<i class="fas fa-info-circle" style="color: #22c55e;"></i>
|
||||
<span>Setting a password also reserves your username — no one else can use it.</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="version-display-bottom">
|
||||
<i class="fas fa-code-branch"></i>
|
||||
<span id="launcherVersion"></span>
|
||||
|
||||
@@ -292,6 +292,25 @@ export async function launch() {
|
||||
}
|
||||
resetPlayButton();
|
||||
|
||||
if (result.usernameTaken) {
|
||||
// Username reserved by another player
|
||||
if (window.LauncherUI && window.LauncherUI.showError) {
|
||||
window.LauncherUI.showError('This username is reserved by another player. Please change your player name in Identity settings.');
|
||||
} else {
|
||||
showNotification('This username is reserved by another player. Please change your player name.', 'error');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.passwordRequired) {
|
||||
// UUID has a password — show interactive password dialog
|
||||
const launchResult = await promptForPasswordAndLaunch(playerName, javaPath, gpuPreference);
|
||||
if (launchResult && launchResult.success) {
|
||||
if (window.electronAPI.minimizeWindow) setTimeout(() => { window.electronAPI.minimizeWindow(); }, 500);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (result.success) {
|
||||
if (window.electronAPI.minimizeWindow) {
|
||||
setTimeout(() => {
|
||||
@@ -354,6 +373,177 @@ export async function launch() {
|
||||
}
|
||||
}
|
||||
|
||||
function promptForPasswordAndLaunch(playerName, javaPath, gpuPreference) {
|
||||
return new Promise((resolve) => {
|
||||
// Remove any existing password prompt
|
||||
const existing = document.querySelector('.custom-confirm-modal');
|
||||
if (existing) existing.remove();
|
||||
|
||||
const overlay = document.createElement('div');
|
||||
overlay.className = 'custom-confirm-modal';
|
||||
overlay.style.cssText = `
|
||||
position: fixed;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background: rgba(0, 0, 0, 0.8);
|
||||
backdrop-filter: blur(4px);
|
||||
z-index: 20000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const dialog = document.createElement('div');
|
||||
dialog.style.cssText = `
|
||||
background: #1f2937;
|
||||
border-radius: 12px;
|
||||
padding: 0;
|
||||
min-width: 380px;
|
||||
max-width: 420px;
|
||||
box-shadow: 0 20px 40px rgba(0, 0, 0, 0.6);
|
||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
||||
`;
|
||||
|
||||
dialog.innerHTML = `
|
||||
<div style="padding: 20px 24px; border-bottom: 1px solid rgba(255,255,255,0.1);">
|
||||
<div style="display: flex; align-items: center; gap: 10px; color: #f59e0b;">
|
||||
<i class="fas fa-lock" style="font-size: 20px;"></i>
|
||||
<h3 style="margin: 0; font-size: 1.1rem; font-weight: 600; color: #e5e7eb;">Password Required</h3>
|
||||
</div>
|
||||
</div>
|
||||
<div style="padding: 20px 24px;">
|
||||
<p style="margin: 0 0 12px 0; color: #9ca3af; font-size: 0.9rem; line-height: 1.5;">This identity is password-protected. Enter your password to continue.</p>
|
||||
<div id="pwErrorMsg" style="display: none; margin-bottom: 12px; padding: 8px 12px; background: rgba(239,68,68,0.15); border: 1px solid rgba(239,68,68,0.3); border-radius: 6px; color: #f87171; font-size: 0.85rem;"></div>
|
||||
<input type="password" id="launchPasswordInput" style="
|
||||
width: 100%;
|
||||
box-sizing: border-box;
|
||||
padding: 10px 14px;
|
||||
background: rgba(0,0,0,0.3);
|
||||
border: 1px solid rgba(255,255,255,0.15);
|
||||
border-radius: 8px;
|
||||
color: #e5e7eb;
|
||||
font-size: 0.95rem;
|
||||
outline: none;
|
||||
" placeholder="Password" autofocus />
|
||||
</div>
|
||||
<div style="padding: 16px 24px; display: flex; gap: 10px; justify-content: flex-end; border-top: 1px solid rgba(255,255,255,0.1);">
|
||||
<button id="pwCancelBtn" style="
|
||||
background: transparent;
|
||||
color: #9ca3af;
|
||||
border: 1px solid rgba(156,163,175,0.3);
|
||||
padding: 9px 18px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-size: 0.9rem;
|
||||
">Cancel</button>
|
||||
<button id="pwConfirmBtn" style="
|
||||
background: #f59e0b;
|
||||
color: #000;
|
||||
border: none;
|
||||
padding: 9px 18px;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
">Login</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
overlay.appendChild(dialog);
|
||||
document.body.appendChild(overlay);
|
||||
|
||||
const input = overlay.querySelector('#launchPasswordInput');
|
||||
const confirmBtn = overlay.querySelector('#pwConfirmBtn');
|
||||
const cancelBtn = overlay.querySelector('#pwCancelBtn');
|
||||
const errorMsg = overlay.querySelector('#pwErrorMsg');
|
||||
|
||||
let busy = false;
|
||||
|
||||
const close = (result) => {
|
||||
overlay.remove();
|
||||
isDownloading = false;
|
||||
if (window.LauncherUI) window.LauncherUI.hideProgress();
|
||||
resetPlayButton();
|
||||
resolve(result);
|
||||
};
|
||||
|
||||
const showError = (msg) => {
|
||||
errorMsg.textContent = msg;
|
||||
errorMsg.style.display = 'block';
|
||||
input.style.borderColor = 'rgba(239,68,68,0.5)';
|
||||
input.value = '';
|
||||
input.focus();
|
||||
};
|
||||
|
||||
const tryLogin = async () => {
|
||||
const password = input.value;
|
||||
if (!password) {
|
||||
showError('Please enter your password.');
|
||||
return;
|
||||
}
|
||||
if (busy) return;
|
||||
busy = true;
|
||||
|
||||
// Show loading state
|
||||
confirmBtn.disabled = true;
|
||||
confirmBtn.textContent = 'Logging in...';
|
||||
errorMsg.style.display = 'none';
|
||||
input.style.borderColor = 'rgba(255,255,255,0.15)';
|
||||
|
||||
try {
|
||||
if (window.LauncherUI) window.LauncherUI.showProgress();
|
||||
isDownloading = true;
|
||||
const playBtn = document.getElementById('play-btn');
|
||||
const playText = playBtn?.querySelector('.play-text');
|
||||
if (playBtn) { playBtn.disabled = true; }
|
||||
if (playText) { playText.textContent = 'LAUNCHING...'; }
|
||||
|
||||
const result = await window.electronAPI.launchGameWithPassword(playerName, javaPath, '', gpuPreference, password);
|
||||
|
||||
if (result.success) {
|
||||
overlay.remove();
|
||||
isDownloading = false;
|
||||
if (window.LauncherUI) window.LauncherUI.hideProgress();
|
||||
resetPlayButton();
|
||||
resolve(result);
|
||||
return;
|
||||
}
|
||||
|
||||
// Wrong password
|
||||
if (result.passwordRequired) {
|
||||
showError(result.error || 'Incorrect password. Please try again.');
|
||||
} else {
|
||||
showError(result.error || 'Launch failed.');
|
||||
}
|
||||
|
||||
isDownloading = false;
|
||||
if (window.LauncherUI) window.LauncherUI.hideProgress();
|
||||
resetPlayButton();
|
||||
} catch (err) {
|
||||
showError(err.message || 'An error occurred.');
|
||||
isDownloading = false;
|
||||
if (window.LauncherUI) window.LauncherUI.hideProgress();
|
||||
resetPlayButton();
|
||||
} finally {
|
||||
busy = false;
|
||||
confirmBtn.disabled = false;
|
||||
confirmBtn.textContent = 'Login';
|
||||
}
|
||||
};
|
||||
|
||||
confirmBtn.addEventListener('click', tryLogin);
|
||||
cancelBtn.addEventListener('click', () => close(null));
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') tryLogin();
|
||||
if (e.key === 'Escape') close(null);
|
||||
});
|
||||
|
||||
setTimeout(() => input.focus(), 100);
|
||||
});
|
||||
}
|
||||
|
||||
function showCustomConfirm(message, title, onConfirm, onCancel = null, confirmText, cancelText) {
|
||||
// Apply defaults with i18n support
|
||||
title = title || (window.i18n ? window.i18n.t('confirm.defaultTitle') : 'Confirm Action');
|
||||
@@ -712,7 +902,7 @@ async function loadIdentities() {
|
||||
}
|
||||
}
|
||||
|
||||
function renderIdentityList(mappings, currentUsername) {
|
||||
async function renderIdentityList(mappings, currentUsername) {
|
||||
const list = document.getElementById('identityList');
|
||||
if (!list) return;
|
||||
|
||||
@@ -721,13 +911,31 @@ function renderIdentityList(mappings, currentUsername) {
|
||||
return;
|
||||
}
|
||||
|
||||
list.innerHTML = mappings.map(m => {
|
||||
// Check password status for all identities in parallel
|
||||
const statusChecks = mappings.map(async m => {
|
||||
try {
|
||||
if (m.uuid && window.electronAPI?.checkPasswordStatus) {
|
||||
const s = await window.electronAPI.checkPasswordStatus(m.uuid);
|
||||
return s?.hasPassword || false;
|
||||
}
|
||||
} catch {}
|
||||
return false;
|
||||
});
|
||||
const statuses = await Promise.all(statusChecks);
|
||||
|
||||
list.innerHTML = mappings.map((m, i) => {
|
||||
const safe = escapeHtml(m.username);
|
||||
const isActive = m.username === currentUsername;
|
||||
const hasPassword = statuses[i];
|
||||
const pwBadge = hasPassword
|
||||
? '<span class="pw-badge locked"><i class="fas fa-lock"></i></span>'
|
||||
: '<span class="pw-badge unlocked"><i class="fas fa-unlock"></i></span>';
|
||||
return `
|
||||
<div class="identity-item ${m.username === currentUsername ? 'active' : ''}"
|
||||
<div class="identity-item ${isActive ? 'active' : ''}"
|
||||
onclick="switchIdentity('${safe.replace(/'/g, "'")}')">
|
||||
<span>${safe}</span>
|
||||
${m.username === currentUsername ? '<i class="fas fa-check ml-auto"></i>' : ''}
|
||||
${pwBadge}
|
||||
${isActive ? '<i class="fas fa-check" style="margin-left:4px;"></i>' : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
@@ -774,6 +982,9 @@ window.switchIdentity = async (username) => {
|
||||
if (settingsInput) settingsInput.value = username;
|
||||
if (window.loadCurrentUuid) window.loadCurrentUuid();
|
||||
|
||||
// Update password shield icon for new identity
|
||||
if (window.updatePasswordShieldIcon) window.updatePasswordShieldIcon();
|
||||
|
||||
} catch (error) {
|
||||
console.error('Failed to switch identity:', error);
|
||||
}
|
||||
|
||||
@@ -941,6 +941,230 @@ function toggleAdvancedSection() {
|
||||
}
|
||||
}
|
||||
|
||||
// Password section toggle
|
||||
function togglePasswordSection() {
|
||||
const content = document.getElementById('passwordSectionContent');
|
||||
const toggle = document.getElementById('passwordSectionToggle');
|
||||
if (!content || !toggle) return;
|
||||
const isOpen = content.style.display !== 'none';
|
||||
content.style.display = isOpen ? 'none' : 'block';
|
||||
const chevron = toggle.querySelector('.uuid-advanced-chevron');
|
||||
if (chevron) chevron.classList.toggle('open', !isOpen);
|
||||
if (!isOpen) refreshPasswordStatus();
|
||||
}
|
||||
|
||||
async function refreshPasswordStatus() {
|
||||
const statusMsg = document.getElementById('passwordStatusMsg');
|
||||
const currentPwInput = document.getElementById('currentPasswordInput');
|
||||
const removeBtn = document.getElementById('removePasswordBtn');
|
||||
const setBtn = document.getElementById('setPasswordBtn');
|
||||
try {
|
||||
const uuid = await window.electronAPI?.getCurrentUuid();
|
||||
if (!uuid) { if (statusMsg) statusMsg.textContent = 'No UUID available'; return; }
|
||||
const result = await window.electronAPI.checkPasswordStatus(uuid);
|
||||
if (result && result.hasPassword) {
|
||||
if (statusMsg) statusMsg.innerHTML = '<i class="fas fa-lock" style="color:#22c55e"></i> Password is set for this UUID';
|
||||
if (currentPwInput) currentPwInput.style.display = '';
|
||||
if (removeBtn) removeBtn.style.display = '';
|
||||
if (setBtn) { const span = setBtn.querySelector('span'); if (span) span.textContent = 'Change Password'; }
|
||||
} else {
|
||||
if (statusMsg) statusMsg.innerHTML = '<i class="fas fa-unlock" style="color:#f59e0b"></i> No password set — anyone can use this UUID';
|
||||
if (currentPwInput) currentPwInput.style.display = 'none';
|
||||
if (removeBtn) removeBtn.style.display = 'none';
|
||||
if (setBtn) { const span = setBtn.querySelector('span'); if (span) span.textContent = 'Set Password'; }
|
||||
}
|
||||
} catch (e) {
|
||||
if (statusMsg) statusMsg.textContent = 'Could not check password status';
|
||||
}
|
||||
}
|
||||
|
||||
window.handleSetPassword = async function () {
|
||||
const newPw = document.getElementById('newPasswordInput');
|
||||
const currentPw = document.getElementById('currentPasswordInput');
|
||||
if (!newPw || !newPw.value || newPw.value.length < 6) {
|
||||
showNotification('Password must be at least 6 characters', 'error');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uuid = await window.electronAPI.getCurrentUuid();
|
||||
const result = await window.electronAPI.setPlayerPassword(uuid, newPw.value, currentPw?.value || null);
|
||||
if (result.success) {
|
||||
showNotification('Password set successfully', 'success');
|
||||
newPw.value = '';
|
||||
if (currentPw) currentPw.value = '';
|
||||
refreshPasswordStatus();
|
||||
updatePasswordShieldIcon();
|
||||
} else {
|
||||
showNotification(result.error || 'Failed to set password', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification('Error: ' + e.message, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
window.handleRemovePassword = async function () {
|
||||
const currentPw = document.getElementById('currentPasswordInput');
|
||||
if (!currentPw || !currentPw.value) {
|
||||
showNotification('Enter your current password to remove it', 'error');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uuid = await window.electronAPI.getCurrentUuid();
|
||||
const result = await window.electronAPI.removePlayerPassword(uuid, currentPw.value);
|
||||
if (result.success) {
|
||||
showNotification('Password removed', 'success');
|
||||
currentPw.value = '';
|
||||
refreshPasswordStatus();
|
||||
updatePasswordShieldIcon();
|
||||
} else {
|
||||
showNotification(result.error || 'Failed to remove password', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification('Error: ' + e.message, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// ─── Password Shield Icon ───
|
||||
|
||||
window.updatePasswordShieldIcon = updatePasswordShieldIcon;
|
||||
async function updatePasswordShieldIcon() {
|
||||
const icon = document.getElementById('passwordShieldIcon');
|
||||
if (!icon) return;
|
||||
try {
|
||||
const uuid = await window.electronAPI?.getCurrentUuid();
|
||||
if (!uuid) {
|
||||
icon.className = 'fas fa-unlock password-shield unprotected';
|
||||
icon.setAttribute('data-tooltip', 'No identity loaded');
|
||||
return;
|
||||
}
|
||||
const result = await window.electronAPI.checkPasswordStatus(uuid);
|
||||
if (result && result.hasPassword) {
|
||||
icon.className = 'fas fa-lock password-shield protected';
|
||||
icon.setAttribute('data-tooltip', 'Protected — click to manage');
|
||||
} else {
|
||||
icon.className = 'fas fa-unlock password-shield unprotected';
|
||||
icon.setAttribute('data-tooltip', 'Click to protect identity');
|
||||
}
|
||||
} catch (e) {
|
||||
icon.className = 'fas fa-unlock password-shield unprotected';
|
||||
icon.setAttribute('data-tooltip', 'Click to protect identity');
|
||||
}
|
||||
}
|
||||
|
||||
// ─── Password Modal ───
|
||||
|
||||
window.openPasswordModal = async function () {
|
||||
const modal = document.getElementById('passwordModal');
|
||||
if (!modal) return;
|
||||
modal.style.display = 'flex';
|
||||
|
||||
// Clear inputs
|
||||
const newPw = document.getElementById('pwModalNewPassword');
|
||||
const curPw = document.getElementById('pwModalCurrentPassword');
|
||||
if (newPw) newPw.value = '';
|
||||
if (curPw) curPw.value = '';
|
||||
|
||||
// Load current identity info
|
||||
try {
|
||||
const username = await window.electronAPI?.loadUsername() || 'Player';
|
||||
const uuid = await window.electronAPI?.getCurrentUuid() || '';
|
||||
document.getElementById('pwModalName').textContent = username;
|
||||
document.getElementById('pwModalUuid').textContent = uuid || 'No UUID';
|
||||
|
||||
// Check password status
|
||||
const result = uuid ? await window.electronAPI.checkPasswordStatus(uuid) : null;
|
||||
const badge = document.getElementById('pwModalStatusBadge');
|
||||
const statusText = document.getElementById('pwModalStatusText');
|
||||
const curPwInput = document.getElementById('pwModalCurrentPassword');
|
||||
const removeBtn = document.getElementById('pwModalRemoveBtn');
|
||||
const setBtn = document.getElementById('pwModalSetBtn');
|
||||
const usernameInfo = document.getElementById('pwModalUsernameInfo');
|
||||
|
||||
if (result && result.hasPassword) {
|
||||
badge.innerHTML = '<span style="color:#22c55e;font-size:0.8em;padding:3px 8px;background:rgba(34,197,94,0.15);border-radius:6px;"><i class="fas fa-lock"></i> Protected</span>';
|
||||
statusText.innerHTML = '<i class="fas fa-check-circle" style="color:#22c55e"></i> Password set — your UUID and username are protected';
|
||||
if (curPwInput) curPwInput.style.display = '';
|
||||
if (removeBtn) removeBtn.style.display = '';
|
||||
if (setBtn) { const s = setBtn.querySelector('span'); if (s) s.textContent = 'Change Password'; }
|
||||
if (usernameInfo) usernameInfo.style.display = 'none';
|
||||
} else {
|
||||
badge.innerHTML = '<span style="color:#f59e0b;font-size:0.8em;padding:3px 8px;background:rgba(245,158,11,0.1);border-radius:6px;"><i class="fas fa-unlock"></i> Open</span>';
|
||||
statusText.innerHTML = '<i class="fas fa-exclamation-triangle" style="color:#f59e0b"></i> Anyone can use this UUID and username';
|
||||
if (curPwInput) curPwInput.style.display = 'none';
|
||||
if (removeBtn) removeBtn.style.display = 'none';
|
||||
if (setBtn) { const s = setBtn.querySelector('span'); if (s) s.textContent = 'Set Password'; }
|
||||
if (usernameInfo) usernameInfo.style.display = '';
|
||||
}
|
||||
} catch (e) {
|
||||
document.getElementById('pwModalStatusText').textContent = 'Could not check password status';
|
||||
}
|
||||
};
|
||||
|
||||
window.closePasswordModal = function () {
|
||||
const modal = document.getElementById('passwordModal');
|
||||
if (modal) modal.style.display = 'none';
|
||||
};
|
||||
|
||||
window.handlePasswordModalSet = async function () {
|
||||
const newPw = document.getElementById('pwModalNewPassword');
|
||||
const curPw = document.getElementById('pwModalCurrentPassword');
|
||||
if (!newPw || !newPw.value || newPw.value.length < 6) {
|
||||
showNotification('Password must be at least 6 characters', 'error');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uuid = await window.electronAPI.getCurrentUuid();
|
||||
const result = await window.electronAPI.setPlayerPassword(uuid, newPw.value, curPw?.value || null);
|
||||
if (result.success) {
|
||||
const msg = result.username_reserved ? 'Password set! Username "' + (result.reserved_username || '') + '" reserved.' : 'Password set!';
|
||||
showNotification(msg, 'success');
|
||||
newPw.value = '';
|
||||
if (curPw) curPw.value = '';
|
||||
updatePasswordShieldIcon();
|
||||
openPasswordModal(); // refresh modal state
|
||||
} else {
|
||||
showNotification(result.error || 'Failed to set password', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification('Error: ' + e.message, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
window.handlePasswordModalRemove = async function () {
|
||||
const curPw = document.getElementById('pwModalCurrentPassword');
|
||||
if (!curPw || !curPw.value) {
|
||||
showNotification('Enter your current password to remove it', 'error');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const uuid = await window.electronAPI.getCurrentUuid();
|
||||
const result = await window.electronAPI.removePlayerPassword(uuid, curPw.value);
|
||||
if (result.success) {
|
||||
showNotification('Password removed', 'success');
|
||||
curPw.value = '';
|
||||
updatePasswordShieldIcon();
|
||||
openPasswordModal(); // refresh modal state
|
||||
} else {
|
||||
showNotification(result.error || 'Failed to remove password', 'error');
|
||||
}
|
||||
} catch (e) {
|
||||
showNotification('Error: ' + e.message, 'error');
|
||||
}
|
||||
};
|
||||
|
||||
// Close modal on backdrop click
|
||||
document.addEventListener('click', (e) => {
|
||||
const modal = document.getElementById('passwordModal');
|
||||
if (modal && e.target === modal) closePasswordModal();
|
||||
});
|
||||
|
||||
// Bind password section toggle (for legacy UUID modal section)
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
const pwToggle = document.getElementById('passwordSectionToggle');
|
||||
if (pwToggle) pwToggle.addEventListener('click', togglePasswordSection);
|
||||
updatePasswordShieldIcon();
|
||||
});
|
||||
|
||||
window.regenerateUuidForUser = async function (username) {
|
||||
try {
|
||||
const message = window.i18n ? window.i18n.t('confirm.regenerateUuidMessage') : 'Are you sure you want to generate a new UUID? This will change your player identity.';
|
||||
|
||||
@@ -6368,6 +6368,79 @@ input[type="text"].uuid-input,
|
||||
color: #22c55e;
|
||||
}
|
||||
|
||||
.identity-btn .password-shield {
|
||||
position: relative;
|
||||
font-size: 0.8rem;
|
||||
padding: 4px 6px;
|
||||
border-radius: 6px;
|
||||
transition: all 0.2s ease;
|
||||
cursor: pointer;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
.identity-btn .password-shield.unprotected {
|
||||
color: #f59e0b;
|
||||
background: rgba(245, 158, 11, 0.15);
|
||||
border: 1px solid rgba(245, 158, 11, 0.3);
|
||||
animation: shieldPulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
.identity-btn .password-shield.protected {
|
||||
color: #22c55e;
|
||||
background: rgba(34, 197, 94, 0.1);
|
||||
border: 1px solid rgba(34, 197, 94, 0.2);
|
||||
animation: none;
|
||||
}
|
||||
|
||||
.identity-btn .password-shield:hover {
|
||||
transform: scale(1.15);
|
||||
filter: brightness(1.3);
|
||||
}
|
||||
|
||||
.identity-btn .password-shield::after {
|
||||
content: attr(data-tooltip);
|
||||
position: absolute;
|
||||
bottom: calc(100% + 8px);
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
background: rgba(0, 0, 0, 0.9);
|
||||
color: #fff;
|
||||
padding: 6px 10px;
|
||||
border-radius: 6px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 400;
|
||||
white-space: nowrap;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s ease;
|
||||
border: 1px solid rgba(255,255,255,0.1);
|
||||
}
|
||||
|
||||
.identity-btn .password-shield:hover::after {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
@keyframes shieldPulse {
|
||||
0%, 100% { opacity: 1; }
|
||||
50% { opacity: 0.5; }
|
||||
}
|
||||
|
||||
/* Password status in identity dropdown */
|
||||
.identity-item .pw-badge {
|
||||
font-size: 0.65rem;
|
||||
margin-left: auto;
|
||||
padding: 1px 5px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
.identity-item .pw-badge.locked {
|
||||
color: #22c55e;
|
||||
background: rgba(34, 197, 94, 0.15);
|
||||
}
|
||||
.identity-item .pw-badge.unlocked {
|
||||
color: #f59e0b;
|
||||
background: rgba(245, 158, 11, 0.1);
|
||||
}
|
||||
|
||||
.identity-dropdown {
|
||||
position: absolute;
|
||||
top: 100%;
|
||||
|
||||
@@ -43,24 +43,45 @@ try {
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
// Fetch tokens from the auth server (properly signed with server's Ed25519 key)
|
||||
async function fetchAuthTokens(uuid, name) {
|
||||
async function fetchAuthTokens(uuid, name, password) {
|
||||
const authServerUrl = getAuthServerUrl();
|
||||
try {
|
||||
console.log(`Fetching auth tokens from ${authServerUrl}/game-session/child`);
|
||||
|
||||
const bodyData = {
|
||||
uuid: uuid,
|
||||
name: name,
|
||||
scopes: ['hytale:server', 'hytale:client']
|
||||
};
|
||||
if (password) bodyData.password = password;
|
||||
|
||||
const response = await fetch(`${authServerUrl}/game-session/child`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
uuid: uuid,
|
||||
name: name,
|
||||
scopes: ['hytale:server', 'hytale:client']
|
||||
})
|
||||
body: JSON.stringify(bodyData)
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
const errBody = await response.json().catch(() => ({}));
|
||||
if (response.status === 401 && errBody.password_required) {
|
||||
const err = new Error('Password required');
|
||||
err.passwordRequired = true;
|
||||
err.attemptsRemaining = errBody.attemptsRemaining;
|
||||
throw err;
|
||||
}
|
||||
if (response.status === 429) {
|
||||
const err = new Error('Too many failed attempts. Try again later.');
|
||||
err.lockedOut = true;
|
||||
err.lockoutSeconds = errBody.lockoutSeconds;
|
||||
throw err;
|
||||
}
|
||||
if (response.status === 403 && errBody.username_taken) {
|
||||
const err = new Error('This username is reserved by another player who has set a password. Please use a different name.');
|
||||
err.usernameTaken = true;
|
||||
throw err;
|
||||
}
|
||||
throw new Error(`Auth server returned ${response.status}`);
|
||||
}
|
||||
|
||||
@@ -77,10 +98,12 @@ async function fetchAuthTokens(uuid, name) {
|
||||
if (payload.username && payload.username !== name && name !== 'Player') {
|
||||
console.warn(`[Auth] Token username mismatch: token has "${payload.username}", expected "${name}". Retrying...`);
|
||||
// Retry once with explicit name
|
||||
const retryBody = { uuid: uuid, name: name, scopes: ['hytale:server', 'hytale:client'] };
|
||||
if (password) retryBody.password = password;
|
||||
const retryResponse = await fetch(`${authServerUrl}/game-session/child`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ uuid: uuid, name: name, scopes: ['hytale:server', 'hytale:client'] })
|
||||
body: JSON.stringify(retryBody)
|
||||
});
|
||||
if (retryResponse.ok) {
|
||||
const retryData = await retryResponse.json();
|
||||
@@ -99,6 +122,10 @@ async function fetchAuthTokens(uuid, name) {
|
||||
console.log('Auth tokens received from server');
|
||||
return { identityToken, sessionToken };
|
||||
} catch (error) {
|
||||
// Re-throw authentication errors — must not fall back to local tokens
|
||||
if (error.passwordRequired || error.lockedOut || error.usernameTaken) {
|
||||
throw error;
|
||||
}
|
||||
console.error('Failed to fetch auth tokens:', error.message);
|
||||
// Fallback to local generation if server unavailable
|
||||
return generateLocalTokens(uuid, name);
|
||||
@@ -147,7 +174,7 @@ function generateLocalTokens(uuid, name) {
|
||||
};
|
||||
}
|
||||
|
||||
async function launchGame(playerNameOverride = null, progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto', branchOverride = null) {
|
||||
async function launchGame(playerNameOverride = null, progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto', branchOverride = null, options = {}) {
|
||||
// ==========================================================================
|
||||
// CACHE INVALIDATION: Clear proxyClient module cache to force fresh .env load
|
||||
// This prevents stale cached values from affecting multiple launch attempts
|
||||
@@ -256,11 +283,12 @@ async function launchGame(playerNameOverride = null, progressCallback, javaPathO
|
||||
const uuid = getUuidForUser(playerName);
|
||||
console.log(`[Launcher] UUID for "${playerName}": ${uuid} (verify this stays constant across launches)`);
|
||||
|
||||
// Fetch tokens from auth server
|
||||
// Fetch tokens from auth server (with password if provided)
|
||||
if (progressCallback) {
|
||||
progressCallback('Fetching authentication tokens...', null, null, null, null);
|
||||
}
|
||||
const { identityToken, sessionToken } = await fetchAuthTokens(uuid, playerName);
|
||||
const launchPassword = options?.password || null;
|
||||
const { identityToken, sessionToken } = await fetchAuthTokens(uuid, playerName, launchPassword);
|
||||
|
||||
// Patch client and server binaries to use custom auth server (BEFORE signing on macOS)
|
||||
// FORCE patch on every launch to ensure consistency
|
||||
@@ -578,7 +606,7 @@ async function launchGame(playerNameOverride = null, progressCallback, javaPathO
|
||||
}
|
||||
}
|
||||
|
||||
async function launchGameWithVersionCheck(playerNameOverride = null, progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto', branchOverride = null) {
|
||||
async function launchGameWithVersionCheck(playerNameOverride = null, progressCallback, javaPathOverride, installPathOverride, gpuPreference = 'auto', branchOverride = null, options = {}) {
|
||||
try {
|
||||
// ==========================================================================
|
||||
// PRE-LAUNCH VALIDATION: Check username is configured
|
||||
@@ -651,7 +679,7 @@ async function launchGameWithVersionCheck(playerNameOverride = null, progressCal
|
||||
progressCallback('Launching game...', 80, null, null, null);
|
||||
}
|
||||
|
||||
const launchResult = await launchGame(playerNameOverride, progressCallback, javaPathOverride, installPathOverride, gpuPreference, branch);
|
||||
const launchResult = await launchGame(playerNameOverride, progressCallback, javaPathOverride, installPathOverride, gpuPreference, branch, options);
|
||||
|
||||
// Ensure we always return a result
|
||||
if (!launchResult) {
|
||||
@@ -665,6 +693,10 @@ async function launchGameWithVersionCheck(playerNameOverride = null, progressCal
|
||||
if (progressCallback) {
|
||||
progressCallback(`Error: ${error.message}`, -1, null, null, null);
|
||||
}
|
||||
// Re-throw authentication errors so IPC handler can return proper flags
|
||||
if (error.passwordRequired || error.lockedOut || error.usernameTaken) {
|
||||
throw error;
|
||||
}
|
||||
// Always return an error response instead of throwing
|
||||
return { success: false, error: error.message || 'Unknown launch error' };
|
||||
}
|
||||
|
||||
155
main.js
155
main.js
@@ -530,7 +530,26 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, g
|
||||
}
|
||||
};
|
||||
|
||||
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference);
|
||||
// Check if UUID has password before launching
|
||||
let launchOptions = {};
|
||||
try {
|
||||
const { getAuthServerUrl } = require('./backend/core/config');
|
||||
const { getUuidForUser } = require('./backend/core/config');
|
||||
const uuid = getUuidForUser(playerName);
|
||||
const authServerUrl = getAuthServerUrl();
|
||||
const statusResp = await fetch(`${authServerUrl}/player/password/status/${uuid}`);
|
||||
if (statusResp.ok) {
|
||||
const status = await statusResp.json();
|
||||
if (status.hasPassword) {
|
||||
// Return to renderer to prompt for password
|
||||
return { success: false, passwordRequired: true, uuid };
|
||||
}
|
||||
}
|
||||
} catch (pwErr) {
|
||||
console.log('[Launch] Password check skipped:', pwErr.message);
|
||||
}
|
||||
|
||||
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference, null, launchOptions);
|
||||
|
||||
if (result.success && result.launched) {
|
||||
const closeOnStart = loadCloseLauncherOnStart();
|
||||
@@ -554,10 +573,62 @@ ipcMain.handle('launch-game', async (event, playerName, javaPath, installPath, g
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
if (error.passwordRequired) {
|
||||
return { success: false, passwordRequired: true, error: 'Password required' };
|
||||
}
|
||||
if (error.lockedOut) {
|
||||
return { success: false, error: 'Too many failed attempts. Try again in ' + Math.ceil((error.lockoutSeconds || 900) / 60) + ' minutes.' };
|
||||
}
|
||||
if (error.usernameTaken) {
|
||||
return { success: false, usernameTaken: true, error: errorMessage };
|
||||
}
|
||||
|
||||
return { success: false, error: errorMessage };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('launch-game-with-password', async (event, playerName, javaPath, installPath, gpuPreference, password) => {
|
||||
try {
|
||||
const progressCallback = (message, percent, speed, downloaded, total, retryState) => {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.webContents.send('progress-update', {
|
||||
message: message || null,
|
||||
percent: percent !== null && percent !== undefined ? Math.min(100, Math.max(0, percent)) : null,
|
||||
speed: speed !== null && speed !== undefined ? speed : null,
|
||||
downloaded: downloaded !== null && downloaded !== undefined ? downloaded : null,
|
||||
total: total !== null && total !== undefined ? total : null,
|
||||
retryState: retryState || null
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
const result = await launchGameWithVersionCheck(playerName, progressCallback, javaPath, installPath, gpuPreference, null, { password });
|
||||
|
||||
if (result.success && result.launched) {
|
||||
const closeOnStart = loadCloseLauncherOnStart();
|
||||
if (closeOnStart) {
|
||||
setTimeout(() => { app.quit(); }, 1000);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Launch with password error:', error);
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
setTimeout(() => { mainWindow.webContents.send('progress-complete'); }, 2000);
|
||||
}
|
||||
if (error.passwordRequired) {
|
||||
return { success: false, passwordRequired: true, error: 'Incorrect password. ' + (error.attemptsRemaining != null ? error.attemptsRemaining + ' attempts remaining.' : '') };
|
||||
}
|
||||
if (error.lockedOut) {
|
||||
return { success: false, error: 'Too many failed attempts. Try again in ' + Math.ceil((error.lockoutSeconds || 900) / 60) + ' minutes.' };
|
||||
}
|
||||
if (error.usernameTaken) {
|
||||
return { success: false, usernameTaken: true, error: error.message || error.toString() };
|
||||
}
|
||||
return { success: false, error: error.message || error.toString() };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('install-game', async (event, playerName, javaPath, installPath, branch) => {
|
||||
try {
|
||||
console.log(`[IPC] install-game called with parameters:`);
|
||||
@@ -1391,6 +1462,88 @@ ipcMain.handle('reset-current-user-uuid', async () => {
|
||||
}
|
||||
});
|
||||
|
||||
// Password Management IPC handlers
|
||||
ipcMain.handle('check-password-status', async (event, uuid) => {
|
||||
try {
|
||||
const { getAuthServerUrl } = require('./backend/core/config');
|
||||
const authServerUrl = getAuthServerUrl();
|
||||
const response = await fetch(`${authServerUrl}/player/password/status/${uuid}`);
|
||||
if (!response.ok) return { hasPassword: false };
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error checking password status:', error);
|
||||
return { hasPassword: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('set-player-password', async (event, uuid, password, currentPassword) => {
|
||||
try {
|
||||
const { getAuthServerUrl } = require('./backend/core/config');
|
||||
const { getUuidForUser, loadUsername } = require('./backend/core/config');
|
||||
const authServerUrl = getAuthServerUrl();
|
||||
// First get a bearer token for auth
|
||||
const name = loadUsername() || 'Player';
|
||||
const tokenResp = await fetch(`${authServerUrl}/game-session/child`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ uuid, name, password: currentPassword || undefined })
|
||||
});
|
||||
if (!tokenResp.ok) {
|
||||
const err = await tokenResp.json().catch(() => ({}));
|
||||
return { success: false, error: err.error || 'Failed to authenticate' };
|
||||
}
|
||||
const tokenData = await tokenResp.json();
|
||||
const bearerToken = tokenData.identityToken || tokenData.IdentityToken;
|
||||
|
||||
const response = await fetch(`${authServerUrl}/player/password/set`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${bearerToken}`
|
||||
},
|
||||
body: JSON.stringify({ uuid, password, currentPassword: currentPassword || undefined })
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error setting password:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('remove-player-password', async (event, uuid, currentPassword) => {
|
||||
try {
|
||||
const { getAuthServerUrl } = require('./backend/core/config');
|
||||
const { loadUsername } = require('./backend/core/config');
|
||||
const authServerUrl = getAuthServerUrl();
|
||||
const name = loadUsername() || 'Player';
|
||||
// Get bearer token with current password
|
||||
const tokenResp = await fetch(`${authServerUrl}/game-session/child`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ uuid, name, password: currentPassword })
|
||||
});
|
||||
if (!tokenResp.ok) {
|
||||
const err = await tokenResp.json().catch(() => ({}));
|
||||
return { success: false, error: err.error || 'Failed to authenticate' };
|
||||
}
|
||||
const tokenData = await tokenResp.json();
|
||||
const bearerToken = tokenData.identityToken || tokenData.IdentityToken;
|
||||
|
||||
const response = await fetch(`${authServerUrl}/player/password/remove`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${bearerToken}`
|
||||
},
|
||||
body: JSON.stringify({ uuid, currentPassword })
|
||||
});
|
||||
return await response.json();
|
||||
} catch (error) {
|
||||
console.error('Error removing password:', error);
|
||||
return { success: false, error: error.message };
|
||||
}
|
||||
});
|
||||
|
||||
ipcMain.handle('get-recent-logs', async (event, maxLines = 100) => {
|
||||
try {
|
||||
const logDir = logger.getLogDirectory();
|
||||
|
||||
10
preload.js
10
preload.js
@@ -2,6 +2,7 @@ const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
contextBridge.exposeInMainWorld('electronAPI', {
|
||||
launchGame: (playerName, javaPath, installPath, gpuPreference) => ipcRenderer.invoke('launch-game', playerName, javaPath, installPath, gpuPreference),
|
||||
launchGameWithPassword: (playerName, javaPath, installPath, gpuPreference, password) => ipcRenderer.invoke('launch-game-with-password', playerName, javaPath, installPath, gpuPreference, password),
|
||||
installGame: (playerName, javaPath, installPath, branch) => ipcRenderer.invoke('install-game', playerName, javaPath, installPath, branch),
|
||||
closeWindow: () => ipcRenderer.invoke('window-close'),
|
||||
minimizeWindow: () => ipcRenderer.invoke('window-minimize'),
|
||||
@@ -107,6 +108,15 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||
deleteUuidForUser: (username) => ipcRenderer.invoke('delete-uuid-for-user', username),
|
||||
resetCurrentUserUuid: () => ipcRenderer.invoke('reset-current-user-uuid'),
|
||||
|
||||
// Password Management methods
|
||||
checkPasswordStatus: (uuid) => ipcRenderer.invoke('check-password-status', uuid),
|
||||
setPlayerPassword: (uuid, password, currentPassword) => ipcRenderer.invoke('set-player-password', uuid, password, currentPassword),
|
||||
removePlayerPassword: (uuid, currentPassword) => ipcRenderer.invoke('remove-player-password', uuid, currentPassword),
|
||||
promptPassword: () => ipcRenderer.invoke('prompt-password'),
|
||||
onPasswordPrompt: (callback) => {
|
||||
ipcRenderer.on('show-password-prompt', (event, data) => callback(data));
|
||||
},
|
||||
|
||||
// Java Wrapper Config API
|
||||
loadWrapperConfig: () => ipcRenderer.invoke('load-wrapper-config'),
|
||||
saveWrapperConfig: (config) => ipcRenderer.invoke('save-wrapper-config', config),
|
||||
|
||||
Reference in New Issue
Block a user