@web-widget/flags-kit
Version:
Flags SDK by Vercel - The feature flags toolkit for Next.js, SvelteKit, and Web Router - Enhanced fork with improved Web Router support
470 lines (461 loc) • 14.4 kB
JavaScript
// package.json
var name = "@web-widget/flags-kit";
var version = "4.1.3";
// src/lib/tracing.ts
import { AsyncLocalStorage } from "async_hooks";
var vercelFlagsTraceSymbol = Symbol.for("flags:global-trace");
function setTracerProvider(tracer) {
Reflect.set(globalThis, vercelFlagsTraceSymbol, tracer);
}
function getTracer() {
const maybeTraceApi = Reflect.get(globalThis, vercelFlagsTraceSymbol);
return maybeTraceApi?.getTracer(name, version);
}
function isPromise(p) {
return p !== null && typeof p === "object" && "then" in p && typeof p.then === "function";
}
var spanContext = new AsyncLocalStorage();
function setSpanAttribute(name2, value) {
spanContext.getStore()?.set(name2, value);
}
function trace(fn, options = {
name: fn.name
}) {
const traced = function(...args) {
const tracer = getTracer();
if (!tracer)
return fn.apply(this, args);
const shouldTrace = process.env.VERCEL_FLAGS_TRACE_VERBOSE === "true" || options.isVerboseTrace === false;
if (!shouldTrace)
return fn.apply(this, args);
return spanContext.run(
/* @__PURE__ */ new Map(),
() => tracer.startActiveSpan(options.name, (span) => {
if (options.attributes)
span.setAttributes(options.attributes);
try {
const result = fn.apply(this, args);
if (isPromise(result)) {
result.then((value) => {
if (options.attributesSuccess) {
span.setAttributes(
options.attributesSuccess(
value
)
);
}
spanContext.getStore()?.forEach((value2, key) => {
span.setAttribute(key, value2);
});
span.setStatus({ code: 1 });
span.end();
}).catch((error) => {
if (options.attributesError) {
span.setAttributes(options.attributesError(error));
}
span.setStatus({
code: 2,
// 2 = Error
message: error instanceof Error ? error.message : void 0
});
spanContext.getStore()?.forEach((value, key) => {
span.setAttribute(key, value);
});
span.end();
});
} else {
if (options.attributesSuccess) {
span.setAttributes(options.attributesSuccess(result));
}
spanContext.getStore()?.forEach((value, key) => {
span.setAttribute(key, value);
});
span.setStatus({ code: 1 });
span.end();
}
return result;
} catch (error) {
if (options.attributesError) {
span.setAttributes(options.attributesError(error));
}
span.setStatus({
code: 2,
// 2 = Error
message: error instanceof Error ? error.message : void 0
});
spanContext.getStore()?.forEach((value, key) => {
span.setAttribute(key, value);
});
span.end();
throw error;
}
})
);
};
return traced;
}
// src/lib/crypto.ts
import { base64url, jwtDecrypt, EncryptJWT } from "jose";
var hasPurpose = (pur, expectedPurpose) => {
return Array.isArray(pur) ? pur.includes(expectedPurpose) : pur === expectedPurpose;
};
async function encryptJwe(payload, secret, expirationTime) {
const encodedSecret = base64url.decode(secret);
if (encodedSecret.length !== 32) {
throw new Error(
"flags: Invalid secret, it must be a 256-bit key (32 bytes)"
);
}
return new EncryptJWT(payload).setExpirationTime(expirationTime).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).encrypt(encodedSecret);
}
async function decryptJwe(text, verify, secret) {
if (typeof text !== "string")
return;
const encodedSecret = base64url.decode(secret);
if (encodedSecret.length !== 32) {
throw new Error(
"flags: Invalid secret, it must be a 256-bit key (32 bytes)"
);
}
try {
const { payload } = await jwtDecrypt(text, encodedSecret);
const decoded = payload;
return verify(decoded) ? decoded : void 0;
} catch {
return void 0;
}
}
async function encryptOverrides(overrides, secret = process?.env?.FLAGS_SECRET, expirationTime = "1y") {
if (!secret)
throw new Error("flags: Missing FLAGS_SECRET");
return encryptJwe({ o: overrides, pur: "overrides" }, secret, expirationTime);
}
async function decryptOverrides(encryptedData, secret = process?.env?.FLAGS_SECRET) {
if (!secret)
throw new Error("flags: Missing FLAGS_SECRET");
const contents = await decryptJwe(
encryptedData,
(data) => hasPurpose(data.pur, "overrides") && Object.hasOwn(data, "o"),
secret
);
return contents?.o;
}
async function encryptFlagValues(flagValues, secret = process?.env?.FLAGS_SECRET, expirationTime = "1y") {
if (!secret)
throw new Error("flags: Missing FLAGS_SECRET");
return encryptJwe({ v: flagValues, pur: "values" }, secret, expirationTime);
}
async function decryptFlagValues(encryptedData, secret = process?.env?.FLAGS_SECRET) {
if (!secret)
throw new Error("flags: Missing FLAGS_SECRET");
const contents = await decryptJwe(
encryptedData,
(data) => hasPurpose(data.pur, "values") && Object.hasOwn(data, "v"),
secret
);
return contents?.v;
}
async function encryptFlagDefinitions(flagDefinitions, secret = process?.env?.FLAGS_SECRET, expirationTime = "1y") {
if (!secret)
throw new Error("flags: Missing FLAGS_SECRET");
return encryptJwe(
{ d: flagDefinitions, pur: "definitions" },
secret,
expirationTime
);
}
async function decryptFlagDefinitions(encryptedData, secret = process?.env?.FLAGS_SECRET) {
if (!secret)
throw new Error("flags: Missing FLAGS_SECRET");
const contents = await decryptJwe(
encryptedData,
(data) => data.pur === "definitions" && Object.hasOwn(data, "d"),
secret
);
return contents?.d;
}
async function createAccessProof(secret = process?.env?.FLAGS_SECRET, expirationTime = "1y") {
if (!secret)
throw new Error("flags: Missing FLAGS_SECRET");
return encryptJwe({ pur: "proof" }, secret, expirationTime);
}
async function verifyAccessProof(encryptedData, secret = process?.env?.FLAGS_SECRET) {
if (!secret)
throw new Error("flags: Missing FLAGS_SECRET");
const contents = await decryptJwe(encryptedData, (data) => hasPurpose(data.pur, "proof"), secret);
return Boolean(contents);
}
// src/lib/verify-access.ts
var verifyAccess = trace(
async function verifyAccess2(authHeader, secret = process?.env?.FLAGS_SECRET) {
if (!authHeader)
return false;
if (!secret)
throw new Error(
"flags: verifyAccess was called without a secret. Please set FLAGS_SECRET environment variable."
);
const valid = await verifyAccessProof(
authHeader.replace(/^Bearer /i, ""),
secret
);
return valid;
},
{
isVerboseTrace: false,
name: "verifyAccess"
}
);
// src/lib/report-value.ts
function reportValue(key, value) {
const symbol = Symbol.for("@vercel/request-context");
const ctx = Reflect.get(globalThis, symbol)?.get();
ctx?.flags?.reportValue(key, value, {
sdkVersion: version
});
}
function internalReportValue(key, value, data) {
const symbol = Symbol.for("@vercel/request-context");
const ctx = Reflect.get(globalThis, symbol)?.get();
ctx?.flags?.reportValue(key, value, {
sdkVersion: version,
...data
});
}
// src/spec-extension/adapters/reflect.ts
var ReflectAdapter = class {
static get(target, prop, receiver) {
const value = Reflect.get(target, prop, receiver);
if (typeof value === "function") {
return value.bind(target);
}
return value;
}
static set(target, prop, value, receiver) {
return Reflect.set(target, prop, value, receiver);
}
static has(target, prop) {
return Reflect.has(target, prop);
}
static deleteProperty(target, prop) {
return Reflect.deleteProperty(target, prop);
}
};
// src/spec-extension/adapters/headers.ts
var ReadonlyHeadersError = class _ReadonlyHeadersError extends Error {
constructor(message) {
super(message);
}
static callable(message) {
throw new _ReadonlyHeadersError(message);
}
};
var HeadersAdapter = class _HeadersAdapter extends Headers {
constructor(headers) {
super();
this.headers = new Proxy(headers, {
get(target, prop, receiver) {
if (typeof prop === "symbol") {
return ReflectAdapter.get(target, prop, receiver);
}
const lowercased = prop.toLowerCase();
const original = Object.keys(headers).find(
(o) => o.toLowerCase() === lowercased
);
if (typeof original === "undefined")
return;
return ReflectAdapter.get(target, original, receiver);
},
set(target, prop, value, receiver) {
if (typeof prop === "symbol") {
return ReflectAdapter.set(target, prop, value, receiver);
}
const lowercased = prop.toLowerCase();
const original = Object.keys(headers).find(
(o) => o.toLowerCase() === lowercased
);
return ReflectAdapter.set(target, original ?? prop, value, receiver);
},
has(target, prop) {
if (typeof prop === "symbol")
return ReflectAdapter.has(target, prop);
const lowercased = prop.toLowerCase();
const original = Object.keys(headers).find(
(o) => o.toLowerCase() === lowercased
);
if (typeof original === "undefined")
return false;
return ReflectAdapter.has(target, original);
},
deleteProperty(target, prop) {
if (typeof prop === "symbol")
return ReflectAdapter.deleteProperty(target, prop);
const lowercased = prop.toLowerCase();
const original = Object.keys(headers).find(
(o) => o.toLowerCase() === lowercased
);
if (typeof original === "undefined")
return true;
return ReflectAdapter.deleteProperty(target, original);
}
});
}
/**
* Seals a Headers instance to prevent modification by throwing an error when
* any mutating method is called.
*/
static seal(headers, errorMessage) {
return new Proxy(headers, {
get(target, prop, receiver) {
switch (prop) {
case "append":
case "delete":
case "set":
return () => ReadonlyHeadersError.callable(errorMessage);
default:
return ReflectAdapter.get(target, prop, receiver);
}
}
});
}
/**
* Merges a header value into a string. This stores multiple values as an
* array, so we need to merge them into a string.
*
* @param value a header value
* @returns a merged header value (a string)
*/
merge(value) {
if (Array.isArray(value))
return value.join(", ");
return value;
}
/**
* Creates a Headers instance from a plain object or a Headers instance.
*
* @param headers a plain object or a Headers instance
* @returns a headers instance
*/
static from(headers) {
if (headers instanceof Headers)
return headers;
return new _HeadersAdapter(headers);
}
append(name2, value) {
const existing = this.headers[name2];
if (typeof existing === "string") {
this.headers[name2] = [existing, value];
} else if (Array.isArray(existing)) {
existing.push(value);
} else {
this.headers[name2] = value;
}
}
delete(name2) {
delete this.headers[name2];
}
get(name2) {
const value = this.headers[name2];
if (typeof value !== "undefined")
return this.merge(value);
return null;
}
has(name2) {
return typeof this.headers[name2] !== "undefined";
}
set(name2, value) {
this.headers[name2] = value;
}
forEach(callbackfn, thisArg) {
for (const [name2, value] of this.entries()) {
callbackfn.call(thisArg, value, name2, this);
}
}
*entries() {
for (const key of Object.keys(this.headers)) {
const name2 = key.toLowerCase();
const value = this.get(name2);
yield [name2, value];
}
}
*keys() {
for (const key of Object.keys(this.headers)) {
const name2 = key.toLowerCase();
yield name2;
}
}
*values() {
for (const key of Object.keys(this.headers)) {
const value = this.get(key);
yield value;
}
}
[Symbol.iterator]() {
return this.entries();
}
};
// src/spec-extension/adapters/request-cookies.ts
var ReadonlyRequestCookiesError = class _ReadonlyRequestCookiesError extends Error {
constructor(message) {
super(message);
}
static callable(message) {
throw new _ReadonlyRequestCookiesError(message);
}
};
var RequestCookiesAdapter = class {
static seal(cookies, errorMessage) {
return new Proxy(cookies, {
get(target, prop, receiver) {
switch (prop) {
case "clear":
case "delete":
case "set":
return () => ReadonlyRequestCookiesError.callable(errorMessage);
default:
return ReflectAdapter.get(target, prop, receiver);
}
}
});
}
};
var SYMBOL_MODIFY_COOKIE_VALUES = Symbol.for("next.mutated.cookies");
// src/lib/merge-provider-data.ts
async function mergeProviderData(itemsPromises) {
const items = await Promise.all(
itemsPromises.map((p) => Promise.resolve(p).catch(() => null))
);
return items.filter((item) => Boolean(item)).reduce(
(acc, item) => {
Object.entries(item.definitions).forEach(([key, definition]) => {
if (!acc.definitions[key])
acc.definitions[key] = {};
Object.assign(acc.definitions[key], definition);
});
if (Array.isArray(item.hints))
acc.hints.push(...item.hints);
return acc;
},
{ definitions: {}, hints: [] }
);
}
export {
version,
setTracerProvider,
setSpanAttribute,
trace,
encryptOverrides,
decryptOverrides,
encryptFlagValues,
decryptFlagValues,
encryptFlagDefinitions,
decryptFlagDefinitions,
createAccessProof,
verifyAccessProof,
verifyAccess,
reportValue,
internalReportValue,
HeadersAdapter,
RequestCookiesAdapter,
mergeProviderData
};
//# sourceMappingURL=chunk-BQ2THYCT.js.map