UNPKG

salsify-experiences-sdk

Version:

SDK to be used by commerce websites to implement product experiences.

247 lines (218 loc) 6.63 kB
import SdkOptions from './options' import SdkSettings, { EnhancedContentSettings } from './settings' import EnhancedContentApi, { attachIframeContextListener } from './enhancedContent' import EventsApi from './events' import { getCookie, setCookie, deleteCookie } from './utils/cookies' import { createLogger } from './utils/logger' import TimeOnPageTracker from './utils/time-on-page-tracker' import { SDK_VERSION } from './version' import { inBrowser } from './utils/runtime' const sessionIdKey = 'salsify_session_id' const defaultOptions = { languageCode: 'en-US', tracking: true, } const defaultEcSettings = { idType: 'SDKID', } /** @internal */ export interface Context { /** URL of the website serving the enhanced content, if operating within the browser */ url: string | undefined /** UUID for the user visiting the website if operating within the browser, * stored in / retrieved from a session cookie unless tracking is set to false * * If tracking is false, this is set to "NOT_TRACKED" */ sessionId: string | undefined /** UUID for the page visit, if operating within the browser * * Not stored in a cookie; reset across page visits */ pageSessionId: string | undefined /** Whether or not cookie-based user tracking is enabled. The host site is responsible * for displaying any user consent dialogs and setting this option based on the user's * preference. */ tracking: boolean /** Client identifier (UUID) provided by Salsify that uniquely identifies the website */ clientId: string /** Locale identifier that determines the language of the page content */ languageCode: string /** Enhanced Content settings, including the product identifier type */ enhancedContent: EnhancedContentSettings /** SDK version */ version: string jsSource: 'bundle' | 'npm' } /** * The Salsify Experiences SDK. * * This class is responsible for initializing the SDK and exposing the public getter methods for each different available API. */ export default class SdkApi { #initialized = false #settings?: SdkSettings #ecApi?: EnhancedContentApi #eventsApi?: EventsApi #context?: Context #stalePageSessionId = false #timeOnPageTracker?: TimeOnPageTracker #jsSource: 'bundle' | 'npm' /** @internal */ public constructor(jsSource: 'bundle' | 'npm') { this.#jsSource = jsSource } /** * Initializes the SDK. * * @example * ```javascript * window.salsifyExperiencesSdk.init({ clientId, enhancedContent: { idType }}) * ``` * * @param options The options to initialize the SDK with. */ public init(options: SdkOptions): void { if (this.#initialized) { throw new Error('Salsify Experiences SDK has already been initialized.') } const ecSettings = { ...defaultEcSettings, ...options.enhancedContent, } this.#settings = { ...defaultOptions, ...options, enhancedContent: ecSettings, } this.#context = { url: inBrowser() ? window.location.href : undefined, sessionId: this.#updateSessionId(this.#settings.tracking), pageSessionId: inBrowser() ? crypto.randomUUID?.() : undefined, tracking: this.#settings.tracking, clientId: this.#settings.clientId, languageCode: this.#settings.languageCode, enhancedContent: this.#settings.enhancedContent, version: SDK_VERSION, jsSource: this.#jsSource, } if (inBrowser()) { attachIframeContextListener(this.#context) } const logger = createLogger(this.#context, this.#settings) logger.log('init', this.#settings) this.#ecApi = new EnhancedContentApi(this.#settings, this.#context, logger, { beforeRender: this.#beforeRender.bind(this), afterRender: this.#afterRender.bind(this), }) if (inBrowser()) { this.#timeOnPageTracker = new TimeOnPageTracker(logger, this.#ecApi) this.#timeOnPageTracker.start() } this.#eventsApi = new EventsApi(logger, { beforeNavigation: this.#beforeNavigation.bind(this) }, this.#ecApi) this.#initialized = true } /** * Whether the SDK has been initialized. * * @example * ```javascript * const salsify = window.salsifyExperiencesSdk; * salsify.initialized; // false * salsify.init({ clientId, enhancedContent: { idType } }); * salsify.initialized; // true * ``` */ public get initialized(): boolean { return this.#initialized } /** * The initialized Enhanced Content API. * * Throws an error if the SDK has not been initialized. * * @example * ```javascript * const salsify = window.salsifyExperiencesSdk; * const ec = salsify.enhancedContent; * ``` */ public get enhancedContent(): EnhancedContentApi { if (!this.#ecApi) { throw new Error('Salsify Experiences SDK has not been initialized.') } return this.#ecApi } /** * The initialized Events API. * * Throws an error if the SDK has not been initialized. * * @example * ```javascript * const salsify = window.salsifyExperiencesSdk; * const events = salsify.events; * ``` */ public get events(): EventsApi { if (!this.#eventsApi) { throw new Error('Salsify Experiences SDK has not been initialized.') } return this.#eventsApi } #getOrCreateSessionId(): string | undefined { let id = getCookie(sessionIdKey) if (!id) { id = crypto.randomUUID?.() if (id) { setCookie(sessionIdKey, id) } } return id } #removeSessionId(): void { deleteCookie(sessionIdKey) } #resetPageSessionId(): void { if (this.#context) { this.#context.pageSessionId = crypto.randomUUID?.() this.#stalePageSessionId = false } } #beforeRender(): void { if (!inBrowser()) return if (this.#context) { this.#context.url = window.location.href } if (this.#stalePageSessionId) { this.#resetPageSessionId() } } #afterRender(): void { if (inBrowser()) { this.#stalePageSessionId = true } } #beforeNavigation(): void { if (!inBrowser()) return if (this.#context) { this.#context.url = window.location.href } if (this.#stalePageSessionId) { this.#timeOnPageTracker?.sendEvent() this.#timeOnPageTracker?.restart() this.#resetPageSessionId() } } #updateSessionId(tracking: boolean): string | undefined { if (inBrowser()) { if (tracking) { return this.#getOrCreateSessionId() } else { this.#removeSessionId() return 'NOT_TRACKED' } } } }