@juspay/neurolink 10.4.2 → 10.4.4
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/CHANGELOG.md +12 -0
- package/dist/auth/tokenStore.d.ts +7 -0
- package/dist/auth/tokenStore.js +54 -0
- package/dist/browser/neurolink.min.js +377 -380
- package/dist/cli/commands/proxy.d.ts +2 -1
- package/dist/cli/commands/proxy.js +290 -63
- package/dist/cli/commands/proxyAnalyze.js +10 -1
- package/dist/lib/auth/tokenStore.d.ts +7 -0
- package/dist/lib/auth/tokenStore.js +54 -0
- package/dist/lib/proxy/proxyAnalysis.js +136 -12
- package/dist/lib/proxy/proxyTranslationEngine.d.ts +1 -0
- package/dist/lib/proxy/proxyTranslationEngine.js +56 -12
- package/dist/lib/proxy/rawStreamCapture.js +47 -1
- package/dist/lib/proxy/requestLogger.d.ts +2 -0
- package/dist/lib/proxy/requestLogger.js +72 -13
- package/dist/lib/proxy/rollingProxyServer.js +79 -8
- package/dist/lib/proxy/rollingWorkerProcess.js +20 -15
- package/dist/lib/proxy/rollingWorkerProtocol.js +3 -0
- package/dist/lib/proxy/rollingWorkerSupervisor.d.ts +1 -0
- package/dist/lib/proxy/rollingWorkerSupervisor.js +31 -11
- package/dist/lib/proxy/runtimeConfig.d.ts +1 -0
- package/dist/lib/proxy/runtimeConfig.js +20 -5
- package/dist/lib/proxy/socketWorkerRuntime.js +41 -13
- package/dist/lib/proxy/sseInterceptor.js +47 -1
- package/dist/lib/proxy/tokenRefresh.d.ts +6 -0
- package/dist/lib/proxy/tokenRefresh.js +70 -15
- package/dist/lib/proxy/usageStats.d.ts +25 -3
- package/dist/lib/proxy/usageStats.js +546 -55
- package/dist/lib/server/routes/claudeProxyRoutes.d.ts +2 -0
- package/dist/lib/server/routes/claudeProxyRoutes.js +261 -297
- package/dist/lib/types/proxy.d.ts +90 -1
- package/dist/proxy/proxyAnalysis.js +136 -12
- package/dist/proxy/proxyTranslationEngine.d.ts +1 -0
- package/dist/proxy/proxyTranslationEngine.js +56 -12
- package/dist/proxy/rawStreamCapture.js +47 -1
- package/dist/proxy/requestLogger.d.ts +2 -0
- package/dist/proxy/requestLogger.js +72 -13
- package/dist/proxy/rollingProxyServer.js +79 -8
- package/dist/proxy/rollingWorkerProcess.js +20 -15
- package/dist/proxy/rollingWorkerProtocol.js +3 -0
- package/dist/proxy/rollingWorkerSupervisor.d.ts +1 -0
- package/dist/proxy/rollingWorkerSupervisor.js +31 -11
- package/dist/proxy/runtimeConfig.d.ts +1 -0
- package/dist/proxy/runtimeConfig.js +20 -5
- package/dist/proxy/socketWorkerRuntime.js +41 -13
- package/dist/proxy/sseInterceptor.js +47 -1
- package/dist/proxy/tokenRefresh.d.ts +6 -0
- package/dist/proxy/tokenRefresh.js +70 -15
- package/dist/proxy/usageStats.d.ts +25 -3
- package/dist/proxy/usageStats.js +546 -55
- package/dist/server/routes/claudeProxyRoutes.d.ts +2 -0
- package/dist/server/routes/claudeProxyRoutes.js +261 -297
- package/dist/types/proxy.d.ts +90 -1
- package/package.json +1 -1
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { logger } from "../utils/logger.js";
|
|
2
2
|
import { tokenStore } from "../auth/tokenStore.js";
|
|
3
|
+
import { ANTHROPIC_TOKEN_URL, ANTHROPIC_TOKEN_URL_FALLBACK, CLAUDE_CLI_USER_AGENT, CLAUDE_CODE_CLIENT_ID, } from "../auth/anthropicOAuth.js";
|
|
3
4
|
import { writeJsonSnapshotAtomically } from "./snapshotPersistence.js";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
const CLIENT_ID = "9d1c250a-e61b-44d9-88ed-5944d1962f5e";
|
|
5
|
+
import { withTimeout } from "../utils/async/withTimeout.js";
|
|
6
|
+
import { redactUrlForError } from "../utils/logSanitize.js";
|
|
7
7
|
const BUFFER_MS = 5 * 60 * 1000;
|
|
8
8
|
const SUCCESS_CACHE_MS = 60_000;
|
|
9
|
-
|
|
9
|
+
// Bound the best-effort persisted-credential reconciliation read so a hung or
|
|
10
|
+
// slow token-store decrypt can never stall (or abort) a live token refresh.
|
|
11
|
+
const PEEK_TOKENS_TIMEOUT_MS = 2_000;
|
|
10
12
|
const refreshesInFlight = new Map();
|
|
11
13
|
export function needsRefresh(account) {
|
|
12
14
|
return !!(account.expiresAt &&
|
|
@@ -23,28 +25,33 @@ async function performTokenRefresh(account) {
|
|
|
23
25
|
if (!account.refreshToken) {
|
|
24
26
|
return { success: false, error: "No refresh token available" };
|
|
25
27
|
}
|
|
26
|
-
|
|
28
|
+
// OAuth 2.0 token endpoints require an `application/x-www-form-urlencoded`
|
|
29
|
+
// request body (RFC 6749 §6); a JSON body is nonstandard for this grant and
|
|
30
|
+
// is rejected by RFC-compliant endpoints. Mirror the interactive auth flow
|
|
31
|
+
// in `anthropicOAuth.ts::_refreshAccessToken`.
|
|
32
|
+
const requestBody = new URLSearchParams({
|
|
27
33
|
grant_type: "refresh_token",
|
|
28
34
|
refresh_token: account.refreshToken,
|
|
29
|
-
client_id:
|
|
35
|
+
client_id: CLAUDE_CODE_CLIENT_ID,
|
|
30
36
|
}).toString();
|
|
31
37
|
const headers = {
|
|
32
38
|
"Content-Type": "application/x-www-form-urlencoded",
|
|
33
|
-
"
|
|
39
|
+
Accept: "application/json",
|
|
40
|
+
"User-Agent": CLAUDE_CLI_USER_AGENT,
|
|
34
41
|
};
|
|
35
|
-
const urls = [
|
|
42
|
+
const urls = [ANTHROPIC_TOKEN_URL, ANTHROPIC_TOKEN_URL_FALLBACK];
|
|
36
43
|
let terminalFailure;
|
|
37
44
|
for (const url of urls) {
|
|
38
45
|
try {
|
|
39
46
|
const resp = await fetch(url, {
|
|
40
47
|
method: "POST",
|
|
41
48
|
headers,
|
|
42
|
-
body:
|
|
49
|
+
body: requestBody,
|
|
43
50
|
signal: AbortSignal.timeout(10_000),
|
|
44
51
|
});
|
|
45
52
|
if (!resp.ok) {
|
|
46
53
|
const errorBody = await resp.text();
|
|
47
|
-
logger.warn(`[token-refresh] failed for ${account.label} at ${url}`, {
|
|
54
|
+
logger.warn(`[token-refresh] failed for ${account.label} at ${redactUrlForError(url)}`, {
|
|
48
55
|
status: resp.status,
|
|
49
56
|
error: errorBody.slice(0, 500),
|
|
50
57
|
});
|
|
@@ -57,7 +64,7 @@ async function performTokenRefresh(account) {
|
|
|
57
64
|
terminalFailure = failure;
|
|
58
65
|
}
|
|
59
66
|
// If primary URL returned a non-ok status, try fallback.
|
|
60
|
-
if (url ===
|
|
67
|
+
if (url === ANTHROPIC_TOKEN_URL) {
|
|
61
68
|
continue;
|
|
62
69
|
}
|
|
63
70
|
return terminalFailure ?? failure;
|
|
@@ -75,11 +82,11 @@ async function performTokenRefresh(account) {
|
|
|
75
82
|
return { success: true };
|
|
76
83
|
}
|
|
77
84
|
catch (e) {
|
|
78
|
-
logger.warn(`[token-refresh] exception for ${account.label} at ${url}`, {
|
|
85
|
+
logger.warn(`[token-refresh] exception for ${account.label} at ${redactUrlForError(url)}`, {
|
|
79
86
|
error: String(e),
|
|
80
87
|
});
|
|
81
88
|
// If primary URL threw, try fallback
|
|
82
|
-
if (url ===
|
|
89
|
+
if (url === ANTHROPIC_TOKEN_URL) {
|
|
83
90
|
continue;
|
|
84
91
|
}
|
|
85
92
|
return terminalFailure ?? { success: false, error: String(e) };
|
|
@@ -151,6 +158,54 @@ export async function refreshToken(account) {
|
|
|
151
158
|
}
|
|
152
159
|
return shared.result;
|
|
153
160
|
}
|
|
161
|
+
async function reloadPersistedTokenStoreAccount(account, target) {
|
|
162
|
+
if (!target || typeof target === "string" || !("providerKey" in target)) {
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
// Best-effort, bounded reconciliation: this only adopts a newer persisted
|
|
166
|
+
// credential generation if one exists. A decryption/IO failure — or a hung
|
|
167
|
+
// read — must NOT abort the caller, which can still refresh with the
|
|
168
|
+
// already-loaded token. Swallow the failure and treat it as "no newer
|
|
169
|
+
// persisted generation found" so refresh proceeds instead of the account
|
|
170
|
+
// being spuriously disabled.
|
|
171
|
+
let stored;
|
|
172
|
+
try {
|
|
173
|
+
stored = await withTimeout(tokenStore.peekTokens(target.providerKey), PEEK_TOKENS_TIMEOUT_MS, "[token-refresh] peekTokens reconciliation timed out");
|
|
174
|
+
}
|
|
175
|
+
catch (err) {
|
|
176
|
+
logger.debug("[token-refresh] skipping persisted-state reconciliation (peek failed)", { error: err instanceof Error ? err.message : String(err) });
|
|
177
|
+
return false;
|
|
178
|
+
}
|
|
179
|
+
if (!stored ||
|
|
180
|
+
stored.expiresAt <= (account.expiresAt ?? 0) ||
|
|
181
|
+
(stored.accessToken === account.token &&
|
|
182
|
+
stored.refreshToken === account.refreshToken &&
|
|
183
|
+
stored.expiresAt === account.expiresAt)) {
|
|
184
|
+
return false;
|
|
185
|
+
}
|
|
186
|
+
account.token = stored.accessToken;
|
|
187
|
+
account.refreshToken = stored.refreshToken;
|
|
188
|
+
account.expiresAt = stored.expiresAt;
|
|
189
|
+
return true;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Refreshes against the latest persisted credential generation. Queued
|
|
193
|
+
* requests can otherwise reject a rotated refresh token and disable a newer
|
|
194
|
+
* login that was saved while they were waiting.
|
|
195
|
+
*/
|
|
196
|
+
export async function refreshTokenFromLatest(account, target) {
|
|
197
|
+
const reloadedBeforeRefresh = await reloadPersistedTokenStoreAccount(account, target);
|
|
198
|
+
if (reloadedBeforeRefresh && !needsRefresh(account)) {
|
|
199
|
+
return { success: true };
|
|
200
|
+
}
|
|
201
|
+
const result = await refreshToken(account);
|
|
202
|
+
if (result.success) {
|
|
203
|
+
return result;
|
|
204
|
+
}
|
|
205
|
+
return (await reloadPersistedTokenStoreAccount(account, target))
|
|
206
|
+
? { success: true }
|
|
207
|
+
: result;
|
|
208
|
+
}
|
|
154
209
|
export function clearRefreshStateForTests() {
|
|
155
210
|
refreshesInFlight.clear();
|
|
156
211
|
}
|
|
@@ -183,7 +238,7 @@ async function persistLegacyCredentials(credPath, account) {
|
|
|
183
238
|
}
|
|
184
239
|
async function persistTokenStoreAccount(providerKey, account) {
|
|
185
240
|
try {
|
|
186
|
-
const existing = await tokenStore.
|
|
241
|
+
const existing = await withTimeout(tokenStore.peekTokens(providerKey), PEEK_TOKENS_TIMEOUT_MS, "[token-refresh] peekTokens for persistence timed out");
|
|
187
242
|
const merged = {
|
|
188
243
|
accessToken: account.token,
|
|
189
244
|
refreshToken: account.refreshToken ?? existing?.refreshToken,
|
|
@@ -191,7 +246,7 @@ async function persistTokenStoreAccount(providerKey, account) {
|
|
|
191
246
|
tokenType: existing?.tokenType ?? "Bearer",
|
|
192
247
|
...(existing?.scope ? { scope: existing.scope } : {}),
|
|
193
248
|
};
|
|
194
|
-
await tokenStore.saveTokens(providerKey, merged);
|
|
249
|
+
await withTimeout(tokenStore.saveTokens(providerKey, merged), PEEK_TOKENS_TIMEOUT_MS, "[token-refresh] saveTokens timed out");
|
|
195
250
|
}
|
|
196
251
|
catch (err) {
|
|
197
252
|
logger.warn("[token-refresh] Failed to persist TokenStore credentials", {
|
|
@@ -5,34 +5,49 @@
|
|
|
5
5
|
* shared snapshot asynchronously, under a cross-process lock, so overlapping
|
|
6
6
|
* rolling workers cannot overwrite each other's counters.
|
|
7
7
|
*/
|
|
8
|
-
import type { AccountStats, ProxyStats, ProxyStatsPersistenceStatus, ProxyUsageStatsStoreOptions } from "../types/index.js";
|
|
8
|
+
import type { AccountStats, ProxyTerminalErrorDetails, ProxyTerminalErrorJournal, ProxyStats, ProxyStatsPersistenceStatus, ProxyUsageStatsSnapshot, ProxyUsageStatsStoreOptions } from "../types/index.js";
|
|
9
9
|
export declare class ProxyUsageStatsStore {
|
|
10
10
|
private readonly now;
|
|
11
11
|
private readonly flushIntervalMs;
|
|
12
12
|
private readonly lockTimeoutMs;
|
|
13
13
|
private readonly staleLockMs;
|
|
14
14
|
private filePath?;
|
|
15
|
+
private terminalErrorsFilePath?;
|
|
15
16
|
private stats;
|
|
16
17
|
private pending;
|
|
18
|
+
private terminalErrors;
|
|
19
|
+
private pendingTerminalErrors;
|
|
17
20
|
private pendingMutations;
|
|
18
21
|
private inFlightMutations;
|
|
22
|
+
private pendingTerminalErrorMutations;
|
|
23
|
+
private inFlightTerminalErrorMutations;
|
|
19
24
|
private revision;
|
|
25
|
+
private terminalErrorsRevision;
|
|
20
26
|
private flushTimer;
|
|
21
27
|
private readonly flushMutex;
|
|
22
28
|
private lastFlushedAt?;
|
|
23
29
|
private lastReconciledAt?;
|
|
24
30
|
private lastRecoveryAt?;
|
|
25
31
|
private lastError?;
|
|
32
|
+
private terminalErrorsLastFlushedAt?;
|
|
33
|
+
private terminalErrorsLastRecoveryAt?;
|
|
34
|
+
private terminalErrorsLastError?;
|
|
35
|
+
private snapshotVersion;
|
|
26
36
|
constructor(options?: ProxyUsageStatsStoreOptions);
|
|
27
37
|
initialize(filePath?: string): Promise<void>;
|
|
28
38
|
recordAttempt(accountLabel: string, accountType: string): void;
|
|
29
39
|
recordFinalSuccess(accountLabel?: string, accountType?: string): void;
|
|
30
40
|
recordAttemptError(accountLabel: string, accountType: string, status: number, rateLimitKind?: "transient" | "quota"): void;
|
|
31
|
-
recordFinalError(
|
|
41
|
+
recordFinalError(status: number, accountLabel?: string, accountType?: string, details?: ProxyTerminalErrorDetails): void;
|
|
32
42
|
getStats(): ProxyStats;
|
|
33
43
|
getAccountStats(label: string): AccountStats | undefined;
|
|
44
|
+
getTerminalErrors(): ProxyTerminalErrorJournal;
|
|
45
|
+
getUsageSnapshot(): ProxyUsageStatsSnapshot;
|
|
34
46
|
getPersistenceStatus(): ProxyStatsPersistenceStatus;
|
|
35
47
|
flush(): Promise<void>;
|
|
48
|
+
private persistStatsDelta;
|
|
49
|
+
private persistTerminalErrorDelta;
|
|
50
|
+
reconcileUsageSnapshot(): Promise<ProxyUsageStatsSnapshot>;
|
|
36
51
|
reconcile(): Promise<ProxyStats>;
|
|
37
52
|
resetMemory(): void;
|
|
38
53
|
resetForTests(): Promise<void>;
|
|
@@ -41,13 +56,18 @@ export declare class ProxyUsageStatsStore {
|
|
|
41
56
|
private applyFinalSuccess;
|
|
42
57
|
private applyAttemptError;
|
|
43
58
|
private applyFinalError;
|
|
59
|
+
private applyTerminalError;
|
|
44
60
|
private ensureAccount;
|
|
45
61
|
private scheduleFlush;
|
|
46
62
|
private cancelFlushTimer;
|
|
47
63
|
private readSnapshot;
|
|
64
|
+
private readTerminalErrorSnapshot;
|
|
48
65
|
private applySnapshot;
|
|
66
|
+
private applyTerminalErrorSnapshot;
|
|
49
67
|
private recoverCorruptSnapshot;
|
|
68
|
+
private recoverCorruptTerminalErrorSnapshot;
|
|
50
69
|
private quarantineCorruptSnapshot;
|
|
70
|
+
private quarantineCorruptTerminalErrorSnapshot;
|
|
51
71
|
private pruneCorruptSnapshots;
|
|
52
72
|
private describeError;
|
|
53
73
|
}
|
|
@@ -55,10 +75,12 @@ export declare function initUsageStats(filePath: string): Promise<void>;
|
|
|
55
75
|
export declare function recordAttempt(accountLabel: string, accountType: string): void;
|
|
56
76
|
export declare function recordFinalSuccess(accountLabel?: string, accountType?: string): void;
|
|
57
77
|
export declare function recordAttemptError(accountLabel: string, accountType: string, status: number, rateLimitKind?: "transient" | "quota"): void;
|
|
58
|
-
export declare function recordFinalError(
|
|
78
|
+
export declare function recordFinalError(status: number, accountLabel?: string, accountType?: string, details?: ProxyTerminalErrorDetails): void;
|
|
59
79
|
export declare function getStats(): ProxyStats;
|
|
60
80
|
export declare function getReconciledStats(): Promise<ProxyStats>;
|
|
81
|
+
export declare function getReconciledUsageSnapshot(): Promise<ProxyUsageStatsSnapshot>;
|
|
61
82
|
export declare function getAccountStats(label: string): AccountStats | undefined;
|
|
83
|
+
export declare function getTerminalErrors(): ProxyTerminalErrorJournal;
|
|
62
84
|
export declare function getUsageStatsPersistenceStatus(): ProxyStatsPersistenceStatus;
|
|
63
85
|
export declare function flushUsageStats(): Promise<void>;
|
|
64
86
|
/** Reset process-local counters while intentionally preserving durable state. */
|