@askalf/dario 3.3.0 → 3.4.1
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/README.md +158 -137
- package/dist/cc-oauth-detect.d.ts +47 -0
- package/dist/cc-oauth-detect.js +232 -0
- package/dist/cc-template.d.ts +4 -4
- package/dist/cc-template.js +5 -5
- package/dist/cli.js +3 -5
- package/dist/oauth.js +21 -14
- package/dist/proxy.d.ts +0 -1
- package/dist/proxy.js +73 -296
- package/package.json +1 -1
|
@@ -0,0 +1,232 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* CC OAuth Auto-Detection
|
|
3
|
+
*
|
|
4
|
+
* Scans the installed Claude Code binary to extract its OAuth configuration
|
|
5
|
+
* (client_id, authorize URL, token URL, scopes). Eliminates the need to
|
|
6
|
+
* hardcode values that Anthropic rotates between CC releases.
|
|
7
|
+
*
|
|
8
|
+
* CC ships two OAuth client configurations in one binary:
|
|
9
|
+
*
|
|
10
|
+
* 1. LOCAL flow — used when the OAuth client owns the callback
|
|
11
|
+
* (i.e. runs an HTTP server on localhost). This is what dario does.
|
|
12
|
+
* Identified by OAUTH_FILE_SUFFIX:"-local-oauth" next to the CLIENT_ID.
|
|
13
|
+
*
|
|
14
|
+
* 2. PLATFORM flow — used when the callback is hosted at
|
|
15
|
+
* platform.claude.com/oauth/code/callback. Different CLIENT_ID.
|
|
16
|
+
* Not applicable to dario.
|
|
17
|
+
*
|
|
18
|
+
* We scan for the LOCAL block and extract its config.
|
|
19
|
+
*
|
|
20
|
+
* Results are cached per-binary-hash at ~/.dario/cc-oauth-cache.json so
|
|
21
|
+
* startup only re-scans when the user upgrades Claude Code.
|
|
22
|
+
*/
|
|
23
|
+
import { readFile, writeFile, mkdir, stat, open as openFile } from 'node:fs/promises';
|
|
24
|
+
import { existsSync } from 'node:fs';
|
|
25
|
+
import { homedir, platform } from 'node:os';
|
|
26
|
+
import { join, dirname } from 'node:path';
|
|
27
|
+
import { createHash } from 'node:crypto';
|
|
28
|
+
// Last-resort fallback if CC binary can't be found or scanned.
|
|
29
|
+
// These values are the known-good v2.1.104 local-oauth flow.
|
|
30
|
+
const FALLBACK = {
|
|
31
|
+
clientId: '22422756-60c9-4084-8eb7-27705fd5cf9a',
|
|
32
|
+
authorizeUrl: 'https://claude.com/cai/oauth/authorize',
|
|
33
|
+
tokenUrl: 'https://platform.claude.com/v1/oauth/token',
|
|
34
|
+
scopes: 'user:profile user:inference user:sessions:claude_code user:mcp_servers',
|
|
35
|
+
source: 'fallback',
|
|
36
|
+
};
|
|
37
|
+
const CACHE_PATH = join(homedir(), '.dario', 'cc-oauth-cache.json');
|
|
38
|
+
function candidatePaths() {
|
|
39
|
+
const home = homedir();
|
|
40
|
+
if (platform() === 'win32') {
|
|
41
|
+
return [
|
|
42
|
+
join(home, '.local', 'bin', 'claude.exe'),
|
|
43
|
+
join(home, 'AppData', 'Roaming', 'npm', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
|
|
44
|
+
join(home, 'AppData', 'Roaming', 'npm', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.mjs'),
|
|
45
|
+
join(home, '.claude', 'local', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
|
|
46
|
+
join(home, '.claude', 'local', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.mjs'),
|
|
47
|
+
];
|
|
48
|
+
}
|
|
49
|
+
return [
|
|
50
|
+
join(home, '.local', 'bin', 'claude'),
|
|
51
|
+
'/usr/local/bin/claude',
|
|
52
|
+
'/opt/homebrew/bin/claude',
|
|
53
|
+
'/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.js',
|
|
54
|
+
'/usr/local/lib/node_modules/@anthropic-ai/claude-code/cli.mjs',
|
|
55
|
+
'/opt/homebrew/lib/node_modules/@anthropic-ai/claude-code/cli.js',
|
|
56
|
+
join(home, '.claude', 'local', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.js'),
|
|
57
|
+
join(home, '.claude', 'local', 'node_modules', '@anthropic-ai', 'claude-code', 'cli.mjs'),
|
|
58
|
+
];
|
|
59
|
+
}
|
|
60
|
+
function findCCBinary() {
|
|
61
|
+
const override = process.env['DARIO_CC_PATH'];
|
|
62
|
+
if (override && existsSync(override))
|
|
63
|
+
return override;
|
|
64
|
+
for (const p of candidatePaths()) {
|
|
65
|
+
if (existsSync(p))
|
|
66
|
+
return p;
|
|
67
|
+
}
|
|
68
|
+
return null;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Fast fingerprint of a binary for caching. We hash the first 64KB plus
|
|
72
|
+
* size+mtime — this discriminates CC versions without reading GBs off disk.
|
|
73
|
+
*/
|
|
74
|
+
async function fingerprintBinary(path) {
|
|
75
|
+
const st = await stat(path);
|
|
76
|
+
const fh = await openFile(path, 'r');
|
|
77
|
+
try {
|
|
78
|
+
const buf = Buffer.alloc(Math.min(65536, st.size));
|
|
79
|
+
await fh.read(buf, 0, buf.length, 0);
|
|
80
|
+
const h = createHash('sha256');
|
|
81
|
+
h.update(buf);
|
|
82
|
+
h.update(String(st.size));
|
|
83
|
+
h.update(String(st.mtimeMs));
|
|
84
|
+
return h.digest('hex').slice(0, 16);
|
|
85
|
+
}
|
|
86
|
+
finally {
|
|
87
|
+
await fh.close();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Scan binary bytes for the LOCAL-oauth OAuth block.
|
|
92
|
+
* Uses Buffer.indexOf to locate anchor strings, then slices a small
|
|
93
|
+
* window of context to run regexes on. This avoids converting the
|
|
94
|
+
* whole binary to a JS string.
|
|
95
|
+
*/
|
|
96
|
+
export function scanBinaryForOAuthConfig(buf) {
|
|
97
|
+
// Anchor: `OAUTH_FILE_SUFFIX:"-local-oauth"` — this is the config-block
|
|
98
|
+
// occurrence, not the switch-case string literal. The switch-case produces
|
|
99
|
+
// just `-local-oauth` bytes, but the config object serializes as
|
|
100
|
+
// `OAUTH_FILE_SUFFIX:"-local-oauth"` with the key+quote prefix, which is
|
|
101
|
+
// stable across minified CC builds.
|
|
102
|
+
const anchor = Buffer.from('OAUTH_FILE_SUFFIX:"-local-oauth"');
|
|
103
|
+
let anchorIdx = buf.indexOf(anchor);
|
|
104
|
+
// Fallback anchor — some builds may tokenize differently.
|
|
105
|
+
if (anchorIdx === -1) {
|
|
106
|
+
const looseAnchor = Buffer.from('"-local-oauth"');
|
|
107
|
+
anchorIdx = buf.indexOf(looseAnchor);
|
|
108
|
+
}
|
|
109
|
+
if (anchorIdx === -1)
|
|
110
|
+
return null;
|
|
111
|
+
// The CLIENT_ID sits within a few hundred bytes BEFORE the anchor
|
|
112
|
+
// (in the same config object). Extract a window around it.
|
|
113
|
+
const windowStart = Math.max(0, anchorIdx - 1024);
|
|
114
|
+
const windowEnd = Math.min(buf.length, anchorIdx + 64);
|
|
115
|
+
const localBlock = buf.slice(windowStart, windowEnd).toString('latin1');
|
|
116
|
+
// Pick the CLIENT_ID that's CLOSEST to the anchor (last occurrence in window).
|
|
117
|
+
const cidRegex = /CLIENT_ID\s*:\s*"([0-9a-f-]{36})"/gi;
|
|
118
|
+
let lastCid = null;
|
|
119
|
+
let m;
|
|
120
|
+
while ((m = cidRegex.exec(localBlock)) !== null) {
|
|
121
|
+
if (m[1])
|
|
122
|
+
lastCid = m[1];
|
|
123
|
+
}
|
|
124
|
+
if (!lastCid)
|
|
125
|
+
return null;
|
|
126
|
+
const clientId = lastCid;
|
|
127
|
+
// Authorize URL: CLAUDE_AI_AUTHORIZE_URL appears once in the binary.
|
|
128
|
+
const authAnchor = Buffer.from('CLAUDE_AI_AUTHORIZE_URL');
|
|
129
|
+
const authIdx = buf.indexOf(authAnchor);
|
|
130
|
+
let authorizeUrl = FALLBACK.authorizeUrl;
|
|
131
|
+
if (authIdx !== -1) {
|
|
132
|
+
const w = buf.slice(authIdx, Math.min(buf.length, authIdx + 256)).toString('latin1');
|
|
133
|
+
const m = /CLAUDE_AI_AUTHORIZE_URL\s*:\s*"([^"]+)"/.exec(w);
|
|
134
|
+
if (m && m[1])
|
|
135
|
+
authorizeUrl = m[1];
|
|
136
|
+
}
|
|
137
|
+
// Token URL: TOKEN_URL — look for the one under platform.claude.com/.../oauth/token
|
|
138
|
+
const tokenAnchor = Buffer.from('TOKEN_URL');
|
|
139
|
+
let searchFrom = 0;
|
|
140
|
+
let tokenUrl = FALLBACK.tokenUrl;
|
|
141
|
+
while (searchFrom < buf.length) {
|
|
142
|
+
const idx = buf.indexOf(tokenAnchor, searchFrom);
|
|
143
|
+
if (idx === -1)
|
|
144
|
+
break;
|
|
145
|
+
const w = buf.slice(idx, Math.min(buf.length, idx + 128)).toString('latin1');
|
|
146
|
+
const m = /TOKEN_URL\s*:\s*"(https:\/\/[^"]*\/oauth\/token[^"]*)"/.exec(w);
|
|
147
|
+
if (m && m[1]) {
|
|
148
|
+
tokenUrl = m[1];
|
|
149
|
+
break;
|
|
150
|
+
}
|
|
151
|
+
searchFrom = idx + tokenAnchor.length;
|
|
152
|
+
}
|
|
153
|
+
// Scopes: contiguous quoted string of "user:X user:Y user:Z ..."
|
|
154
|
+
// Search for an anchor like "user:profile " which is the first scope.
|
|
155
|
+
const scopeAnchor = Buffer.from('"user:profile ');
|
|
156
|
+
let scopes = FALLBACK.scopes;
|
|
157
|
+
const scopeIdx = buf.indexOf(scopeAnchor);
|
|
158
|
+
if (scopeIdx !== -1) {
|
|
159
|
+
const w = buf.slice(scopeIdx, Math.min(buf.length, scopeIdx + 512)).toString('latin1');
|
|
160
|
+
const m = /"(user:profile(?:\s+user:[a-z_:]+)+)"/.exec(w);
|
|
161
|
+
if (m && m[1])
|
|
162
|
+
scopes = m[1];
|
|
163
|
+
}
|
|
164
|
+
return { clientId, authorizeUrl, tokenUrl, scopes };
|
|
165
|
+
}
|
|
166
|
+
async function loadCache() {
|
|
167
|
+
try {
|
|
168
|
+
const raw = await readFile(CACHE_PATH, 'utf-8');
|
|
169
|
+
const parsed = JSON.parse(raw);
|
|
170
|
+
if (parsed?.hash && parsed?.config?.clientId) {
|
|
171
|
+
return { hash: parsed.hash, config: parsed.config };
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
catch { /* no cache */ }
|
|
175
|
+
return null;
|
|
176
|
+
}
|
|
177
|
+
async function saveCache(hash, config) {
|
|
178
|
+
try {
|
|
179
|
+
await mkdir(dirname(CACHE_PATH), { recursive: true });
|
|
180
|
+
await writeFile(CACHE_PATH, JSON.stringify({ hash, config, savedAt: Date.now() }, null, 2));
|
|
181
|
+
}
|
|
182
|
+
catch { /* ignore cache write errors */ }
|
|
183
|
+
}
|
|
184
|
+
let memoized = null;
|
|
185
|
+
/**
|
|
186
|
+
* Get the OAuth config for dario to use. Scans the installed CC binary
|
|
187
|
+
* on first call, caches to disk, and memoizes in-process for subsequent
|
|
188
|
+
* calls. If no binary is found or scanning fails, falls back to the
|
|
189
|
+
* known-good v2.1.104 values.
|
|
190
|
+
*/
|
|
191
|
+
export async function detectCCOAuthConfig() {
|
|
192
|
+
if (memoized)
|
|
193
|
+
return memoized;
|
|
194
|
+
try {
|
|
195
|
+
const ccPath = findCCBinary();
|
|
196
|
+
if (!ccPath) {
|
|
197
|
+
memoized = FALLBACK;
|
|
198
|
+
return memoized;
|
|
199
|
+
}
|
|
200
|
+
const hash = await fingerprintBinary(ccPath);
|
|
201
|
+
// Check cache
|
|
202
|
+
const cached = await loadCache();
|
|
203
|
+
if (cached && cached.hash === hash) {
|
|
204
|
+
memoized = { ...cached.config, source: 'cached', ccPath, ccHash: hash };
|
|
205
|
+
return memoized;
|
|
206
|
+
}
|
|
207
|
+
// Read binary and scan
|
|
208
|
+
const buf = await readFile(ccPath);
|
|
209
|
+
const scanned = scanBinaryForOAuthConfig(buf);
|
|
210
|
+
if (!scanned) {
|
|
211
|
+
memoized = { ...FALLBACK, ccPath, ccHash: hash };
|
|
212
|
+
return memoized;
|
|
213
|
+
}
|
|
214
|
+
const detected = {
|
|
215
|
+
...scanned,
|
|
216
|
+
source: 'detected',
|
|
217
|
+
ccPath,
|
|
218
|
+
ccHash: hash,
|
|
219
|
+
};
|
|
220
|
+
await saveCache(hash, detected);
|
|
221
|
+
memoized = detected;
|
|
222
|
+
return memoized;
|
|
223
|
+
}
|
|
224
|
+
catch {
|
|
225
|
+
memoized = FALLBACK;
|
|
226
|
+
return memoized;
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/** Test-only: reset in-process memoization. */
|
|
230
|
+
export function _resetDetectorCache() {
|
|
231
|
+
memoized = null;
|
|
232
|
+
}
|
package/dist/cc-template.d.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude Code request template
|
|
2
|
+
* Claude Code request template.
|
|
3
3
|
*
|
|
4
4
|
* Tool definitions, system prompt, and request structure are loaded from
|
|
5
|
-
* cc-template-data.json
|
|
6
|
-
*
|
|
5
|
+
* cc-template-data.json and sent verbatim — this gives byte-level fidelity
|
|
6
|
+
* with the shape of a real Claude Code request.
|
|
7
7
|
*/
|
|
8
|
-
/** CC's exact tool definitions — loaded from
|
|
8
|
+
/** CC's exact tool definitions — loaded from the template JSON. */
|
|
9
9
|
export declare const CC_TOOL_DEFINITIONS: {
|
|
10
10
|
name: string;
|
|
11
11
|
description: string;
|
package/dist/cc-template.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Claude Code request template
|
|
2
|
+
* Claude Code request template.
|
|
3
3
|
*
|
|
4
4
|
* Tool definitions, system prompt, and request structure are loaded from
|
|
5
|
-
* cc-template-data.json
|
|
6
|
-
*
|
|
5
|
+
* cc-template-data.json and sent verbatim — this gives byte-level fidelity
|
|
6
|
+
* with the shape of a real Claude Code request.
|
|
7
7
|
*/
|
|
8
8
|
import { readFileSync } from 'node:fs';
|
|
9
9
|
import { join, dirname } from 'node:path';
|
|
@@ -11,7 +11,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
11
11
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
12
12
|
// Load template data at module init — fail fast if missing
|
|
13
13
|
const TEMPLATE = JSON.parse(readFileSync(join(__dirname, 'cc-template-data.json'), 'utf-8'));
|
|
14
|
-
/** CC's exact tool definitions — loaded from
|
|
14
|
+
/** CC's exact tool definitions — loaded from the template JSON. */
|
|
15
15
|
export const CC_TOOL_DEFINITIONS = TEMPLATE.tools;
|
|
16
16
|
/** CC's static system prompt (~25KB). */
|
|
17
17
|
export const CC_SYSTEM_PROMPT = TEMPLATE.system_prompt;
|
|
@@ -182,7 +182,7 @@ export function buildCCRequest(clientBody, billingTag, cache1h, identity, opts =
|
|
|
182
182
|
systemText = systemText.replace(pattern, '');
|
|
183
183
|
}
|
|
184
184
|
// ── Build the CC request from template ──
|
|
185
|
-
// Key order matches CC v2.1.104
|
|
185
|
+
// Key order matches CC v2.1.104 exactly:
|
|
186
186
|
// model, messages, system, tools, metadata, max_tokens, thinking, context_management, output_config, stream
|
|
187
187
|
//
|
|
188
188
|
// System prompt structure (3 blocks, matching real CC):
|
package/dist/cli.js
CHANGED
|
@@ -126,12 +126,11 @@ async function proxy() {
|
|
|
126
126
|
process.exit(1);
|
|
127
127
|
}
|
|
128
128
|
const verbose = args.includes('--verbose') || args.includes('-v');
|
|
129
|
-
const cliBackend = args.includes('--cli');
|
|
130
129
|
const passthrough = args.includes('--passthrough') || args.includes('--thin');
|
|
131
130
|
const preserveTools = args.includes('--preserve-tools') || args.includes('--keep-tools');
|
|
132
131
|
const modelArg = args.find(a => a.startsWith('--model='));
|
|
133
132
|
const model = modelArg ? modelArg.split('=')[1] : undefined;
|
|
134
|
-
await startProxy({ port, verbose, model,
|
|
133
|
+
await startProxy({ port, verbose, model, passthrough, preserveTools });
|
|
135
134
|
}
|
|
136
135
|
async function help() {
|
|
137
136
|
console.log(`
|
|
@@ -149,15 +148,14 @@ async function help() {
|
|
|
149
148
|
Shortcuts: opus, sonnet, haiku
|
|
150
149
|
Full IDs: claude-opus-4-6, claude-sonnet-4-6
|
|
151
150
|
Default: passthrough (client decides)
|
|
152
|
-
--
|
|
153
|
-
--passthrough Thin proxy — OAuth swap only, no injection
|
|
151
|
+
--passthrough, --thin Thin proxy — OAuth swap only, no injection
|
|
154
152
|
--preserve-tools Keep client tool schemas (for agents with custom tools)
|
|
155
153
|
--port=PORT Port to listen on (default: 3456)
|
|
156
154
|
--verbose, -v Log all requests
|
|
157
155
|
|
|
158
156
|
Quick start:
|
|
159
157
|
dario login # auto-detects Claude Code credentials
|
|
160
|
-
dario proxy
|
|
158
|
+
dario proxy --model=opus # or: dario proxy --passthrough
|
|
161
159
|
|
|
162
160
|
Then point any Anthropic SDK at http://localhost:3456:
|
|
163
161
|
export ANTHROPIC_BASE_URL=http://localhost:3456
|
package/dist/oauth.js
CHANGED
|
@@ -8,13 +8,17 @@ import { randomBytes, createHash } from 'node:crypto';
|
|
|
8
8
|
import { readFile, writeFile, mkdir, rename } from 'node:fs/promises';
|
|
9
9
|
import { dirname, join } from 'node:path';
|
|
10
10
|
import { homedir } from 'node:os';
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
//
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
//
|
|
17
|
-
|
|
11
|
+
import { detectCCOAuthConfig } from './cc-oauth-detect.js';
|
|
12
|
+
// OAuth config is auto-detected at runtime from the installed Claude Code
|
|
13
|
+
// binary. This eliminates the "Anthropic rotated the client_id again" class
|
|
14
|
+
// of bugs — dario stays in sync with whatever CC version the user has
|
|
15
|
+
// installed, forever. See cc-oauth-detect.ts for the scanner.
|
|
16
|
+
//
|
|
17
|
+
// Hardcoded fallbacks live in cc-oauth-detect.ts and are the known-good
|
|
18
|
+
// CC v2.1.104 local-oauth flow values.
|
|
19
|
+
async function getOAuthConfig() {
|
|
20
|
+
return detectCCOAuthConfig();
|
|
21
|
+
}
|
|
18
22
|
// Refresh 30 min before expiry
|
|
19
23
|
const REFRESH_BUFFER_MS = 30 * 60 * 1000;
|
|
20
24
|
// After a failed refresh, don't retry for 60s to avoid spam
|
|
@@ -116,17 +120,18 @@ export async function startAutoOAuthFlow() {
|
|
|
116
120
|
server.listen(0, 'localhost', async () => {
|
|
117
121
|
const addr = server.address();
|
|
118
122
|
port = typeof addr === 'object' && addr ? addr.port : 0;
|
|
123
|
+
const cfg = await getOAuthConfig();
|
|
119
124
|
const params = new URLSearchParams({
|
|
120
125
|
code: 'true',
|
|
121
|
-
client_id:
|
|
126
|
+
client_id: cfg.clientId,
|
|
122
127
|
response_type: 'code',
|
|
123
128
|
redirect_uri: `http://localhost:${port}/callback`,
|
|
124
|
-
scope:
|
|
129
|
+
scope: cfg.scopes,
|
|
125
130
|
code_challenge: codeChallenge,
|
|
126
131
|
code_challenge_method: 'S256',
|
|
127
132
|
state,
|
|
128
133
|
});
|
|
129
|
-
const authUrl = `${
|
|
134
|
+
const authUrl = `${cfg.authorizeUrl}?${params.toString()}`;
|
|
130
135
|
// Open browser
|
|
131
136
|
console.log(' Opening browser to sign in...');
|
|
132
137
|
console.log(` If the browser didn't open, visit: ${authUrl}`);
|
|
@@ -152,12 +157,13 @@ export async function startAutoOAuthFlow() {
|
|
|
152
157
|
* Exchange code using the localhost redirect URI.
|
|
153
158
|
*/
|
|
154
159
|
async function exchangeCodeWithRedirect(code, codeVerifier, state, port) {
|
|
155
|
-
const
|
|
160
|
+
const cfg = await getOAuthConfig();
|
|
161
|
+
const res = await fetch(cfg.tokenUrl, {
|
|
156
162
|
method: 'POST',
|
|
157
163
|
headers: { 'Content-Type': 'application/json' },
|
|
158
164
|
body: JSON.stringify({
|
|
159
165
|
grant_type: 'authorization_code',
|
|
160
|
-
client_id:
|
|
166
|
+
client_id: cfg.clientId,
|
|
161
167
|
code,
|
|
162
168
|
redirect_uri: `http://localhost:${port}/callback`,
|
|
163
169
|
code_verifier: codeVerifier,
|
|
@@ -201,16 +207,17 @@ async function doRefreshTokens() {
|
|
|
201
207
|
throw new Error('No refresh token available. Run `dario login` first.');
|
|
202
208
|
}
|
|
203
209
|
const oauth = creds.claudeAiOauth;
|
|
210
|
+
const cfg = await getOAuthConfig();
|
|
204
211
|
for (let attempt = 0; attempt < 3; attempt++) {
|
|
205
212
|
if (attempt > 0)
|
|
206
213
|
await new Promise(r => setTimeout(r, 1000 * Math.pow(2, attempt)));
|
|
207
|
-
const res = await fetch(
|
|
214
|
+
const res = await fetch(cfg.tokenUrl, {
|
|
208
215
|
method: 'POST',
|
|
209
216
|
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
|
210
217
|
body: new URLSearchParams({
|
|
211
218
|
grant_type: 'refresh_token',
|
|
212
219
|
refresh_token: oauth.refreshToken,
|
|
213
|
-
client_id:
|
|
220
|
+
client_id: cfg.clientId,
|
|
214
221
|
}),
|
|
215
222
|
signal: AbortSignal.timeout(15000),
|
|
216
223
|
});
|