@vercel/flags-core 1.11.0-31e6462-20261026142820 → 1.11.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.
package/CHANGELOG.md CHANGED
@@ -1,15 +1,25 @@
1
1
  # @vercel/flags-core
2
2
 
3
- ## 1.11.0-31e6462-20261026142820
3
+ ## 1.11.0
4
4
 
5
5
  ### Minor Changes
6
6
 
7
- - [#539](https://github.com/vercel/flags/pull/539) [`fc7da82`](https://github.com/vercel/flags/commit/fc7da82d4cffb9d4662335e984e09fec22ba4a6b) Thanks [@vincent-derks](https://github.com/vincent-derks)! - Add a `projectId` option to read the flags of a connected project.
7
+ - [#539](https://github.com/vercel/flags/pull/539) [`24983f9`](https://github.com/vercel/flags/commit/24983f90eec9789fbed1a7a556d41070218aecb6) Thanks [@vincent-derks](https://github.com/vincent-derks)! - Prepare for upcoming Vercel Flags Project Connections with a new `projectId` option.
8
+
9
+ ## 1.10.3
10
+
11
+ ### Patch Changes
12
+
13
+ - [#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.
14
+
15
+ ## 1.10.2
8
16
 
9
17
  ### Patch Changes
10
18
 
11
19
  - [#567](https://github.com/vercel/flags/pull/567) [`5f738f0`](https://github.com/vercel/flags/commit/5f738f08f182838d0efb13fd3efb89751d48cb0a) Thanks [@vincent-derks](https://github.com/vincent-derks)! - Improve guidance for authorization errors.
12
20
 
21
+ - [#573](https://github.com/vercel/flags/pull/573) [`e2a1a39`](https://github.com/vercel/flags/commit/e2a1a394e3de4b271c8305bae6cbcf053814ab70) Thanks [@luismeyer](https://github.com/luismeyer)! - Read embedded flag definitions when the client is created, using the SDK key or `VERCEL_PROJECT_ID` in OIDC mode.
22
+
13
23
  ## 1.10.1
14
24
 
15
25
  ### Patch Changes
@@ -576,7 +576,7 @@ function bulkEvaluate(flags, shared) {
576
576
  }
577
577
 
578
578
  // package.json
579
- var version = "1.11.0-31e6462-20261026142820";
579
+ var version = "1.11.0";
580
580
 
581
581
  // src/lib/report-value.ts
582
582
  function internalReportValue(key, value, data) {
@@ -1110,17 +1110,26 @@ function unauthorizedMessage(sourceProjectId) {
1110
1110
  }
1111
1111
  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`;
1112
1112
  }
1113
+ var oidcTokenMessage = [
1114
+ "@vercel/flags-core: Failed to get OIDC token.",
1115
+ "Are you running in a Vercel Environment where OIDC tokens are available?",
1116
+ "Did you mean to use an SDK Key instead? Use the environment variable FLAGS or pass it directly to the client."
1117
+ ].join(" ");
1118
+ var missingCredentials = /`vc link|vercel login|^The 'x-vercel-oidc-token' header is missing from the request\.$/;
1119
+ var OidcTokenError = class extends Error {
1120
+ constructor(cause) {
1121
+ super(oidcTokenMessage, { cause });
1122
+ this.name = "OidcTokenError";
1123
+ }
1124
+ };
1113
1125
  async function getOidcToken() {
1114
1126
  try {
1115
1127
  return await getVercelOidcToken();
1116
- } catch {
1117
- throw new Error(
1118
- [
1119
- "@vercel/flags-core: Failed to get OIDC token.",
1120
- "Are you running in a Vercel Environment where OIDC tokens are available?",
1121
- "Did you mean to use an SDK Key instead? Use the environment variable FLAGS or pass it directly to the client."
1122
- ].join(" ")
1123
- );
1128
+ } catch (error) {
1129
+ if (error instanceof Error && missingCredentials.test(error.message)) {
1130
+ throw new OidcTokenError(error);
1131
+ }
1132
+ throw new Error(oidcTokenMessage, { cause: error });
1124
1133
  }
1125
1134
  }
1126
1135
  function getProjectIdFromOidcToken(oidcToken) {
@@ -1177,13 +1186,24 @@ var Authentication = class {
1177
1186
  }
1178
1187
  return await getOidcToken();
1179
1188
  }
1180
- async resolveBundledDefinitionsLookup() {
1189
+ getStaticBundledDefinitionsLookup() {
1181
1190
  if (this.sdkKey) {
1182
1191
  return { type: "sdk-key", sdkKey: this.sdkKey };
1183
1192
  }
1184
1193
  if (this.sourceProjectId) {
1185
1194
  return { type: "project-id", projectId: this.sourceProjectId };
1186
1195
  }
1196
+ const projectId = process.env.VERCEL_PROJECT_ID;
1197
+ if (projectId) {
1198
+ return { type: "project-id", projectId };
1199
+ }
1200
+ return void 0;
1201
+ }
1202
+ async resolveBundledDefinitionsLookup() {
1203
+ const lookup = this.getStaticBundledDefinitionsLookup();
1204
+ if (lookup) {
1205
+ return lookup;
1206
+ }
1187
1207
  const oidcToken = await this.resolveToken();
1188
1208
  return {
1189
1209
  type: "project-id",
@@ -1693,6 +1713,13 @@ var BundledSource = class {
1693
1713
  }
1694
1714
  return void 0;
1695
1715
  }
1716
+ /**
1717
+ * Starts reading bundled definitions without waiting for the result.
1718
+ */
1719
+ preload() {
1720
+ this.getResult().catch(() => {
1721
+ });
1722
+ }
1696
1723
  getResult() {
1697
1724
  if (!this.promise) {
1698
1725
  this.promise = this.options.readBundledDefinitions(this.options.auth);
@@ -2160,7 +2187,8 @@ async function fetchDatafile(options) {
2160
2187
  return await Promise.race([fetchAttempt(attempt + 1), aborted]);
2161
2188
  } catch (error) {
2162
2189
  signal.throwIfAborted();
2163
- if (attempt === maxAttempts - 1 || error instanceof DatafileHttpError && !error.retryable) {
2190
+ if (attempt === maxAttempts - 1 || error instanceof DatafileHttpError && !error.retryable || // Retrying will not produce a token that is not there.
2191
+ error instanceof OidcTokenError) {
2164
2192
  throw error instanceof Error ? error : new Error("Unknown fetch error");
2165
2193
  }
2166
2194
  debug(options.clientName, "datafile.fetch.retry", () => ({
@@ -2578,7 +2606,7 @@ async function connectStream(config, callbacks) {
2578
2606
  rejectInit(error);
2579
2607
  }
2580
2608
  onDisconnect?.();
2581
- abortController.abort();
2609
+ abortController.abort(error);
2582
2610
  break;
2583
2611
  }
2584
2612
  if (!response.ok) {
@@ -2680,7 +2708,7 @@ async function connectStream(config, callbacks) {
2680
2708
  }
2681
2709
  if (error instanceof TokenResolutionError && !initialDataReceived) {
2682
2710
  rejectInit(error.cause);
2683
- abortController.abort();
2711
+ abortController.abort(error.cause);
2684
2712
  break;
2685
2713
  }
2686
2714
  if (connectionAbort.signal.reason !== PING_TIMEOUT) {
@@ -2754,7 +2782,7 @@ var StreamSource = class extends TypedEmitter {
2754
2782
  if (this.abortController === abortController) {
2755
2783
  this.promise = void 0;
2756
2784
  this.abortController = void 0;
2757
- this.emit("exhausted");
2785
+ this.emit("exhausted", abortController.signal.reason);
2758
2786
  }
2759
2787
  },
2760
2788
  { once: true }
@@ -2815,7 +2843,7 @@ var UNKNOWN_FRESHNESS = {
2815
2843
  assess: () => ({ status: "unknown" })
2816
2844
  };
2817
2845
  function isUnauthorizedError(error) {
2818
- if (error instanceof UnauthorizedError) {
2846
+ if (error instanceof UnauthorizedError || error instanceof OidcTokenError) {
2819
2847
  return true;
2820
2848
  }
2821
2849
  if (!(error instanceof Error)) {
@@ -2850,8 +2878,9 @@ var Controller = class {
2850
2878
  // Build-step deduplication
2851
2879
  buildDataPromise = null;
2852
2880
  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.
2881
+ // Suppresses usage tracking while the credential is rejected or no OIDC
2882
+ // token is available. Set on an auth failure from any source, cleared as
2883
+ // soon as a source delivers data again.
2855
2884
  unauthorized = false;
2856
2885
  constructor(options) {
2857
2886
  this.options = normalizeOptions(options);
@@ -2888,6 +2917,9 @@ var Controller = class {
2888
2917
  auth: this.options.auth,
2889
2918
  readBundledDefinitions
2890
2919
  });
2920
+ if (!this.options.datafile && this.options.auth.getStaticBundledDefinitionsLookup()) {
2921
+ this.bundledSource.preload();
2922
+ }
2891
2923
  this.wireSourceEvents();
2892
2924
  if (this.options.datafile) {
2893
2925
  this.cache.seed(tagData({ ...this.options.datafile }, "provided"));
@@ -2944,10 +2976,16 @@ var Controller = class {
2944
2976
  this.transition("degraded", "stream-disconnected");
2945
2977
  }
2946
2978
  };
2947
- onStreamExhausted = () => {
2979
+ onStreamExhausted = (reason) => {
2948
2980
  if (this.state !== "streaming" && this.state !== "degraded") {
2949
2981
  return;
2950
2982
  }
2983
+ if (isUnauthorizedError(reason)) {
2984
+ if (this.state === "streaming") {
2985
+ this.transition("degraded", "stream-auth-failed");
2986
+ }
2987
+ return;
2988
+ }
2951
2989
  void this.activateFallbackSource().catch(() => {
2952
2990
  });
2953
2991
  };
@@ -2959,7 +2997,7 @@ var Controller = class {
2959
2997
  this.onSourceError(error);
2960
2998
  if (this.state === "polling" && isUnauthorizedError(error)) {
2961
2999
  this.pollingSource.stop();
2962
- this.transition("degraded", "polling-unauthorized");
3000
+ this.transition("degraded", "polling-auth-failed");
2963
3001
  }
2964
3002
  };
2965
3003
  // ---------------------------------------------------------------------------
@@ -3369,6 +3407,10 @@ var Controller = class {
3369
3407
  this.transition("degraded", "stream-still-connecting");
3370
3408
  return;
3371
3409
  }
3410
+ if (outcome === "auth-failed") {
3411
+ this.transition("degraded", "stream-auth-failed");
3412
+ return;
3413
+ }
3372
3414
  }
3373
3415
  if (this.options.polling.enabled) {
3374
3416
  if (this.state !== "polling") {
@@ -3401,7 +3443,7 @@ var Controller = class {
3401
3443
  return "connected";
3402
3444
  } catch (error) {
3403
3445
  this.noteUnauthorized(error);
3404
- return "failed";
3446
+ return isUnauthorizedError(error) ? "auth-failed" : "failed";
3405
3447
  }
3406
3448
  }
3407
3449
  let timeoutId;
@@ -3435,7 +3477,7 @@ var Controller = class {
3435
3477
  } catch (error) {
3436
3478
  clearTimeout(timeoutId);
3437
3479
  this.noteUnauthorized(error);
3438
- return "failed";
3480
+ return isUnauthorizedError(error) ? "auth-failed" : "failed";
3439
3481
  }
3440
3482
  }
3441
3483
  // ---------------------------------------------------------------------------
@@ -3763,4 +3805,4 @@ export {
3763
3805
  resetDefaultFlagsClient,
3764
3806
  createClient
3765
3807
  };
3766
- //# sourceMappingURL=chunk-WMVRYZBA.js.map
3808
+ //# sourceMappingURL=chunk-755SFCGD.js.map