UNPKG

@apptus/esales-api

Version:

Library for making requests to Elevate 4 API v3

8 lines (7 loc) 41 kB
{ "version": 3, "sources": ["../src/mod.ts", "../src/util/asserts.ts", "../src/util/guards.ts", "../src/util/once.ts", "../src/util/url.ts", "../src/config.ts", "../src/query.ts", "../src/notification.ts", "../src/helpers/type.ts", "../src/session.ts"], "sourcesContent": ["import { type Config, type NotificationConfig, resolveConfig, resolveNotificationConfig } from './config.ts';\nimport { Query } from './query.ts';\nimport { Notification } from './notification.ts';\n\n\n/**\n * Initialization for interacting with the Elevate Storefront API\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/\n */\nexport function elevate(config: Config) {\n const resolvedConfig = resolveConfig(config);\n\n return Object.freeze({\n /** The base URL to the Elevate cluster hosting the API */\n clusterUrl: resolvedConfig.clusterUrl,\n /** Send queries to retrieve products, facets and more */\n query: new Query(resolvedConfig),\n /** Send notifications for clicks, add-to-carts and more */\n notify: new Notification(resolvedConfig)\n });\n}\n\n/**\n * @deprecated eSales has changed name to Elevate.\n * @see https://docs.elevate.voyado.cloud/elevate/4/changelog/#esales-is-voyado-elevate\n */\nexport const esales = elevate;\n\n/**\n * Initialization for interacting with the Elevate Storefront Queries API\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/\n */\nexport function queries(config: Config) {\n return new Query(resolveConfig(config));\n}\n\n/**\n * Initialization for interacting with the Elevate Storefront Notifications API\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/\n */\nexport function notifications(config: NotificationConfig) {\n return new Notification(resolveNotificationConfig(config));\n}\n\n\n// Export Typescript interfaces/types\nexport * from './models/mod.ts';\nexport type { Config, NotificationConfig, Session, SessionMetadata, Touchpoint } from './config.ts';\n\n// Helper functions\nexport * from './helpers/mod.ts';\nexport { type LocalStorageSession, localStorageBackedSession } from './session.ts';\n", "export class AssertionError extends Error {\n override name = 'AssertionError';\n constructor(message: string) {\n super(message);\n }\n}\n\n/** Asserts that the provided condition is truthy */\nexport function assert(condition: unknown, message = ''): asserts condition {\n if (!condition) throw new AssertionError(message);\n}\n\n/** Asserts that the provided value is a non-empty string */\nexport function assertString(value: unknown, message?: string): asserts value is string {\n assert(typeof value === 'string' && value.length > 0, message);\n}\n", "/** Returns `true` if provided value is not `undefined | null` */\nexport function isDefined<T>(value: T): value is NonNullable<T> {\n return value != null;\n}\n\n/** Returns `true` if provided value is a non-empty string */\nexport function isString(value: unknown): value is string {\n return typeof value === 'string' && value.length > 0;\n}\n", "/**\n * Only ever execute the supplied function once. Subsequent calls\n * will return the value from the first invocation.\n */\nexport function once<T extends (...args: unknown[]) => unknown>(method: T): T {\n let executed = false;\n let returnValue: unknown;\n\n return function onceInner(this: unknown, ...args: unknown[]) {\n if (!executed) {\n executed = true;\n returnValue = method.apply(this, args);\n }\n return returnValue;\n } as T;\n}\n", "import type { BaseOptions } from '../config.ts';\nimport type { FacetParams } from '../mod.ts';\nimport { isDefined } from './guards.ts';\n\n\n/** Convert interface to a record-type, to allow passing it to index-signature methods */\nexport type Type<T extends object> = Record<keyof T, T[keyof T]>;\nexport type UrlParams = Record<string, PrimitiveParam>;\nexport type PrimitiveParam = string | number | boolean | string[] | number[] | boolean[] | null | undefined;\n\nexport class ResponseError extends Error {\n constructor(readonly details: { message: string }) {\n super(details.message);\n }\n}\n\n/**\n * Creates the base URL for the Elevate server, including required parameters such as:\n * - customerKey\n * - sessionKey\n * - market\n */\nexport async function createBaseUrl(endpoint: `queries/${string}` | `notifications/${string}`, config: BaseOptions) {\n const { market, clusterUrl, session } = config;\n const { customerKey, sessionKey } = await session();\n const url = new URL(`/api/storefront/v3/${endpoint}`, clusterUrl);\n\n addUrlParams(url, { market, customerKey, sessionKey });\n\n return url;\n}\n\n/**\n * Converts arrays of parameters into pipe separated values,\n * and adds their values to the provided URL object.\n */\nexport function addUrlParams(url: URL, params: UrlParams) {\n for (const [key, value] of Object.entries(params)) {\n if (isDefined(value)) {\n const values = Array.isArray(value) ? value : [value];\n url.searchParams.set(key, values.map(String).join('|'));\n }\n }\n}\n\n/** Converts `facets` parameter to a `UrlParams` object */\nexport function facetsToParams(facets: FacetParams = {}) {\n const result: UrlParams = {};\n for (const [name, value] of Object.entries(facets)) {\n if (Array.isArray(value) || typeof value !== 'object') {\n result[`f.${name}`] = value;\n } else {\n // TODO(csv): skip checking for undefined? Already filtered in `addUrlParams()`\n if (isDefined(value.min)) result[`f.${name}.min`] = value.min;\n if (isDefined(value.max)) result[`f.${name}.max`] = value.max;\n }\n }\n return result;\n}\n", "import { assert, assertString } from './util/mod.ts';\n\n\n/** Used to determine server settings to use. */\ntype Touchpoint = 'desktop' | 'mobile';\n\n/** Session is used to handle session information for a specific visitor. */\ninterface Session {\n /**\n * Method for retrieving session information, synchronously\n * or asynchronously. Returns a `SessionMetadata` object that\n * contains `customerKey` and `sessionKey`.\n *\n * @example\n * ```ts\n * declare const session: Session;\n *\n * const { customerKey, sessionKey } = await session();\n * ```\n */\n (): SessionMetadata | Promise<SessionMetadata>;\n}\n\n/** Metadata information regarding a visitor, used during requests to the Elevate Storefront API. */\ninterface SessionMetadata {\n /**\n * A key that uniquely identifies the current visitor. Using UUIDs as keys are recommended.\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/common/query-parameters/#mandatory-parameters\n */\n customerKey: string;\n /**\n * A unique key, identifying the session. Using UUIDs as keys are recommended.\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/common/query-parameters/#mandatory-parameters\n */\n sessionKey: string;\n}\n\n/**\n * Configuration required during initialization for the Elevate Storefront API.\n *\n * @example\n * ```ts\n * const config: Config = { ... };\n * const api = elevate(config);\n * ```\n */\ninterface Config {\n /**\n * The ID for the Elevate cluster for all subsequent API requests.\n * Production/Test/Development clusters each have their own unique ID.\n * The value for `clusterId` can be found in credentials tab in\n * the Voyado Elevate app,\n *\n * The value should be:\n * - An identifier in a format similar to `'w00000000'`\n * - A URL with the origin to the Elevate cluster, e.g.\n * `'https://w00000000.api.esales.apptus.cloud/'`\n *\n * @example\n * ```ts\n * const api = elevate({ clusterId: 'w00000000' });\n * ```\n */\n clusterId: string;\n /** Required for loading correct products and behavior data. */\n market: string;\n /** Required for loading localized product and facet data. */\n locale: string;\n /** Used to determine various server settings. */\n touchpoint: Touchpoint;\n /** Configures how session metadata (customerKey & sessionKey) is retrieved before requests to the API */\n session: Session;\n}\n\n/**\n * Subset of Config for interacting with the Notifications API.\n * @see Config\n */\ntype NotificationConfig = Pick<Config, 'clusterId' | 'market' | 'session'>;\n\n/**\n * Created from a validated `Config`. Used internally.\n * @internal\n */\ninterface BaseOptions extends Omit<NotificationConfig, 'clusterId'> {\n clusterUrl: string;\n}\n\n/**\n * Created from a validated `Config`. Used internally.\n * @internal\n */\ninterface FullOptions extends Omit<Config, 'clusterId'> {\n clusterUrl: string;\n}\n\nfunction validateBaseConfig(config: unknown): asserts config is NotificationConfig {\n assert(typeof config === 'object' && config !== null, 'API config must be an \"object\"');\n\n const c: Partial<NotificationConfig> = config;\n\n assertString(c.clusterId, 'Property \"clusterId\" must be a non-empty string');\n assertString(c.market, 'Property \"market\" must be a non-empty string');\n assert(typeof c.session === 'function', 'Property \"session\" must be a method.');\n}\n\n/**\n * Validates that user supplied config is in a correct format. `unknown` is used as parameter\n * value to indicate that we do not yet trust the user input.\n *\n * @param config raw configuration for creating the API library to be validated\n */\nexport function validateConfig(config: unknown): asserts config is Config {\n validateBaseConfig(config);\n\n const c: Partial<Config> = config;\n const touchpoints: Touchpoint[] = ['desktop', 'mobile'];\n\n assertString(c.locale, 'Property \"locale\" must be a string and valid locale, e.g. \"en-US\".');\n assert(touchpoints.includes(c.touchpoint!), `Property \"touchpoint\" must be one of: ${touchpoints.join(', ')}`);\n}\n\nfunction resolveClusterUrl<T extends NotificationConfig>(config: T) {\n const { clusterId, ...rest } = config;\n\n const clusterUrl = (() => {\n /** Ensures consistent casing and trailing slash of URL's */\n const normalizeUrl = (url: string) => new URL(url).href;\n\n try {\n // Will throw for invalid URL, and normalize trailing slash\n return normalizeUrl(clusterId);\n } catch { /* empty */ }\n return normalizeUrl(`https://${clusterId}.api.esales.apptus.cloud/`);\n })();\n\n return { ...rest, clusterUrl };\n}\n\n/** Prepares user-supplied configuration for internal use. */\nexport function resolveConfig(config: Config): FullOptions {\n // TODO(csv): Allow this code to be removed in a production build?\n validateConfig(config);\n return resolveClusterUrl(config);\n}\n\n/** Prepares user-supplied configuration for internal use. */\nexport function resolveNotificationConfig(config: NotificationConfig): BaseOptions {\n // TODO(csv): Allow this code to be removed in a production build?\n validateBaseConfig(config);\n return resolveClusterUrl(config);\n}\n\nexport type { Config, NotificationConfig, BaseOptions, FullOptions, Session, SessionMetadata, Touchpoint };\n", "import { assert, addUrlParams, createBaseUrl, facetsToParams, ResponseError, type Type, type UrlParams } from './util/mod.ts';\nimport type { FullOptions } from './config.ts';\nimport type { FacetParams } from './mod.ts';\nimport type * as m from './models/mod.ts';\n\n\nexport class Query {\n constructor(private readonly __config: FullOptions) {}\n\n /**\n * Retrieve recommendations for a product that has just been added to the cart.\n *\n * @param params query parameter options to submit\n * @param body configuration options to submit\n *\n * @example\n * ```ts\n * const res = await api.query.addToCartPopup({ variantKey: 'variant-key-123' }, {\n * recommendationLists: [{\n * id: 'addons',\n * algorithm: 'ADD_TO_CART_RECS'\n * }]\n * });\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/add-to-cart-popup/\n */\n addToCartPopup(params: m.AddToCartPopupParams, recommendationLists: m.AddToCartPopupBody) {\n return this.__query<m.AddToCartPopup>('add-to-cart-popup', {\n params: params as Type<m.AddToCartPopupParams>,\n body: { recommendationLists }\n });\n }\n\n /**\n * Autocomplete based on provided query parameter, for search suggestions,\n * product suggestions, and more.\n *\n * @param params query parameter options to submit\n * @param body configuration options to submit\n *\n * @example\n * ```ts\n * const res = await api.query.autocomplete({ q: 'shirt' });\n * const sale = await api.query.autocomplete({ q: 'dress' }, {\n * productFilter: { discount: 50 }\n * p});\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/autocomplete/\n */\n autocomplete(params: m.AutocompleteParams, body?: m.AutocompleteBody) {\n return this.__query<m.Autocomplete>('autocomplete', { params: params as Type<m.AutocompleteParams>, body });\n }\n\n /**\n * Retrieve the complete navigation tree, suitable for a top/mobile navigation of the site.\n *\n * @param params query parameter options to submit\n *\n * @example\n * ```ts\n * const tree = await api.query.navigationTree();\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/navigation-tree/\n */\n navigationTree(params?: m.NavigationTreeParams) {\n return this.__query<m.NavigationTree>('navigation-tree', { params: params as Type<m.NavigationTreeParams> });\n }\n\n /**\n * Returns a product listing with facets based on provided query, optionally\n * with related navigation included.\n *\n * @param params query parameter options to submit\n * @param body configuration options to submit\n *\n * @example\n * ```ts\n * const res = await api.query.searchPage({ q: 'shirt' });\n * const sale = await api.query.searchPage({ q: 'dress' }, {\n * primaryList: {\n * productFilter: { discount: 50 }\n * },\n * navigation: { include: true }\n * });\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/search-page/\n */\n searchPage(params: m.SearchPageParams, body?: m.SearchPageBody) {\n const { facets, ...p } = params;\n return this.__query<m.SearchPage>('search-page', { facets, params: { ...p }, body });\n }\n\n /**\n * Retrieves product information and related info suitable to show on a Product page.\n * Can be configured to show various recommendation lists based on body configuration settings.\n *\n * @param params query parameter options to submit\n * @param body configuration options to submit\n *\n * @example\n * ```ts\n * const res = await api.query.productPage({ productKey: 'p123' }, {\n * recommendationLists: [{\n * id: 'alts',\n * algorithm: 'ALTERNATIVES'\n * }]\n * });\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/product-page/\n */\n productPage(params: m.ProductPageParams, body?: m.ProductPageBody) {\n return this.__query<m.ProductPage>('product-page', { params: params as Type<m.ProductPageParams>, body });\n }\n\n /**\n * Retrieve products - and possibly recommendation lists - suitable for display\n * on a cart page.\n *\n * @param params query parameter options to submit\n * @param body configuration options to submit\n *\n * @example\n * ```ts\n * const res = await api.query.cartPage({ cart: ['p123', 'p456'] }, {\n * recommendationLists: [{\n * id: 'addons',\n * algorithm: 'CART'\n * }]\n * });\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/cart-page/\n */\n cartPage(params: m.CartPageParams, body?: m.CartPageBody) {\n return this.__query<m.CartPage>('cart-page', { params: params as Type<m.CartPageParams>, body });\n }\n\n /**\n * Request for generic landing pages, or category pages. Can return product listing with facets,\n * recommendation lists, or both. Suitable for the start/home page, intermediate category pages,\n * brand pages, and more.\n *\n * @param params query parameter options to submit\n * @param body configuration options to submit\n *\n * @example\n * ```ts\n * const res = await api.query.landingPage();\n * const sale = await api.query.landingPage({ limit: 30, skip: 0 }, {\n * primaryList: {\n * productFilter: { discount: 50 }\n * },\n * recommendationLists: [{\n * id: 'personal',\n * algorithm: 'PERSONAL'\n * }]\n * });\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/landing-page/\n */\n landingPage(params: m.LandingPageParams = {}, body?: m.LandingPageBody) {\n const { facets, ...p } = params;\n return this.__query<m.LandingPage>('landing-page', { facets, params: { ...p }, body });\n }\n\n /**\n * Request for retrieving a list of sponsored products for a Page.\n *\n * @beta \u26A0\uFE0F This request is currently limited to a private beta and will fail otherwise.\n *\n * @param params query parameter options to submit\n *\n * @example\n * ```ts\n * const res = await api.query.sponsoredPage({\n * pageReference: '/women/tops'\n * });\n * ```\n * @see https://docs.apptus.com/elevate/4/integration/api/specifications/storefront/v3/queries/sponsored-page/\n */\n sponsoredPage(params: m.SponsoredPageParams) {\n return this.__query<m.SponsoredPage>('sponsored-page', { params: params as Type<m.SponsoredPageParams> });\n }\n\n /**\n * Request for retrieving a list of banners for a Page.\n *\n * @beta \u26A0\uFE0F This request is currently limited to a private beta and will fail otherwise.\n *\n * @param params query parameter options to submit\n *\n * @example\n * ```ts\n * const res = await api.query.banners({\n * pageReference: '/women/tops'\n * });\n * ```\n * @see https://docs.apptus.com/elevate/4/integration/api/specifications/storefront/v3/queries/banners/\n */\n banners(params: m.BannersParams) {\n return this.__query<m.Banners>('banners', { params: params as Type<m.BannersParams> });\n }\n\n /**\n * Mirrors the Product Page, but for Content instead of Products. Retrieves content\n * information for the provided `contentKeys` (required).\n *\n * @param params query parameter options to submit\n *\n * @example\n * ```ts\n * const res = await api.query.contentInformation({\n * contentKeys: ['ck_123456', 'ck_234567']\n * });\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/content-information/\n */\n contentInformation(params: m.ContentInformationParams) {\n return this.__query<m.ContentInformation>('content-information', { params: params as Type<m.ContentInformationParams> });\n }\n\n /**\n * Mirrors the Search Page request, but for Content instead of Products. Returns content\n * results matching the provided query. Suiteable for searching in e.g. FAQ or customer service,\n * where no product results are required.\n *\n * @param params query parameter options to submit\n * @param body configuration options to submit\n *\n * @example\n * ```ts\n * const res = await this.contentSearchPage({ q: 'returns' });\n * ```\n * @example\n * ```ts\n * const res = await this.contentSearchPage({ q: 'shipping', skip: 20 }, {\n * primaryList: {\n * contentFilter: {\n * type: 'faq'\n * }\n * }\n * });\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/queries/content-search-page/\n */\n contentSearchPage(params: m.ContentSearchPageParams, body?: m.ContentSearchPageBody) {\n return this.__query<m.ContentSearchPage>('content-search-page', { params: params as Type<m.ContentSearchPageParams>, body });\n }\n\n private async __query<T>(endpoint: string, options: { params?: UrlParams, facets?: FacetParams, body?: object }) {\n const { params = {}, facets, body } = options;\n assert(typeof params === 'object', 'If provided, params must be an object');\n\n const url = await createBaseUrl(`queries/${endpoint}`, this.__config);\n const { locale, touchpoint } = this.__config;\n addUrlParams(url, { locale, touchpoint, ...params, ...facetsToParams(facets) });\n\n const init: RequestInit = !body ?\n { method: 'GET' } :\n { method: 'POST', headers: { 'Content-Type': 'text/plain' }, body: JSON.stringify(body) };\n const res = await fetch(url, init);\n const content = await res.json();\n\n // TODO: res.ok\n if (res.status >= 200 && res.status < 400) {\n return content as T;\n }\n\n throw new ResponseError(content);\n }\n}\n", "import { assert, createBaseUrl } from './util/mod.ts';\nimport type { BaseOptions } from './config.ts';\nimport type { FavoritePayload } from './models/notifications.ts';\n\n\nexport class Notification {\n constructor(private readonly __config: BaseOptions) {}\n\n /**\n * Send click notification with ticket. Tries to queue fetch\n * request with `keepalive` option, with one retry if it fails.\n *\n * @param ticket is present on product and variant\n *\n * @example\n * ```ts\n * await api.notify.click('OzE7cHJ...yM7Mjg7');\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/notifications/click/\n */\n click(ticket: string) {\n return this.__notification('click', { ticket });\n }\n\n /**\n * Send impression notification. Tries to queue fetch\n * request with `keepalive` option, with one retry if it fails.\n *\n * @beta \u26A0\uFE0F This request is currently limited to a private beta and will fail otherwise.\n *\n * @param ticket attached to a sponsored product or list\n *\n * @example\n * ```ts\n * await api.notify.adImpression('OzE7cHJ...yM7Mjg7');\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/notifications/ad-impression/\n */\n adImpression(ticket: string) {\n return this.__notification('ad-impression', { ticket });\n }\n\n /**\n * Send add to cart notification with ticket. Tries to queue fetch\n * request with `keepalive` option, with one retry if it fails.\n *\n * @param ticket is present on product and variant\n *\n * @example\n * ```ts\n * await api.notify.addToCart('OzE7cHJ...jOzI4Ow');\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/notifications/add-to-cart/\n */\n addToCart(ticket: string) {\n return this.__notification('add-to-cart', { ticket });\n }\n\n /**\n * Send add favorite notification with product-key. Tries to queue fetch\n * request with `keepalive` option, with one retry if it fails.\n *\n * @param productKeyOrPayload a `Product.key` or object with variant or product key\n *\n * @example\n * ```ts\n * await api.notify.addFavorite('pk_123456');\n * await api.notify.addFavorite({ variantKey: 'vk_234567' });\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/notifications/add-favorite/\n */\n addFavorite(productKeyOrPayload: string | FavoritePayload) {\n const body = typeof productKeyOrPayload === 'string' ?\n { productKey: productKeyOrPayload } : productKeyOrPayload;\n return this.__notification('add-favorite', body);\n }\n\n /**\n * Send remove favorite notification with product-key. Tries to queue fetch\n * request with `keepalive` option, with one retry if it fails.\n *\n * @param productKeyOrPayload a `Product.key` or object with variant or product key\n *\n * @example\n * ```ts\n * await api.notify.removeFavorite('pk_123456');\n * await api.notify.removeFavorite({ variantKey: 'vk_234567' });\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/notifications/remove-favorite/\n */\n removeFavorite(productKeyOrPayload: string | FavoritePayload) {\n const body = typeof productKeyOrPayload === 'string' ?\n { productKey: productKeyOrPayload } : productKeyOrPayload;\n return this.__notification('remove-favorite', body);\n }\n\n /**\n * Send a notification to remove recent searches for the current `customerKey`.\n *\n * The values in the array should match values on `RecentSearch.q` returned\n * from `Autocomplete.recentSearches`, to ensure they are removed correctly.\n *\n * Tries to queue fetch request with `keepalive` option, with one retry if it fails.\n *\n * @param phrases An array of recent search phrases to remove or the string 'removeAll' to remove all phrases.\n *\n * @example\n * ```ts\n * await api.notify.removeRecentSearches('removeAll');\n * await api.notify.removeRecentSearches(['gift card']);\n * await api.notify.removeRecentSearches(['dress', 'jacket', 'shoes']);\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/notifications/remove-recent-searches/\n */\n removeRecentSearches(phrases: string[] | 'removeAll') {\n return this.__notification('remove-recent-searches', phrases === 'removeAll' ?\n { removeAll: true } : { phrases });\n }\n\n /**\n * Send a notification to remove recently viewed products for the current `customerKey`.\n *\n * When removing individual products, `Product.key` should be used as ID for specific products\n * to remove from Recently viewed.\n *\n * Tries to queue fetch request with `keepalive` option, with one retry if it fails.\n *\n * @param productKeys An array of productKeys to remove or the string 'removeAll' to remove all products.\n *\n * @example\n * ```ts\n * await api.notify.removeRecentlyViewed('removeAll');\n * await api.notify.removeRecentlyViewed(['pk_123456']);\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/notifications/remove-recently-viewed/\n */\n removeRecentlyViewed(productKeys: string[] | 'removeAll') {\n return this.__notification('remove-recently-viewed', productKeys === 'removeAll' ?\n { removeAll: true } : { productKeys });\n }\n\n /**\n * Send end notification. Tries to queue fetch request with `keepalive` option,\n * with one retry if it fails.\n *\n * @example\n * ```ts\n * await api.notify.end();\n * ```\n * @see https://docs.elevate.voyado.cloud/elevate/4/integration/api/specifications/storefront/v3/notifications/end/\n */\n end() {\n return this.__notification('end');\n }\n\n private async __notification(endpoint: string, body?: object) {\n const send = async (keepalive: boolean) => {\n const url = await createBaseUrl(`notifications/${endpoint}`, this.__config);\n const data = body ? JSON.stringify(body) : undefined;\n const res = await fetch(url, { method: 'POST', body: data, keepalive });\n assert(res.ok);\n };\n\n try {\n await send(true);\n } catch {\n // TODO(csv): Add `this.__config` option to skip retrying without keepalive?\n // E.g. `Config.notificationMode: 'keepaliveWithRetry' | 'keepalive' | 'regular'\n\n // Retry without `keepalive`\n await send(false);\n }\n }\n}\n", "import type { CheckboxFacet, ColorFacet, Facet, RangeFacet, SizeFacet, TextFacet } from '../models/mod.ts';\n\n\n/** Will return `true` if the provided facet is a text facet */\nexport const isTextFacet = (facet: Facet): facet is TextFacet => facet.type === 'TEXT';\n\n/** Will return `true` if the provided facet is a color facet */\nexport const isColorFacet = (facet: Facet): facet is ColorFacet => facet.type === 'COLOR';\n\n/** Will return `true` if the provided facet is a range facet */\nexport const isRangeFacet = (facet: Facet): facet is RangeFacet => facet.type === 'RANGE';\n\n/** Will return `true` if the provided facet is a size facet */\nexport const isSizeFacet = (facet: Facet): facet is SizeFacet => facet.type === 'SIZE';\n\n/** Will return `true` if the provided facet is a checkbox facet */\nexport const isCheckboxFacet = (facet: Facet): facet is CheckboxFacet => facet.type === 'CHECKBOX';\n", "import { assert, assertString, once, isString } from './util/mod.ts';\nimport type { Session, SessionMetadata } from './config.ts';\n\n\n/** Helps retrieve and update session metadata and persist it in LocalStorage. */\nexport interface LocalStorageSession extends Session {\n /**\n * Method for retrieving session information synchronously\n * from `LocalStorage`. Returns a `SessionMetadata` object that\n * contains `customerKey` and `sessionKey`.\n *\n * @example\n * ```ts\n * declare const session: LocalStorageSession;\n *\n * const { customerKey, sessionKey } = session();\n * ```\n */\n (): SessionMetadata;\n /**\n * Generates new customer- & sessionKeys via `crypto.randomUUID()` and persists them.\n * Suitable to use e.g. when a visitor logs out.\n */\n reset(): void;\n /**\n * Updates the `customerKey` to the provided value and persists it.\n * Suitable to use e.g. when a visitor is identified by signing in to their account.\n */\n updateCustomerKey(key: string): void;\n}\n\nconst __storage: Storage = /* @__PURE__ */ (() => globalThis.localStorage)();\n\n/** @internal exported for testing */\nexport const __sessionMetadataCache = new Map<string, SessionMetadata>();\n\n/**\n * Create a `Session` compatible object for retrieving and mutating session metadata.\n * This method is only intended to be used in Browsers.\n *\n * Storage is backed by LocalStorage, where reads/writes will happen. Reads are cached\n * in memory, and invalidated by `storage` events on Window. This method can be called\n * multiple times, but shares Window listener between them.\n *\n * If session information does not exist, customer- & sessionKeys will be generated\n * automatically via `crypto.randomUUID()`.\n *\n * @param storageKey - The key that will be used to store session metadata. Defaults to `'voyado.session'`.\n *\n * @example\n * ```ts\n * import { elevate, localStorageBackedSession } from '@apptus/esales-api';\n *\n * const api = elevate({\n * // ...\n * session: localStorageBackedSession()\n * });\n * ```\n * @example\n * ```ts\n * const session = localStorageBackedSession();\n *\n * // Set a specific customerKey (e.g. on visitor sign-in)\n * session.updateCustomerKey(user.id);\n *\n * // Generate new customer/sessionKeys (e.g. on visitor signout)\n * session.reset();\n * ```\n */\nexport function localStorageBackedSession(storageKey = 'voyado.session'): LocalStorageSession {\n enableCachePruning();\n\n const fetcher = () => read(storageKey);\n fetcher.updateCustomerKey = (customerKey: string) => update(storageKey, { ...fetcher(), customerKey });\n fetcher.reset = () => update(storageKey, generateSession());\n\n return fetcher;\n}\n\nconst enableCachePruning = /* @__PURE__ */ once(() => {\n globalThis.addEventListener('storage', ({ key, storageArea }: StorageEvent) => {\n if (key && storageArea === __storage) __sessionMetadataCache.delete(key);\n });\n});\n\nfunction generateSession(): SessionMetadata {\n return { customerKey: crypto.randomUUID(), sessionKey: crypto.randomUUID() };\n}\n\nfunction read(key: string): SessionMetadata {\n // Update cached value by reading from LocalStorage if needed\n if (!__sessionMetadataCache.has(key)) {\n const strData = __storage.getItem(key);\n const session = generateSession();\n\n try {\n assertString(strData);\n\n const d: unknown = JSON.parse(strData);\n assert(d && typeof d === 'object');\n\n if ('customerKey' in d && isString(d.customerKey)) session.customerKey = d.customerKey;\n if ('sessionKey' in d && isString(d.sessionKey)) session.sessionKey = d.sessionKey;\n\n update(key, session, strData);\n } catch {\n update(key, session);\n }\n }\n\n return __sessionMetadataCache.get(key)!;\n}\n\nfunction update(key: string, data: SessionMetadata, prevData = '') {\n updateCache(key, data);\n const currData = JSON.stringify(data);\n if (prevData !== currData) __storage.setItem(key, currData);\n}\n\nfunction updateCache(key: string, data: SessionMetadata) {\n __sessionMetadataCache.set(key, data);\n}\n"], "mappings": "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", "names": [] }