@commercelayer/js-auth
Version:
A JavaScript library designed to simplify authentication when interacting with the Commerce Layer API.
1 lines • 62.3 kB
Source Map (JSON)
{"version":3,"sources":["../src/index.ts","../src/errors/TokenError.ts","../src/errors/InvalidTokenError.ts","../src/utils/base64.ts","../src/jwtDecode.ts","../src/utils/camelCaseToSnakeCase.ts","../src/utils/extractIssuer.ts","../src/utils/mapKeys.ts","../src/revoke.ts","../src/utils/hasOwner.ts","../src/utils/snakeCaseToCamelCase.ts","../src/authenticate.ts","../src/apiCredentials/dedupConcurrentCalls.ts","../src/apiCredentials/apiCredentials.ts","../src/apiCredentials/storage.ts","../src/apiCredentials/index.ts","../src/errors/TokenExpiredError.ts","../src/getCoreApiBaseEndpoint.ts","../src/getProvisioningApiBaseEndpoint.ts","../src/jwtEncode.ts","../src/utils/hasExpired.ts","../src/jwtVerify.ts"],"sourcesContent":["export {\n type ApiCredentialsAuthorization,\n createCompositeStorage,\n makeIntegration,\n makeSalesChannel,\n type Storage,\n type StorageValue,\n} from \"./apiCredentials/index.js\"\nexport { authenticate } from \"./authenticate.js\"\nexport { InvalidTokenError } from \"./errors/InvalidTokenError.js\"\nexport { TokenError } from \"./errors/TokenError.js\"\nexport { TokenExpiredError } from \"./errors/TokenExpiredError.js\"\nexport { getCoreApiBaseEndpoint } from \"./getCoreApiBaseEndpoint.js\"\nexport { getProvisioningApiBaseEndpoint } from \"./getProvisioningApiBaseEndpoint.js\"\nexport {\n type JWTDashboard,\n type JWTIntegration,\n type JWTSalesChannel,\n type JWTUser,\n type JWTWebApp,\n jwtDecode,\n jwtIsDashboard,\n jwtIsIntegration,\n jwtIsSalesChannel,\n jwtIsUser,\n jwtIsWebApp,\n} from \"./jwtDecode.js\"\nexport { createAssertion } from \"./jwtEncode.js\"\nexport { jwtVerify } from \"./jwtVerify.js\"\nexport { revoke } from \"./revoke.js\"\nexport type {\n AuthenticateOptions,\n AuthenticateReturn,\n GrantType,\n RevokeOptions,\n RevokeReturn,\n} from \"./types/index.js\"\n","/**\n * A token error occurred.\n */\nexport class TokenError extends Error {\n constructor(message: string) {\n super(message)\n this.name = \"TokenError\"\n }\n}\n","import { TokenError } from \"./TokenError.js\"\n\n/**\n * The token is not valid.\n */\nexport class InvalidTokenError extends TokenError {\n constructor(message: string) {\n super(message)\n this.name = \"InvalidTokenError\"\n }\n}\n","/**\n * Creates a [Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64) URL safe encoded [ASCII](https://developer.mozilla.org/en-US/docs/Glossary/ASCII)\n * string from a _binary string_ (i.e., a string in which each character in the string is treated as a byte of binary data).\n *\n * The \"Base64 URL safe\" omits the padding `=` and replaces `+/` with `-_` to avoid characters that might cause problems in URL path segments or query parameters.\n *\n * This method works both in Node.js and browsers.\n *\n * @param stringToEncode The binary string to encode.\n * @returns An ASCII string containing the Base64 URL safe representation of `stringToEncode`.\n */\nexport function encodeBase64URLSafe(\n stringToEncode: string,\n encoding: \"utf-8\" | \"binary\",\n): string {\n if (typeof btoa !== \"undefined\") {\n let utf8String = stringToEncode\n\n if (encoding === \"utf-8\") {\n // Encode the string as UTF-8\n const utf8Bytes = new TextEncoder().encode(stringToEncode)\n\n // Convert the UTF-8 bytes to a Base64 string\n utf8String = String.fromCharCode(...utf8Bytes)\n }\n\n return (\n btoa(utf8String)\n // Remove padding equal characters\n .replaceAll(\"=\", \"\")\n // Replace characters according to base64url specifications\n .replaceAll(\"+\", \"-\")\n .replaceAll(\"/\", \"_\")\n )\n }\n\n return Buffer.from(stringToEncode, encoding).toString(\"base64url\")\n}\n\n/**\n * Decodes a string of data\n * which has been encoded using [Base64](https://developer.mozilla.org/en-US/docs/Glossary/Base64) URL safe encoding.\n *\n * The \"Base64 URL safe\" omits the padding `=` and replaces `+/` with `-_` to avoid characters that might cause problems in URL path segments or query parameters.\n *\n * This method works both in Node.js and browsers.\n *\n * @param encodedData A binary string (i.e., a string in which each character in the string is treated as a byte of binary data) containing Base64 URL safe -encoded data.\n * @returns An ASCII string containing decoded data from `encodedData`.\n */\nexport function decodeBase64URLSafe(\n encodedData: string,\n encoding: \"utf-8\" | \"binary\",\n): string {\n if (typeof atob !== \"undefined\") {\n const decoded = atob(\n encodedData\n // Replace characters according to base64url specifications\n .replaceAll(\"-\", \"+\")\n .replaceAll(\"_\", \"/\")\n // Add padding if necessary\n .padEnd(encodedData.length + ((4 - (encodedData.length % 4)) % 4), \"=\"),\n )\n\n if (encoding === \"utf-8\") {\n // Decode the Base64 string into bytes\n const byteArray = new Uint8Array(\n [...decoded].map((char) => char.charCodeAt(0)),\n )\n\n // Convert the bytes back to a UTF-8 string\n return new TextDecoder().decode(byteArray)\n }\n\n return decoded\n }\n\n return Buffer.from(encodedData, \"base64url\").toString(encoding)\n}\n","import { InvalidTokenError } from \"./errors/InvalidTokenError.js\"\nimport { decodeBase64URLSafe } from \"./utils/base64.js\"\n\n/**\n * Decode a Commerce Layer access token without verifying if the signature is valid.\n *\n * _You should not use this for untrusted messages, since this helper method does not verify whether the signature is valid.\n * If you need to verify the access token before decoding, you can use `jwtVerify` instead._\n */\nexport function jwtDecode(accessToken: string): CommerceLayerJWT {\n const [encodedHeader, encodedPayload, signature] = `${accessToken}`.split(\".\")\n\n if (encodedHeader == null || encodedPayload == null || signature == null) {\n throw new InvalidTokenError(\"Invalid token format\")\n }\n\n return {\n header: JSON.parse(decodeBase64URLSafe(encodedHeader, \"binary\")),\n payload: JSON.parse(decodeBase64URLSafe(encodedPayload, \"utf-8\")),\n signature,\n }\n}\n\nexport interface CommerceLayerJWT {\n /** The header typically consists of two parts: the type of the token, which is JWT, and the signing algorithm being used, such as HMAC SHA256 or RSA. */\n header: {\n /** Signing algorithm being used (e.g. `HMAC`, `SHA256`, `RSA`, `RS512`). */\n alg: string\n /** Type of the token (usually `JWT`). */\n typ?: string\n /** Key ID */\n kid: string\n }\n\n payload: Payload\n\n signature: string\n}\n\ntype Payload =\n | JWTDashboard\n | JWTUser\n | JWTSalesChannel\n | JWTIntegration\n | JWTWebApp\n\ninterface JWTBase {\n /** The type of credentials you're using to authenticate to the APIs. */\n application: {\n id: string\n public: boolean\n client_id: string\n /** Whether the application is confidential (`true` when the `client_secret` is provided). */\n confidential: boolean\n }\n\n /** Scope used to restrict access to a specific active market and/or stock location. */\n scope: string\n /** The token expiration time, expressed as an [epoch](https://www.epoch101.com/). */\n exp: number\n /** The environment type (true for test mode, false for live mode). */\n test: boolean\n /** A randomly generated number, less than one. */\n rand: number\n /** Issued at (seconds since Unix epoch). */\n iat: number\n /** Who created and signed this token (e.g. `\"https://auth.commercelayer.io\"`). */\n iss: string\n}\n\n/**\n * A JWT payload that represents a `user`.\n */\nexport type JWTUser = JWTBase & {\n /** The type of credentials you're using to authenticate to the APIs. */\n application: {\n kind: \"user\"\n }\n /** The authenticated user. */\n user: {\n id: string\n }\n}\n\n/**\n * A JWT payload that represents a `dashboard`.\n */\nexport type JWTDashboard = JWTBase & {\n /** The type of credentials you're using to authenticate to the APIs. */\n application: {\n kind: \"dashboard\"\n }\n /** The authenticated user. */\n user: {\n id: string\n }\n}\n\nexport type JWTOrganizationBase = JWTBase & {\n /** The organization in scope. */\n organization: {\n id: string\n slug: string\n enterprise: boolean\n region: string\n }\n /** The owner (if any) authenticating to the APIs. */\n owner?: {\n id: string\n type: \"Customer\" | \"User\"\n }\n /**\n * Any other information (key/value pairs) you want to enrich the token with,\n * when using the [JWT Bearer flow](https://docs.commercelayer.io/core/authentication/jwt-bearer).\n */\n custom_claim?: Record<string, string>\n /**\n * The market(s) in scope.\n * This is available only when the scope is defined in the request.\n */\n market?: {\n id: string[]\n stock_location_ids: string[]\n geocoder_id: string | null\n allows_external_prices: boolean\n }\n}\n\n/**\n * A JWT payload that represents a `webapp`.\n */\nexport type JWTWebApp = SetRequired<JWTOrganizationBase, \"owner\"> & {\n /** The type of credentials you're using to authenticate to the APIs. */\n application: {\n kind: \"webapp\"\n }\n}\n\n/** Create a type that makes the given keys required. The remaining keys are kept as is. */\ntype SetRequired<T, K extends keyof T> = T & { [P in K]-?: T[P] }\n\n/**\n * A JWT payload that represents a `sales_channel`.\n */\nexport type JWTSalesChannel = JWTOrganizationBase & {\n /** The type of credentials you're using to authenticate to the APIs. */\n application: {\n kind: \"sales_channel\"\n }\n}\n\n/**\n * A JWT payload that represents an `integration`.\n */\nexport type JWTIntegration = JWTOrganizationBase & {\n /** The type of credentials you're using to authenticate to the APIs. */\n application: {\n kind: \"integration\"\n }\n}\n\n/**\n * Checks if the provided payload represents a `user`.\n * @param payload The payload to be checked.\n * @returns\n */\nexport function jwtIsUser(payload: Payload): payload is JWTUser {\n return payload.application.kind === \"user\"\n}\n\n/**\n * Checks if the provided payload represents a `dashboard`.\n * @param payload The payload to be checked.\n * @returns\n */\nexport function jwtIsDashboard(payload: Payload): payload is JWTDashboard {\n return payload.application.kind === \"dashboard\"\n}\n\n/**\n * Checks if the provided payload represents an `integration`.\n * @param payload The payload to be checked.\n * @returns\n */\nexport function jwtIsIntegration(payload: Payload): payload is JWTIntegration {\n return payload.application.kind === \"integration\"\n}\n\n/**\n * Checks if the provided payload represents a `sales_channel`.\n * @param payload The payload to be checked.\n * @returns\n */\nexport function jwtIsSalesChannel(\n payload: Payload,\n): payload is JWTSalesChannel {\n return payload.application.kind === \"sales_channel\"\n}\n\n/**\n * Checks if the provided payload represents a `webapp`.\n * @param payload The payload to be checked.\n * @returns\n */\nexport function jwtIsWebApp(payload: Payload): payload is JWTWebApp {\n return payload.application.kind === \"webapp\"\n}\n","export type CamelCaseToSnakeCase<\n T extends string,\n P extends string = \"\",\n> = string extends T\n ? string\n : T extends `${infer C}${infer R}`\n ? CamelCaseToSnakeCase<\n R,\n `${P}${C extends Lowercase<C> ? \"\" : \"_\"}${Lowercase<C>}`\n >\n : P\n\nexport function camelCaseToSnakeCase<S extends string>(\n str: S,\n): CamelCaseToSnakeCase<S> {\n return str.replace(\n /[A-Z]/g,\n (letter) => `_${letter.toLowerCase()}`,\n ) as CamelCaseToSnakeCase<S>\n}\n","import type { CommerceLayerJWT } from \"src/jwtDecode.js\"\n\n/**\n * Extract the `iss` from the decoded JWT.\n *\n * This is not as simple as `decodedJWT.payload.iss` because:\n * - at the beginning the `iss` was not required.\n * - the value can be `https://commercelayer.io` is old tokens.\n */\nexport function extractIssuer(\n decodedJWT: CommerceLayerJWT,\n): `https://auth.${string}` {\n return decodedJWT?.payload?.iss?.startsWith(\"https://auth.\")\n ? (decodedJWT.payload.iss as `https://auth.${string}`)\n : \"https://auth.commercelayer.io\"\n}\n","export function mapKeys(\n obj: Record<string, unknown>,\n fn: (key: string) => string,\n): Record<string, unknown> {\n return Object.keys(obj).reduce((acc: Record<string, unknown>, key) => {\n const camelKey = fn(key)\n acc[camelKey] = obj[key]\n return acc\n }, {})\n}\n","import { jwtDecode } from \"./jwtDecode.js\"\nimport type { RevokeOptions, RevokeReturn } from \"./types/index.js\"\n\nimport { camelCaseToSnakeCase } from \"./utils/camelCaseToSnakeCase.js\"\nimport { extractIssuer } from \"./utils/extractIssuer.js\"\nimport { mapKeys } from \"./utils/mapKeys.js\"\n\n/**\n * Revoke a previously generated access token (refresh tokens included) before its natural expiration date.\n *\n * @param options Revoke options\n * @returns\n * @example\n * ```ts\n * await revoke({\n * clientId: '{{ integrationClientId }}',\n * clientSecret: '{{ integrationClientSecret }}',\n * token: authenticateResponse.accessToken\n * })\n * ```\n */\nexport async function revoke(options: RevokeOptions): Promise<RevokeReturn> {\n const body = mapKeys(options, camelCaseToSnakeCase)\n const decodedJWT = jwtDecode(options.token)\n const issuer = extractIssuer(decodedJWT)\n\n const response = await fetch(`${issuer}/oauth/revoke`, {\n method: \"POST\",\n cache: \"no-store\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n },\n body: JSON.stringify(body),\n })\n\n return (await response.json()) as RevokeReturn\n}\n","import type { CommerceLayerJWT, JWTOrganizationBase } from \"src/jwtDecode.js\"\n\n/**\n * Checks if the payload contains an owner field, which indicates that it is a customer token.\n */\nexport function hasOwner(\n payload: CommerceLayerJWT[\"payload\"],\n): payload is PayloadWithOwner {\n return \"owner\" in payload && payload.owner?.id != null\n}\n\ntype PayloadWithOwner = Extract<\n CommerceLayerJWT[\"payload\"],\n JWTOrganizationBase\n> & {\n owner: NonNullable<JWTOrganizationBase[\"owner\"]>\n}\n","export type SnakeCaseToCamelCase<S extends string> =\n S extends `${infer T}_${infer U}`\n ? `${Lowercase<T>}${Capitalize<SnakeCaseToCamelCase<U>>}`\n : S\n\nexport function snakeCaseToCamelCase<S extends string>(\n str: S,\n): SnakeCaseToCamelCase<S> {\n return str.replace(/([-_][a-z])/g, (group) =>\n group.toUpperCase().replace(\"-\", \"\").replace(\"_\", \"\"),\n ) as SnakeCaseToCamelCase<S>\n}\n","import type {\n AuthenticateOptions,\n AuthenticateReturn,\n GrantType,\n} from \"./types/index.js\"\n\nimport { camelCaseToSnakeCase } from \"./utils/camelCaseToSnakeCase.js\"\nimport { mapKeys } from \"./utils/mapKeys.js\"\nimport { snakeCaseToCamelCase } from \"./utils/snakeCaseToCamelCase.js\"\n\ninterface TokenJson {\n errors?: unknown\n expires: Date\n expires_in: number\n [key: string]: unknown\n}\n\n/**\n * Authenticate helper used to get the access token.\n *\n * _Please note that the authentication endpoint is subject to a [rate limit](https://docs.commercelayer.io/core/rate-limits)\n * of **max 30 reqs / 1 min** both in live and test mode._\n * @param grantType The type of OAuth 2.0 grant being used for authentication.\n * @param options Authenticate options\n * @returns\n * @example\n * ```ts\n * import { authenticate } from '@commercelayer/js-auth'\n *\n * const auth = await authenticate('client_credentials', {\n * clientId: '{{ clientId }}',\n * scope: 'market:id:DGzAouppwn'\n * })\n *\n * console.log(auth.accessToken)\n * ```\n */\nexport async function authenticate<TGrantType extends GrantType>(\n grantType: TGrantType,\n {\n domain = \"commercelayer.io\",\n headers,\n ...options\n }: AuthenticateOptions<TGrantType>,\n): Promise<AuthenticateReturn<TGrantType>> {\n const body = mapKeys(\n {\n grant_type: grantType,\n ...options,\n },\n camelCaseToSnakeCase,\n )\n\n const response = await fetch(`https://auth.${domain}/oauth/token`, {\n method: \"POST\",\n cache: \"no-store\",\n headers: {\n \"Content-Type\": \"application/json\",\n Accept: \"application/json\",\n ...headers,\n },\n body: JSON.stringify(body),\n })\n\n const json: TokenJson = await response.json()\n\n if (json.errors == null) {\n json.expires = new Date(Date.now() + json.expires_in * 1000)\n }\n\n if (json.errors != null && !Array.isArray(json.errors)) {\n json.errors = [json.errors]\n }\n\n return mapKeys(\n json,\n snakeCaseToCamelCase,\n ) as unknown as AuthenticateReturn<TGrantType>\n}\n","/**\n * Deduplicates concurrent calls to a function with the same arguments.\n * If multiple calls are made with the same arguments, only the first call\n * will be executed, and all subsequent calls will receive the same result\n * once it resolves. This is useful for preventing duplicate API calls or\n * expensive operations that are called simultaneously with the same parameters.\n */\n// biome-ignore lint/suspicious/noExplicitAny: It accepts any function type.\nexport const dedupConcurrentCalls = <T extends (...args: any[]) => any>(\n fn: T,\n): ((...args: Parameters<T>) => Promise<Awaited<ReturnType<T>>>) => {\n type Return = Awaited<ReturnType<T>>\n type Args = Parameters<T>\n\n const ongoingCalls = new Map<\n string,\n {\n requestQueue: Array<{\n resolve: (value: Return) => void\n reject: Parameters<ConstructorParameters<typeof Promise>[0]>[1]\n }>\n }\n >()\n\n return function (this: unknown, ...args: Args): Promise<Return> {\n return new Promise((resolve, reject) => {\n const argsKey = JSON.stringify(args)\n const current = ongoingCalls.get(argsKey)\n\n if (current === undefined) {\n ongoingCalls.set(argsKey, { requestQueue: [] })\n\n void Promise.resolve(fn.apply(this, args))\n .then((result) => {\n const query = ongoingCalls.get(argsKey)?.requestQueue ?? []\n ongoingCalls.delete(argsKey)\n\n resolve(result)\n for (const item of query) {\n item.resolve(result)\n }\n })\n .catch((error) => {\n const query = ongoingCalls.get(argsKey)?.requestQueue ?? []\n ongoingCalls.delete(argsKey)\n\n reject(error)\n for (const item of query) {\n item.reject(error)\n }\n })\n } else {\n current.requestQueue.push({\n resolve,\n reject,\n })\n }\n })\n }\n}\n","import { authenticate } from \"../authenticate.js\"\nimport { jwtDecode } from \"../jwtDecode.js\"\nimport { hasOwner } from \"../utils/hasOwner.js\"\nimport { dedupConcurrentCalls } from \"./dedupConcurrentCalls.js\"\nimport type { StorageValue, StoreOptions } from \"./storage.js\"\nimport type {\n ApiCredentialsAuthorization,\n AuthOptions,\n SetRequired,\n} from \"./types.js\"\n\ntype MakeAuthReturn = {\n options: AuthOptions\n getAuthorization: () => Promise<ApiCredentialsAuthorization>\n setAuthorization: (\n auth: SetAuthorizationOptions,\n ) => Promise<ApiCredentialsAuthorization>\n removeAuthorization: (\n type: ApiCredentialsAuthorization[\"ownerType\"],\n ) => Promise<void>\n}\n\nexport function makeAuth(\n options: AuthOptions,\n store: StoreOptions,\n {\n logPrefix,\n guestOnly = false,\n }: {\n logPrefix: string\n /**\n * Whether to allow only guest authorizations.\n * If set to `true`, the helper will only return guest authorizations\n * and will not attempt to read or refresh customer authorizations.\n * This is useful for integrations that do not require customer authentication,\n * such as backend integrations or public APIs.\n * @default false\n */\n guestOnly?: boolean\n },\n): MakeAuthReturn {\n const authOptions: SetRequired<AuthOptions, \"scope\"> = {\n ...options,\n scope: options.scope ?? \"market:all\",\n }\n\n function log(message: string, ...args: unknown[]) {\n if (authOptions.debug === true) {\n console.log(\n `[CommerceLayer • auth.js] [${logPrefix}] ${message}`,\n ...args,\n )\n }\n }\n\n const getStorageKey =\n store.getKey ??\n ((configuration, type) => {\n return `cl_${type}-${configuration.clientId}-${configuration.scope}`\n })\n\n const getAuthorization: MakeAuthReturn[\"getAuthorization\"] = async () => {\n /**\n * Threshold duration before token expiration, in seconds.\n *\n * When `authorization.expiresIn` drops below this value,\n * a new token will be requested to prevent usage of a nearly expired token.\n *\n * According to the Commerce Layer documentation, a new token is issued\n * 15 minutes (900 seconds) before the previous token expires.\n * During this overlap window, both tokens are valid.\n *\n * @example\n * 600 seconds (10 minutes)\n */\n const expirationThreshold = 10 * 60\n\n try {\n if (guestOnly === false) {\n // read `customer` authorization\n const customerKey = await getStorageKey(\n {\n clientId: authOptions.clientId,\n scope: authOptions.scope,\n },\n \"customer\",\n )\n\n log(\"Checking for customer key:\", customerKey)\n\n const customerAuthorization = toAuthorization(\n await (store.customerStorage ?? store.storage).getItem(customerKey),\n )\n\n if (customerAuthorization?.ownerType === \"customer\") {\n if (customerAuthorization.expiresIn >= expirationThreshold) {\n log(\n \"Found customer authorization in storage\",\n customerAuthorization,\n )\n\n return customerAuthorization\n }\n\n log(\"Customer authorization expired\", customerAuthorization)\n\n if (customerAuthorization.refreshToken != null) {\n const refreshTokenResponse = await authenticate(\"refresh_token\", {\n ...authOptions,\n refreshToken: customerAuthorization.refreshToken,\n })\n\n const authorization = await setAuthorization({\n accessToken: refreshTokenResponse.accessToken,\n scope: refreshTokenResponse.scope,\n refreshToken: refreshTokenResponse.refreshToken,\n })\n\n log(\"Refreshed customer authorization\", authorization)\n\n return authorization\n }\n }\n }\n\n // read `guest` authorization\n const guestKey = await getStorageKey(\n {\n clientId: authOptions.clientId,\n scope: authOptions.scope,\n },\n \"guest\",\n )\n\n log(\"Checking for guest key:\", guestKey)\n\n const guestAuthorization = toAuthorization(\n await store.storage.getItem(guestKey),\n )\n\n if (\n guestAuthorization?.ownerType === \"guest\" &&\n guestAuthorization.expiresIn >= expirationThreshold\n ) {\n log(\"Found guest authorization in storage\", guestAuthorization)\n\n return guestAuthorization\n }\n\n // requesting a new token\n\n log(\"No valid authorization found, requesting a new guest token\")\n\n // create `guest` authorization\n const clientCredentialsResponse = await authenticate(\n \"client_credentials\",\n authOptions,\n )\n\n const authorization = await setAuthorization({\n accessToken: clientCredentialsResponse.accessToken,\n scope: clientCredentialsResponse.scope,\n })\n\n return authorization\n } catch (error) {\n log(\"Error getting the authorization.\", error)\n throw error\n }\n }\n\n const setAuthorization: MakeAuthReturn[\"setAuthorization\"] = async (auth) => {\n const authorization = toAuthorization(auth)\n\n const key = await getStorageKey(\n { clientId: authOptions.clientId, scope: auth.scope },\n authorization.ownerType,\n )\n\n const value: StorageValue = {\n accessToken: authorization.accessToken,\n scope: authorization.scope,\n refreshToken:\n authorization.ownerType === \"customer\"\n ? authorization.refreshToken\n : undefined,\n }\n\n await (authorization.ownerType === \"customer\"\n ? (store.customerStorage ?? store.storage)\n : store.storage\n ).setItem(key, value)\n\n log(\"Stored authorization in storage\", authorization)\n\n return authorization\n }\n\n const removeAuthorization: MakeAuthReturn[\"removeAuthorization\"] = async (\n type,\n ) => {\n const key = await getStorageKey(\n { clientId: authOptions.clientId, scope: authOptions.scope },\n type,\n )\n\n await (type === \"customer\"\n ? (store.customerStorage ?? store.storage)\n : store.storage\n ).removeItem(key)\n\n log(`Removed \"${type}\" authorization from storage`, key)\n }\n\n return {\n options: authOptions,\n getAuthorization: dedupConcurrentCalls(getAuthorization),\n setAuthorization,\n removeAuthorization,\n }\n}\n\n/**\n * Converts the `SetAuthorizationOptions` into an `Authorization` object.\n */\nfunction toAuthorization<Options extends SetAuthorizationOptions | null>(\n options: Options,\n): Options extends null\n ? ApiCredentialsAuthorization | null\n : ApiCredentialsAuthorization {\n if (options == null) {\n return null as Options extends null\n ? ApiCredentialsAuthorization | null\n : ApiCredentialsAuthorization\n }\n\n const decodedJWT = jwtDecode(options.accessToken)\n\n if (\n hasOwner(decodedJWT.payload) &&\n decodedJWT.payload.owner.type.toLowerCase() !== \"customer\"\n ) {\n throw new Error(\n \"The provided access token does not contain a valid customer owner.\",\n )\n }\n\n const type = hasOwner(decodedJWT.payload)\n ? ({\n ownerType: \"customer\",\n ownerId: decodedJWT.payload.owner.id,\n refreshToken: options.refreshToken,\n } as const)\n : ({ ownerType: \"guest\" } as const)\n\n const expiresIn = Math.round(decodedJWT.payload.exp - Date.now() / 1000)\n\n const authorization: ApiCredentialsAuthorization = {\n ...type,\n tokenType: \"bearer\",\n createdAt: decodedJWT.payload.iat,\n scope: options.scope,\n accessToken: options.accessToken,\n expiresIn,\n expires: new Date(Date.now() + expiresIn * 1000),\n }\n\n return authorization\n}\n\ntype SetAuthorizationOptions = StorageValue\n","import type { AuthenticateReturn } from \"../types/index.js\"\nimport type { ApiCredentialsAuthorization } from \"./types.js\"\n\n/**\n * Creates a composite storage that combines multiple storages.\n *\n * When retrieving an item, it returns the first non-null value from the configured storages.\n *\n * The order of the configured storages is important, as it determines priority. The first storage in the array is checked first, followed by the second, and so on.\n *\n * Using a composite storage can be useful for reducing the load on the underlying storage.\n * For example, if you want to use Redis but avoid hitting it on every request, you can use a memory storage as the first storage and Redis as the second.\n * This way, the memory storage is checked first, and if the item is not found there, it falls back to Redis and is then saved to memory for subsequent requests.\n *\n * @param storages - An array of storage instances to combine.\n * @returns A composite storage instance.\n *\n * @example\n * const compositeStorage = createCompositeStorage([memoryStorage, redisStorage]);\n * const value = await compositeStorage.getItem('myKey');\n */\nexport function createCompositeStorage(storages: Storage[]): Storage {\n return {\n async getItem(key: string) {\n for (const storage of storages) {\n const value = await storage.getItem(key)\n\n if (value !== null) {\n for (const previousStorage of storages.slice(\n 0,\n storages.indexOf(storage),\n )) {\n await previousStorage.setItem(key, value)\n }\n\n return value\n }\n }\n return null\n },\n\n async setItem(key: string, value: StorageValue) {\n await Promise.all(storages.map((storage) => storage.setItem(key, value)))\n },\n\n async removeItem(key: string) {\n await Promise.all(storages.map((storage) => storage.removeItem(key)))\n },\n\n async dispose() {\n await Promise.all(storages.map((storage) => storage.dispose?.()))\n },\n }\n}\n\nexport type StoreOptions = {\n /**\n * Function to get the key for storing the authorization in the storage.\n * This function receives the configuration and the type of authorization\n * and should return a unique key for that authorization.\n */\n getKey?: (\n configuration: {\n clientId: string\n scope: string\n },\n type: ApiCredentialsAuthorization[\"ownerType\"],\n ) => Promise<string>\n /**\n * Storage instance for storing authorizations.\n * This storage will be used for both \"guest\" and \"customer\" authorizations.\n *\n * If you need to use a different store for \"customer\" authorizations,\n * you can provide a different storage instance using `customerStorage` option.\n */\n storage: Storage\n /**\n * Storage instance for storing \"customer\" authorizations.\n * If not provided, the `storage` option will be used.\n *\n * This is useful if you want to separate guest and customer authorizations,\n * for example, using `localStorage` for guest authorizations\n * and `sessionStorage` for customer authorizations.\n */\n customerStorage?: Storage\n}\n\n/**\n * Storage interface for managing key/value pairs.\n *\n * This interface is used to abstract the storage mechanism, allowing for different implementations (e.g., localStorage, sessionStorage, in-memory storage).\n */\nexport interface Storage {\n /**\n * Returns the current value associated with the given key, or null if the given key does not exist.\n */\n getItem: (key: string) => Promise<StorageValue | null>\n\n /**\n * Sets the value of the pair identified by key to value, creating a new key/value pair if none existed for key previously.\n */\n setItem: (key: string, value: StorageValue) => Promise<void>\n\n /**\n * Removes the key/value pair with the given key, if a key/value pair with the given key exists.\n */\n removeItem: (key: string) => Promise<void>\n\n /**\n * Disposes all mounted storages to ensure there are no open-handles left.\n * Call it before exiting process.\n */\n dispose?: () => Promise<void>\n}\n\nexport type StorageValue = Pick<\n AuthenticateReturn<\"password\">,\n \"accessToken\" | \"scope\"\n> & {\n refreshToken?: string\n}\n","import { jwtDecode } from \"../jwtDecode.js\"\nimport { revoke } from \"../revoke.js\"\nimport type { RevokeReturn } from \"../types/index.js\"\nimport { hasOwner } from \"../utils/hasOwner.js\"\nimport { makeAuth } from \"./apiCredentials.js\"\nimport type { StorageValue, StoreOptions } from \"./storage.js\"\nimport type {\n ApiCredentialsAuthorization,\n AuthOptions,\n SetRequired,\n} from \"./types.js\"\n\nexport {\n createCompositeStorage,\n type Storage,\n type StorageValue,\n} from \"./storage.js\"\nexport type { ApiCredentialsAuthorization } from \"./types.js\"\n\n/**\n * [**Integrations**](https://docs.commercelayer.io/core/api-credentials#integration) are used\n * to develop backend integrations with any 3rd-party system.\n *\n * This helper manages the caching mechanism to avoid unnecessary API calls.\n */\nexport function makeIntegration(\n options: SetRequired<AuthOptions, \"clientSecret\">,\n store: Omit<StoreOptions, \"customerStorage\">,\n): {\n options: SetRequired<AuthOptions, \"clientSecret\">\n /**\n * Get the current integration authorization.\n *\n * This will return the authorization from configured storage.\n * If the authorization is not found or expired, it will fetch a new one.\n */\n getAuthorization: ReturnType<typeof makeAuth>[\"getAuthorization\"]\n /**\n * Remove the authorization from configured storages.\n *\n * This is particularly useful when you want to get a fresh authorization.\n * **Note:** This will not revoke the token.\n */\n removeAuthorization: (type?: \"all\") => Promise<void>\n /**\n * Revoke the current integration authorization.\n *\n * This will remove the authorization from memory and storage, and revoke the access token.\n */\n revokeAuthorization: () => Promise<RevokeReturn>\n} {\n const auth = makeAuth(options, store, {\n logPrefix: \"integration\",\n guestOnly: true,\n })\n\n return {\n options,\n getAuthorization: auth.getAuthorization,\n removeAuthorization: async () => {\n return await auth.removeAuthorization(\"guest\")\n },\n revokeAuthorization: async () => {\n const { ownerType: type, accessToken } = await auth.getAuthorization()\n\n if (type === \"guest\") {\n await auth.removeAuthorization(\"guest\")\n\n return await revoke({\n clientId: options.clientId,\n clientSecret: options.clientSecret,\n token: accessToken,\n })\n }\n\n return {}\n },\n }\n}\n\n/**\n * [**Sales channels**](https://docs.commercelayer.io/core/api-credentials#sales-channel) are used\n * to build any customer touchpoint (e.g. your storefront with a fully-functional shopping cart and checkout flow).\n *\n * This helper manages the caching mechanism to avoid unnecessary API calls\n * and provides methods to set and remove customer authorizations.\n */\nexport function makeSalesChannel(\n options: Omit<AuthOptions, \"clientSecret\">,\n store: StoreOptions,\n): {\n options: Omit<AuthOptions, \"clientSecret\">\n /**\n * Get the current sales channel authorization.\n *\n * This will return the authorization from the configured storage.\n * If the authorization is not found or expired, it will fetch a new one.\n * If the customer is logged in with a `refreshToken`, it will also attempt to refresh the token when expired.\n */\n getAuthorization: ReturnType<typeof makeAuth>[\"getAuthorization\"]\n /**\n * Remove the authorization from configured storages.\n *\n * This is particularly useful when you want to get a fresh authorization.\n * **Note:** This will not revoke the token.\n */\n removeAuthorization: (\n type?: \"all\" | ApiCredentialsAuthorization[\"ownerType\"],\n ) => Promise<void>\n /**\n * Sets the customer authorization.\n * This will store the authorization in memory and storage.\n * It will also validate the access token to ensure it contains a valid customer owner.\n *\n * The option `refreshToken` is optional and can be used to set a refresh token for the customer (e.g. \"remember me\" functionality).\n * It will be used to refresh the access token when it expires.\n */\n setCustomer: (options: StorageValue) => Promise<ApiCredentialsAuthorization>\n /**\n * Logs out the current customer.\n *\n * This will remove the customer authorization from memory and storage, and revoke the access token.\n */\n logoutCustomer: () => Promise<RevokeReturn>\n} {\n const auth = makeAuth(options, store, {\n logPrefix: \"sales_channel\",\n })\n\n return {\n options,\n getAuthorization: auth.getAuthorization,\n removeAuthorization: async (type = \"all\") => {\n if (type === \"all\") {\n await auth.removeAuthorization(\"customer\")\n await auth.removeAuthorization(\"guest\")\n return\n }\n\n return auth.removeAuthorization(type)\n },\n setCustomer: async (options) => {\n const decodedJWT = jwtDecode(options.accessToken)\n\n if (hasOwner(decodedJWT.payload)) {\n return await auth.setAuthorization(options)\n }\n\n throw new Error(\n \"The provided access token does not contain a valid customer owner.\",\n )\n },\n logoutCustomer: async () => {\n const { ownerType: type, accessToken } = await auth.getAuthorization()\n\n if (type === \"customer\") {\n await auth.removeAuthorization(\"customer\")\n\n return await revoke({\n clientId: options.clientId,\n token: accessToken,\n })\n }\n\n return {}\n },\n }\n}\n","import { TokenError } from \"./TokenError.js\"\n\n/**\n * The token expired.\n */\nexport class TokenExpiredError extends TokenError {\n constructor() {\n super(\"Token expired\")\n this.name = \"TokenExpiredError\"\n }\n}\n","import { InvalidTokenError } from \"./errors/InvalidTokenError.js\"\nimport { jwtDecode } from \"./jwtDecode.js\"\nimport { extractIssuer } from \"./utils/extractIssuer.js\"\n\n/**\n * Derives the [Core API base endpoint](https://docs.commercelayer.io/core/api-specification#base-endpoint) given a valid access token.\n *\n * @example\n * ```ts\n * getCoreApiBaseEndpoint('eyJhbGciOiJS...') //= \"https://yourdomain.commercelayer.io\"\n * ```\n *\n * The method requires a valid access token with an `organization` in the payload.\n *\n * @param accessToken - The access token to decode.\n * @param options - An options object to configure behavior.\n * @returns The core API base endpoint as a string, or `null` if the token is invalid and `shouldThrow` is `false`.\n * @throws InvalidTokenError - If the token is invalid and `shouldThrow` is true.\n */\nexport function getCoreApiBaseEndpoint(\n accessToken: string,\n options?: {\n /**\n * Whether to throw an error if the token is invalid.\n * @default true\n */\n shouldThrow?: true\n },\n): string\n\n/**\n * Derives the [Core API base endpoint](https://docs.commercelayer.io/core/api-specification#base-endpoint) given a valid access token.\n *\n * @example\n * ```ts\n * getCoreApiBaseEndpoint('eyJhbGciOiJS...') //= \"https://yourdomain.commercelayer.io\"\n * ```\n *\n * The method requires a valid access token with an `organization` in the payload.\n *\n * @param accessToken - The access token to decode.\n * @param options - An options object to configure behavior.\n * @returns The core API base endpoint as a string, or `null` if the token is invalid and `shouldThrow` is `false`.\n * @throws InvalidTokenError - If the token is invalid and `shouldThrow` is true.\n */\nexport function getCoreApiBaseEndpoint(\n accessToken: string,\n options: {\n /**\n * Whether to throw an error if the token is invalid.\n * @default true\n */\n shouldThrow: false\n },\n): string | null\n\nexport function getCoreApiBaseEndpoint(\n accessToken: string,\n options: {\n shouldThrow?: boolean\n } = {},\n): string | null {\n const { shouldThrow = true } = options\n const decodedJWT = jwtDecode(accessToken)\n\n if (!(\"organization\" in decodedJWT.payload)) {\n if (shouldThrow) {\n throw new InvalidTokenError(\"Invalid token format\")\n }\n\n return null\n }\n\n return extractIssuer(decodedJWT).replace(\n \"auth\",\n decodedJWT.payload.organization.slug,\n )\n}\n","import { InvalidTokenError } from \"./errors/InvalidTokenError.js\"\nimport { jwtDecode } from \"./jwtDecode.js\"\nimport { extractIssuer } from \"./utils/extractIssuer.js\"\n\n/**\n * Returns the [Provisioning API base endpoint](https://docs.commercelayer.io/provisioning/getting-started/api-specification#base-endpoint) given a valid access token.\n *\n * @example\n * ```ts\n * getProvisioningApiBaseEndpoint('eyJhbGciOiJS...') //= \"https://provisioning.commercelayer.io\"\n * ```\n *\n * The method requires a valid access token for Provisioning API.\n *\n * @param accessToken - The access token to decode.\n * @param options - An options object to configure behavior.\n * @returns The provisioning API base endpoint as a string, or `null` if the token is invalid and `shouldThrow` is `false`.\n * @throws InvalidTokenError - If the token is invalid and `shouldThrow` is true.\n */\nexport function getProvisioningApiBaseEndpoint(\n accessToken: string,\n options?: {\n /**\n * Whether to throw an error if the token is invalid.\n * @default true\n */\n shouldThrow?: true\n },\n): string\n\n/**\n * Returns the [Provisioning API base endpoint](https://docs.commercelayer.io/provisioning/getting-started/api-specification#base-endpoint) given a valid access token.\n *\n * @example\n * ```ts\n * getProvisioningApiBaseEndpoint('eyJhbGciOiJS...') //= \"https://provisioning.commercelayer.io\"\n * ```\n *\n * The method requires a valid access token for Provisioning API.\n *\n * @param accessToken - The access token to decode.\n * @param options - An options object to configure behavior.\n * @returns The provisioning API base endpoint as a string, or `null` if the token is invalid and `shouldThrow` is `false`.\n * @throws InvalidTokenError - If the token is invalid and `shouldThrow` is true.\n */\nexport function getProvisioningApiBaseEndpoint(\n accessToken: string,\n options: {\n /**\n * Whether to throw an error if the token is invalid.\n * @default true\n */\n shouldThrow: false\n },\n): string | null\n\nexport function getProvisioningApiBaseEndpoint(\n accessToken: string,\n options: {\n shouldThrow?: boolean\n } = {},\n): string | null {\n const { shouldThrow = true } = options\n const decodedJWT = jwtDecode(accessToken)\n\n if (!decodedJWT?.payload?.scope?.includes(\"provisioning-api\")) {\n if (shouldThrow) {\n throw new InvalidTokenError(\"Invalid token format\")\n }\n\n return null\n }\n\n return extractIssuer(decodedJWT).replace(\"auth\", \"provisioning\")\n}\n","import { encodeBase64URLSafe } from \"./utils/base64.js\"\n\ninterface Owner {\n type: \"User\" | \"Customer\"\n id: string\n}\n\n/**\n * Create a JWT assertion as the first step of the [JWT bearer token authorization grant flow](https://docs.commercelayer.io/core/authentication/jwt-bearer).\n *\n * The JWT assertion is a digitally signed JSON object containing information\n * about the client and the user on whose behalf the access token is being requested.\n *\n * This JWT assertion can include information such as the issuer (typically the client),\n * the owner (the user on whose behalf the request is made), and any other relevant claims.\n *\n * @example\n * ```ts\n * const assertion = await createAssertion({\n * payload: {\n * 'https://commercelayer.io/claims': {\n * owner: {\n * type: 'Customer',\n * id: '4tepftJsT2'\n * },\n * custom_claim: {\n * customer: {\n * first_name: 'John',\n * last_name: 'Doe'\n * }\n * }\n * }\n * }\n * })\n * ```\n */\nexport async function createAssertion({ payload }: Assertion): Promise<string> {\n return await jwtEncode(payload, \"cl\")\n}\n\n/** RequireAtLeastOne helps create a type where at least one of the properties of an interface (can be any property) is required to exist. */\ntype RequireAtLeastOne<T> = {\n [K in keyof T]-?: Required<Pick<T, K>> & Partial<Pick<T, Exclude<keyof T, K>>>\n}[keyof T]\n\ninterface Assertion {\n /** Assertion payload. */\n payload: {\n /** At least one of `owner` or `custom_claim` is required. You cannot use an empty object. */\n \"https://commercelayer.io/claims\": RequireAtLeastOne<{\n /** The customer or user you want to make the calls on behalf of. */\n owner?: Owner\n /** Any other information (key/value pairs) you want to enrich the token with. */\n custom_claim?: Record<string, unknown>\n }>\n }\n}\n\nasync function jwtEncode(\n payload: Record<string, unknown>,\n secret: string,\n): Promise<string> {\n const header = { alg: \"HS512\", typ: \"JWT\" }\n\n const encodedHeader = encodeBase64URLSafe(JSON.stringify(header), \"binary\")\n\n const encodedPayload = encodeBase64URLSafe(\n JSON.stringify({\n ...payload,\n iat: Math.floor(Date.now() / 1000),\n }),\n \"utf-8\",\n )\n\n const unsignedToken = `${encodedHeader}.${encodedPayload}`\n\n const signature = await createSignature(unsignedToken, secret)\n\n return `${unsignedToken}.${signature}`\n}\n\nasync function createSignature(data: string, secret: string): Promise<string> {\n const enc = new TextEncoder()\n const algorithm = { name: \"HMAC\", hash: \"SHA-512\" }\n\n const key = await crypto.subtle.importKey(\n \"raw\",\n enc.encode(secret),\n algorithm,\n false,\n [\"sign\", \"verify\"],\n )\n\n const signature = await crypto.subtle.sign(\n algorithm.name,\n key,\n enc.encode(data),\n )\n\n return encodeBase64URLSafe(\n String.fromCharCode(...new Uint8Array(signature)),\n \"binary\",\n )\n}\n","import type { CommerceLayerJWT } from \"src/jwtDecode.js\"\n\n/**\n * Check if a Commerce Layer JWT has expired.\n *\n * @param decodedJWT - The decoded JWT to check.\n * @returns `true` if the JWT has expired, `false` otherwise.\n */\nexport function hasExpired(decodedJWT: CommerceLayerJWT): boolean {\n return Date.now() >= decodedJWT.payload.exp * 1000\n}\n","import { InvalidTokenError } from \"./errors/InvalidTokenError.js\"\nimport { TokenError } from \"./errors/TokenError.js\"\nimport { TokenExpiredError } from \"./errors/TokenExpiredError.js\"\nimport { type CommerceLayerJWT, jwtDecode } from \"./jwtDecode.js\"\nimport { decodeBase64URLSafe } from \"./utils/base64.js\"\nimport { extractIssuer } from \"./utils/extractIssuer.js\"\nimport { hasExpired } from \"./utils/hasExpired.js\"\n\n/**\n * Validate the integrity and authenticity of the JWT.\n * It checks if the token is valid by verifying the signature against the public key used to create it.\n * This is useful to ensure that the token hasn't been tampered with and originates from Commerce Layer.\n *\n * When the verification succeeds, it resolves to the decoded access token, it rejects otherwise.\n */\nexport async function jwtVerify(\n accessToken: string,\n { ignoreExpiration = false, jwk }: JwtVerifyOptions = {},\n): Promise<CommerceLayerJWT> {\n const decodedJWT = jwtDecode(accessToken)\n\n const jsonWebKey = jwk ?? (await getJsonWebKey(decodedJWT))\n\n if (jsonWebKey == null || jsonWebKey.kid !== decodedJWT.header.kid) {\n throw new InvalidTokenError('Invalid token \"kid\"')\n }\n\n if (!ignoreExpiration && hasExpired(decodedJWT)) {\n throw new TokenExpiredError()\n }\n\n const algorithm: RsaHashedImportParams = {\n name: \"RSASSA-PKCS1-v1_5\",\n hash: \"SHA-512\",\n }\n\n const publicKey = await crypto.subtle.importKey(\n \"jwk\",\n jsonWebKey,\n algorithm,\n true,\n [\"verify\"],\n )\n\n const rawSignature = new Uint8Array(\n Array.from(decodeBase64URLSafe(decodedJWT.signature, \"binary\"), (c) =>\n c.charCodeAt(0),\n ),\n )\n\n const rawData = new TextEncoder().encode(\n accessToken.split(\".\").slice(0, 2).join(\".\"),\n )\n\n const isValid = await crypto.subtle.verify(\n algorithm,\n publicKey,\n rawSignature,\n rawData,\n )\n\n if (!isValid) {\n throw new InvalidTokenError(\"Invalid signature\")\n }\n\n return decodedJWT\n}\n\ntype CommerceLayerJsonWebKey = JsonWebKey & { kid: string }\n\ninterface JwtVerifyOptions {\n /**\n * Do not validate the token expiration when set to `true`.\n * @default false\n */\n ignoreExpiration?: boolean\n\n /**\n * Json Web Key used to verify the signature.\n *\n * The `kid` must match the `kid` from decoded accessToken.\n *\n * By default, we pick the jwk from https://auth.commercelayer.io/.well-known/jwks.json using the `kid` from the accessToken.\n */\n jwk?: CommerceLayerJsonWebKey\n}\n\n/** JWKS in-memory cache. */\nconst JWKSCache: Record<string, CommerceLayerJsonWebKey | undefined> = {}\n\n/**\n * Get the `JsonWebKey` given a key identifier.\n * @param kid Key identifier.\n * @returns\n */\nasync function getJsonWebKey(\n jwt: CommerceLayerJWT,\n): Promise<CommerceLayerJsonWebKey | undefined> {\n const { kid } = jwt.header\n\n if (JWKSCache[kid] != null) {\n return JWKSCache[kid]\n }\n\n const keys = await getJsonWebKeys(jwt)\n\n JWKSCache[kid] = keys.find((key) => key.kid === kid)\n\n return JWKSCache[kid]\n}\n\n/**\n * Retrieve RSA public keys from our JWKS (JSON Web Key Set) endpoint.\n * @returns\n */\nasync function getJsonWebKeys(\n jwt: CommerceLayerJWT,\n): Promise<CommerceLayerJsonWebKey[]> {\n const jwksUrl = `${extractIssuer(jwt)}/.well-known/jwks.json`\n\n const response = await fetch(jwksUrl).then<{\n keys: CommerceLayerJsonWebKey[] | undefined\n }>(async (res) => await res.json())\n\n if (response.keys == null) {\n throw new TokenError(\n `Invalid jwks response from \"${jwksUrl}\": ${JSON.stringify(response)}`,\n )\n }\n\n return response.keys\n}\n"],"mappings":"0aAAA,IAAAA,GAAA,GAAAC,EAAAD,GAAA,uBAAAE,EAAA,eAAAC,EAAA,sBAAAC,EAAA,iBAAAC,EAAA,oBAAAC,EAAA,2BAAAC,EAAA,2BAAAC,EAAA,mCAAAC,EAAA,cAAAC,EAAA,mBAAAC,EAAA,qBAAAC,EAAA,sBAAAC,EAAA,cAAAC,EAAA,gBAAAC,EAAA,cAAAC,EAAA,oBAAAC,EAAA,qBAAAC,EAAA,WAAAC,IAAA,eAAAC,GAAApB,ICGO,IAAMqB,EAAN,cAAyB,KAAM,CACpC,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,YACd,CACF,ECHO,IAAMC,EAAN,cAAgCC,CAAW,CAChD,YAAYC,EAAiB,CAC3B,MAAMA,CAAO,EACb,KAAK,KAAO,mBACd,CACF,ECCO,SAASC,EACdC,EACAC,EACQ,CACR,GAAI,OAAO,KAAS,IAAa,CAC/B,IAAIC,EAAaF,EAEjB,GAAIC,IAAa,QAAS,CAExB,IAAME,EAAY,IAAI,YAAY,EAAE,OAAOH,CAAc,EAGzDE,EAAa,OAAO,aAAa,GAAGC,CAAS,CAC/C,CAEA,OACE,KAAKD,CAAU,EAEZ,WAAW,IAAK,EAAE,EAElB,WAAW,IAAK,GAAG,EACnB,WAAW,IAAK,GAAG,CAE1B,CAEA,OAAO,OAAO,KAAKF,EAAgBC,CAAQ,EAAE,SAAS,WAAW,CACnE,CAaO,SAASG,EACdC,EACAJ,EACQ,CACR,GAAI,OAAO,KAAS,IAAa,CAC/B,IAAMK,EAAU,