clauddy 1.21.1 → 1.21.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/auth.js +25 -3
- package/main.js +27 -0
- package/package.json +1 -1
package/auth.js
CHANGED
|
@@ -35,12 +35,25 @@ function setDataDir(dir) {
|
|
|
35
35
|
tokens = null
|
|
36
36
|
profile = null
|
|
37
37
|
pending = null
|
|
38
|
+
unreadable = false
|
|
38
39
|
}
|
|
39
40
|
|
|
41
|
+
// Set when the file itself could not be read for a reason other than "it is not
|
|
42
|
+
// there" — a disk still waking up, say. That must not read as a logout, or the
|
|
43
|
+
// widget asks for a login it doesn't need and every account looks gone. A file
|
|
44
|
+
// we did read but cannot parse is a different matter: that one really is dead.
|
|
45
|
+
let unreadable = false
|
|
40
46
|
function load() {
|
|
41
47
|
if (tokens) return tokens
|
|
48
|
+
let raw = null
|
|
42
49
|
try {
|
|
43
|
-
|
|
50
|
+
raw = fs.readFileSync(tokenPath, 'utf8')
|
|
51
|
+
unreadable = false
|
|
52
|
+
} catch (e) {
|
|
53
|
+
unreadable = e?.code !== 'ENOENT'
|
|
54
|
+
}
|
|
55
|
+
try {
|
|
56
|
+
tokens = raw == null ? null : JSON.parse(raw)
|
|
44
57
|
} catch {
|
|
45
58
|
tokens = null
|
|
46
59
|
}
|
|
@@ -56,12 +69,15 @@ function save(t) {
|
|
|
56
69
|
function clear() {
|
|
57
70
|
tokens = null
|
|
58
71
|
profile = null
|
|
72
|
+
unreadable = false
|
|
59
73
|
try {
|
|
60
74
|
fs.unlinkSync(tokenPath)
|
|
61
75
|
} catch {}
|
|
62
76
|
}
|
|
63
77
|
function isConnected() {
|
|
64
|
-
|
|
78
|
+
// a token we merely failed to read still counts: the account is connected,
|
|
79
|
+
// we just couldn't see it this instant
|
|
80
|
+
return !!load() || unreadable
|
|
65
81
|
}
|
|
66
82
|
|
|
67
83
|
// Step 1: build the authorize URL (opens in the browser)
|
|
@@ -152,7 +168,13 @@ async function refresh() {
|
|
|
152
168
|
|
|
153
169
|
async function validToken() {
|
|
154
170
|
const t = load()
|
|
155
|
-
|
|
171
|
+
// status 0 is "try again later", not "log in again": only a token that is
|
|
172
|
+
// genuinely absent should send the user back through the browser
|
|
173
|
+
if (!t) {
|
|
174
|
+
throw Object.assign(new Error(unreadable ? 'token unreadable' : 'not connected'), {
|
|
175
|
+
status: unreadable ? 0 : 401,
|
|
176
|
+
})
|
|
177
|
+
}
|
|
156
178
|
if (!t.expires_at || t.expires_at - Date.now() < 60000) await refresh()
|
|
157
179
|
return load().access_token
|
|
158
180
|
}
|
package/main.js
CHANGED
|
@@ -8,6 +8,7 @@ const {
|
|
|
8
8
|
Tray,
|
|
9
9
|
Menu,
|
|
10
10
|
nativeImage,
|
|
11
|
+
powerMonitor,
|
|
11
12
|
} = require('electron')
|
|
12
13
|
const path = require('node:path')
|
|
13
14
|
const fs = require('node:fs')
|
|
@@ -336,6 +337,7 @@ function createWindow() {
|
|
|
336
337
|
for (const a of accounts.list()) pruneEmpty(a.id)
|
|
337
338
|
accounts.pruneOrphans() // folders those slots left behind
|
|
338
339
|
applyAccount(accounts.active())
|
|
340
|
+
powerMonitor?.on?.('resume', onResume)
|
|
339
341
|
const { workAreaSize } = screen.getPrimaryDisplay()
|
|
340
342
|
const H = 480
|
|
341
343
|
|
|
@@ -648,6 +650,7 @@ function watchDebug() {
|
|
|
648
650
|
// ---- real usage via OAuth (authoritative %), polled slowly with 429 backoff ----
|
|
649
651
|
let usageTimer = null
|
|
650
652
|
let usageBackoff = 5 * 60 * 1000
|
|
653
|
+
let authFails = 0 // consecutive 401s — see pollUsage
|
|
651
654
|
function scheduleUsagePoll() {
|
|
652
655
|
clearTimeout(usageTimer)
|
|
653
656
|
if (auth.isConnected()) usageTimer = setTimeout(pollUsage, usageBackoff)
|
|
@@ -656,11 +659,21 @@ async function pollUsage() {
|
|
|
656
659
|
try {
|
|
657
660
|
const u = await auth.fetchUsage()
|
|
658
661
|
usageBackoff = 5 * 60 * 1000
|
|
662
|
+
authFails = 0
|
|
659
663
|
pushRealUsage(u)
|
|
660
664
|
} catch (e) {
|
|
661
665
|
if (e && e.status === 429) {
|
|
662
666
|
usageBackoff = Math.min(usageBackoff * 2, 30 * 60 * 1000)
|
|
663
667
|
} else if (e && e.status === 401) {
|
|
668
|
+
// Refreshing rotates the refresh token, so a request that raced the one
|
|
669
|
+
// that rotated it comes back rejected while the session is perfectly
|
|
670
|
+
// alive. Clearing on that first answer throws away a good login, so the
|
|
671
|
+
// second rejection in a row is what counts.
|
|
672
|
+
if (++authFails < 2) {
|
|
673
|
+
usageBackoff = 60 * 1000
|
|
674
|
+
scheduleUsagePoll()
|
|
675
|
+
return
|
|
676
|
+
}
|
|
664
677
|
auth.clear()
|
|
665
678
|
pushRealUsage(null)
|
|
666
679
|
sendAccounts()
|
|
@@ -674,9 +687,23 @@ async function pollUsage() {
|
|
|
674
687
|
scheduleUsagePoll()
|
|
675
688
|
}
|
|
676
689
|
function startUsagePoll() {
|
|
690
|
+
authFails = 0
|
|
677
691
|
if (auth.isConnected()) pollUsage()
|
|
678
692
|
}
|
|
679
693
|
|
|
694
|
+
// Waking up: the poll timer was frozen through the sleep, so the numbers on
|
|
695
|
+
// screen are as old as the nap. Re-state what we know — a read that failed on
|
|
696
|
+
// the way down would otherwise leave a stale "log in" panel up — and poll
|
|
697
|
+
// again, after a beat, since the network is rarely back the instant we are.
|
|
698
|
+
function onResume() {
|
|
699
|
+
if (!auth.isConnected()) return
|
|
700
|
+
if (win && !win.isDestroyed()) win.webContents.send('auth-state', { connected: true })
|
|
701
|
+
clearTimeout(usageTimer)
|
|
702
|
+
usageBackoff = 5 * 60 * 1000
|
|
703
|
+
usageTimer = setTimeout(pollUsage, 5000)
|
|
704
|
+
sendProfile()
|
|
705
|
+
}
|
|
706
|
+
|
|
680
707
|
// push the logged-in account's identity (email + plan) to the renderer
|
|
681
708
|
let profileTimer = null
|
|
682
709
|
async function sendProfile(tries = 0) {
|