@vercel/flags-core 1.4.0 → 1.5.0-1602289-20260528072704
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 +30 -0
- package/dist/{chunk-2F6JQ5SL.js → chunk-COEI5LTV.js} +195 -87
- package/dist/chunk-COEI5LTV.js.map +1 -0
- package/dist/{chunk-OABKRAPP.js → chunk-NWSTCCYM.js} +195 -87
- package/dist/chunk-NWSTCCYM.js.map +1 -0
- package/dist/{chunk-3Z4P7Q6O.cjs → chunk-PUMNG7MP.cjs} +207 -99
- package/dist/chunk-PUMNG7MP.cjs.map +1 -0
- package/dist/{chunk-WKSYRFUY.cjs → chunk-WIDJEHH6.cjs} +206 -98
- package/dist/chunk-WIDJEHH6.cjs.map +1 -0
- package/dist/index.default.cjs +2 -2
- package/dist/index.default.d.cts +21 -6
- package/dist/index.default.d.ts +21 -6
- package/dist/index.default.js +1 -1
- package/dist/index.next-js.cjs +2 -2
- package/dist/index.next-js.d.cts +21 -6
- package/dist/index.next-js.d.ts +21 -6
- package/dist/index.next-js.js +1 -1
- package/dist/openfeature.default.cjs +2 -2
- package/dist/openfeature.default.d.cts +1 -1
- package/dist/openfeature.default.d.ts +1 -1
- package/dist/openfeature.default.js +1 -1
- package/dist/openfeature.next-js.cjs +2 -2
- package/dist/openfeature.next-js.d.cts +1 -1
- package/dist/openfeature.next-js.d.ts +1 -1
- package/dist/openfeature.next-js.js +1 -1
- package/dist/{types-FKiSedQ9.d.cts → types-CAT1SKk-.d.cts} +3 -2
- package/dist/{types-FKiSedQ9.d.ts → types-CAT1SKk-.d.ts} +3 -2
- package/package.json +15 -4
- package/dist/chunk-2F6JQ5SL.js.map +0 -1
- package/dist/chunk-3Z4P7Q6O.cjs.map +0 -1
- package/dist/chunk-OABKRAPP.js.map +0 -1
- package/dist/chunk-WKSYRFUY.cjs.map +0 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,35 @@
|
|
|
1
1
|
# @vercel/flags-core
|
|
2
2
|
|
|
3
|
+
## 1.5.0-1602289-20260528072704
|
|
4
|
+
|
|
5
|
+
### Minor Changes
|
|
6
|
+
|
|
7
|
+
- [#371](https://github.com/vercel/flags/pull/371) [`bd4d01a`](https://github.com/vercel/flags/commit/bd4d01a9b2b5d70bf7ae62cda645d8cd7292ad83) Thanks [@vincent-derks](https://github.com/vincent-derks)! - Add jitter to ingest retries and the batch-flush window.
|
|
8
|
+
|
|
9
|
+
The usage tracker now uses AWS-style "Full Jitter" exponential backoff between
|
|
10
|
+
retry attempts (replacing the previous deterministic 100/200ms schedule) and
|
|
11
|
+
randomizes the 5s batch-flush window by ±20% to desynchronize concurrent
|
|
12
|
+
processes. When all retry attempts are exhausted the SDK now logs a structured
|
|
13
|
+
warning so consumers can alert on dropped batches.
|
|
14
|
+
|
|
15
|
+
- [#390](https://github.com/vercel/flags/pull/390) [`503ac50`](https://github.com/vercel/flags/commit/503ac5077588b9d641b751f48df8575cb6993556) Thanks [@luismeyer](https://github.com/luismeyer)! - Add OIDC authentication support for Vercel Flags clients and generated flag definitions.
|
|
16
|
+
|
|
17
|
+
`@vercel/flags-core` can now create clients without an SDK key and authenticate with a Vercel OIDC token, while still supporting SDK keys and connection strings. Bundled definitions can be looked up by SDK key hash or OIDC project ID.
|
|
18
|
+
|
|
19
|
+
`@vercel/prepare-flags-definitions` now collects both SDK keys and `VERCEL_OIDC_TOKEN`, fetches definitions for each auth entry, deduplicates identical definitions across SDK keys and OIDC project IDs, and writes generated maps keyed by SDK key hash or project ID.
|
|
20
|
+
|
|
21
|
+
`@flags-sdk/vercel` now supports provider data lookup for Vercel flag origins that do not include an SDK key, allowing OIDC-backed clients to resolve project metadata.
|
|
22
|
+
|
|
23
|
+
### Patch Changes
|
|
24
|
+
|
|
25
|
+
- [#382](https://github.com/vercel/flags/pull/382) [`4d90e91`](https://github.com/vercel/flags/commit/4d90e912a4d7c9d4ef986d5e8dc609c30b203242) Thanks [@dferber90](https://github.com/dferber90)! - Speed up flag evaluation on the hot path.
|
|
26
|
+
|
|
27
|
+
- `handleOutcome` no longer recomputes `scaledWeights` on every split-outcome evaluation; the per-outcome scaled weights are cached on first call.
|
|
28
|
+
- `matchConditions` no longer recompiles `RegExp` on every REGEX / NOT_REGEX condition; the compiled regex is cached on first call.
|
|
29
|
+
- `Controller.read()` and `getDatafile()` no longer re-destructure and re-spread the in-memory datafile on every call; the result is cached and rebuilt only when stream/poll replaces the underlying data.
|
|
30
|
+
|
|
31
|
+
In micro-benchmarks the pure `evaluate()` path is ~22% faster for split outcomes and ~32% faster for regex conditions. The full `client.evaluate()` path is 14–22% faster across all scenarios.
|
|
32
|
+
|
|
3
33
|
## 1.4.0
|
|
4
34
|
|
|
5
35
|
### Minor Changes
|
|
@@ -32,6 +32,24 @@ var Packed;
|
|
|
32
32
|
|
|
33
33
|
// src/evaluate.ts
|
|
34
34
|
var MAX_REGEX_INPUT_LENGTH = 1e4;
|
|
35
|
+
var UINT32_MAX = 4294967295;
|
|
36
|
+
var SCALED_WEIGHTS = /* @__PURE__ */ Symbol("@vercel/flags-core:scaledWeights");
|
|
37
|
+
var COMPILED_REGEX = /* @__PURE__ */ Symbol("@vercel/flags-core:compiledRegex");
|
|
38
|
+
function getScaledWeights(outcome) {
|
|
39
|
+
const cached = outcome[SCALED_WEIGHTS];
|
|
40
|
+
if (cached) return cached;
|
|
41
|
+
const total = sum(outcome.weights);
|
|
42
|
+
const scaled = outcome.weights.map((w) => w / total * UINT32_MAX);
|
|
43
|
+
outcome[SCALED_WEIGHTS] = scaled;
|
|
44
|
+
return scaled;
|
|
45
|
+
}
|
|
46
|
+
function getCompiledRegex(rhs) {
|
|
47
|
+
const cached = rhs[COMPILED_REGEX];
|
|
48
|
+
if (cached) return cached;
|
|
49
|
+
const compiled = new RegExp(rhs.pattern, rhs.flags);
|
|
50
|
+
rhs[COMPILED_REGEX] = compiled;
|
|
51
|
+
return compiled;
|
|
52
|
+
}
|
|
35
53
|
function exhaustivenessCheck(_) {
|
|
36
54
|
throw new Error("Exhaustiveness check failed");
|
|
37
55
|
}
|
|
@@ -218,12 +236,12 @@ function matchConditions(conditions, params) {
|
|
|
218
236
|
return (isNumber(rhs) || isString(rhs)) && lhs <= rhs;
|
|
219
237
|
case "regex" /* REGEX */:
|
|
220
238
|
if (isString(lhs) && lhs.length <= MAX_REGEX_INPUT_LENGTH && typeof rhs === "object" && !Array.isArray(rhs) && rhs?.type === "regex") {
|
|
221
|
-
return
|
|
239
|
+
return getCompiledRegex(rhs).test(lhs);
|
|
222
240
|
}
|
|
223
241
|
return false;
|
|
224
242
|
case "!regex" /* NOT_REGEX */:
|
|
225
243
|
if (isString(lhs) && lhs.length <= MAX_REGEX_INPUT_LENGTH && typeof rhs === "object" && !Array.isArray(rhs) && rhs?.type === "regex") {
|
|
226
|
-
return !
|
|
244
|
+
return !getCompiledRegex(rhs).test(lhs);
|
|
227
245
|
}
|
|
228
246
|
return false;
|
|
229
247
|
case "before" /* BEFORE */: {
|
|
@@ -295,13 +313,9 @@ function handleOutcome(params, outcome) {
|
|
|
295
313
|
if (typeof lhs !== "string") {
|
|
296
314
|
return { value: defaultOutcome, outcomeType: "split" /* SPLIT */ };
|
|
297
315
|
}
|
|
298
|
-
const maxValue = 4294967295;
|
|
299
316
|
const value = hashInput(lhs, params.definition.seed);
|
|
300
|
-
const
|
|
301
|
-
const
|
|
302
|
-
(weight) => weight / sumOfWeights * maxValue
|
|
303
|
-
);
|
|
304
|
-
const variantIndex = findWeightedIndex(scaledWeights, value, maxValue);
|
|
317
|
+
const scaledWeights = getScaledWeights(outcome);
|
|
318
|
+
const variantIndex = findWeightedIndex(scaledWeights, value, UINT32_MAX);
|
|
305
319
|
return {
|
|
306
320
|
value: variantIndex === -1 ? defaultOutcome : getVariant(params.definition.variants, variantIndex),
|
|
307
321
|
outcomeType: "split" /* SPLIT */
|
|
@@ -357,9 +371,8 @@ function handleOutcome(params, outcome) {
|
|
|
357
371
|
outcomeType: "rollout" /* ROLLOUT */
|
|
358
372
|
};
|
|
359
373
|
}
|
|
360
|
-
const maxValue = 4294967295;
|
|
361
374
|
const value = hashInput(lhs, params.definition.seed);
|
|
362
|
-
const threshold = currentPromille / 1e5 *
|
|
375
|
+
const threshold = currentPromille / 1e5 * UINT32_MAX;
|
|
363
376
|
return {
|
|
364
377
|
value: value < threshold ? getVariant(params.definition.variants, outcome.rollToVariant) : getVariant(
|
|
365
378
|
params.definition.variants,
|
|
@@ -437,7 +450,7 @@ function findWeightedIndex(weights, value, maxValue) {
|
|
|
437
450
|
}
|
|
438
451
|
|
|
439
452
|
// package.json
|
|
440
|
-
var version = "1.
|
|
453
|
+
var version = "1.5.0-1602289-20260528072704";
|
|
441
454
|
|
|
442
455
|
// src/lib/report-value.ts
|
|
443
456
|
function internalReportValue(key, value, data) {
|
|
@@ -632,7 +645,7 @@ function hashSdkKey(sdkKey) {
|
|
|
632
645
|
sdkKeyHashCache.set(sdkKey, promise);
|
|
633
646
|
return promise;
|
|
634
647
|
}
|
|
635
|
-
async function readBundledDefinitions(
|
|
648
|
+
async function readBundledDefinitions(auth) {
|
|
636
649
|
let get;
|
|
637
650
|
try {
|
|
638
651
|
const module = await import(
|
|
@@ -649,10 +662,20 @@ async function readBundledDefinitions(sdkKey) {
|
|
|
649
662
|
if (typeof get !== "function") {
|
|
650
663
|
return { definitions: null, state: "missing-file" };
|
|
651
664
|
}
|
|
652
|
-
|
|
665
|
+
let lookup;
|
|
666
|
+
try {
|
|
667
|
+
lookup = await auth.resolveBundledDefinitionsLookup();
|
|
668
|
+
} catch (error) {
|
|
669
|
+
return { definitions: null, state: "unexpected-error", error };
|
|
670
|
+
}
|
|
671
|
+
if (lookup.type === "project-id") {
|
|
672
|
+
const entry2 = get(lookup.projectId);
|
|
673
|
+
return entry2 ? { definitions: entry2, state: "ok" } : { definitions: null, state: "missing-entry" };
|
|
674
|
+
}
|
|
675
|
+
const entry = get(lookup.sdkKey);
|
|
653
676
|
if (entry) return { definitions: entry, state: "ok" };
|
|
654
677
|
try {
|
|
655
|
-
const hashedKey = await hashSdkKey(sdkKey);
|
|
678
|
+
const hashedKey = await hashSdkKey(lookup.sdkKey);
|
|
656
679
|
const hashedEntry = get(hashedKey);
|
|
657
680
|
if (hashedEntry) return { definitions: hashedEntry, state: "ok" };
|
|
658
681
|
} catch (error) {
|
|
@@ -663,6 +686,23 @@ async function readBundledDefinitions(sdkKey) {
|
|
|
663
686
|
|
|
664
687
|
// src/utils/usage-tracker.ts
|
|
665
688
|
import { waitUntil } from "@vercel/functions";
|
|
689
|
+
|
|
690
|
+
// src/utils/backoff.ts
|
|
691
|
+
var DEFAULT_BASE_MS = 250;
|
|
692
|
+
var DEFAULT_CAP_MS = 5e3;
|
|
693
|
+
function getRetryDelayMs(attempt, options = {}) {
|
|
694
|
+
const baseMs = options.baseMs ?? DEFAULT_BASE_MS;
|
|
695
|
+
const capMs = options.capMs ?? DEFAULT_CAP_MS;
|
|
696
|
+
const ceiling = Math.min(capMs, baseMs * 2 ** Math.max(0, attempt - 1));
|
|
697
|
+
return Math.floor(Math.random() * ceiling);
|
|
698
|
+
}
|
|
699
|
+
function getJitteredWaitMs(baseMs, ratio) {
|
|
700
|
+
const min = baseMs * (1 - ratio);
|
|
701
|
+
const span = baseMs * 2 * ratio;
|
|
702
|
+
return Math.floor(min + Math.random() * span);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
// src/utils/usage-tracker.ts
|
|
666
706
|
var RESOLVED_VOID = Promise.resolve();
|
|
667
707
|
var isDebugMode = process.env.DEBUG?.includes("@vercel/flags-core");
|
|
668
708
|
var debugLog = (...args) => {
|
|
@@ -672,6 +712,7 @@ var debugLog = (...args) => {
|
|
|
672
712
|
var MAX_RETRIES = 3;
|
|
673
713
|
var MAX_BATCH_SIZE = 50;
|
|
674
714
|
var MAX_BATCH_WAIT_MS = 5e3;
|
|
715
|
+
var BATCH_WAIT_JITTER_RATIO = 0.2;
|
|
675
716
|
var SYMBOL_FOR_REQ_CONTEXT = /* @__PURE__ */ Symbol.for("@vercel/request-context");
|
|
676
717
|
var fromSymbol = globalThis;
|
|
677
718
|
function getRequestContext() {
|
|
@@ -778,7 +819,10 @@ var UsageTracker = class {
|
|
|
778
819
|
const pending = (async () => {
|
|
779
820
|
await new Promise((res) => {
|
|
780
821
|
this.batcher.resolveWait = res;
|
|
781
|
-
timeout = setTimeout(
|
|
822
|
+
timeout = setTimeout(
|
|
823
|
+
res,
|
|
824
|
+
getJitteredWaitMs(MAX_BATCH_WAIT_MS, BATCH_WAIT_JITTER_RATIO)
|
|
825
|
+
);
|
|
782
826
|
});
|
|
783
827
|
this.batcher.pending = null;
|
|
784
828
|
this.batcher.resolveWait = null;
|
|
@@ -800,6 +844,7 @@ var UsageTracker = class {
|
|
|
800
844
|
const eventsToSend = this.batcher.events;
|
|
801
845
|
this.batcher.events = [];
|
|
802
846
|
const flushId = ++this.flushCounter;
|
|
847
|
+
const token = await this.options.auth.resolveToken();
|
|
803
848
|
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
|
|
804
849
|
try {
|
|
805
850
|
const response = await this.options.fetch(
|
|
@@ -808,7 +853,7 @@ var UsageTracker = class {
|
|
|
808
853
|
method: "POST",
|
|
809
854
|
headers: {
|
|
810
855
|
"Content-Type": "application/json",
|
|
811
|
-
Authorization: `Bearer ${
|
|
856
|
+
Authorization: `Bearer ${token}`,
|
|
812
857
|
"User-Agent": `VercelFlagsCore/${version}`,
|
|
813
858
|
...process.env.VERCEL_ENV ? { "X-Vercel-Env": process.env.VERCEL_ENV } : null,
|
|
814
859
|
...isDebugMode ? { "x-vercel-debug-ingest": "1" } : null
|
|
@@ -832,7 +877,12 @@ Response body: ${await response.text().catch(() => null)}`
|
|
|
832
877
|
error
|
|
833
878
|
);
|
|
834
879
|
if (attempt < MAX_RETRIES) {
|
|
835
|
-
|
|
880
|
+
const delayMs = getRetryDelayMs(attempt);
|
|
881
|
+
await new Promise((res) => setTimeout(res, delayMs));
|
|
882
|
+
} else {
|
|
883
|
+
console.error(
|
|
884
|
+
`@vercel/flags-core: Dropped ${eventsToSend.length} events after ${MAX_RETRIES} attempts (flushId=${flushId})`
|
|
885
|
+
);
|
|
836
886
|
}
|
|
837
887
|
}
|
|
838
888
|
}
|
|
@@ -855,11 +905,10 @@ var FallbackEntryNotFoundError = class extends Error {
|
|
|
855
905
|
|
|
856
906
|
// src/controller/bundled-source.ts
|
|
857
907
|
var BundledSource = class {
|
|
858
|
-
promise;
|
|
859
|
-
options;
|
|
860
908
|
constructor(options) {
|
|
861
909
|
this.options = options;
|
|
862
910
|
}
|
|
911
|
+
promise;
|
|
863
912
|
/**
|
|
864
913
|
* Load bundled definitions.
|
|
865
914
|
* Throws if bundled definitions are not available.
|
|
@@ -904,7 +953,7 @@ var BundledSource = class {
|
|
|
904
953
|
}
|
|
905
954
|
getResult() {
|
|
906
955
|
if (!this.promise) {
|
|
907
|
-
this.promise = this.options.readBundledDefinitions(this.options.
|
|
956
|
+
this.promise = this.options.readBundledDefinitions(this.options.auth);
|
|
908
957
|
}
|
|
909
958
|
return this.promise;
|
|
910
959
|
}
|
|
@@ -913,6 +962,7 @@ var BundledSource = class {
|
|
|
913
962
|
// src/controller/fetch-datafile.ts
|
|
914
963
|
var DEFAULT_FETCH_TIMEOUT_MS = 1e4;
|
|
915
964
|
async function fetchDatafile(options) {
|
|
965
|
+
const token = await options.auth.resolveToken();
|
|
916
966
|
const controller = new AbortController();
|
|
917
967
|
const timeoutId = setTimeout(
|
|
918
968
|
() => controller.abort(),
|
|
@@ -929,7 +979,7 @@ async function fetchDatafile(options) {
|
|
|
929
979
|
try {
|
|
930
980
|
const res = await options.fetch(`${options.host}/v1/datafile`, {
|
|
931
981
|
headers: {
|
|
932
|
-
Authorization: `Bearer ${
|
|
982
|
+
Authorization: `Bearer ${token}`,
|
|
933
983
|
"User-Agent": `VercelFlagsCore/${version}`,
|
|
934
984
|
...process.env.VERCEL_ENV ? { "X-Vercel-Env": process.env.VERCEL_ENV } : null
|
|
935
985
|
},
|
|
@@ -986,7 +1036,7 @@ function normalizeOptions(options) {
|
|
|
986
1036
|
};
|
|
987
1037
|
}
|
|
988
1038
|
return {
|
|
989
|
-
|
|
1039
|
+
auth: options.auth,
|
|
990
1040
|
datafile: options.datafile,
|
|
991
1041
|
stream,
|
|
992
1042
|
polling,
|
|
@@ -1097,12 +1147,11 @@ var UnauthorizedError = class extends Error {
|
|
|
1097
1147
|
}
|
|
1098
1148
|
};
|
|
1099
1149
|
async function connectStream(config, callbacks) {
|
|
1100
|
-
const {
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
} = config;
|
|
1150
|
+
const { host, abortController, fetch: fetchFn = globalThis.fetch } = config;
|
|
1151
|
+
const token = config.token ?? config.sdkKey;
|
|
1152
|
+
if (!token) {
|
|
1153
|
+
throw new Error("stream: missing auth token");
|
|
1154
|
+
}
|
|
1106
1155
|
const { onDatafile, onPrimed, onDisconnect } = callbacks;
|
|
1107
1156
|
let retryCount = 0;
|
|
1108
1157
|
let lastAttemptTime = 0;
|
|
@@ -1144,7 +1193,7 @@ async function connectStream(config, callbacks) {
|
|
|
1144
1193
|
try {
|
|
1145
1194
|
lastAttemptTime = Date.now();
|
|
1146
1195
|
const headers = {
|
|
1147
|
-
Authorization: `Bearer ${
|
|
1196
|
+
Authorization: `Bearer ${token}`,
|
|
1148
1197
|
"User-Agent": `VercelFlagsCore/${version}`,
|
|
1149
1198
|
"X-Retry-Attempt": String(retryCount)
|
|
1150
1199
|
};
|
|
@@ -1298,27 +1347,29 @@ var StreamSource = class extends TypedEmitter {
|
|
|
1298
1347
|
{ once: true }
|
|
1299
1348
|
);
|
|
1300
1349
|
try {
|
|
1301
|
-
const promise =
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
{
|
|
1310
|
-
onDatafile: (newData) => {
|
|
1311
|
-
this.emit("data", newData);
|
|
1312
|
-
this.emit("connected");
|
|
1313
|
-
},
|
|
1314
|
-
onPrimed: (message) => {
|
|
1315
|
-
this.emit("primed", message);
|
|
1316
|
-
this.emit("connected");
|
|
1350
|
+
const promise = this.options.auth.resolveToken().then(
|
|
1351
|
+
(token) => connectStream(
|
|
1352
|
+
{
|
|
1353
|
+
host: this.options.host,
|
|
1354
|
+
token,
|
|
1355
|
+
abortController,
|
|
1356
|
+
fetch: this.options.fetch,
|
|
1357
|
+
revision: this.revision
|
|
1317
1358
|
},
|
|
1318
|
-
|
|
1319
|
-
|
|
1359
|
+
{
|
|
1360
|
+
onDatafile: (newData) => {
|
|
1361
|
+
this.emit("data", newData);
|
|
1362
|
+
this.emit("connected");
|
|
1363
|
+
},
|
|
1364
|
+
onPrimed: (message) => {
|
|
1365
|
+
this.emit("primed", message);
|
|
1366
|
+
this.emit("connected");
|
|
1367
|
+
},
|
|
1368
|
+
onDisconnect: () => {
|
|
1369
|
+
this.emit("disconnected");
|
|
1370
|
+
}
|
|
1320
1371
|
}
|
|
1321
|
-
|
|
1372
|
+
)
|
|
1322
1373
|
);
|
|
1323
1374
|
this.promise = promise;
|
|
1324
1375
|
return promise;
|
|
@@ -1370,6 +1421,11 @@ var Controller = class {
|
|
|
1370
1421
|
state = "idle";
|
|
1371
1422
|
// Data state — tagged with origin
|
|
1372
1423
|
data;
|
|
1424
|
+
// Memoized data spread for read() / getDatafile().
|
|
1425
|
+
// Rebuilt only when `this.data` reference changes (e.g. on stream/poll update).
|
|
1426
|
+
// Holds the result of stripping `_origin`; metrics are appended per-call.
|
|
1427
|
+
dataViewSource = void 0;
|
|
1428
|
+
dataViewBase = void 0;
|
|
1373
1429
|
// Sources (I/O delegates)
|
|
1374
1430
|
streamSource;
|
|
1375
1431
|
pollingSource;
|
|
@@ -1383,11 +1439,6 @@ var Controller = class {
|
|
|
1383
1439
|
// Suppresses usage tracking when the SDK key is unauthorized
|
|
1384
1440
|
unauthorized = false;
|
|
1385
1441
|
constructor(options) {
|
|
1386
|
-
if (!options.sdkKey || typeof options.sdkKey !== "string" || !options.sdkKey.startsWith("vf_")) {
|
|
1387
|
-
throw new Error(
|
|
1388
|
-
'@vercel/flags-core: SDK key must be a string starting with "vf_"'
|
|
1389
|
-
);
|
|
1390
|
-
}
|
|
1391
1442
|
this.options = normalizeOptions(options);
|
|
1392
1443
|
this.streamSource = new StreamSource(
|
|
1393
1444
|
this.options,
|
|
@@ -1395,7 +1446,7 @@ var Controller = class {
|
|
|
1395
1446
|
);
|
|
1396
1447
|
this.pollingSource = new PollingSource(this.options);
|
|
1397
1448
|
this.bundledSource = new BundledSource({
|
|
1398
|
-
|
|
1449
|
+
auth: this.options.auth,
|
|
1399
1450
|
readBundledDefinitions
|
|
1400
1451
|
});
|
|
1401
1452
|
this.wireSourceEvents();
|
|
@@ -1541,11 +1592,15 @@ var Controller = class {
|
|
|
1541
1592
|
this.isFirstGetData = false;
|
|
1542
1593
|
const [result, cacheStatus] = await this.resolveData();
|
|
1543
1594
|
const readMs = Date.now() - startTime;
|
|
1544
|
-
const
|
|
1545
|
-
const source = originToMetricsSource(_origin);
|
|
1595
|
+
const source = originToMetricsSource(result._origin);
|
|
1546
1596
|
this.trackRead(startTime, cacheHadDefinitions, isFirstRead, source);
|
|
1597
|
+
if (this.dataViewSource !== result) {
|
|
1598
|
+
const { _origin, ...rest } = result;
|
|
1599
|
+
this.dataViewBase = rest;
|
|
1600
|
+
this.dataViewSource = result;
|
|
1601
|
+
}
|
|
1547
1602
|
return {
|
|
1548
|
-
...
|
|
1603
|
+
...this.dataViewBase,
|
|
1549
1604
|
metrics: {
|
|
1550
1605
|
readMs,
|
|
1551
1606
|
source,
|
|
@@ -1591,7 +1646,7 @@ var Controller = class {
|
|
|
1591
1646
|
try {
|
|
1592
1647
|
const fetched = await fetchDatafile({
|
|
1593
1648
|
host: this.options.host,
|
|
1594
|
-
|
|
1649
|
+
auth: this.options.auth,
|
|
1595
1650
|
fetch: this.options.fetch
|
|
1596
1651
|
});
|
|
1597
1652
|
this.data = tagData(fetched, "fetched");
|
|
@@ -1605,8 +1660,13 @@ var Controller = class {
|
|
|
1605
1660
|
}
|
|
1606
1661
|
}
|
|
1607
1662
|
const source = originToMetricsSource(result._origin);
|
|
1663
|
+
if (this.dataViewSource !== result) {
|
|
1664
|
+
const { _origin, ...rest } = result;
|
|
1665
|
+
this.dataViewBase = rest;
|
|
1666
|
+
this.dataViewSource = result;
|
|
1667
|
+
}
|
|
1608
1668
|
return {
|
|
1609
|
-
...
|
|
1669
|
+
...this.dataViewBase,
|
|
1610
1670
|
metrics: {
|
|
1611
1671
|
readMs: Date.now() - startTime,
|
|
1612
1672
|
source,
|
|
@@ -1782,7 +1842,7 @@ var Controller = class {
|
|
|
1782
1842
|
try {
|
|
1783
1843
|
const fetched = await fetchDatafile({
|
|
1784
1844
|
host: this.options.host,
|
|
1785
|
-
|
|
1845
|
+
auth: this.options.auth,
|
|
1786
1846
|
fetch: this.options.fetch
|
|
1787
1847
|
});
|
|
1788
1848
|
return tagData(fetched, "fetched");
|
|
@@ -1814,7 +1874,7 @@ var Controller = class {
|
|
|
1814
1874
|
try {
|
|
1815
1875
|
const fetched = await fetchDatafile({
|
|
1816
1876
|
host: this.options.host,
|
|
1817
|
-
|
|
1877
|
+
auth: this.options.auth,
|
|
1818
1878
|
fetch: this.options.fetch
|
|
1819
1879
|
});
|
|
1820
1880
|
this.data = tagData(fetched, "fetched");
|
|
@@ -1866,7 +1926,7 @@ var Controller = class {
|
|
|
1866
1926
|
try {
|
|
1867
1927
|
const fetched = await fetchDatafile({
|
|
1868
1928
|
host: this.options.host,
|
|
1869
|
-
|
|
1929
|
+
auth: this.options.auth,
|
|
1870
1930
|
fetch: this.options.fetch
|
|
1871
1931
|
});
|
|
1872
1932
|
this.data = tagData(fetched, "fetched");
|
|
@@ -1939,6 +1999,9 @@ var Controller = class {
|
|
|
1939
1999
|
}
|
|
1940
2000
|
};
|
|
1941
2001
|
|
|
2002
|
+
// src/controller/auth.ts
|
|
2003
|
+
import { getVercelOidcToken } from "@vercel/oidc";
|
|
2004
|
+
|
|
1942
2005
|
// src/utils/sdk-keys.ts
|
|
1943
2006
|
var SDK_KEY_REGEX = /^vf_(?:server|client)_/;
|
|
1944
2007
|
function parseSdkKeyFromFlagsConnectionString(text) {
|
|
@@ -1953,28 +2016,80 @@ function parseSdkKeyFromFlagsConnectionString(text) {
|
|
|
1953
2016
|
return null;
|
|
1954
2017
|
}
|
|
1955
2018
|
|
|
2019
|
+
// src/controller/auth.ts
|
|
2020
|
+
async function getOidcToken() {
|
|
2021
|
+
try {
|
|
2022
|
+
return await getVercelOidcToken();
|
|
2023
|
+
} catch {
|
|
2024
|
+
throw new Error(
|
|
2025
|
+
[
|
|
2026
|
+
"@vercel/flags-core: Failed to get OIDC token.",
|
|
2027
|
+
"Are you running in a Vercel Environment where OIDC tokens are available?",
|
|
2028
|
+
"Did you mean to use an SDK Key instead? Use the environment variable FLAGS or pass it directly to the client."
|
|
2029
|
+
].join(" ")
|
|
2030
|
+
);
|
|
2031
|
+
}
|
|
2032
|
+
}
|
|
2033
|
+
function getProjectIdFromOidcToken(oidcToken) {
|
|
2034
|
+
const tokenParts = oidcToken.split(".");
|
|
2035
|
+
if (tokenParts.length !== 3 || !tokenParts[1]) {
|
|
2036
|
+
throw new Error("@vercel/flags-core: Invalid OIDC token");
|
|
2037
|
+
}
|
|
2038
|
+
const payload = JSON.parse(
|
|
2039
|
+
Buffer.from(tokenParts[1], "base64url").toString("utf8")
|
|
2040
|
+
);
|
|
2041
|
+
if (typeof payload.project_id !== "string" || !payload.project_id) {
|
|
2042
|
+
throw new Error(
|
|
2043
|
+
"@vercel/flags-core: Missing project_id claim in OIDC token"
|
|
2044
|
+
);
|
|
2045
|
+
}
|
|
2046
|
+
return payload.project_id;
|
|
2047
|
+
}
|
|
2048
|
+
var Authentication = class {
|
|
2049
|
+
sdkKey;
|
|
2050
|
+
constructor(sdkKeyOrConnectionString) {
|
|
2051
|
+
if (sdkKeyOrConnectionString !== void 0) {
|
|
2052
|
+
if (typeof sdkKeyOrConnectionString !== "string") {
|
|
2053
|
+
throw new Error(
|
|
2054
|
+
`@vercel/flags-core: Invalid sdkKey. Expected string, got ${typeof sdkKeyOrConnectionString}`
|
|
2055
|
+
);
|
|
2056
|
+
}
|
|
2057
|
+
const parsed = parseSdkKeyFromFlagsConnectionString(
|
|
2058
|
+
sdkKeyOrConnectionString
|
|
2059
|
+
);
|
|
2060
|
+
if (!parsed) {
|
|
2061
|
+
throw new Error("@vercel/flags-core: Missing sdkKey");
|
|
2062
|
+
}
|
|
2063
|
+
this.sdkKey = parsed;
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
async resolveToken() {
|
|
2067
|
+
if (this.sdkKey) {
|
|
2068
|
+
return this.sdkKey;
|
|
2069
|
+
}
|
|
2070
|
+
return await getOidcToken();
|
|
2071
|
+
}
|
|
2072
|
+
async resolveBundledDefinitionsLookup() {
|
|
2073
|
+
if (this.sdkKey) {
|
|
2074
|
+
return { type: "sdk-key", sdkKey: this.sdkKey };
|
|
2075
|
+
}
|
|
2076
|
+
const oidcToken = await this.resolveToken();
|
|
2077
|
+
return {
|
|
2078
|
+
type: "project-id",
|
|
2079
|
+
projectId: getProjectIdFromOidcToken(oidcToken)
|
|
2080
|
+
};
|
|
2081
|
+
}
|
|
2082
|
+
};
|
|
2083
|
+
|
|
1956
2084
|
// src/index.make.ts
|
|
1957
2085
|
function make(createRawClient) {
|
|
1958
2086
|
let _defaultFlagsClient = null;
|
|
1959
2087
|
function createClient2(sdkKeyOrConnectionString, options) {
|
|
1960
|
-
|
|
1961
|
-
|
|
1962
|
-
if (typeof sdkKeyOrConnectionString !== "string")
|
|
1963
|
-
throw new Error(
|
|
1964
|
-
`@vercel/flags-core: Invalid sdkKey. Expected string, got ${typeof sdkKeyOrConnectionString}`
|
|
1965
|
-
);
|
|
1966
|
-
const sdkKey = parseSdkKeyFromFlagsConnectionString(
|
|
1967
|
-
sdkKeyOrConnectionString
|
|
1968
|
-
);
|
|
1969
|
-
if (!sdkKey) {
|
|
1970
|
-
throw new Error(
|
|
1971
|
-
"@vercel/flags-core: Missing sdkKey in connection string"
|
|
1972
|
-
);
|
|
1973
|
-
}
|
|
1974
|
-
const controller = new Controller({ sdkKey, ...options });
|
|
2088
|
+
const auth = new Authentication(sdkKeyOrConnectionString);
|
|
2089
|
+
const controller = new Controller({ auth, ...options });
|
|
1975
2090
|
return createRawClient({
|
|
1976
2091
|
controller,
|
|
1977
|
-
origin: { provider: "vercel", sdkKey }
|
|
2092
|
+
origin: { provider: "vercel", sdkKey: auth.sdkKey }
|
|
1978
2093
|
});
|
|
1979
2094
|
}
|
|
1980
2095
|
function resetDefaultFlagsClient2() {
|
|
@@ -1983,14 +2098,7 @@ function make(createRawClient) {
|
|
|
1983
2098
|
const flagsClient2 = new Proxy({}, {
|
|
1984
2099
|
get(_, prop) {
|
|
1985
2100
|
if (!_defaultFlagsClient) {
|
|
1986
|
-
|
|
1987
|
-
throw new Error("flags: Missing environment variable FLAGS");
|
|
1988
|
-
}
|
|
1989
|
-
const sdkKey = parseSdkKeyFromFlagsConnectionString(process.env.FLAGS);
|
|
1990
|
-
if (!sdkKey) {
|
|
1991
|
-
throw new Error("@vercel/flags-core: Missing sdkKey");
|
|
1992
|
-
}
|
|
1993
|
-
_defaultFlagsClient = createClient2(sdkKey);
|
|
2101
|
+
_defaultFlagsClient = createClient2(process.env.FLAGS);
|
|
1994
2102
|
}
|
|
1995
2103
|
return _defaultFlagsClient[prop];
|
|
1996
2104
|
}
|
|
@@ -2051,4 +2159,4 @@ export {
|
|
|
2051
2159
|
resetDefaultFlagsClient,
|
|
2052
2160
|
createClient
|
|
2053
2161
|
};
|
|
2054
|
-
//# sourceMappingURL=chunk-
|
|
2162
|
+
//# sourceMappingURL=chunk-COEI5LTV.js.map
|