@cortexkit/anthropic-auth-core 1.0.0

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.
@@ -0,0 +1,606 @@
1
+ import { randomUUID } from 'node:crypto';
2
+ import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
3
+ import { homedir } from 'node:os';
4
+ import { dirname, join } from 'node:path';
5
+ import { CACHE_1H_MODES, CLAUDE_CODE_VERSION, CLIENT_ID, DEFAULT_CACHE_1H_MODE, TOKEN_URL, } from "./constants.js";
6
+ export const ACCOUNT_FILE_NAME = 'anthropic-auth.json';
7
+ export const QUOTA_URL = 'https://api.anthropic.com/api/oauth/usage';
8
+ const DEFAULT_FALLBACK_ON = [401, 403, 429];
9
+ const DEFAULT_REFRESH_BEFORE_EXPIRY_MINUTES = 30;
10
+ const DEFAULT_QUOTA_CHECK_INTERVAL_MINUTES = 5;
11
+ const DEFAULT_MINIMUM_REMAINING = {
12
+ five_hour: 0,
13
+ seven_day: 0,
14
+ };
15
+ const DEFAULT_FAIL_CLOSED_ON_UNKNOWN_QUOTA = true;
16
+ const BACKGROUND_TICK_MS = 60_000;
17
+ function getConfigDir() {
18
+ if (process.env.OPENCODE_CONFIG_DIR?.trim()) {
19
+ return process.env.OPENCODE_CONFIG_DIR.trim();
20
+ }
21
+ return join(process.env.XDG_CONFIG_HOME || join(homedir(), '.config'), 'opencode');
22
+ }
23
+ export function getAccountStoragePath() {
24
+ return (process.env.OPENCODE_ANTHROPIC_AUTH_FILE?.trim() ||
25
+ join(getConfigDir(), ACCOUNT_FILE_NAME));
26
+ }
27
+ function isRecord(value) {
28
+ return value != null && typeof value === 'object' && !Array.isArray(value);
29
+ }
30
+ function normalizeAccount(value) {
31
+ if (!isRecord(value))
32
+ return null;
33
+ if (value.type !== 'oauth')
34
+ return null;
35
+ if (typeof value.refresh !== 'string' || !value.refresh.trim())
36
+ return null;
37
+ return {
38
+ id: typeof value.id === 'string' && value.id.trim()
39
+ ? value.id.trim()
40
+ : randomUUID(),
41
+ label: typeof value.label === 'string' ? value.label : undefined,
42
+ type: 'oauth',
43
+ access: typeof value.access === 'string' ? value.access : undefined,
44
+ refresh: value.refresh,
45
+ expires: typeof value.expires === 'number' ? value.expires : undefined,
46
+ enabled: typeof value.enabled === 'boolean' ? value.enabled : undefined,
47
+ addedAt: typeof value.addedAt === 'number' ? value.addedAt : undefined,
48
+ lastUsed: typeof value.lastUsed === 'number' ? value.lastUsed : undefined,
49
+ lastRefreshedAt: typeof value.lastRefreshedAt === 'number'
50
+ ? value.lastRefreshedAt
51
+ : undefined,
52
+ lastRefreshError: normalizeOperationError(value.lastRefreshError),
53
+ lastQuotaRefreshError: normalizeOperationError(value.lastQuotaRefreshError),
54
+ quota: normalizeQuota(value.quota),
55
+ };
56
+ }
57
+ function normalizeOperationError(value) {
58
+ if (!isRecord(value))
59
+ return undefined;
60
+ if (typeof value.message !== 'string')
61
+ return undefined;
62
+ const checkedAt = Number(value.checkedAt);
63
+ if (!Number.isFinite(checkedAt))
64
+ return undefined;
65
+ return { message: value.message, checkedAt };
66
+ }
67
+ function normalizeQuota(value) {
68
+ if (!isRecord(value))
69
+ return undefined;
70
+ const quota = {};
71
+ for (const key of ['five_hour', 'seven_day']) {
72
+ const window = value[key];
73
+ if (!isRecord(window))
74
+ continue;
75
+ const usedPercent = Number(window.usedPercent);
76
+ const remainingPercent = Number(window.remainingPercent);
77
+ const checkedAt = Number(window.checkedAt);
78
+ if (!Number.isFinite(usedPercent) ||
79
+ !Number.isFinite(remainingPercent) ||
80
+ !Number.isFinite(checkedAt)) {
81
+ continue;
82
+ }
83
+ quota[key] = {
84
+ usedPercent,
85
+ remainingPercent,
86
+ checkedAt,
87
+ resetsAt: typeof window.resetsAt === 'string' ? window.resetsAt : undefined,
88
+ };
89
+ }
90
+ return Object.keys(quota).length ? quota : undefined;
91
+ }
92
+ function normalizeStorage(value) {
93
+ if (!isRecord(value) || !Array.isArray(value.accounts))
94
+ return null;
95
+ return {
96
+ version: 1,
97
+ main: { type: 'opencode', provider: 'anthropic' },
98
+ fallbackOn: Array.isArray(value.fallbackOn)
99
+ ? value.fallbackOn.filter((status) => Number.isInteger(status))
100
+ : undefined,
101
+ refresh: isRecord(value.refresh) ? value.refresh : undefined,
102
+ quota: isRecord(value.quota) ? value.quota : undefined,
103
+ claudeCache: isRecord(value.claudeCache) ? value.claudeCache : undefined,
104
+ dump: isRecord(value.dump) ? value.dump : undefined,
105
+ relay: isRecord(value.relay) ? value.relay : undefined,
106
+ accounts: value.accounts
107
+ .map(normalizeAccount)
108
+ .filter((account) => account != null),
109
+ };
110
+ }
111
+ export async function loadAccounts(path = getAccountStoragePath()) {
112
+ try {
113
+ const raw = await readFile(path, 'utf8');
114
+ return normalizeStorage(JSON.parse(raw));
115
+ }
116
+ catch (error) {
117
+ if (error.code === 'ENOENT')
118
+ return null;
119
+ return null;
120
+ }
121
+ }
122
+ async function loadExistingTopLevelFields(path) {
123
+ try {
124
+ const raw = await readFile(path, 'utf8');
125
+ const parsed = JSON.parse(raw);
126
+ return isRecord(parsed) ? parsed : {};
127
+ }
128
+ catch {
129
+ return {};
130
+ }
131
+ }
132
+ function omitUndefinedTopLevel(value) {
133
+ return Object.fromEntries(Object.entries(value).filter(([, entry]) => entry !== undefined));
134
+ }
135
+ export async function saveAccounts(storage, path = getAccountStoragePath()) {
136
+ await mkdir(dirname(path), { recursive: true });
137
+ const existing = await loadExistingTopLevelFields(path);
138
+ const next = { ...existing, ...omitUndefinedTopLevel(storage) };
139
+ const tempPath = `${path}.${randomUUID()}.tmp`;
140
+ await writeFile(tempPath, `${JSON.stringify(next, null, 2)}\n`, {
141
+ encoding: 'utf8',
142
+ mode: 0o600,
143
+ });
144
+ await rename(tempPath, path);
145
+ }
146
+ export function isCache1hPersistentlyEnabled(storage) {
147
+ return storage?.claudeCache?.enabled === true;
148
+ }
149
+ function normalizeCache1hMode(value) {
150
+ return typeof value === 'string' &&
151
+ CACHE_1H_MODES.includes(value)
152
+ ? value
153
+ : DEFAULT_CACHE_1H_MODE;
154
+ }
155
+ export function getCache1hPersistentMode(storage) {
156
+ return normalizeCache1hMode(storage?.claudeCache?.mode);
157
+ }
158
+ export async function setCache1hPersistentEnabled(enabled, mode, path = getAccountStoragePath()) {
159
+ const storage = (await loadAccounts(path)) ?? {
160
+ version: 1,
161
+ main: { type: 'opencode', provider: 'anthropic' },
162
+ accounts: [],
163
+ };
164
+ storage.claudeCache = {
165
+ ...(storage.claudeCache ?? {}),
166
+ enabled,
167
+ mode: mode ?? getCache1hPersistentMode(storage),
168
+ };
169
+ await saveAccounts(storage, path);
170
+ return storage;
171
+ }
172
+ export async function setCache1hPersistentMode(mode, path = getAccountStoragePath()) {
173
+ const storage = (await loadAccounts(path)) ?? {
174
+ version: 1,
175
+ main: { type: 'opencode', provider: 'anthropic' },
176
+ accounts: [],
177
+ };
178
+ storage.claudeCache = {
179
+ ...(storage.claudeCache ?? {}),
180
+ enabled: storage.claudeCache?.enabled === true,
181
+ mode,
182
+ };
183
+ await saveAccounts(storage, path);
184
+ return storage;
185
+ }
186
+ export function isDumpPersistentlyEnabled(storage) {
187
+ return storage?.dump?.enabled === true;
188
+ }
189
+ export async function setDumpPersistentEnabled(enabled, path = getAccountStoragePath()) {
190
+ const storage = (await loadAccounts(path)) ?? {
191
+ version: 1,
192
+ main: { type: 'opencode', provider: 'anthropic' },
193
+ accounts: [],
194
+ };
195
+ storage.dump = {
196
+ ...(storage.dump ?? {}),
197
+ enabled,
198
+ };
199
+ await saveAccounts(storage, path);
200
+ return storage;
201
+ }
202
+ function getFallbackStatuses(storage) {
203
+ return storage?.fallbackOn?.length ? storage.fallbackOn : DEFAULT_FALLBACK_ON;
204
+ }
205
+ export function shouldFallbackStatus(status, storage) {
206
+ return getFallbackStatuses(storage).includes(status);
207
+ }
208
+ function normalizeThresholds(storage) {
209
+ const configured = storage?.quota?.minimumRemaining || {};
210
+ return {
211
+ five_hour: configured.five_hour ??
212
+ configured['5h'] ??
213
+ DEFAULT_MINIMUM_REMAINING.five_hour,
214
+ seven_day: configured.seven_day ??
215
+ configured['1w'] ??
216
+ DEFAULT_MINIMUM_REMAINING.seven_day,
217
+ };
218
+ }
219
+ function quotaEnabled(storage) {
220
+ return storage?.quota?.enabled !== false;
221
+ }
222
+ function refreshEnabled(storage) {
223
+ return storage?.refresh?.enabled !== false;
224
+ }
225
+ function refreshBeforeExpiryMs(storage) {
226
+ const minutes = storage?.refresh?.refreshBeforeExpiryMinutes ??
227
+ DEFAULT_REFRESH_BEFORE_EXPIRY_MINUTES;
228
+ return Math.max(0, minutes) * 60_000;
229
+ }
230
+ export function getQuotaCheckIntervalMs(storage) {
231
+ const minutes = storage?.quota?.checkIntervalMinutes ?? DEFAULT_QUOTA_CHECK_INTERVAL_MINUTES;
232
+ return Math.max(1, minutes) * 60_000;
233
+ }
234
+ function failClosedOnUnknownQuota(storage) {
235
+ return (storage?.quota?.failClosedOnUnknownQuota ??
236
+ DEFAULT_FAIL_CLOSED_ON_UNKNOWN_QUOTA);
237
+ }
238
+ export function quotaSnapshotPassesPolicy(quota, storage) {
239
+ if (!quotaEnabled(storage))
240
+ return true;
241
+ const thresholds = normalizeThresholds(storage);
242
+ for (const key of ['five_hour', 'seven_day']) {
243
+ const window = quota?.[key];
244
+ if (!window)
245
+ return !failClosedOnUnknownQuota(storage);
246
+ if (window.remainingPercent < thresholds[key])
247
+ return false;
248
+ }
249
+ return true;
250
+ }
251
+ export function getQuotaNextRefreshAt(quota, storage, now) {
252
+ if (!quotaEnabled(storage))
253
+ return now + getQuotaCheckIntervalMs(storage);
254
+ const thresholds = normalizeThresholds(storage);
255
+ const blockedResetTimes = [];
256
+ for (const key of ['five_hour', 'seven_day']) {
257
+ const window = quota?.[key];
258
+ if (!window)
259
+ return now + getQuotaCheckIntervalMs(storage);
260
+ if (window.remainingPercent >= thresholds[key])
261
+ continue;
262
+ const resetTime = window.resetsAt ? Date.parse(window.resetsAt) : Number.NaN;
263
+ if (!Number.isFinite(resetTime) || resetTime <= now) {
264
+ return now + getQuotaCheckIntervalMs(storage);
265
+ }
266
+ blockedResetTimes.push(resetTime);
267
+ }
268
+ if (!blockedResetTimes.length)
269
+ return now + getQuotaCheckIntervalMs(storage);
270
+ return Math.min(...blockedResetTimes) + 60_000;
271
+ }
272
+ function tokenNeedsRefresh(account, storage, now) {
273
+ return (!account.access ||
274
+ !account.expires ||
275
+ account.expires - now <= refreshBeforeExpiryMs(storage));
276
+ }
277
+ function quotaIsStale(account, storage, now) {
278
+ if (!quotaEnabled(storage))
279
+ return false;
280
+ const maxAge = getQuotaCheckIntervalMs(storage);
281
+ return ['five_hour', 'seven_day'].some((key) => {
282
+ const window = account.quota?.[key];
283
+ return !window || now - window.checkedAt >= maxAge;
284
+ });
285
+ }
286
+ function clampPercent(value) {
287
+ if (!Number.isFinite(value))
288
+ return 0;
289
+ if (value < 0)
290
+ return 0;
291
+ if (value > 100)
292
+ return 100;
293
+ return value;
294
+ }
295
+ function mapUsageWindow(window, checkedAt) {
296
+ if (typeof window?.utilization !== 'number')
297
+ return undefined;
298
+ const usedPercent = clampPercent(window.utilization);
299
+ return {
300
+ usedPercent,
301
+ remainingPercent: clampPercent(100 - usedPercent),
302
+ resetsAt: window.resets_at,
303
+ checkedAt,
304
+ };
305
+ }
306
+ export async function fetchOAuthQuotaSnapshot(input) {
307
+ const fetchImpl = input.fetchImpl ?? fetch;
308
+ const response = await fetchImpl(QUOTA_URL, {
309
+ method: 'GET',
310
+ headers: {
311
+ Authorization: `Bearer ${input.accessToken}`,
312
+ Accept: 'application/json',
313
+ 'Content-Type': 'application/json',
314
+ 'anthropic-beta': 'oauth-2025-04-20',
315
+ 'User-Agent': `claude-code/${CLAUDE_CODE_VERSION}`,
316
+ },
317
+ });
318
+ if (!response.ok) {
319
+ const body = await response.text().catch(() => '');
320
+ throw new Error(`Claude quota check failed: ${response.status} — ${body}`);
321
+ }
322
+ const checkedAt = input.now?.() ?? Date.now();
323
+ const usage = (await response.json());
324
+ return {
325
+ five_hour: mapUsageWindow(usage.five_hour, checkedAt),
326
+ seven_day: mapUsageWindow(usage.seven_day, checkedAt),
327
+ };
328
+ }
329
+ function updateStoredAccount(storage, account) {
330
+ const index = storage.accounts.findIndex((candidate) => candidate.id === account.id);
331
+ if (index >= 0)
332
+ storage.accounts[index] = account;
333
+ }
334
+ function formatErrorMessage(error) {
335
+ return error instanceof Error ? error.message : String(error);
336
+ }
337
+ function recordRefreshError(account, error, now) {
338
+ account.lastRefreshError = {
339
+ message: formatErrorMessage(error),
340
+ checkedAt: now,
341
+ };
342
+ }
343
+ function recordQuotaRefreshError(account, error, now) {
344
+ account.lastQuotaRefreshError = {
345
+ message: formatErrorMessage(error),
346
+ checkedAt: now,
347
+ };
348
+ }
349
+ export class FallbackAccountManager {
350
+ now;
351
+ fetchImpl;
352
+ configPath;
353
+ refreshPromises = new Map();
354
+ refreshTimer = null;
355
+ quotaTimer = null;
356
+ constructor(options = {}) {
357
+ this.now = options.now ?? Date.now;
358
+ this.fetchImpl = options.fetchImpl ?? fetch;
359
+ this.configPath = options.configPath ?? getAccountStoragePath();
360
+ }
361
+ async load() {
362
+ return loadAccounts(this.configPath);
363
+ }
364
+ async save(storage) {
365
+ await saveAccounts(storage, this.configPath);
366
+ }
367
+ startBackgroundRefresh() {
368
+ void this.refreshDueAccounts().catch(() => { });
369
+ void this.refreshQuotaForDueAccounts().catch(() => { });
370
+ if (!this.refreshTimer) {
371
+ this.refreshTimer = setInterval(() => {
372
+ void this.refreshDueAccounts().catch(() => { });
373
+ }, BACKGROUND_TICK_MS);
374
+ if ('unref' in this.refreshTimer)
375
+ this.refreshTimer.unref();
376
+ }
377
+ if (!this.quotaTimer) {
378
+ this.quotaTimer = setInterval(() => {
379
+ void this.refreshQuotaForDueAccounts().catch(() => { });
380
+ }, BACKGROUND_TICK_MS);
381
+ if ('unref' in this.quotaTimer)
382
+ this.quotaTimer.unref();
383
+ }
384
+ }
385
+ stopBackgroundRefresh() {
386
+ if (this.refreshTimer)
387
+ clearInterval(this.refreshTimer);
388
+ if (this.quotaTimer)
389
+ clearInterval(this.quotaTimer);
390
+ this.refreshTimer = null;
391
+ this.quotaTimer = null;
392
+ }
393
+ async getUsableFallbackAccounts() {
394
+ const storage = await this.load();
395
+ if (!storage)
396
+ return [];
397
+ const usable = [];
398
+ let changed = false;
399
+ for (const account of storage.accounts) {
400
+ if (account.enabled === false)
401
+ continue;
402
+ try {
403
+ let next = account;
404
+ if (tokenNeedsRefresh(next, storage, this.now())) {
405
+ next = await this.refreshAccount(next, storage);
406
+ changed = true;
407
+ }
408
+ if (quotaIsStale(next, storage, this.now())) {
409
+ next = await this.refreshAccountQuota(next, storage);
410
+ changed = true;
411
+ }
412
+ if (this.accountPassesQuotaPolicy(next, storage))
413
+ usable.push(next);
414
+ }
415
+ catch {
416
+ if (!failClosedOnUnknownQuota(storage))
417
+ usable.push(account);
418
+ }
419
+ }
420
+ if (changed)
421
+ await this.save(storage);
422
+ return usable;
423
+ }
424
+ async markUsed(account) {
425
+ const storage = await this.load();
426
+ if (!storage)
427
+ return;
428
+ const stored = storage.accounts.find((candidate) => candidate.id === account.id);
429
+ if (!stored)
430
+ return;
431
+ stored.lastUsed = this.now();
432
+ await this.save(storage);
433
+ }
434
+ accountPassesQuotaPolicy(account, storage) {
435
+ return quotaSnapshotPassesPolicy(account.quota, storage);
436
+ }
437
+ async refreshDueAccounts() {
438
+ const storage = await this.load();
439
+ if (!storage || !refreshEnabled(storage))
440
+ return;
441
+ let changed = false;
442
+ for (const account of storage.accounts) {
443
+ if (account.enabled === false)
444
+ continue;
445
+ if (!tokenNeedsRefresh(account, storage, this.now()))
446
+ continue;
447
+ try {
448
+ await this.refreshAccount(account, storage);
449
+ changed = true;
450
+ }
451
+ catch (error) {
452
+ recordRefreshError(account, error, this.now());
453
+ updateStoredAccount(storage, account);
454
+ changed = true;
455
+ // Background refresh must not break the plugin request path.
456
+ }
457
+ }
458
+ if (changed)
459
+ await this.save(storage);
460
+ }
461
+ async refreshQuotaForDueAccounts() {
462
+ const storage = await this.load();
463
+ if (!storage || !quotaEnabled(storage))
464
+ return;
465
+ let changed = false;
466
+ for (const account of storage.accounts) {
467
+ if (account.enabled === false)
468
+ continue;
469
+ let next = account;
470
+ try {
471
+ if (tokenNeedsRefresh(next, storage, this.now())) {
472
+ next = await this.refreshAccount(next, storage);
473
+ changed = true;
474
+ }
475
+ if (!quotaIsStale(next, storage, this.now()))
476
+ continue;
477
+ await this.refreshAccountQuota(next, storage);
478
+ changed = true;
479
+ }
480
+ catch (error) {
481
+ recordQuotaRefreshError(account, error, this.now());
482
+ updateStoredAccount(storage, account);
483
+ changed = true;
484
+ // Quota probes are advisory; failed probes fail closed at selection time.
485
+ }
486
+ }
487
+ if (changed)
488
+ await this.save(storage);
489
+ }
490
+ async refreshQuotaForAllAccounts() {
491
+ const storage = await this.load();
492
+ const errors = [];
493
+ if (!storage || !quotaEnabled(storage))
494
+ return { storage, errors };
495
+ let changed = false;
496
+ for (const account of storage.accounts) {
497
+ if (account.enabled === false)
498
+ continue;
499
+ let next = account;
500
+ try {
501
+ if (tokenNeedsRefresh(next, storage, this.now())) {
502
+ next = await this.refreshAccount(next, storage);
503
+ changed = true;
504
+ }
505
+ await this.refreshAccountQuota(next, storage);
506
+ changed = true;
507
+ }
508
+ catch (error) {
509
+ recordQuotaRefreshError(account, error, this.now());
510
+ updateStoredAccount(storage, account);
511
+ changed = true;
512
+ errors.push({
513
+ accountId: account.id,
514
+ message: formatErrorMessage(error),
515
+ });
516
+ }
517
+ }
518
+ if (changed)
519
+ await this.save(storage);
520
+ return { storage, errors };
521
+ }
522
+ async refreshAccount(account, storage, options = {}) {
523
+ const existing = this.refreshPromises.get(account.id);
524
+ if (existing) {
525
+ const refreshed = await existing;
526
+ updateStoredAccount(storage, refreshed);
527
+ return refreshed;
528
+ }
529
+ const promise = this.refreshAccountNow(account, storage, options).finally(() => {
530
+ this.refreshPromises.delete(account.id);
531
+ });
532
+ this.refreshPromises.set(account.id, promise);
533
+ const refreshed = await promise;
534
+ updateStoredAccount(storage, refreshed);
535
+ return refreshed;
536
+ }
537
+ async refreshAccountNow(account, storage, options) {
538
+ const latestStorage = await this.load();
539
+ const latestAccount = latestStorage?.accounts.find((candidate) => candidate.id === account.id);
540
+ if (latestAccount &&
541
+ !options.force &&
542
+ !tokenNeedsRefresh(latestAccount, latestStorage, this.now())) {
543
+ updateStoredAccount(storage, latestAccount);
544
+ return latestAccount;
545
+ }
546
+ const sourceAccount = latestAccount ?? account;
547
+ const refreshToken = sourceAccount.refresh;
548
+ const response = await this.fetchImpl(TOKEN_URL, {
549
+ method: 'POST',
550
+ headers: {
551
+ 'Content-Type': 'application/json',
552
+ Accept: 'application/json, text/plain, */*',
553
+ 'User-Agent': 'axios/1.13.6',
554
+ },
555
+ body: JSON.stringify({
556
+ grant_type: 'refresh_token',
557
+ refresh_token: refreshToken,
558
+ client_id: CLIENT_ID,
559
+ }),
560
+ });
561
+ if (!response.ok) {
562
+ const body = await response.text().catch(() => '');
563
+ throw new Error(`Fallback token refresh failed: ${response.status} — ${body}`);
564
+ }
565
+ const json = (await response.json());
566
+ const refreshedAt = this.now();
567
+ sourceAccount.access = json.access_token;
568
+ sourceAccount.refresh = json.refresh_token ?? refreshToken;
569
+ sourceAccount.expires = refreshedAt + json.expires_in * 1000;
570
+ sourceAccount.lastRefreshedAt = refreshedAt;
571
+ sourceAccount.lastRefreshError = undefined;
572
+ updateStoredAccount(storage, sourceAccount);
573
+ return sourceAccount;
574
+ }
575
+ async refreshAccountQuota(account, storage) {
576
+ let target = account;
577
+ if (!target.access) {
578
+ throw new Error(`Fallback account ${account.id} has no access token`);
579
+ }
580
+ try {
581
+ target.quota = await fetchOAuthQuotaSnapshot({
582
+ accessToken: target.access,
583
+ fetchImpl: this.fetchImpl,
584
+ now: this.now,
585
+ });
586
+ }
587
+ catch (error) {
588
+ const message = error instanceof Error ? error.message : String(error);
589
+ if (!message.includes('Claude quota check failed: 401'))
590
+ throw error;
591
+ target = await this.refreshAccount(account, storage, {
592
+ force: true,
593
+ });
594
+ if (!target.access)
595
+ throw error;
596
+ target.quota = await fetchOAuthQuotaSnapshot({
597
+ accessToken: target.access,
598
+ fetchImpl: this.fetchImpl,
599
+ now: this.now,
600
+ });
601
+ }
602
+ target.lastQuotaRefreshError = undefined;
603
+ updateStoredAccount(storage, target);
604
+ return target;
605
+ }
606
+ }
package/dist/auth.d.ts ADDED
@@ -0,0 +1,16 @@
1
+ export type AuthorizationResult = {
2
+ url: string;
3
+ redirectUri: string;
4
+ state: string;
5
+ verifier: string;
6
+ };
7
+ export declare function authorize(mode: 'max' | 'console'): Promise<AuthorizationResult>;
8
+ export type ExchangeResult = {
9
+ type: 'success';
10
+ refresh: string;
11
+ access: string;
12
+ expires: number;
13
+ } | {
14
+ type: 'failed';
15
+ };
16
+ export declare function exchange(input: string, verifier: string, redirectUri: string, expectedState?: string): Promise<ExchangeResult>;