trying to fix the log out issue
This commit is contained in:
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
157
main.js
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
|
|
@ -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'),
|
||||
|
|
|
|||
|
|
@ -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');
|
||||
}
|
||||
|
|
@ -139,7 +161,7 @@ function hasNoUsage(data) {
|
|||
const weeklyResetsAt = data.seven_day?.resets_at;
|
||||
|
||||
return sessionUtilization === 0 && !sessionResetsAt &&
|
||||
weeklyUtilization === 0 && !weeklyResetsAt;
|
||||
weeklyUtilization === 0 && !weeklyResetsAt;
|
||||
}
|
||||
|
||||
// Update UI with 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';
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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,16 +125,18 @@
|
|||
<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">
|
||||
<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"/>
|
||||
<line x1="10" y1="2" x2="10" y2="4"/>
|
||||
<line x1="14" y1="2" x2="14" y2="4"/>
|
||||
<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" />
|
||||
<line x1="10" y1="2" x2="10" y2="4" />
|
||||
<line x1="14" y1="2" x2="14" y2="4" />
|
||||
</svg>
|
||||
If you find this useful, buy me a coffee!
|
||||
</button>
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
Loading…
Reference in a new issue