UNPKG

@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
// 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