@vercel/flags-core 1.10.2 → 1.10.3

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 CHANGED
@@ -1,5 +1,11 @@
1
1
  # @vercel/flags-core
2
2
 
3
+ ## 1.10.3
4
+
5
+ ### Patch Changes
6
+
7
+ - [#578](https://github.com/vercel/flags/pull/578) [`63d13be`](https://github.com/vercel/flags/commit/63d13be0193102a4f0cbcbf48e7f67efad96a462) Thanks [@luismeyer](https://github.com/luismeyer)! - Fail fast on authentication errors. When the stream gets a 401 or no OIDC token is available, the client no longer falls back to polling, which would fail the same way, and goes straight to revalidating over HTTP on reads. When polling is the primary source, its first auth failure stops it, and a missing OIDC token is no longer retried. A failed OIDC token refresh is still retried. A client without credentials no longer keeps scripts and test files running.
8
+
3
9
  ## 1.10.2
4
10
 
5
11
  ### Patch Changes
@@ -576,7 +576,7 @@ function bulkEvaluate(flags, shared) {
576
576
  }
577
577
 
578
578
  // package.json
579
- var version = "1.10.2";
579
+ var version = "1.10.3";
580
580
 
581
581
  // src/lib/report-value.ts
582
582
  function internalReportValue(key, value, data) {
@@ -1106,17 +1106,26 @@ function unauthorizedMessage(sourceProjectId) {
1106
1106
  }
1107
1107
  return `unauthorized (401): this deployment is not allowed to read the flags of project "${sourceProjectId}". Check that the project is connected. See https://vercel.com/docs/flags/vercel-flags/project-connections`;
1108
1108
  }
1109
+ var oidcTokenMessage = [
1110
+ "@vercel/flags-core: Failed to get OIDC token.",
1111
+ "Are you running in a Vercel Environment where OIDC tokens are available?",
1112
+ "Did you mean to use an SDK Key instead? Use the environment variable FLAGS or pass it directly to the client."
1113
+ ].join(" ");
1114
+ var missingCredentials = /`vc link|vercel login|^The 'x-vercel-oidc-token' header is missing from the request\.$/;
1115
+ var OidcTokenError = class extends Error {
1116
+ constructor(cause) {
1117
+ super(oidcTokenMessage, { cause });
1118
+ this.name = "OidcTokenError";
1119
+ }
1120
+ };
1109
1121
  async function getOidcToken() {
1110
1122
  try {
1111
1123
  return await getVercelOidcToken();
1112
- } catch {
1113
- throw new Error(
1114
- [
1115
- "@vercel/flags-core: Failed to get OIDC token.",
1116
- "Are you running in a Vercel Environment where OIDC tokens are available?",
1117
- "Did you mean to use an SDK Key instead? Use the environment variable FLAGS or pass it directly to the client."
1118
- ].join(" ")
1119
- );
1124
+ } catch (error) {
1125
+ if (error instanceof Error && missingCredentials.test(error.message)) {
1126
+ throw new OidcTokenError(error);
1127
+ }
1128
+ throw new Error(oidcTokenMessage, { cause: error });
1120
1129
  }
1121
1130
  }
1122
1131
  function getProjectIdFromOidcToken(oidcToken) {
@@ -2160,7 +2169,8 @@ async function fetchDatafile(options) {
2160
2169
  return await Promise.race([fetchAttempt(attempt + 1), aborted]);
2161
2170
  } catch (error) {
2162
2171
  signal.throwIfAborted();
2163
- if (attempt === maxAttempts - 1 || error instanceof DatafileHttpError && !error.retryable) {
2172
+ if (attempt === maxAttempts - 1 || error instanceof DatafileHttpError && !error.retryable || // Retrying will not produce a token that is not there.
2173
+ error instanceof OidcTokenError) {
2164
2174
  throw error instanceof Error ? error : new Error("Unknown fetch error");
2165
2175
  }
2166
2176
  debug(options.clientName, "datafile.fetch.retry", () => ({
@@ -2578,7 +2588,7 @@ async function connectStream(config, callbacks) {
2578
2588
  rejectInit(error);
2579
2589
  }
2580
2590
  onDisconnect?.();
2581
- abortController.abort();
2591
+ abortController.abort(error);
2582
2592
  break;
2583
2593
  }
2584
2594
  if (!response.ok) {
@@ -2680,7 +2690,7 @@ async function connectStream(config, callbacks) {
2680
2690
  }
2681
2691
  if (error instanceof TokenResolutionError && !initialDataReceived) {
2682
2692
  rejectInit(error.cause);
2683
- abortController.abort();
2693
+ abortController.abort(error.cause);
2684
2694
  break;
2685
2695
  }
2686
2696
  if (connectionAbort.signal.reason !== PING_TIMEOUT) {
@@ -2754,7 +2764,7 @@ var StreamSource = class extends TypedEmitter {
2754
2764
  if (this.abortController === abortController) {
2755
2765
  this.promise = void 0;
2756
2766
  this.abortController = void 0;
2757
- this.emit("exhausted");
2767
+ this.emit("exhausted", abortController.signal.reason);
2758
2768
  }
2759
2769
  },
2760
2770
  { once: true }
@@ -2815,7 +2825,7 @@ var UNKNOWN_FRESHNESS = {
2815
2825
  assess: () => ({ status: "unknown" })
2816
2826
  };
2817
2827
  function isUnauthorizedError(error) {
2818
- if (error instanceof UnauthorizedError) {
2828
+ if (error instanceof UnauthorizedError || error instanceof OidcTokenError) {
2819
2829
  return true;
2820
2830
  }
2821
2831
  if (!(error instanceof Error)) {
@@ -2850,8 +2860,9 @@ var Controller = class {
2850
2860
  // Build-step deduplication
2851
2861
  buildDataPromise = null;
2852
2862
  buildReadTracked = false;
2853
- // Suppresses usage tracking while the credential is rejected. Set on a 401
2854
- // from any source, cleared as soon as a source delivers data again.
2863
+ // Suppresses usage tracking while the credential is rejected or no OIDC
2864
+ // token is available. Set on an auth failure from any source, cleared as
2865
+ // soon as a source delivers data again.
2855
2866
  unauthorized = false;
2856
2867
  constructor(options) {
2857
2868
  this.options = normalizeOptions(options);
@@ -2947,10 +2958,16 @@ var Controller = class {
2947
2958
  this.transition("degraded", "stream-disconnected");
2948
2959
  }
2949
2960
  };
2950
- onStreamExhausted = () => {
2961
+ onStreamExhausted = (reason) => {
2951
2962
  if (this.state !== "streaming" && this.state !== "degraded") {
2952
2963
  return;
2953
2964
  }
2965
+ if (isUnauthorizedError(reason)) {
2966
+ if (this.state === "streaming") {
2967
+ this.transition("degraded", "stream-auth-failed");
2968
+ }
2969
+ return;
2970
+ }
2954
2971
  void this.activateFallbackSource().catch(() => {
2955
2972
  });
2956
2973
  };
@@ -2962,7 +2979,7 @@ var Controller = class {
2962
2979
  this.onSourceError(error);
2963
2980
  if (this.state === "polling" && isUnauthorizedError(error)) {
2964
2981
  this.pollingSource.stop();
2965
- this.transition("degraded", "polling-unauthorized");
2982
+ this.transition("degraded", "polling-auth-failed");
2966
2983
  }
2967
2984
  };
2968
2985
  // ---------------------------------------------------------------------------
@@ -3372,6 +3389,10 @@ var Controller = class {
3372
3389
  this.transition("degraded", "stream-still-connecting");
3373
3390
  return;
3374
3391
  }
3392
+ if (outcome === "auth-failed") {
3393
+ this.transition("degraded", "stream-auth-failed");
3394
+ return;
3395
+ }
3375
3396
  }
3376
3397
  if (this.options.polling.enabled) {
3377
3398
  if (this.state !== "polling") {
@@ -3404,7 +3425,7 @@ var Controller = class {
3404
3425
  return "connected";
3405
3426
  } catch (error) {
3406
3427
  this.noteUnauthorized(error);
3407
- return "failed";
3428
+ return isUnauthorizedError(error) ? "auth-failed" : "failed";
3408
3429
  }
3409
3430
  }
3410
3431
  let timeoutId;
@@ -3438,7 +3459,7 @@ var Controller = class {
3438
3459
  } catch (error) {
3439
3460
  clearTimeout(timeoutId);
3440
3461
  this.noteUnauthorized(error);
3441
- return "failed";
3462
+ return isUnauthorizedError(error) ? "auth-failed" : "failed";
3442
3463
  }
3443
3464
  }
3444
3465
  // ---------------------------------------------------------------------------
@@ -3766,4 +3787,4 @@ export {
3766
3787
  resetDefaultFlagsClient,
3767
3788
  createClient
3768
3789
  };
3769
- //# sourceMappingURL=chunk-LGS6CVED.js.map
3790
+ //# sourceMappingURL=chunk-2P2UTT7O.js.map