flags
Version:
Flags SDK by Vercel - The feature flags toolkit for Next.js and SvelteKit
406 lines (397 loc) • 12 kB
JavaScript
// package.json
var name = "flags";
var version = "3.2.0";
// 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 { jwtDecrypt, base64url, EncryptJWT } from "jose";
async function encryptJWT(payload, expirationTime, secret) {
return new EncryptJWT(payload).setExpirationTime(expirationTime).setProtectedHeader({ alg: "dir", enc: "A256GCM" }).encrypt(base64url.decode(secret));
}
async function decryptJWT(cookie, verify, secret) {
if (typeof cookie !== "string")
return;
try {
const { payload } = await jwtDecrypt(cookie, base64url.decode(secret));
const decoded = payload;
if (!verify || verify(decoded)) {
delete decoded.iat;
delete decoded.exp;
return decoded;
}
} catch {
}
}
async function encrypt(value, secret = process?.env?.FLAGS_SECRET) {
if (!secret)
throw new Error("Missing FLAGS_SECRET");
return encryptJWT({ c: value }, "1y", secret);
}
async function decrypt(encryptedData, secret = process?.env?.FLAGS_SECRET) {
if (!secret)
throw new Error("Missing FLAGS_SECRET");
const content = await decryptJWT(encryptedData, null, secret);
return content?.c;
}
// 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 data = await decrypt(
authHeader?.replace(/^Bearer /i, ""),
secret
);
return data !== void 0;
},
{
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() {
super(
"Headers cannot be modified. Read more: https://nextjs.org/docs/app/api-reference/functions/headers"
);
}
static callable() {
throw new _ReadonlyHeadersError();
}
};
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) {
return new Proxy(headers, {
get(target, prop, receiver) {
switch (prop) {
case "append":
case "delete":
case "set":
return ReadonlyHeadersError.callable;
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() {
super(
"Cookies can only be modified in a Server Action or Route Handler. Read more: https://nextjs.org/docs/app/api-reference/functions/cookies#options"
);
}
static callable() {
throw new _ReadonlyRequestCookiesError();
}
};
var RequestCookiesAdapter = class {
static seal(cookies) {
return new Proxy(cookies, {
get(target, prop, receiver) {
switch (prop) {
case "clear":
case "delete":
case "set":
return ReadonlyRequestCookiesError.callable;
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 {
setTracerProvider,
setSpanAttribute,
trace,
encrypt,
decrypt,
verifyAccess,
reportValue,
internalReportValue,
HeadersAdapter,
RequestCookiesAdapter,
mergeProviderData
};
//# sourceMappingURL=chunk-RDI4Q5KS.js.map