@acosmi/sdk-ts 1.4.0 → 1.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.
@@ -2242,6 +2242,9 @@ var FilterStatusFallbackTkdistSkew = "fallback-tkdist-deployment-skew";
2242
2242
  var FilterStatusFallbackNoBuckets = "fallback-no-buckets";
2243
2243
  var FilterStatusFallbackMissingUser = "fallback-missing-userid";
2244
2244
  var FilterStatusUnknown = "";
2245
+ var ErrOAuthCORSBlocked = "oauth_cors_blocked";
2246
+ var ErrRefreshProxyFailed = "refresh_proxy_failed";
2247
+ var ErrTokenExpired = "token_expired";
2245
2248
  function newDeferred() {
2246
2249
  let resolve;
2247
2250
  let reject;
@@ -2259,6 +2262,10 @@ var Client = class _Client {
2259
2262
  complianceBaseURL;
2260
2263
  /** OAuth metadata profile — 刷新 token 时发现 metadata 用 (默认 'desktop') */
2261
2264
  oauthMetadataProfile;
2265
+ /** Browser Web OAuth refresh strategy (default 'direct') */
2266
+ browserRefreshMode;
2267
+ /** Same-origin refresh proxy URL for browserRefreshMode='server-proxy' */
2268
+ refreshProxyURL;
2262
2269
  /** OAuth metadata (lazy loaded) */
2263
2270
  meta = null;
2264
2271
  /** 当前 token (内存) */
@@ -2294,6 +2301,8 @@ var Client = class _Client {
2294
2301
  this.serverURL = (cfg.serverURL ?? "https://acosmi.com").replace(/\/+$/, "");
2295
2302
  this.complianceBaseURL = cfg.complianceBaseURL ? cfg.complianceBaseURL.replace(/\/+$/, "") : null;
2296
2303
  this.oauthMetadataProfile = cfg.oauthMetadataProfile ?? "desktop";
2304
+ this.browserRefreshMode = cfg.browserRefreshMode ?? "direct";
2305
+ this.refreshProxyURL = cfg.refreshProxyURL ?? null;
2297
2306
  this.store = cfg.store ?? defaultTokenStore();
2298
2307
  this.fetchImpl = cfg.fetchImpl ?? globalThis.fetch.bind(globalThis);
2299
2308
  this.retryPolicy = effectivePolicy(cfg.retryPolicy ?? null);
@@ -2537,27 +2546,7 @@ var Client = class _Client {
2537
2546
  if (!tokenSetIsExpired(this.tokens)) {
2538
2547
  return this.tokens.access_token;
2539
2548
  }
2540
- if (this.meta == null) {
2541
- try {
2542
- this.meta = await discoverWithProfile(this.serverURL, this.oauthMetadataProfile, signal);
2543
- } catch (e) {
2544
- throw new Error(
2545
- `discover for refresh: ${e instanceof Error ? e.message : String(e)}`
2546
- );
2547
- }
2548
- }
2549
- let tokenResp;
2550
- try {
2551
- tokenResp = await refreshToken(this.meta, this.tokens.client_id, this.tokens.refresh_token, signal);
2552
- } catch (e) {
2553
- throw new Error(`refresh token: ${e instanceof Error ? e.message : String(e)}`);
2554
- }
2555
- this.tokens = newTokenSet(tokenResp, this.tokens.client_id, this.serverURL);
2556
- try {
2557
- await this.store.save(this.tokens);
2558
- } catch (e) {
2559
- console.warn(`[acosmi-sdk] warning: save refreshed token failed: ${e instanceof Error ? e.message : String(e)}`);
2560
- }
2549
+ await this.refreshCurrentToken(signal);
2561
2550
  return this.tokens.access_token;
2562
2551
  })
2563
2552
  );
@@ -2570,26 +2559,111 @@ var Client = class _Client {
2570
2559
  if (this.tokens == null) {
2571
2560
  throw new Error("no tokens to refresh");
2572
2561
  }
2573
- if (this.meta == null) {
2574
- this.meta = await discoverWithProfile(this.serverURL, this.oauthMetadataProfile, signal);
2575
- }
2576
- const tokenResp = await refreshToken(
2577
- this.meta,
2578
- this.tokens.client_id,
2579
- this.tokens.refresh_token,
2580
- signal
2581
- );
2582
- this.tokens = newTokenSet(tokenResp, this.tokens.client_id, this.serverURL);
2583
- try {
2584
- await this.store.save(this.tokens);
2585
- } catch (e) {
2586
- console.warn(
2587
- `[acosmi-sdk] warning: save refreshed token failed: ${e instanceof Error ? e.message : String(e)}`
2588
- );
2589
- }
2562
+ await this.refreshCurrentToken(signal);
2590
2563
  })
2591
2564
  );
2592
2565
  }
2566
+ async refreshCurrentToken(signal) {
2567
+ if (this.tokens == null) {
2568
+ throw new Error("no tokens to refresh");
2569
+ }
2570
+ if (this.browserRefreshMode === "none") {
2571
+ throw new Error(`${ErrTokenExpired}: token refresh disabled`);
2572
+ }
2573
+ if (this.browserRefreshMode === "server-proxy") {
2574
+ await this.refreshCurrentTokenViaProxy(signal);
2575
+ return;
2576
+ }
2577
+ await this.refreshCurrentTokenDirect(signal);
2578
+ }
2579
+ async refreshCurrentTokenDirect(signal) {
2580
+ if (this.tokens == null) {
2581
+ throw new Error("no tokens to refresh");
2582
+ }
2583
+ if (this.meta == null) {
2584
+ try {
2585
+ this.meta = await discoverWithProfile(this.serverURL, this.oauthMetadataProfile, signal);
2586
+ } catch (e) {
2587
+ throw new Error(
2588
+ `discover for refresh: ${e instanceof Error ? e.message : String(e)}`
2589
+ );
2590
+ }
2591
+ }
2592
+ let tokenResp;
2593
+ try {
2594
+ tokenResp = await refreshToken(
2595
+ this.meta,
2596
+ this.tokens.client_id,
2597
+ this.tokens.refresh_token,
2598
+ signal
2599
+ );
2600
+ } catch (e) {
2601
+ const message = e instanceof Error ? e.message : String(e);
2602
+ if (isLikelyBrowserOAuthCORSError(message)) {
2603
+ throw new Error(`${ErrOAuthCORSBlocked}: refresh token: ${message}`);
2604
+ }
2605
+ throw new Error(`refresh token: ${message}`);
2606
+ }
2607
+ this.tokens = newTokenSet(tokenResp, this.tokens.client_id, this.serverURL);
2608
+ await this.saveRefreshedToken();
2609
+ }
2610
+ async refreshCurrentTokenViaProxy(signal) {
2611
+ if (this.tokens == null) {
2612
+ throw new Error("no tokens to refresh");
2613
+ }
2614
+ if (!this.refreshProxyURL) {
2615
+ throw new Error(`${ErrRefreshProxyFailed}: refreshProxyURL is required`);
2616
+ }
2617
+ let resp;
2618
+ try {
2619
+ resp = await this.fetchImpl(this.refreshProxyURL, {
2620
+ method: "POST",
2621
+ headers: { "Content-Type": "application/json" },
2622
+ body: JSON.stringify({
2623
+ client_id: this.tokens.client_id,
2624
+ refresh_token: this.tokens.refresh_token,
2625
+ server_url: this.tokens.server_url || this.serverURL
2626
+ }),
2627
+ signal
2628
+ });
2629
+ } catch (e) {
2630
+ throw new Error(
2631
+ `${ErrRefreshProxyFailed}: ${e instanceof Error ? e.message : String(e)}`
2632
+ );
2633
+ }
2634
+ if (!resp.ok) {
2635
+ let message = "";
2636
+ try {
2637
+ const body2 = await resp.json();
2638
+ if (typeof body2.error === "string") message = body2.error;
2639
+ } catch {
2640
+ }
2641
+ throw new Error(`${ErrRefreshProxyFailed}: HTTP ${resp.status}: ${message}`);
2642
+ }
2643
+ let body;
2644
+ try {
2645
+ body = await resp.json();
2646
+ } catch (e) {
2647
+ throw new Error(
2648
+ `${ErrRefreshProxyFailed}: decode: ${e instanceof Error ? e.message : String(e)}`
2649
+ );
2650
+ }
2651
+ if (!isTokenSetLike(body.tokenSet)) {
2652
+ throw new Error(`${ErrRefreshProxyFailed}: response missing tokenSet`);
2653
+ }
2654
+ this.tokens = body.tokenSet;
2655
+ await this.saveRefreshedToken();
2656
+ }
2657
+ async saveRefreshedToken() {
2658
+ if (this.tokens == null) return;
2659
+ try {
2660
+ await this.store.save(this.tokens);
2661
+ } catch (e) {
2662
+ console.warn(
2663
+ `[acosmi-sdk] warning: save refreshed token failed: ${e instanceof Error ? e.message : String(e)}`
2664
+ );
2665
+ }
2666
+ }
2593
2667
  /** 互斥锁 helper (替代 Go sync.Mutex) */
2594
2668
  withMu(fn) {
2595
2669
  const next = this.mu.then(fn, fn);
@@ -3288,6 +3362,15 @@ function defaultTokenStore() {
3288
3362
  }
3289
3363
  return new InMemoryTokenStore();
3290
3364
  }
3365
+ function isLikelyBrowserOAuthCORSError(message) {
3366
+ const lower = message.toLowerCase();
3367
+ return lower.includes("failed to fetch") || lower.includes("networkerror") || lower.includes("cors") || lower.includes("http 403");
3368
+ }
3369
+ function isTokenSetLike(value) {
3370
+ if (value == null || typeof value !== "object") return false;
3371
+ const candidate = value;
3372
+ return typeof candidate.access_token === "string" && typeof candidate.refresh_token === "string" && typeof candidate.expires_at === "string" && typeof candidate.scope === "string" && typeof candidate.client_id === "string" && typeof candidate.server_url === "string";
3373
+ }
3291
3374
  function zeroModelCapabilities() {
3292
3375
  return {
3293
3376
  supports_thinking: false,
@@ -5478,8 +5561,10 @@ exports.ErrBrowserOpen = ErrBrowserOpen;
5478
5561
  exports.ErrComplianceStepUpRequired = ErrComplianceStepUpRequired;
5479
5562
  exports.ErrDiscovery = ErrDiscovery;
5480
5563
  exports.ErrEnvelopeGateClosed = ErrEnvelopeGateClosed;
5564
+ exports.ErrOAuthCORSBlocked = ErrOAuthCORSBlocked;
5481
5565
  exports.ErrProviderNotConfigured = ErrProviderNotConfigured;
5482
5566
  exports.ErrProviderUnknownNoRetry = ErrProviderUnknownNoRetry;
5567
+ exports.ErrRefreshProxyFailed = ErrRefreshProxyFailed;
5483
5568
  exports.ErrRegistration = ErrRegistration;
5484
5569
  exports.ErrSSLProxy = ErrSSLProxy;
5485
5570
  exports.ErrSealApprovalContractHashMismatch = ErrSealApprovalContractHashMismatch;
@@ -5493,6 +5578,7 @@ exports.ErrSealUseAlreadyConsumed = ErrSealUseAlreadyConsumed;
5493
5578
  exports.ErrStateMismatch = ErrStateMismatch;
5494
5579
  exports.ErrTimeout = ErrTimeout;
5495
5580
  exports.ErrTokenExchange = ErrTokenExchange;
5581
+ exports.ErrTokenExpired = ErrTokenExpired;
5496
5582
  exports.EventAuthURL = EventAuthURL;
5497
5583
  exports.EventComplete = EventComplete;
5498
5584
  exports.EventError = EventError;