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 mainWindow = null;
|
||||||
let loginWindow = null;
|
let loginWindow = null;
|
||||||
|
let silentLoginWindow = null;
|
||||||
let tray = null;
|
let tray = null;
|
||||||
|
|
||||||
// Window configuration
|
// 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() {
|
function createTray() {
|
||||||
try {
|
try {
|
||||||
tray = new Tray(path.join(__dirname, 'assets/tray-icon.png'));
|
tray = new Tray(path.join(__dirname, 'assets/tray-icon.png'));
|
||||||
|
|
@ -331,7 +475,18 @@ ipcMain.handle('fetch-usage-data', async () => {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
console.error('[Main] Response status:', error.response.status);
|
console.error('[Main] Response status:', error.response.status);
|
||||||
if (error.response.status === 401 || error.response.status === 403) {
|
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;
|
throw error;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "claude-usage-widget",
|
"name": "claude-usage-widget",
|
||||||
"version": "1.0.0",
|
"version": "1.3.0",
|
||||||
"description": "Desktop widget for Claude.ai usage monitoring",
|
"description": "Desktop widget for Claude.ai usage monitoring",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
@ -47,4 +47,4 @@
|
||||||
"!*.md"
|
"!*.md"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -24,6 +24,15 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
||||||
onRefreshUsage: (callback) => {
|
onRefreshUsage: (callback) => {
|
||||||
ipcRenderer.on('refresh-usage', () => 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
|
// API
|
||||||
fetchUsageData: () => ipcRenderer.invoke('fetch-usage-data'),
|
fetchUsageData: () => ipcRenderer.invoke('fetch-usage-data'),
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ const elements = {
|
||||||
loadingContainer: document.getElementById('loadingContainer'),
|
loadingContainer: document.getElementById('loadingContainer'),
|
||||||
loginContainer: document.getElementById('loginContainer'),
|
loginContainer: document.getElementById('loginContainer'),
|
||||||
noUsageContainer: document.getElementById('noUsageContainer'),
|
noUsageContainer: document.getElementById('noUsageContainer'),
|
||||||
|
autoLoginContainer: document.getElementById('autoLoginContainer'),
|
||||||
mainContent: document.getElementById('mainContent'),
|
mainContent: document.getElementById('mainContent'),
|
||||||
loginBtn: document.getElementById('loginBtn'),
|
loginBtn: document.getElementById('loginBtn'),
|
||||||
refreshBtn: document.getElementById('refreshBtn'),
|
refreshBtn: document.getElementById('refreshBtn'),
|
||||||
|
|
@ -103,6 +104,25 @@ function setupEventListeners() {
|
||||||
window.electronAPI.onRefreshUsage(async () => {
|
window.electronAPI.onRefreshUsage(async () => {
|
||||||
await fetchUsageData();
|
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
|
// Fetch usage data from Claude API
|
||||||
|
|
@ -122,9 +142,11 @@ async function fetchUsageData() {
|
||||||
updateUI(data);
|
updateUI(data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching usage data:', error);
|
console.error('Error fetching usage data:', error);
|
||||||
if (error.message.includes('Unauthorized')) {
|
if (error.message.includes('SessionExpired') || error.message.includes('Unauthorized')) {
|
||||||
await window.electronAPI.deleteCredentials();
|
// Session expired - silent login attempt is in progress
|
||||||
showLoginRequired();
|
// Show auto-login UI while waiting
|
||||||
|
credentials = { sessionKey: null, organizationId: null };
|
||||||
|
showAutoLoginAttempt();
|
||||||
} else {
|
} else {
|
||||||
showError('Failed to fetch usage data');
|
showError('Failed to fetch usage data');
|
||||||
}
|
}
|
||||||
|
|
@ -139,7 +161,7 @@ function hasNoUsage(data) {
|
||||||
const weeklyResetsAt = data.seven_day?.resets_at;
|
const weeklyResetsAt = data.seven_day?.resets_at;
|
||||||
|
|
||||||
return sessionUtilization === 0 && !sessionResetsAt &&
|
return sessionUtilization === 0 && !sessionResetsAt &&
|
||||||
weeklyUtilization === 0 && !weeklyResetsAt;
|
weeklyUtilization === 0 && !weeklyResetsAt;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update UI with usage data
|
// Update UI with usage data
|
||||||
|
|
@ -316,6 +338,7 @@ function showLoading() {
|
||||||
elements.loadingContainer.style.display = 'block';
|
elements.loadingContainer.style.display = 'block';
|
||||||
elements.loginContainer.style.display = 'none';
|
elements.loginContainer.style.display = 'none';
|
||||||
elements.noUsageContainer.style.display = 'none';
|
elements.noUsageContainer.style.display = 'none';
|
||||||
|
elements.autoLoginContainer.style.display = 'none';
|
||||||
elements.mainContent.style.display = 'none';
|
elements.mainContent.style.display = 'none';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -323,6 +346,7 @@ function showLoginRequired() {
|
||||||
elements.loadingContainer.style.display = 'none';
|
elements.loadingContainer.style.display = 'none';
|
||||||
elements.loginContainer.style.display = 'flex'; // Use flex to preserve centering
|
elements.loginContainer.style.display = 'flex'; // Use flex to preserve centering
|
||||||
elements.noUsageContainer.style.display = 'none';
|
elements.noUsageContainer.style.display = 'none';
|
||||||
|
elements.autoLoginContainer.style.display = 'none';
|
||||||
elements.mainContent.style.display = 'none';
|
elements.mainContent.style.display = 'none';
|
||||||
stopAutoUpdate();
|
stopAutoUpdate();
|
||||||
}
|
}
|
||||||
|
|
@ -331,13 +355,24 @@ function showNoUsage() {
|
||||||
elements.loadingContainer.style.display = 'none';
|
elements.loadingContainer.style.display = 'none';
|
||||||
elements.loginContainer.style.display = 'none';
|
elements.loginContainer.style.display = 'none';
|
||||||
elements.noUsageContainer.style.display = 'flex';
|
elements.noUsageContainer.style.display = 'flex';
|
||||||
|
elements.autoLoginContainer.style.display = 'none';
|
||||||
elements.mainContent.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() {
|
function showMainContent() {
|
||||||
elements.loadingContainer.style.display = 'none';
|
elements.loadingContainer.style.display = 'none';
|
||||||
elements.loginContainer.style.display = 'none';
|
elements.loginContainer.style.display = 'none';
|
||||||
elements.noUsageContainer.style.display = 'none';
|
elements.noUsageContainer.style.display = 'none';
|
||||||
|
elements.autoLoginContainer.style.display = 'none';
|
||||||
elements.mainContent.style.display = 'block';
|
elements.mainContent.style.display = 'block';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,17 @@
|
||||||
</div>
|
</div>
|
||||||
</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 -->
|
<!-- Main Content -->
|
||||||
<div class="content" id="mainContent" style="display: none;">
|
<div class="content" id="mainContent" style="display: none;">
|
||||||
<!-- Session Usage -->
|
<!-- Session Usage -->
|
||||||
|
|
@ -114,16 +125,18 @@
|
||||||
<div class="settings-content">
|
<div class="settings-content">
|
||||||
<button class="icon-close-settings-btn" id="closeSettingsBtn" title="Close">×</button>
|
<button class="icon-close-settings-btn" id="closeSettingsBtn" title="Close">×</button>
|
||||||
<p class="disclaimer">
|
<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>
|
</p>
|
||||||
|
|
||||||
<button class="coffee-btn" id="coffeeBtn">
|
<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"
|
||||||
<path d="M17 8h1a4 4 0 1 1 0 8h-1"/>
|
stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
|
||||||
<path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z"/>
|
<path d="M17 8h1a4 4 0 1 1 0 8h-1" />
|
||||||
<line x1="6" y1="2" x2="6" y2="4"/>
|
<path d="M3 8h14v9a4 4 0 0 1-4 4H7a4 4 0 0 1-4-4Z" />
|
||||||
<line x1="10" y1="2" x2="10" y2="4"/>
|
<line x1="6" y1="2" x2="6" y2="4" />
|
||||||
<line x1="14" y1="2" x2="14" y2="4"/>
|
<line x1="10" y1="2" x2="10" y2="4" />
|
||||||
|
<line x1="14" y1="2" x2="14" y2="4" />
|
||||||
</svg>
|
</svg>
|
||||||
If you find this useful, buy me a coffee!
|
If you find this useful, buy me a coffee!
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -230,6 +230,48 @@ body {
|
||||||
margin-bottom: 0;
|
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 */
|
/* Main Content */
|
||||||
.content {
|
.content {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|
@ -530,4 +572,4 @@ body {
|
||||||
|
|
||||||
.timer-progress.danger {
|
.timer-progress.danger {
|
||||||
stroke: #ef4444;
|
stroke: #ef4444;
|
||||||
}
|
}
|
||||||
Loading…
Reference in a new issue