trying to fix the log out issue

This commit is contained in:
Slavomir Durej 2025-12-19 16:49:02 +00:00
parent a9a155fdd1
commit 6ea9251bfe
7 changed files with 269 additions and 15 deletions

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

After

Width:  |  Height:  |  Size: 36 KiB

157
main.js
View file

@ -9,6 +9,7 @@ const store = new Store({
let mainWindow = null;
let loginWindow = null;
let silentLoginWindow = null;
let tray = null;
// Window configuration
@ -176,6 +177,149 @@ function createLoginWindow() {
});
}
// Attempt silent login in a hidden browser window
async function attemptSilentLogin() {
console.log('[Main] Attempting silent login...');
// Notify renderer that we're trying to auto-login
if (mainWindow) {
mainWindow.webContents.send('silent-login-started');
}
return new Promise((resolve) => {
silentLoginWindow = new BrowserWindow({
width: 800,
height: 700,
show: false, // Hidden window
webPreferences: {
nodeIntegration: false,
contextIsolation: true
}
});
silentLoginWindow.loadURL('https://claude.ai');
let loginCheckInterval = null;
let hasLoggedIn = false;
const SILENT_LOGIN_TIMEOUT = 15000; // 15 seconds timeout
// Function to check login status
async function checkLoginStatus() {
if (hasLoggedIn || !silentLoginWindow) return;
try {
const cookies = await session.defaultSession.cookies.get({
url: 'https://claude.ai',
name: 'sessionKey'
});
if (cookies.length > 0) {
const sessionKey = cookies[0].value;
console.log('[Main] Silent login: Session key found, attempting to get org ID...');
// Fetch org ID from API
let orgId = null;
try {
const response = await axios.get('https://claude.ai/api/organizations', {
headers: {
'Cookie': `sessionKey=${sessionKey}`,
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
}
});
if (response.data && Array.isArray(response.data) && response.data.length > 0) {
orgId = response.data[0].uuid || response.data[0].id;
console.log('[Main] Silent login: Org ID fetched from API:', orgId);
}
} catch (err) {
console.log('[Main] Silent login: API not ready yet:', err.message);
}
if (sessionKey && orgId) {
hasLoggedIn = true;
if (loginCheckInterval) {
clearInterval(loginCheckInterval);
loginCheckInterval = null;
}
console.log('[Main] Silent login successful!');
store.set('sessionKey', sessionKey);
store.set('organizationId', orgId);
if (mainWindow) {
mainWindow.webContents.send('login-success', { sessionKey, organizationId: orgId });
}
silentLoginWindow.close();
resolve(true);
}
}
} catch (error) {
console.error('[Main] Silent login check error:', error);
}
}
// Check on page load
silentLoginWindow.webContents.on('did-finish-load', async () => {
const url = silentLoginWindow.webContents.getURL();
console.log('[Main] Silent login page loaded:', url);
if (url.includes('claude.ai')) {
await checkLoginStatus();
}
});
// Also check on navigation
silentLoginWindow.webContents.on('did-navigate', async (event, url) => {
console.log('[Main] Silent login navigated to:', url);
if (url.includes('claude.ai')) {
await checkLoginStatus();
}
});
// Poll periodically
loginCheckInterval = setInterval(async () => {
if (!hasLoggedIn && silentLoginWindow) {
await checkLoginStatus();
} else if (loginCheckInterval) {
clearInterval(loginCheckInterval);
loginCheckInterval = null;
}
}, 1000);
// Timeout - if silent login doesn't work, fall back to visible login
setTimeout(() => {
if (!hasLoggedIn) {
console.log('[Main] Silent login timeout, falling back to visible login...');
if (loginCheckInterval) {
clearInterval(loginCheckInterval);
loginCheckInterval = null;
}
if (silentLoginWindow) {
silentLoginWindow.close();
}
// Notify renderer that silent login failed
if (mainWindow) {
mainWindow.webContents.send('silent-login-failed');
}
// Open visible login window
createLoginWindow();
resolve(false);
}
}, SILENT_LOGIN_TIMEOUT);
silentLoginWindow.on('closed', () => {
if (loginCheckInterval) {
clearInterval(loginCheckInterval);
loginCheckInterval = null;
}
silentLoginWindow = null;
});
});
}
function createTray() {
try {
tray = new Tray(path.join(__dirname, 'assets/tray-icon.png'));
@ -331,7 +475,18 @@ ipcMain.handle('fetch-usage-data', async () => {
if (error.response) {
console.error('[Main] Response status:', error.response.status);
if (error.response.status === 401 || error.response.status === 403) {
throw new Error('Unauthorized');
// Session expired - attempt silent re-login
console.log('[Main] Session expired, attempting silent re-login...');
store.delete('sessionKey');
store.delete('organizationId');
// Don't clear cookies - we need them for silent login to work with OAuth
// The silent login will use existing Google/OAuth session if available
// Attempt silent login (will notify renderer appropriately)
attemptSilentLogin();
throw new Error('SessionExpired');
}
}
throw error;

View file

@ -1,6 +1,6 @@
{
"name": "claude-usage-widget",
"version": "1.0.0",
"version": "1.3.0",
"description": "Desktop widget for Claude.ai usage monitoring",
"main": "main.js",
"scripts": {

View file

@ -24,6 +24,15 @@ contextBridge.exposeInMainWorld('electronAPI', {
onRefreshUsage: (callback) => {
ipcRenderer.on('refresh-usage', () => callback());
},
onSessionExpired: (callback) => {
ipcRenderer.on('session-expired', () => callback());
},
onSilentLoginStarted: (callback) => {
ipcRenderer.on('silent-login-started', () => callback());
},
onSilentLoginFailed: (callback) => {
ipcRenderer.on('silent-login-failed', () => callback());
},
// API
fetchUsageData: () => ipcRenderer.invoke('fetch-usage-data'),

View file

@ -10,6 +10,7 @@ const elements = {
loadingContainer: document.getElementById('loadingContainer'),
loginContainer: document.getElementById('loginContainer'),
noUsageContainer: document.getElementById('noUsageContainer'),
autoLoginContainer: document.getElementById('autoLoginContainer'),
mainContent: document.getElementById('mainContent'),
loginBtn: document.getElementById('loginBtn'),
refreshBtn: document.getElementById('refreshBtn'),
@ -103,6 +104,25 @@ function setupEventListeners() {
window.electronAPI.onRefreshUsage(async () => {
await fetchUsageData();
});
// Listen for session expiration events (403 errors) - only used as fallback
window.electronAPI.onSessionExpired(() => {
console.log('Session expired event received');
credentials = { sessionKey: null, organizationId: null };
showLoginRequired();
});
// Listen for silent login attempts
window.electronAPI.onSilentLoginStarted(() => {
console.log('Silent login started...');
showAutoLoginAttempt();
});
// Listen for silent login failures (falls back to visible login)
window.electronAPI.onSilentLoginFailed(() => {
console.log('Silent login failed, manual login required');
showLoginRequired();
});
}
// Fetch usage data from Claude API
@ -122,9 +142,11 @@ async function fetchUsageData() {
updateUI(data);
} catch (error) {
console.error('Error fetching usage data:', error);
if (error.message.includes('Unauthorized')) {
await window.electronAPI.deleteCredentials();
showLoginRequired();
if (error.message.includes('SessionExpired') || error.message.includes('Unauthorized')) {
// Session expired - silent login attempt is in progress
// Show auto-login UI while waiting
credentials = { sessionKey: null, organizationId: null };
showAutoLoginAttempt();
} else {
showError('Failed to fetch usage data');
}
@ -316,6 +338,7 @@ function showLoading() {
elements.loadingContainer.style.display = 'block';
elements.loginContainer.style.display = 'none';
elements.noUsageContainer.style.display = 'none';
elements.autoLoginContainer.style.display = 'none';
elements.mainContent.style.display = 'none';
}
@ -323,6 +346,7 @@ function showLoginRequired() {
elements.loadingContainer.style.display = 'none';
elements.loginContainer.style.display = 'flex'; // Use flex to preserve centering
elements.noUsageContainer.style.display = 'none';
elements.autoLoginContainer.style.display = 'none';
elements.mainContent.style.display = 'none';
stopAutoUpdate();
}
@ -331,13 +355,24 @@ function showNoUsage() {
elements.loadingContainer.style.display = 'none';
elements.loginContainer.style.display = 'none';
elements.noUsageContainer.style.display = 'flex';
elements.autoLoginContainer.style.display = 'none';
elements.mainContent.style.display = 'none';
}
function showAutoLoginAttempt() {
elements.loadingContainer.style.display = 'none';
elements.loginContainer.style.display = 'none';
elements.noUsageContainer.style.display = 'none';
elements.autoLoginContainer.style.display = 'flex';
elements.mainContent.style.display = 'none';
stopAutoUpdate();
}
function showMainContent() {
elements.loadingContainer.style.display = 'none';
elements.loginContainer.style.display = 'none';
elements.noUsageContainer.style.display = 'none';
elements.autoLoginContainer.style.display = 'none';
elements.mainContent.style.display = 'block';
}

View file

@ -69,6 +69,17 @@
</div>
</div>
<!-- Auto-Login Attempt State -->
<div class="auto-login-container" id="autoLoginContainer" style="display: none;">
<div class="auto-login-content">
<div class="spinner"></div>
<div class="auto-login-text-group">
<h3>Trying to auto-login...</h3>
<p>Please wait while we reconnect</p>
</div>
</div>
</div>
<!-- Main Content -->
<div class="content" id="mainContent" style="display: none;">
<!-- Session Usage -->
@ -114,11 +125,13 @@
<div class="settings-content">
<button class="icon-close-settings-btn" id="closeSettingsBtn" title="Close">×</button>
<p class="disclaimer">
<strong>Disclaimer:</strong> Unofficial tool not affiliated with Anthropic. Use at your own discretion.
<strong>Disclaimer:</strong> Unofficial tool not affiliated with Anthropic. Use at your own
discretion.
</p>
<button class="coffee-btn" id="coffeeBtn">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor"
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<path d="M17 8h1a4 4 0 1 1 0 8h-1" />
<path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z" />
<line x1="6" y1="2" x2="6" y2="4" />

View file

@ -230,6 +230,48 @@ body {
margin-bottom: 0;
}
/* Auto-Login Attempt State */
.auto-login-container {
padding: 0 16px;
flex: 1;
display: flex;
align-items: center;
justify-content: center;
}
.auto-login-content {
display: flex;
align-items: center;
gap: 16px;
text-align: left;
width: 100%;
}
.auto-login-content .spinner {
width: 32px;
height: 32px;
flex-shrink: 0;
margin: 0;
}
.auto-login-text-group {
display: flex;
flex-direction: column;
justify-content: center;
}
.auto-login-content h3 {
color: #e0e0e0;
font-size: 14px;
margin-bottom: 2px;
}
.auto-login-content p {
color: #a0a0a0;
font-size: 11px;
margin-bottom: 0;
}
/* Main Content */
.content {
padding: 20px;