@selwise/widget 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/npm/cjs/constants.js +167 -0
- package/dist/npm/cjs/constants.js.map +1 -0
- package/dist/npm/cjs/core/conflict-resolver.js +203 -0
- package/dist/npm/cjs/core/conflict-resolver.js.map +1 -0
- package/dist/npm/cjs/core/event-batcher.js +977 -0
- package/dist/npm/cjs/core/event-batcher.js.map +1 -0
- package/dist/npm/cjs/core/experiment-overrides.js +178 -0
- package/dist/npm/cjs/core/experiment-overrides.js.map +1 -0
- package/dist/npm/cjs/core/experiments.js +317 -0
- package/dist/npm/cjs/core/experiments.js.map +1 -0
- package/dist/npm/cjs/core/frequency-caps.js +297 -0
- package/dist/npm/cjs/core/frequency-caps.js.map +1 -0
- package/dist/npm/cjs/core/index.js +27 -0
- package/dist/npm/cjs/core/index.js.map +1 -0
- package/dist/npm/cjs/core/indexeddb-queue.js +333 -0
- package/dist/npm/cjs/core/indexeddb-queue.js.map +1 -0
- package/dist/npm/cjs/core/journey-tracker.js +243 -0
- package/dist/npm/cjs/core/journey-tracker.js.map +1 -0
- package/dist/npm/cjs/core/page-targeting/index.js +72 -0
- package/dist/npm/cjs/core/page-targeting/index.js.map +1 -0
- package/dist/npm/cjs/core/script-executor.js +80 -0
- package/dist/npm/cjs/core/script-executor.js.map +1 -0
- package/dist/npm/cjs/core/segments.js +172 -0
- package/dist/npm/cjs/core/segments.js.map +1 -0
- package/dist/npm/cjs/core/tracker.js +798 -0
- package/dist/npm/cjs/core/tracker.js.map +1 -0
- package/dist/npm/cjs/datalayer/basket.js +385 -0
- package/dist/npm/cjs/datalayer/basket.js.map +1 -0
- package/dist/npm/cjs/datalayer/cart-association.js +226 -0
- package/dist/npm/cjs/datalayer/cart-association.js.map +1 -0
- package/dist/npm/cjs/datalayer/checkout.js +382 -0
- package/dist/npm/cjs/datalayer/checkout.js.map +1 -0
- package/dist/npm/cjs/datalayer/constants.js +163 -0
- package/dist/npm/cjs/datalayer/constants.js.map +1 -0
- package/dist/npm/cjs/datalayer/index.js +1010 -0
- package/dist/npm/cjs/datalayer/index.js.map +1 -0
- package/dist/npm/cjs/datalayer/listener.js +416 -0
- package/dist/npm/cjs/datalayer/listener.js.map +1 -0
- package/dist/npm/cjs/datalayer/page.js +343 -0
- package/dist/npm/cjs/datalayer/page.js.map +1 -0
- package/dist/npm/cjs/datalayer/product.js +517 -0
- package/dist/npm/cjs/datalayer/product.js.map +1 -0
- package/dist/npm/cjs/datalayer/reverse-mappings.js +139 -0
- package/dist/npm/cjs/datalayer/reverse-mappings.js.map +1 -0
- package/dist/npm/cjs/datalayer/search.js +363 -0
- package/dist/npm/cjs/datalayer/search.js.map +1 -0
- package/dist/npm/cjs/datalayer/types.js +134 -0
- package/dist/npm/cjs/datalayer/types.js.map +1 -0
- package/dist/npm/cjs/datalayer/user.js +372 -0
- package/dist/npm/cjs/datalayer/user.js.map +1 -0
- package/dist/npm/cjs/debug/index.js +679 -0
- package/dist/npm/cjs/debug/index.js.map +1 -0
- package/dist/npm/cjs/entries/cdn.js +72 -0
- package/dist/npm/cjs/entries/cdn.js.map +1 -0
- package/dist/npm/cjs/index.js +10 -0
- package/dist/npm/cjs/index.js.map +1 -0
- package/dist/npm/cjs/modules/contract/recommendation-contract.js +119 -0
- package/dist/npm/cjs/modules/contract/recommendation-contract.js.map +1 -0
- package/dist/npm/cjs/modules/contract/search-contract.js +18 -0
- package/dist/npm/cjs/modules/contract/search-contract.js.map +1 -0
- package/dist/npm/cjs/modules/contract/tracking-contract.js +16 -0
- package/dist/npm/cjs/modules/contract/tracking-contract.js.map +1 -0
- package/dist/npm/cjs/modules/contract/widget-contract.js +10 -0
- package/dist/npm/cjs/modules/contract/widget-contract.js.map +1 -0
- package/dist/npm/cjs/modules/products/tracking.js +214 -0
- package/dist/npm/cjs/modules/products/tracking.js.map +1 -0
- package/dist/npm/cjs/modules/recommendation/cart-handlers.js +52 -0
- package/dist/npm/cjs/modules/recommendation/cart-handlers.js.map +1 -0
- package/dist/npm/cjs/modules/recommendation/components/product-card-renderer.js +287 -0
- package/dist/npm/cjs/modules/recommendation/components/product-card-renderer.js.map +1 -0
- package/dist/npm/cjs/modules/recommendation/index.js +678 -0
- package/dist/npm/cjs/modules/recommendation/index.js.map +1 -0
- package/dist/npm/cjs/modules/recommendation/renderers/grid.js +21 -0
- package/dist/npm/cjs/modules/recommendation/renderers/grid.js.map +1 -0
- package/dist/npm/cjs/modules/recommendation/renderers/list.js +22 -0
- package/dist/npm/cjs/modules/recommendation/renderers/list.js.map +1 -0
- package/dist/npm/cjs/modules/recommendation/renderers/masonry.js +22 -0
- package/dist/npm/cjs/modules/recommendation/renderers/masonry.js.map +1 -0
- package/dist/npm/cjs/modules/recommendation/renderers/slider-renderer.js +145 -0
- package/dist/npm/cjs/modules/recommendation/renderers/slider-renderer.js.map +1 -0
- package/dist/npm/cjs/modules/recommendation/slider-controls.js +201 -0
- package/dist/npm/cjs/modules/recommendation/slider-controls.js.map +1 -0
- package/dist/npm/cjs/modules/recommendation/tracking.js +247 -0
- package/dist/npm/cjs/modules/recommendation/tracking.js.map +1 -0
- package/dist/npm/cjs/modules/recommendation/utils/dom.js +70 -0
- package/dist/npm/cjs/modules/recommendation/utils/dom.js.map +1 -0
- package/dist/npm/cjs/modules/recommendation/utils/format.js +13 -0
- package/dist/npm/cjs/modules/recommendation/utils/format.js.map +1 -0
- package/dist/npm/cjs/modules/recommendation/utils/index.js +22 -0
- package/dist/npm/cjs/modules/recommendation/utils/index.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/helpers.js +16 -0
- package/dist/npm/cjs/modules/renderer/helpers.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/index.js +52 -0
- package/dist/npm/cjs/modules/renderer/index.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/style-resolver.js +11 -0
- package/dist/npm/cjs/modules/renderer/style-resolver.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/styles/index.js +254 -0
- package/dist/npm/cjs/modules/renderer/styles/index.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/styles.js +7 -0
- package/dist/npm/cjs/modules/renderer/styles.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/utils/helpers.js +243 -0
- package/dist/npm/cjs/modules/renderer/utils/helpers.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/utils/image-renderer.js +65 -0
- package/dist/npm/cjs/modules/renderer/utils/image-renderer.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/utils/placement.js +34 -0
- package/dist/npm/cjs/modules/renderer/utils/placement.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/utils.js +61 -0
- package/dist/npm/cjs/modules/renderer/utils.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/announcement-bar.js +84 -0
- package/dist/npm/cjs/modules/renderer/widgets/announcement-bar.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/cart-count.js +211 -0
- package/dist/npm/cjs/modules/renderer/widgets/cart-count.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/cookie-consent.js +459 -0
- package/dist/npm/cjs/modules/renderer/widgets/cookie-consent.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/countdown-timer.js +132 -0
- package/dist/npm/cjs/modules/renderer/widgets/countdown-timer.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/custom.js +58 -0
- package/dist/npm/cjs/modules/renderer/widgets/custom.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/embedded-card.js +97 -0
- package/dist/npm/cjs/modules/renderer/widgets/embedded-card.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/exit-intent-popup.js +151 -0
- package/dist/npm/cjs/modules/renderer/widgets/exit-intent-popup.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/floating-button.js +70 -0
- package/dist/npm/cjs/modules/renderer/widgets/floating-button.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/helpers.js +87 -0
- package/dist/npm/cjs/modules/renderer/widgets/helpers.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/index.js +39 -0
- package/dist/npm/cjs/modules/renderer/widgets/index.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/inline-banner.js +124 -0
- package/dist/npm/cjs/modules/renderer/widgets/inline-banner.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/newsletter.js +280 -0
- package/dist/npm/cjs/modules/renderer/widgets/newsletter.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/product-view-count.js +197 -0
- package/dist/npm/cjs/modules/renderer/widgets/product-view-count.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/slide-in.js +105 -0
- package/dist/npm/cjs/modules/renderer/widgets/slide-in.js.map +1 -0
- package/dist/npm/cjs/modules/renderer/widgets/sticky-footer.js +47 -0
- package/dist/npm/cjs/modules/renderer/widgets/sticky-footer.js.map +1 -0
- package/dist/npm/cjs/modules/search/cart/index.js +42 -0
- package/dist/npm/cjs/modules/search/cart/index.js.map +1 -0
- package/dist/npm/cjs/modules/search/components/index.js +13 -0
- package/dist/npm/cjs/modules/search/components/index.js.map +1 -0
- package/dist/npm/cjs/modules/search/components/search-box.js +28 -0
- package/dist/npm/cjs/modules/search/components/search-box.js.map +1 -0
- package/dist/npm/cjs/modules/search/components/search-item.js +197 -0
- package/dist/npm/cjs/modules/search/components/search-item.js.map +1 -0
- package/dist/npm/cjs/modules/search/components/suggestions.js +132 -0
- package/dist/npm/cjs/modules/search/components/suggestions.js.map +1 -0
- package/dist/npm/cjs/modules/search/index.js +646 -0
- package/dist/npm/cjs/modules/search/index.js.map +1 -0
- package/dist/npm/cjs/modules/search/renderers/position-renderers.js +229 -0
- package/dist/npm/cjs/modules/search/renderers/position-renderers.js.map +1 -0
- package/dist/npm/cjs/modules/search/renderers/results-renderer.js +180 -0
- package/dist/npm/cjs/modules/search/renderers/results-renderer.js.map +1 -0
- package/dist/npm/cjs/modules/search/services/api-service.js +121 -0
- package/dist/npm/cjs/modules/search/services/api-service.js.map +1 -0
- package/dist/npm/cjs/modules/search/services/tracking-service.js +263 -0
- package/dist/npm/cjs/modules/search/services/tracking-service.js.map +1 -0
- package/dist/npm/cjs/modules/search/styles.js +864 -0
- package/dist/npm/cjs/modules/search/styles.js.map +1 -0
- package/dist/npm/cjs/modules/search/utils/helpers.js +100 -0
- package/dist/npm/cjs/modules/search/utils/helpers.js.map +1 -0
- package/dist/npm/cjs/modules/search/utils/icons.js +41 -0
- package/dist/npm/cjs/modules/search/utils/icons.js.map +1 -0
- package/dist/npm/cjs/modules/search/utils/index.js +22 -0
- package/dist/npm/cjs/modules/search/utils/index.js.map +1 -0
- package/dist/npm/cjs/modules/search/utils/storage.js +58 -0
- package/dist/npm/cjs/modules/search/utils/storage.js.map +1 -0
- package/dist/npm/cjs/selwise.js +1160 -0
- package/dist/npm/cjs/selwise.js.map +1 -0
- package/dist/npm/cjs/sentry-test.js +118 -0
- package/dist/npm/cjs/sentry-test.js.map +1 -0
- package/dist/npm/cjs/sentry.js +451 -0
- package/dist/npm/cjs/sentry.js.map +1 -0
- package/dist/npm/cjs/shared/cart/handlers.js +259 -0
- package/dist/npm/cjs/shared/cart/handlers.js.map +1 -0
- package/dist/npm/cjs/shared/cart/index.js +23 -0
- package/dist/npm/cjs/shared/cart/index.js.map +1 -0
- package/dist/npm/cjs/shared/cart/types.js +7 -0
- package/dist/npm/cjs/shared/cart/types.js.map +1 -0
- package/dist/npm/cjs/shared/product-card/index.js +24 -0
- package/dist/npm/cjs/shared/product-card/index.js.map +1 -0
- package/dist/npm/cjs/shared/product-card/renderer.js +341 -0
- package/dist/npm/cjs/shared/product-card/renderer.js.map +1 -0
- package/dist/npm/cjs/shared/product-card/styles.js +195 -0
- package/dist/npm/cjs/shared/product-card/styles.js.map +1 -0
- package/dist/npm/cjs/shared/product-card/types.js +67 -0
- package/dist/npm/cjs/shared/product-card/types.js.map +1 -0
- package/dist/npm/cjs/types/config.js +6 -0
- package/dist/npm/cjs/types/config.js.map +1 -0
- package/dist/npm/cjs/types/experiments.js +17 -0
- package/dist/npm/cjs/types/experiments.js.map +1 -0
- package/dist/npm/cjs/types/global-api.js +3 -0
- package/dist/npm/cjs/types/global-api.js.map +1 -0
- package/dist/npm/cjs/types/index.js +27 -0
- package/dist/npm/cjs/types/index.js.map +1 -0
- package/dist/npm/cjs/types/recommendation.js +6 -0
- package/dist/npm/cjs/types/recommendation.js.map +1 -0
- package/dist/npm/cjs/types/renderer.js +6 -0
- package/dist/npm/cjs/types/renderer.js.map +1 -0
- package/dist/npm/cjs/types/search.js +6 -0
- package/dist/npm/cjs/types/search.js.map +1 -0
- package/dist/npm/cjs/utils/api-url.js +35 -0
- package/dist/npm/cjs/utils/api-url.js.map +1 -0
- package/dist/npm/cjs/utils/debounce.js +74 -0
- package/dist/npm/cjs/utils/debounce.js.map +1 -0
- package/dist/npm/cjs/utils/event-id.js +23 -0
- package/dist/npm/cjs/utils/event-id.js.map +1 -0
- package/dist/npm/cjs/utils/index.js +28 -0
- package/dist/npm/cjs/utils/index.js.map +1 -0
- package/dist/npm/cjs/utils/logger.js +74 -0
- package/dist/npm/cjs/utils/logger.js.map +1 -0
- package/dist/npm/cjs/utils/sanitize.js +159 -0
- package/dist/npm/cjs/utils/sanitize.js.map +1 -0
- package/dist/npm/cjs/utils/storage.js +285 -0
- package/dist/npm/cjs/utils/storage.js.map +1 -0
- package/dist/npm/cjs/utils/tracking-metadata.js +18 -0
- package/dist/npm/cjs/utils/tracking-metadata.js.map +1 -0
- package/dist/npm/cjs/utils/url-matcher.js +11 -0
- package/dist/npm/cjs/utils/url-matcher.js.map +1 -0
- package/dist/npm/esm/constants.d.ts +130 -0
- package/dist/npm/esm/constants.js +164 -0
- package/dist/npm/esm/constants.js.map +1 -0
- package/dist/npm/esm/core/conflict-resolver.d.ts +96 -0
- package/dist/npm/esm/core/conflict-resolver.js +198 -0
- package/dist/npm/esm/core/conflict-resolver.js.map +1 -0
- package/dist/npm/esm/core/event-batcher.d.ts +253 -0
- package/dist/npm/esm/core/event-batcher.js +973 -0
- package/dist/npm/esm/core/event-batcher.js.map +1 -0
- package/dist/npm/esm/core/experiment-overrides.d.ts +7 -0
- package/dist/npm/esm/core/experiment-overrides.js +172 -0
- package/dist/npm/esm/core/experiment-overrides.js.map +1 -0
- package/dist/npm/esm/core/experiments.d.ts +90 -0
- package/dist/npm/esm/core/experiments.js +313 -0
- package/dist/npm/esm/core/experiments.js.map +1 -0
- package/dist/npm/esm/core/frequency-caps.d.ts +124 -0
- package/dist/npm/esm/core/frequency-caps.js +292 -0
- package/dist/npm/esm/core/frequency-caps.js.map +1 -0
- package/dist/npm/esm/core/index.d.ts +10 -0
- package/dist/npm/esm/core/index.js +11 -0
- package/dist/npm/esm/core/index.js.map +1 -0
- package/dist/npm/esm/core/indexeddb-queue.d.ts +83 -0
- package/dist/npm/esm/core/indexeddb-queue.js +328 -0
- package/dist/npm/esm/core/indexeddb-queue.js.map +1 -0
- package/dist/npm/esm/core/journey-tracker.d.ts +102 -0
- package/dist/npm/esm/core/journey-tracker.js +237 -0
- package/dist/npm/esm/core/journey-tracker.js.map +1 -0
- package/dist/npm/esm/core/page-targeting/index.d.ts +8 -0
- package/dist/npm/esm/core/page-targeting/index.js +67 -0
- package/dist/npm/esm/core/page-targeting/index.js.map +1 -0
- package/dist/npm/esm/core/script-executor.d.ts +31 -0
- package/dist/npm/esm/core/script-executor.js +76 -0
- package/dist/npm/esm/core/script-executor.js.map +1 -0
- package/dist/npm/esm/core/segments.d.ts +39 -0
- package/dist/npm/esm/core/segments.js +168 -0
- package/dist/npm/esm/core/segments.js.map +1 -0
- package/dist/npm/esm/core/tracker.d.ts +182 -0
- package/dist/npm/esm/core/tracker.js +794 -0
- package/dist/npm/esm/core/tracker.js.map +1 -0
- package/dist/npm/esm/datalayer/basket.d.ts +104 -0
- package/dist/npm/esm/datalayer/basket.js +381 -0
- package/dist/npm/esm/datalayer/basket.js.map +1 -0
- package/dist/npm/esm/datalayer/cart-association.d.ts +65 -0
- package/dist/npm/esm/datalayer/cart-association.js +222 -0
- package/dist/npm/esm/datalayer/cart-association.js.map +1 -0
- package/dist/npm/esm/datalayer/checkout.d.ts +96 -0
- package/dist/npm/esm/datalayer/checkout.js +378 -0
- package/dist/npm/esm/datalayer/checkout.js.map +1 -0
- package/dist/npm/esm/datalayer/constants.d.ts +77 -0
- package/dist/npm/esm/datalayer/constants.js +160 -0
- package/dist/npm/esm/datalayer/constants.js.map +1 -0
- package/dist/npm/esm/datalayer/index.d.ts +212 -0
- package/dist/npm/esm/datalayer/index.js +991 -0
- package/dist/npm/esm/datalayer/index.js.map +1 -0
- package/dist/npm/esm/datalayer/listener.d.ts +108 -0
- package/dist/npm/esm/datalayer/listener.js +411 -0
- package/dist/npm/esm/datalayer/listener.js.map +1 -0
- package/dist/npm/esm/datalayer/page.d.ts +94 -0
- package/dist/npm/esm/datalayer/page.js +339 -0
- package/dist/npm/esm/datalayer/page.js.map +1 -0
- package/dist/npm/esm/datalayer/product.d.ts +133 -0
- package/dist/npm/esm/datalayer/product.js +513 -0
- package/dist/npm/esm/datalayer/product.js.map +1 -0
- package/dist/npm/esm/datalayer/reverse-mappings.d.ts +62 -0
- package/dist/npm/esm/datalayer/reverse-mappings.js +135 -0
- package/dist/npm/esm/datalayer/reverse-mappings.js.map +1 -0
- package/dist/npm/esm/datalayer/search.d.ts +108 -0
- package/dist/npm/esm/datalayer/search.js +359 -0
- package/dist/npm/esm/datalayer/search.js.map +1 -0
- package/dist/npm/esm/datalayer/types.d.ts +292 -0
- package/dist/npm/esm/datalayer/types.js +131 -0
- package/dist/npm/esm/datalayer/types.js.map +1 -0
- package/dist/npm/esm/datalayer/user.d.ts +104 -0
- package/dist/npm/esm/datalayer/user.js +368 -0
- package/dist/npm/esm/datalayer/user.js.map +1 -0
- package/dist/npm/esm/debug/index.d.ts +173 -0
- package/dist/npm/esm/debug/index.js +673 -0
- package/dist/npm/esm/debug/index.js.map +1 -0
- package/dist/npm/esm/entries/cdn.d.ts +1 -0
- package/dist/npm/esm/entries/cdn.js +35 -0
- package/dist/npm/esm/entries/cdn.js.map +1 -0
- package/dist/npm/esm/index.d.ts +4 -0
- package/dist/npm/esm/index.js +4 -0
- package/dist/npm/esm/index.js.map +1 -0
- package/dist/npm/esm/modules/contract/recommendation-contract.d.ts +5 -0
- package/dist/npm/esm/modules/contract/recommendation-contract.js +107 -0
- package/dist/npm/esm/modules/contract/recommendation-contract.js.map +1 -0
- package/dist/npm/esm/modules/contract/search-contract.d.ts +1 -0
- package/dist/npm/esm/modules/contract/search-contract.js +2 -0
- package/dist/npm/esm/modules/contract/search-contract.js.map +1 -0
- package/dist/npm/esm/modules/contract/tracking-contract.d.ts +1 -0
- package/dist/npm/esm/modules/contract/tracking-contract.js +2 -0
- package/dist/npm/esm/modules/contract/tracking-contract.js.map +1 -0
- package/dist/npm/esm/modules/contract/widget-contract.d.ts +1 -0
- package/dist/npm/esm/modules/contract/widget-contract.js +2 -0
- package/dist/npm/esm/modules/contract/widget-contract.js.map +1 -0
- package/dist/npm/esm/modules/products/tracking.d.ts +122 -0
- package/dist/npm/esm/modules/products/tracking.js +203 -0
- package/dist/npm/esm/modules/products/tracking.js.map +1 -0
- package/dist/npm/esm/modules/recommendation/cart-handlers.d.ts +10 -0
- package/dist/npm/esm/modules/recommendation/cart-handlers.js +49 -0
- package/dist/npm/esm/modules/recommendation/cart-handlers.js.map +1 -0
- package/dist/npm/esm/modules/recommendation/components/product-card-renderer.d.ts +37 -0
- package/dist/npm/esm/modules/recommendation/components/product-card-renderer.js +281 -0
- package/dist/npm/esm/modules/recommendation/components/product-card-renderer.js.map +1 -0
- package/dist/npm/esm/modules/recommendation/index.d.ts +11 -0
- package/dist/npm/esm/modules/recommendation/index.js +674 -0
- package/dist/npm/esm/modules/recommendation/index.js.map +1 -0
- package/dist/npm/esm/modules/recommendation/renderers/grid.d.ts +5 -0
- package/dist/npm/esm/modules/recommendation/renderers/grid.js +18 -0
- package/dist/npm/esm/modules/recommendation/renderers/grid.js.map +1 -0
- package/dist/npm/esm/modules/recommendation/renderers/list.d.ts +5 -0
- package/dist/npm/esm/modules/recommendation/renderers/list.js +19 -0
- package/dist/npm/esm/modules/recommendation/renderers/list.js.map +1 -0
- package/dist/npm/esm/modules/recommendation/renderers/masonry.d.ts +5 -0
- package/dist/npm/esm/modules/recommendation/renderers/masonry.js +19 -0
- package/dist/npm/esm/modules/recommendation/renderers/masonry.js.map +1 -0
- package/dist/npm/esm/modules/recommendation/renderers/slider-renderer.d.ts +10 -0
- package/dist/npm/esm/modules/recommendation/renderers/slider-renderer.js +140 -0
- package/dist/npm/esm/modules/recommendation/renderers/slider-renderer.js.map +1 -0
- package/dist/npm/esm/modules/recommendation/slider-controls.d.ts +17 -0
- package/dist/npm/esm/modules/recommendation/slider-controls.js +187 -0
- package/dist/npm/esm/modules/recommendation/slider-controls.js.map +1 -0
- package/dist/npm/esm/modules/recommendation/tracking.d.ts +28 -0
- package/dist/npm/esm/modules/recommendation/tracking.js +237 -0
- package/dist/npm/esm/modules/recommendation/tracking.js.map +1 -0
- package/dist/npm/esm/modules/recommendation/utils/dom.d.ts +10 -0
- package/dist/npm/esm/modules/recommendation/utils/dom.js +63 -0
- package/dist/npm/esm/modules/recommendation/utils/dom.js.map +1 -0
- package/dist/npm/esm/modules/recommendation/utils/format.d.ts +4 -0
- package/dist/npm/esm/modules/recommendation/utils/format.js +10 -0
- package/dist/npm/esm/modules/recommendation/utils/format.js.map +1 -0
- package/dist/npm/esm/modules/recommendation/utils/index.d.ts +5 -0
- package/dist/npm/esm/modules/recommendation/utils/index.js +6 -0
- package/dist/npm/esm/modules/recommendation/utils/index.js.map +1 -0
- package/dist/npm/esm/modules/renderer/helpers.d.ts +5 -0
- package/dist/npm/esm/modules/renderer/helpers.js +6 -0
- package/dist/npm/esm/modules/renderer/helpers.js.map +1 -0
- package/dist/npm/esm/modules/renderer/index.d.ts +8 -0
- package/dist/npm/esm/modules/renderer/index.js +48 -0
- package/dist/npm/esm/modules/renderer/index.js.map +1 -0
- package/dist/npm/esm/modules/renderer/style-resolver.d.ts +1 -0
- package/dist/npm/esm/modules/renderer/style-resolver.js +8 -0
- package/dist/npm/esm/modules/renderer/style-resolver.js.map +1 -0
- package/dist/npm/esm/modules/renderer/styles/index.d.ts +12 -0
- package/dist/npm/esm/modules/renderer/styles/index.js +250 -0
- package/dist/npm/esm/modules/renderer/styles/index.js.map +1 -0
- package/dist/npm/esm/modules/renderer/styles.d.ts +1 -0
- package/dist/npm/esm/modules/renderer/styles.js +2 -0
- package/dist/npm/esm/modules/renderer/styles.js.map +1 -0
- package/dist/npm/esm/modules/renderer/utils/helpers.d.ts +58 -0
- package/dist/npm/esm/modules/renderer/utils/helpers.js +225 -0
- package/dist/npm/esm/modules/renderer/utils/helpers.js.map +1 -0
- package/dist/npm/esm/modules/renderer/utils/image-renderer.d.ts +17 -0
- package/dist/npm/esm/modules/renderer/utils/image-renderer.js +60 -0
- package/dist/npm/esm/modules/renderer/utils/image-renderer.js.map +1 -0
- package/dist/npm/esm/modules/renderer/utils/placement.d.ts +6 -0
- package/dist/npm/esm/modules/renderer/utils/placement.js +31 -0
- package/dist/npm/esm/modules/renderer/utils/placement.js.map +1 -0
- package/dist/npm/esm/modules/renderer/utils.d.ts +11 -0
- package/dist/npm/esm/modules/renderer/utils.js +44 -0
- package/dist/npm/esm/modules/renderer/utils.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/announcement-bar.d.ts +5 -0
- package/dist/npm/esm/modules/renderer/widgets/announcement-bar.js +81 -0
- package/dist/npm/esm/modules/renderer/widgets/announcement-bar.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/cart-count.d.ts +6 -0
- package/dist/npm/esm/modules/renderer/widgets/cart-count.js +208 -0
- package/dist/npm/esm/modules/renderer/widgets/cart-count.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/cookie-consent.d.ts +18 -0
- package/dist/npm/esm/modules/renderer/widgets/cookie-consent.js +454 -0
- package/dist/npm/esm/modules/renderer/widgets/cookie-consent.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/countdown-timer.d.ts +5 -0
- package/dist/npm/esm/modules/renderer/widgets/countdown-timer.js +129 -0
- package/dist/npm/esm/modules/renderer/widgets/countdown-timer.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/custom.d.ts +5 -0
- package/dist/npm/esm/modules/renderer/widgets/custom.js +55 -0
- package/dist/npm/esm/modules/renderer/widgets/custom.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/embedded-card.d.ts +5 -0
- package/dist/npm/esm/modules/renderer/widgets/embedded-card.js +94 -0
- package/dist/npm/esm/modules/renderer/widgets/embedded-card.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/exit-intent-popup.d.ts +6 -0
- package/dist/npm/esm/modules/renderer/widgets/exit-intent-popup.js +147 -0
- package/dist/npm/esm/modules/renderer/widgets/exit-intent-popup.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/floating-button.d.ts +5 -0
- package/dist/npm/esm/modules/renderer/widgets/floating-button.js +67 -0
- package/dist/npm/esm/modules/renderer/widgets/floating-button.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/helpers.d.ts +29 -0
- package/dist/npm/esm/modules/renderer/widgets/helpers.js +82 -0
- package/dist/npm/esm/modules/renderer/widgets/helpers.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/index.d.ts +17 -0
- package/dist/npm/esm/modules/renderer/widgets/index.js +19 -0
- package/dist/npm/esm/modules/renderer/widgets/index.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/inline-banner.d.ts +5 -0
- package/dist/npm/esm/modules/renderer/widgets/inline-banner.js +121 -0
- package/dist/npm/esm/modules/renderer/widgets/inline-banner.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/newsletter.d.ts +6 -0
- package/dist/npm/esm/modules/renderer/widgets/newsletter.js +277 -0
- package/dist/npm/esm/modules/renderer/widgets/newsletter.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/product-view-count.d.ts +6 -0
- package/dist/npm/esm/modules/renderer/widgets/product-view-count.js +194 -0
- package/dist/npm/esm/modules/renderer/widgets/product-view-count.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/slide-in.d.ts +5 -0
- package/dist/npm/esm/modules/renderer/widgets/slide-in.js +102 -0
- package/dist/npm/esm/modules/renderer/widgets/slide-in.js.map +1 -0
- package/dist/npm/esm/modules/renderer/widgets/sticky-footer.d.ts +5 -0
- package/dist/npm/esm/modules/renderer/widgets/sticky-footer.js +44 -0
- package/dist/npm/esm/modules/renderer/widgets/sticky-footer.js.map +1 -0
- package/dist/npm/esm/modules/search/cart/index.d.ts +26 -0
- package/dist/npm/esm/modules/search/cart/index.js +39 -0
- package/dist/npm/esm/modules/search/cart/index.js.map +1 -0
- package/dist/npm/esm/modules/search/components/index.d.ts +6 -0
- package/dist/npm/esm/modules/search/components/index.js +7 -0
- package/dist/npm/esm/modules/search/components/index.js.map +1 -0
- package/dist/npm/esm/modules/search/components/search-box.d.ts +11 -0
- package/dist/npm/esm/modules/search/components/search-box.js +25 -0
- package/dist/npm/esm/modules/search/components/search-box.js.map +1 -0
- package/dist/npm/esm/modules/search/components/search-item.d.ts +20 -0
- package/dist/npm/esm/modules/search/components/search-item.js +193 -0
- package/dist/npm/esm/modules/search/components/search-item.js.map +1 -0
- package/dist/npm/esm/modules/search/components/suggestions.d.ts +10 -0
- package/dist/npm/esm/modules/search/components/suggestions.js +129 -0
- package/dist/npm/esm/modules/search/components/suggestions.js.map +1 -0
- package/dist/npm/esm/modules/search/index.d.ts +67 -0
- package/dist/npm/esm/modules/search/index.js +642 -0
- package/dist/npm/esm/modules/search/index.js.map +1 -0
- package/dist/npm/esm/modules/search/renderers/position-renderers.d.ts +23 -0
- package/dist/npm/esm/modules/search/renderers/position-renderers.js +221 -0
- package/dist/npm/esm/modules/search/renderers/position-renderers.js.map +1 -0
- package/dist/npm/esm/modules/search/renderers/results-renderer.d.ts +20 -0
- package/dist/npm/esm/modules/search/renderers/results-renderer.js +176 -0
- package/dist/npm/esm/modules/search/renderers/results-renderer.js.map +1 -0
- package/dist/npm/esm/modules/search/services/api-service.d.ts +18 -0
- package/dist/npm/esm/modules/search/services/api-service.js +115 -0
- package/dist/npm/esm/modules/search/services/api-service.js.map +1 -0
- package/dist/npm/esm/modules/search/services/tracking-service.d.ts +16 -0
- package/dist/npm/esm/modules/search/services/tracking-service.js +250 -0
- package/dist/npm/esm/modules/search/services/tracking-service.js.map +1 -0
- package/dist/npm/esm/modules/search/styles.d.ts +13 -0
- package/dist/npm/esm/modules/search/styles.js +860 -0
- package/dist/npm/esm/modules/search/styles.js.map +1 -0
- package/dist/npm/esm/modules/search/utils/helpers.d.ts +24 -0
- package/dist/npm/esm/modules/search/utils/helpers.js +93 -0
- package/dist/npm/esm/modules/search/utils/helpers.js.map +1 -0
- package/dist/npm/esm/modules/search/utils/icons.d.ts +16 -0
- package/dist/npm/esm/modules/search/utils/icons.js +36 -0
- package/dist/npm/esm/modules/search/utils/icons.js.map +1 -0
- package/dist/npm/esm/modules/search/utils/index.d.ts +6 -0
- package/dist/npm/esm/modules/search/utils/index.js +7 -0
- package/dist/npm/esm/modules/search/utils/index.js.map +1 -0
- package/dist/npm/esm/modules/search/utils/storage.d.ts +7 -0
- package/dist/npm/esm/modules/search/utils/storage.js +52 -0
- package/dist/npm/esm/modules/search/utils/storage.js.map +1 -0
- package/dist/npm/esm/package.json +1 -0
- package/dist/npm/esm/selwise.d.ts +209 -0
- package/dist/npm/esm/selwise.js +1123 -0
- package/dist/npm/esm/selwise.js.map +1 -0
- package/dist/npm/esm/sentry-test.d.ts +45 -0
- package/dist/npm/esm/sentry-test.js +109 -0
- package/dist/npm/esm/sentry-test.js.map +1 -0
- package/dist/npm/esm/sentry.d.ts +86 -0
- package/dist/npm/esm/sentry.js +441 -0
- package/dist/npm/esm/sentry.js.map +1 -0
- package/dist/npm/esm/shared/cart/handlers.d.ts +46 -0
- package/dist/npm/esm/shared/cart/handlers.js +249 -0
- package/dist/npm/esm/shared/cart/handlers.js.map +1 -0
- package/dist/npm/esm/shared/cart/index.d.ts +6 -0
- package/dist/npm/esm/shared/cart/index.js +7 -0
- package/dist/npm/esm/shared/cart/index.js.map +1 -0
- package/dist/npm/esm/shared/cart/types.d.ts +30 -0
- package/dist/npm/esm/shared/cart/types.js +6 -0
- package/dist/npm/esm/shared/cart/types.js.map +1 -0
- package/dist/npm/esm/shared/product-card/index.d.ts +7 -0
- package/dist/npm/esm/shared/product-card/index.js +8 -0
- package/dist/npm/esm/shared/product-card/index.js.map +1 -0
- package/dist/npm/esm/shared/product-card/renderer.d.ts +35 -0
- package/dist/npm/esm/shared/product-card/renderer.js +334 -0
- package/dist/npm/esm/shared/product-card/renderer.js.map +1 -0
- package/dist/npm/esm/shared/product-card/styles.d.ts +17 -0
- package/dist/npm/esm/shared/product-card/styles.js +190 -0
- package/dist/npm/esm/shared/product-card/styles.js.map +1 -0
- package/dist/npm/esm/shared/product-card/types.d.ts +84 -0
- package/dist/npm/esm/shared/product-card/types.js +63 -0
- package/dist/npm/esm/shared/product-card/types.js.map +1 -0
- package/dist/npm/esm/types/config.d.ts +26 -0
- package/dist/npm/esm/types/config.js +5 -0
- package/dist/npm/esm/types/config.js.map +1 -0
- package/dist/npm/esm/types/experiments.d.ts +47 -0
- package/dist/npm/esm/types/experiments.js +14 -0
- package/dist/npm/esm/types/experiments.js.map +1 -0
- package/dist/npm/esm/types/global-api.d.ts +40 -0
- package/dist/npm/esm/types/global-api.js +2 -0
- package/dist/npm/esm/types/global-api.js.map +1 -0
- package/dist/npm/esm/types/index.d.ts +9 -0
- package/dist/npm/esm/types/index.js +11 -0
- package/dist/npm/esm/types/index.js.map +1 -0
- package/dist/npm/esm/types/recommendation.d.ts +186 -0
- package/dist/npm/esm/types/recommendation.js +5 -0
- package/dist/npm/esm/types/recommendation.js.map +1 -0
- package/dist/npm/esm/types/renderer.d.ts +99 -0
- package/dist/npm/esm/types/renderer.js +5 -0
- package/dist/npm/esm/types/renderer.js.map +1 -0
- package/dist/npm/esm/types/search.d.ts +186 -0
- package/dist/npm/esm/types/search.js +5 -0
- package/dist/npm/esm/types/search.js.map +1 -0
- package/dist/npm/esm/utils/api-url.d.ts +1 -0
- package/dist/npm/esm/utils/api-url.js +32 -0
- package/dist/npm/esm/utils/api-url.js.map +1 -0
- package/dist/npm/esm/utils/debounce.d.ts +27 -0
- package/dist/npm/esm/utils/debounce.js +70 -0
- package/dist/npm/esm/utils/debounce.js.map +1 -0
- package/dist/npm/esm/utils/event-id.d.ts +5 -0
- package/dist/npm/esm/utils/event-id.js +20 -0
- package/dist/npm/esm/utils/event-id.js.map +1 -0
- package/dist/npm/esm/utils/index.d.ts +11 -0
- package/dist/npm/esm/utils/index.js +12 -0
- package/dist/npm/esm/utils/index.js.map +1 -0
- package/dist/npm/esm/utils/logger.d.ts +31 -0
- package/dist/npm/esm/utils/logger.js +66 -0
- package/dist/npm/esm/utils/logger.js.map +1 -0
- package/dist/npm/esm/utils/sanitize.d.ts +38 -0
- package/dist/npm/esm/utils/sanitize.js +148 -0
- package/dist/npm/esm/utils/sanitize.js.map +1 -0
- package/dist/npm/esm/utils/storage.d.ts +72 -0
- package/dist/npm/esm/utils/storage.js +271 -0
- package/dist/npm/esm/utils/storage.js.map +1 -0
- package/dist/npm/esm/utils/tracking-metadata.d.ts +1 -0
- package/dist/npm/esm/utils/tracking-metadata.js +15 -0
- package/dist/npm/esm/utils/tracking-metadata.js.map +1 -0
- package/dist/npm/esm/utils/url-matcher.d.ts +4 -0
- package/dist/npm/esm/utils/url-matcher.js +8 -0
- package/dist/npm/esm/utils/url-matcher.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,973 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Event Batcher
|
|
3
|
+
* Advanced event batching with offline support and retry logic
|
|
4
|
+
* Now with consent-based event filtering
|
|
5
|
+
*/
|
|
6
|
+
import { generateEventId, resolveApiUrl, sanitizeTrackingMetadata } from '../utils/index.js';
|
|
7
|
+
import { CONSENT_EVENT_CATEGORIES, STORAGE_KEYS } from '../constants.js';
|
|
8
|
+
import { getOrCreateJourneyId, getOrCreateCorrelationId, isJourneyExpired } from '../utils/storage.js';
|
|
9
|
+
import { getIndexedDBQueue } from './indexeddb-queue.js';
|
|
10
|
+
import { testModeLog, testModeWarn, testModeError } from '../utils/logger.js';
|
|
11
|
+
const STORAGE_KEY = 'selwise_event_queue';
|
|
12
|
+
const STORAGE_VERSION = 2;
|
|
13
|
+
export class EventBatcher {
|
|
14
|
+
constructor(config = {}) {
|
|
15
|
+
this.queue = [];
|
|
16
|
+
this.consentQueue = []; // Events waiting for consent
|
|
17
|
+
this.flushTimeout = null;
|
|
18
|
+
this.pageHideHandler = null;
|
|
19
|
+
this.isFlushing = false;
|
|
20
|
+
// Consent state tracking
|
|
21
|
+
this.consentChecked = false;
|
|
22
|
+
this.consentGranted = false;
|
|
23
|
+
// Enable IndexedDB for consent queue persistence
|
|
24
|
+
this.useIndexedDBForConsent = true;
|
|
25
|
+
// Extended config options
|
|
26
|
+
this.maxRetries = 3;
|
|
27
|
+
this.maxQueueSize = 100;
|
|
28
|
+
this.maxStorageEvents = 50;
|
|
29
|
+
this.debugMode = false;
|
|
30
|
+
// Deduplication
|
|
31
|
+
this.processedEvents = new Set();
|
|
32
|
+
// Correlation tracking for attribution
|
|
33
|
+
this.lastEventId = null;
|
|
34
|
+
// Journey sequence counter - tracks position within journey
|
|
35
|
+
this.journeyEventCount = 0;
|
|
36
|
+
// Metrics tracking for monitoring
|
|
37
|
+
this.metrics = {
|
|
38
|
+
eventsDropped: 0,
|
|
39
|
+
batchesFailed: 0,
|
|
40
|
+
sendBeaconFailed: 0,
|
|
41
|
+
overflowStored: 0,
|
|
42
|
+
};
|
|
43
|
+
// Track whether initialization is complete
|
|
44
|
+
this.initialized = false;
|
|
45
|
+
this.config = {
|
|
46
|
+
apiUrl: resolveApiUrl(config.apiUrl),
|
|
47
|
+
siteKey: config.siteKey || '',
|
|
48
|
+
batchSize: config.batchSize ?? 50,
|
|
49
|
+
flushInterval: config.flushInterval ?? 30000, // 30 seconds
|
|
50
|
+
enablePersistence: config.enablePersistence ?? true,
|
|
51
|
+
enableSendBeacon: config.enableSendBeacon ?? true,
|
|
52
|
+
};
|
|
53
|
+
// Extended config options
|
|
54
|
+
this.maxRetries = config.maxRetries ?? 3;
|
|
55
|
+
this.maxQueueSize = config.maxQueueSize ?? 100;
|
|
56
|
+
this.maxStorageEvents = config.maxStorageEvents ?? 50;
|
|
57
|
+
this.debugMode = config.debugMode ?? false;
|
|
58
|
+
// Initialize overflow storage
|
|
59
|
+
this.overflowStorage = getIndexedDBQueue();
|
|
60
|
+
// Generate tab-specific correlation ID (prevents multi-tab collisions)
|
|
61
|
+
this.tabCorrelationId = this.generateTabCorrelationId();
|
|
62
|
+
this.sessionData = {
|
|
63
|
+
sessionId: config.sessionId || this.generateSessionId(),
|
|
64
|
+
visitorId: config.visitorId || this.generateVisitorId(),
|
|
65
|
+
siteUserId: config.siteUserId,
|
|
66
|
+
};
|
|
67
|
+
// Load persisted events from localStorage
|
|
68
|
+
if (this.config.enablePersistence) {
|
|
69
|
+
this.loadFromStorage();
|
|
70
|
+
}
|
|
71
|
+
// NOTE: loadFromOverflowStorage() is NOT called in constructor
|
|
72
|
+
// Call initialize() after construction to recover overflow events
|
|
73
|
+
// Setup page hide handler for send beacon
|
|
74
|
+
if (this.config.enableSendBeacon) {
|
|
75
|
+
this.setupPageHideHandler();
|
|
76
|
+
}
|
|
77
|
+
// Start periodic cleanup of processed events
|
|
78
|
+
this.startCleanupInterval();
|
|
79
|
+
if (this.debugMode) {
|
|
80
|
+
testModeLog('EventBatcher', 'Initialized with config:', {
|
|
81
|
+
...this.config,
|
|
82
|
+
maxRetries: this.maxRetries,
|
|
83
|
+
maxQueueSize: this.maxQueueSize,
|
|
84
|
+
maxStorageEvents: this.maxStorageEvents,
|
|
85
|
+
});
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Asynchronous initialization - must be called after constructor
|
|
90
|
+
* This loads events from IndexedDB overflow storage and immediately flushes them
|
|
91
|
+
*
|
|
92
|
+
* IMPORTANT: Call this after creating EventBatcher instance
|
|
93
|
+
*/
|
|
94
|
+
async initialize() {
|
|
95
|
+
if (this.initialized) {
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
try {
|
|
99
|
+
// Load overflow events and immediately flush them
|
|
100
|
+
await this.loadFromOverflowStorage();
|
|
101
|
+
if (this.queue.length > 0) {
|
|
102
|
+
if (this.debugMode) {
|
|
103
|
+
testModeLog('EventBatcher', 'Flushing', this.queue.length, 'recovered events');
|
|
104
|
+
}
|
|
105
|
+
// Trigger immediate flush for recovered events
|
|
106
|
+
await this.flush();
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
catch (error) {
|
|
110
|
+
if (this.debugMode) {
|
|
111
|
+
testModeError('EventBatcher', 'Initialization error:', error);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
finally {
|
|
115
|
+
this.initialized = true;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
/**
|
|
119
|
+
* Check if the batcher has been initialized
|
|
120
|
+
*/
|
|
121
|
+
isReady() {
|
|
122
|
+
return this.initialized;
|
|
123
|
+
}
|
|
124
|
+
setSessionData(sessionId, visitorId, siteUserId) {
|
|
125
|
+
this.sessionData = { sessionId, visitorId, siteUserId };
|
|
126
|
+
}
|
|
127
|
+
setSiteKey(siteKey) {
|
|
128
|
+
this.config.siteKey = siteKey;
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Update configuration dynamically (e.g., after fetching from server)
|
|
132
|
+
*/
|
|
133
|
+
updateConfig(config) {
|
|
134
|
+
if (config.batchSize !== undefined) {
|
|
135
|
+
this.config.batchSize = config.batchSize;
|
|
136
|
+
if (this.debugMode)
|
|
137
|
+
testModeLog('EventBatcher', 'batchSize updated:', config.batchSize);
|
|
138
|
+
}
|
|
139
|
+
if (config.flushInterval !== undefined) {
|
|
140
|
+
this.config.flushInterval = config.flushInterval;
|
|
141
|
+
if (this.debugMode)
|
|
142
|
+
testModeLog('EventBatcher', 'flushInterval updated:', config.flushInterval);
|
|
143
|
+
}
|
|
144
|
+
if (config.enablePersistence !== undefined) {
|
|
145
|
+
this.config.enablePersistence = config.enablePersistence;
|
|
146
|
+
if (this.debugMode)
|
|
147
|
+
testModeLog('EventBatcher', 'enablePersistence updated:', config.enablePersistence);
|
|
148
|
+
}
|
|
149
|
+
if (config.enableSendBeacon !== undefined) {
|
|
150
|
+
this.config.enableSendBeacon = config.enableSendBeacon;
|
|
151
|
+
if (this.debugMode)
|
|
152
|
+
testModeLog('EventBatcher', 'enableSendBeacon updated:', config.enableSendBeacon);
|
|
153
|
+
}
|
|
154
|
+
if (config.maxRetries !== undefined) {
|
|
155
|
+
this.maxRetries = config.maxRetries;
|
|
156
|
+
if (this.debugMode)
|
|
157
|
+
testModeLog('EventBatcher', 'maxRetries updated:', config.maxRetries);
|
|
158
|
+
}
|
|
159
|
+
if (config.maxQueueSize !== undefined) {
|
|
160
|
+
this.maxQueueSize = config.maxQueueSize;
|
|
161
|
+
if (this.debugMode)
|
|
162
|
+
testModeLog('EventBatcher', 'maxQueueSize updated:', config.maxQueueSize);
|
|
163
|
+
}
|
|
164
|
+
if (config.maxStorageEvents !== undefined) {
|
|
165
|
+
this.maxStorageEvents = config.maxStorageEvents;
|
|
166
|
+
if (this.debugMode)
|
|
167
|
+
testModeLog('EventBatcher', 'maxStorageEvents updated:', config.maxStorageEvents);
|
|
168
|
+
}
|
|
169
|
+
if (config.debugMode !== undefined) {
|
|
170
|
+
this.debugMode = config.debugMode;
|
|
171
|
+
testModeLog('EventBatcher', 'debugMode updated:', config.debugMode);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
/**
|
|
175
|
+
* Add an event to the batch queue
|
|
176
|
+
* Checks consent before adding - queues events if consent pending
|
|
177
|
+
* Populates correlation fields for attribution tracking
|
|
178
|
+
*/
|
|
179
|
+
add(event) {
|
|
180
|
+
const queuedEvent = {
|
|
181
|
+
...event,
|
|
182
|
+
metadata: sanitizeTrackingMetadata(event.metadata),
|
|
183
|
+
};
|
|
184
|
+
// Ensure eventId exists for deduplication and parent-child linking
|
|
185
|
+
if (!queuedEvent.eventId) {
|
|
186
|
+
queuedEvent.eventId = generateEventId();
|
|
187
|
+
}
|
|
188
|
+
// Deduplication check
|
|
189
|
+
if (this.processedEvents.has(queuedEvent.eventId)) {
|
|
190
|
+
return; // Skip duplicate
|
|
191
|
+
}
|
|
192
|
+
// Add timestamp and retry count
|
|
193
|
+
queuedEvent.timestamp = queuedEvent.timestamp || Date.now();
|
|
194
|
+
queuedEvent.eventTs = queuedEvent.eventTs || queuedEvent.timestamp;
|
|
195
|
+
queuedEvent.eventSchemaVersion = queuedEvent.eventSchemaVersion || 1;
|
|
196
|
+
queuedEvent.retryCount = queuedEvent.retryCount || 0;
|
|
197
|
+
// Populate attribution fields with tab-specific correlation
|
|
198
|
+
// Combine global correlation with tab-specific ID to prevent multi-tab collisions
|
|
199
|
+
const globalCorrelation = getOrCreateCorrelationId();
|
|
200
|
+
queuedEvent.correlationId = `${globalCorrelation}_${this.tabCorrelationId}`;
|
|
201
|
+
queuedEvent.parentEventId = this.lastEventId ?? undefined; // Link to previous event for chain tracking
|
|
202
|
+
queuedEvent.journeyId = getOrCreateJourneyId();
|
|
203
|
+
// Store current event as parent for next event
|
|
204
|
+
this.lastEventId = queuedEvent.eventId;
|
|
205
|
+
// Check consent if siteKey is available
|
|
206
|
+
if (this.config.siteKey) {
|
|
207
|
+
const canSend = this.checkConsentForEvent(queuedEvent);
|
|
208
|
+
if (!canSend) {
|
|
209
|
+
// Event requires consent and not yet granted - queue for later
|
|
210
|
+
this.consentQueue.push(queuedEvent);
|
|
211
|
+
if (this.debugMode) {
|
|
212
|
+
testModeLog('EventBatcher', 'Event queued pending consent:', queuedEvent.name);
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
// Check if queue is at capacity - use overflow storage instead of dropping
|
|
218
|
+
if (this.queue.length >= this.maxQueueSize) {
|
|
219
|
+
this.storeInOverflow([queuedEvent]);
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
// Consent granted or event is necessary - add to main queue
|
|
223
|
+
this.queue.push(queuedEvent);
|
|
224
|
+
this.processedEvents.add(queuedEvent.eventId);
|
|
225
|
+
// Persist to localStorage
|
|
226
|
+
if (this.config.enablePersistence) {
|
|
227
|
+
this.saveToStorage();
|
|
228
|
+
}
|
|
229
|
+
// Check if we should flush
|
|
230
|
+
if (this.queue.length >= this.config.batchSize) {
|
|
231
|
+
this.flush();
|
|
232
|
+
}
|
|
233
|
+
else {
|
|
234
|
+
this.scheduleFlush();
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Check if an event can be sent based on current consent state
|
|
239
|
+
*/
|
|
240
|
+
checkConsentForEvent(event) {
|
|
241
|
+
const eventName = event.name;
|
|
242
|
+
// Find which category this event belongs to
|
|
243
|
+
// Check each category individually
|
|
244
|
+
const categories = Object.keys(CONSENT_EVENT_CATEGORIES);
|
|
245
|
+
for (const category of categories) {
|
|
246
|
+
const events = CONSENT_EVENT_CATEGORIES[category];
|
|
247
|
+
if (events.indexOf(eventName) >= 0) {
|
|
248
|
+
// Found the category - check consent
|
|
249
|
+
try {
|
|
250
|
+
const key = `${STORAGE_KEYS.CONSENT}_${this.config.siteKey}`;
|
|
251
|
+
const data = localStorage.getItem(key);
|
|
252
|
+
if (data) {
|
|
253
|
+
const state = JSON.parse(data);
|
|
254
|
+
// Check if consent was granted for this category
|
|
255
|
+
return state.categories?.[category] ?? false;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
catch { }
|
|
259
|
+
// No consent given yet - queue the event
|
|
260
|
+
return false;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
// Event not in any category - considered necessary (always allowed)
|
|
264
|
+
return true;
|
|
265
|
+
}
|
|
266
|
+
/**
|
|
267
|
+
* Schedule a flush after the configured interval
|
|
268
|
+
*/
|
|
269
|
+
scheduleFlush() {
|
|
270
|
+
if (this.flushTimeout) {
|
|
271
|
+
clearTimeout(this.flushTimeout);
|
|
272
|
+
}
|
|
273
|
+
this.flushTimeout = window.setTimeout(() => {
|
|
274
|
+
this.flush();
|
|
275
|
+
}, this.config.flushInterval);
|
|
276
|
+
}
|
|
277
|
+
/**
|
|
278
|
+
* Flush all queued events to the server
|
|
279
|
+
*/
|
|
280
|
+
async flush() {
|
|
281
|
+
if (this.isFlushing || this.queue.length === 0) {
|
|
282
|
+
return;
|
|
283
|
+
}
|
|
284
|
+
if (!this.config.siteKey) {
|
|
285
|
+
// No siteKey configured; drop events
|
|
286
|
+
this.queue = [];
|
|
287
|
+
this.saveToStorage();
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
this.isFlushing = true;
|
|
291
|
+
const events = this.queue.splice(0);
|
|
292
|
+
try {
|
|
293
|
+
const success = await this.sendBatch(events);
|
|
294
|
+
if (success) {
|
|
295
|
+
// Clear processed events cache for sent events
|
|
296
|
+
events.forEach(e => this.processedEvents.delete(e.eventId));
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
// Re-queue with retry logic
|
|
300
|
+
this.requeueEvents(events);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
catch (error) {
|
|
304
|
+
// Re-queue on error
|
|
305
|
+
this.requeueEvents(events);
|
|
306
|
+
}
|
|
307
|
+
finally {
|
|
308
|
+
this.isFlushing = false;
|
|
309
|
+
this.saveToStorage();
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
/**
|
|
313
|
+
* Send batch to server
|
|
314
|
+
* Returns true if successful, false otherwise
|
|
315
|
+
* Only increments journey counter on confirmed success
|
|
316
|
+
*/
|
|
317
|
+
async sendBatch(events) {
|
|
318
|
+
// Check if journey has expired - reset counter if so
|
|
319
|
+
if (isJourneyExpired()) {
|
|
320
|
+
this.journeyEventCount = 0;
|
|
321
|
+
}
|
|
322
|
+
// Calculate sequence numbers BEFORE sending (assign temporarily)
|
|
323
|
+
// These will only be committed if the send succeeds
|
|
324
|
+
const sequences = events.map((_, index) => this.journeyEventCount + index + 1);
|
|
325
|
+
// Add journey sequence to each event
|
|
326
|
+
const eventsWithSequence = events.map((event, index) => ({
|
|
327
|
+
...event,
|
|
328
|
+
eventSchemaVersion: event.eventSchemaVersion || 1,
|
|
329
|
+
eventTs: event.eventTs || event.timestamp || Date.now(),
|
|
330
|
+
metadata: sanitizeTrackingMetadata(event.metadata),
|
|
331
|
+
journeySequence: sequences[index],
|
|
332
|
+
}));
|
|
333
|
+
const payload = {
|
|
334
|
+
sessionId: this.sessionData.sessionId,
|
|
335
|
+
visitorId: this.sessionData.visitorId,
|
|
336
|
+
journeyId: getOrCreateJourneyId(),
|
|
337
|
+
siteUserId: this.sessionData.siteUserId,
|
|
338
|
+
pageUrl: window.location.href,
|
|
339
|
+
referrer: document.referrer || undefined,
|
|
340
|
+
events: eventsWithSequence,
|
|
341
|
+
};
|
|
342
|
+
try {
|
|
343
|
+
const response = await fetch(`${this.config.apiUrl}/public/sites/${this.config.siteKey}/events/batch`, {
|
|
344
|
+
method: 'POST',
|
|
345
|
+
headers: { 'Content-Type': 'application/json' },
|
|
346
|
+
body: JSON.stringify(payload),
|
|
347
|
+
keepalive: true,
|
|
348
|
+
});
|
|
349
|
+
const success = response.ok;
|
|
350
|
+
// Only update counter AFTER confirmed success
|
|
351
|
+
if (success) {
|
|
352
|
+
this.journeyEventCount += events.length;
|
|
353
|
+
}
|
|
354
|
+
else {
|
|
355
|
+
this.metrics.batchesFailed++;
|
|
356
|
+
this.reportMetric('batch_failure', 1);
|
|
357
|
+
}
|
|
358
|
+
return success;
|
|
359
|
+
}
|
|
360
|
+
catch (error) {
|
|
361
|
+
this.metrics.batchesFailed++;
|
|
362
|
+
this.reportMetric('batch_failure', 1);
|
|
363
|
+
return false;
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Re-queue events with exponential backoff
|
|
368
|
+
* Uses overflow storage instead of dropping events when queue is full
|
|
369
|
+
*/
|
|
370
|
+
requeueEvents(events) {
|
|
371
|
+
for (const event of events) {
|
|
372
|
+
event.retryCount = (event.retryCount || 0) + 1;
|
|
373
|
+
// Use configured max retry count
|
|
374
|
+
if (event.retryCount <= this.maxRetries) {
|
|
375
|
+
// Check if queue has space
|
|
376
|
+
if (this.queue.length < this.maxQueueSize) {
|
|
377
|
+
this.queue.push(event);
|
|
378
|
+
}
|
|
379
|
+
else {
|
|
380
|
+
// Store in overflow instead of dropping
|
|
381
|
+
this.storeInOverflow([event]);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
// Events exceeding max retries
|
|
386
|
+
if (this.debugMode) {
|
|
387
|
+
testModeLog('EventBatcher', 'Dropped event after', this.maxRetries, 'retries:', event.eventId);
|
|
388
|
+
}
|
|
389
|
+
this.metrics.eventsDropped++;
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
// Sort by timestamp and retry count (oldest, least retried first)
|
|
393
|
+
this.queue.sort((a, b) => {
|
|
394
|
+
const aRetryCount = a.retryCount || 0;
|
|
395
|
+
const bRetryCount = b.retryCount || 0;
|
|
396
|
+
if (aRetryCount !== bRetryCount) {
|
|
397
|
+
return aRetryCount - bRetryCount;
|
|
398
|
+
}
|
|
399
|
+
return a.timestamp - b.timestamp;
|
|
400
|
+
});
|
|
401
|
+
}
|
|
402
|
+
/**
|
|
403
|
+
* Setup page hide handler for send beacon
|
|
404
|
+
*
|
|
405
|
+
* KNOWN LIMITATION: sendBeacon doesn't provide success/failure feedback.
|
|
406
|
+
* - The browser returns true if it accepted the payload, NOT if server received it
|
|
407
|
+
* - Network failures after browser acceptance are undetectable
|
|
408
|
+
* - Journey sequence counter is NOT incremented (see persistPendingJourneyIncrement)
|
|
409
|
+
* - Sequence numbers from sendBeacon may not match actual server-side sequences
|
|
410
|
+
*
|
|
411
|
+
* This is a fundamental limitation of the sendBeacon API with no workaround.
|
|
412
|
+
*/
|
|
413
|
+
setupPageHideHandler() {
|
|
414
|
+
this.pageHideHandler = () => {
|
|
415
|
+
if (this.queue.length === 0 || !this.config.siteKey) {
|
|
416
|
+
return;
|
|
417
|
+
}
|
|
418
|
+
const events = this.queue.splice(0);
|
|
419
|
+
// Calculate sequence numbers
|
|
420
|
+
const sequences = events.map((_, index) => this.journeyEventCount + index + 1);
|
|
421
|
+
// Add journey sequence to each event
|
|
422
|
+
const eventsWithSequence = events.map((event, index) => ({
|
|
423
|
+
...event,
|
|
424
|
+
eventSchemaVersion: event.eventSchemaVersion || 1,
|
|
425
|
+
eventTs: event.eventTs || event.timestamp || Date.now(),
|
|
426
|
+
metadata: sanitizeTrackingMetadata(event.metadata),
|
|
427
|
+
journeySequence: sequences[index],
|
|
428
|
+
}));
|
|
429
|
+
const payload = {
|
|
430
|
+
sessionId: this.sessionData.sessionId,
|
|
431
|
+
visitorId: this.sessionData.visitorId,
|
|
432
|
+
journeyId: getOrCreateJourneyId(),
|
|
433
|
+
siteUserId: this.sessionData.siteUserId,
|
|
434
|
+
pageUrl: window.location.href,
|
|
435
|
+
referrer: document.referrer || undefined,
|
|
436
|
+
events: eventsWithSequence,
|
|
437
|
+
};
|
|
438
|
+
// Use sendBeacon for reliable delivery on page unload
|
|
439
|
+
const data = new Blob([JSON.stringify(payload)], { type: 'application/json' });
|
|
440
|
+
if (navigator.sendBeacon) {
|
|
441
|
+
const sent = navigator.sendBeacon(`${this.config.apiUrl}/public/sites/${this.config.siteKey}/events/batch`, data);
|
|
442
|
+
if (sent) {
|
|
443
|
+
// sendBeacon accepted the payload (browser will queue it)
|
|
444
|
+
// Persist the counter state to localStorage for recovery
|
|
445
|
+
// The actual increment will happen when we can verify
|
|
446
|
+
this.persistPendingJourneyIncrement(events.length);
|
|
447
|
+
}
|
|
448
|
+
else {
|
|
449
|
+
// sendBeacon rejected (queue full, etc.)
|
|
450
|
+
this.metrics.sendBeaconFailed++;
|
|
451
|
+
this.reportMetric('sendBeacon_failure', 1);
|
|
452
|
+
// Re-queue events
|
|
453
|
+
this.queue.push(...events);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
else {
|
|
457
|
+
// sendBeacon not available, re-queue
|
|
458
|
+
this.queue.push(...events);
|
|
459
|
+
}
|
|
460
|
+
};
|
|
461
|
+
// Listen for page hide events
|
|
462
|
+
document.addEventListener('visibilitychange', () => {
|
|
463
|
+
if (document.visibilityState === 'hidden') {
|
|
464
|
+
this.pageHideHandler?.();
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
// Also listen for beforeunload
|
|
468
|
+
window.addEventListener('beforeunload', () => {
|
|
469
|
+
this.pageHideHandler?.();
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
/**
|
|
473
|
+
* Persist pending journey counter increment to localStorage
|
|
474
|
+
*
|
|
475
|
+
* KNOWN LIMITATION: sendBeacon doesn't provide success/failure feedback.
|
|
476
|
+
* The browser queues the request but we can't know if it reached the server.
|
|
477
|
+
*
|
|
478
|
+
* We do NOT increment the journey counter here because:
|
|
479
|
+
* 1. sendBeacon may fail silently (network, server error, queue full)
|
|
480
|
+
* 2. Counter increment would cause permanent gaps in sequence numbers
|
|
481
|
+
* 3. There's no server validation endpoint to confirm receipt
|
|
482
|
+
*
|
|
483
|
+
* Consequence: On page hide, journey sequence numbers may not match
|
|
484
|
+
* server-side sequences. This is accepted as a known limitation of
|
|
485
|
+
* the sendBeacon API.
|
|
486
|
+
*/
|
|
487
|
+
persistPendingJourneyIncrement(count) {
|
|
488
|
+
try {
|
|
489
|
+
const state = {
|
|
490
|
+
count,
|
|
491
|
+
timestamp: Date.now(),
|
|
492
|
+
journeyId: getOrCreateJourneyId(),
|
|
493
|
+
};
|
|
494
|
+
localStorage.setItem('selwise_pending_journey_increment', JSON.stringify(state));
|
|
495
|
+
// NOTE: We do NOT increment journeyEventCount here
|
|
496
|
+
// The counter is only incremented after confirmed success in sendBatch()
|
|
497
|
+
// This means sendBeacon events will have sequence numbers that don't
|
|
498
|
+
// match the actual counter, but this is unavoidable without server validation
|
|
499
|
+
}
|
|
500
|
+
catch {
|
|
501
|
+
// Silently fail
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Check and recover pending journey increment on initialization
|
|
506
|
+
* If server confirmed receipt, the increment is valid
|
|
507
|
+
* Otherwise we need to roll back
|
|
508
|
+
*/
|
|
509
|
+
async recoverPendingJourneyIncrement() {
|
|
510
|
+
try {
|
|
511
|
+
const stored = localStorage.getItem('selwise_pending_journey_increment');
|
|
512
|
+
if (!stored) {
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
const state = JSON.parse(stored);
|
|
516
|
+
const age = Date.now() - state.timestamp;
|
|
517
|
+
// If older than 5 minutes, assume it was processed or is stale
|
|
518
|
+
if (age > 5 * 60 * 1000) {
|
|
519
|
+
localStorage.removeItem('selwise_pending_journey_increment');
|
|
520
|
+
return;
|
|
521
|
+
}
|
|
522
|
+
// Check with server if these events were received
|
|
523
|
+
// For now, we'll just clear it after 5 minutes
|
|
524
|
+
// In a production system, you'd have an endpoint to validate
|
|
525
|
+
localStorage.removeItem('selwise_pending_journey_increment');
|
|
526
|
+
}
|
|
527
|
+
catch {
|
|
528
|
+
localStorage.removeItem('selwise_pending_journey_increment');
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* Save queue to localStorage for offline persistence
|
|
533
|
+
* Includes quota checking to prevent quota exhaustion DoS
|
|
534
|
+
*/
|
|
535
|
+
saveToStorage() {
|
|
536
|
+
if (!this.config.enablePersistence)
|
|
537
|
+
return;
|
|
538
|
+
try {
|
|
539
|
+
// Check storage quota before saving to prevent quota exhaustion
|
|
540
|
+
if (!this.checkStorageQuota()) {
|
|
541
|
+
if (this.debugMode) {
|
|
542
|
+
testModeLog('EventBatcher', 'Storage quota exceeded, skipping save');
|
|
543
|
+
}
|
|
544
|
+
return;
|
|
545
|
+
}
|
|
546
|
+
const stored = {
|
|
547
|
+
version: STORAGE_VERSION,
|
|
548
|
+
queue: this.queue.slice(0, this.maxStorageEvents),
|
|
549
|
+
timestamp: Date.now(),
|
|
550
|
+
};
|
|
551
|
+
localStorage.setItem(STORAGE_KEY, JSON.stringify(stored));
|
|
552
|
+
if (this.debugMode) {
|
|
553
|
+
testModeLog('EventBatcher', 'Saved', stored.queue.length, 'events to storage');
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
catch (e) {
|
|
557
|
+
if (this.debugMode) {
|
|
558
|
+
testModeLog('EventBatcher', 'Failed to save to storage:', e);
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
}
|
|
562
|
+
/**
|
|
563
|
+
* Check if we have enough storage quota for Selwise data
|
|
564
|
+
* Returns false if quota would be exceeded (prevents DoS via quota exhaustion)
|
|
565
|
+
*/
|
|
566
|
+
checkStorageQuota() {
|
|
567
|
+
try {
|
|
568
|
+
// Maximum storage quota for Selwise (2MB)
|
|
569
|
+
const SELWISE_QUOTA = 2 * 1024 * 1024;
|
|
570
|
+
// Calculate current Selwise storage usage
|
|
571
|
+
let totalUsage = 0;
|
|
572
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
573
|
+
const key = localStorage.key(i);
|
|
574
|
+
if (key && key.startsWith('selwise_')) {
|
|
575
|
+
const value = localStorage.getItem(key);
|
|
576
|
+
if (value) {
|
|
577
|
+
totalUsage += key.length + value.length;
|
|
578
|
+
}
|
|
579
|
+
}
|
|
580
|
+
}
|
|
581
|
+
// Estimate the size of data we're about to save
|
|
582
|
+
const estimatedSize = JSON.stringify({
|
|
583
|
+
version: STORAGE_VERSION,
|
|
584
|
+
queue: this.queue.slice(0, this.maxStorageEvents),
|
|
585
|
+
timestamp: Date.now(),
|
|
586
|
+
}).length;
|
|
587
|
+
// Check if we'd exceed quota
|
|
588
|
+
if (totalUsage + estimatedSize > SELWISE_QUOTA) {
|
|
589
|
+
// Try cleanup first
|
|
590
|
+
this.cleanupOldStorage();
|
|
591
|
+
// Recalculate after cleanup
|
|
592
|
+
let newUsage = 0;
|
|
593
|
+
for (let i = 0; i < localStorage.length; i++) {
|
|
594
|
+
const key = localStorage.key(i);
|
|
595
|
+
if (key && key.startsWith('selwise_')) {
|
|
596
|
+
const value = localStorage.getItem(key);
|
|
597
|
+
if (value) {
|
|
598
|
+
newUsage += key.length + value.length;
|
|
599
|
+
}
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
return (newUsage + estimatedSize) <= SELWISE_QUOTA;
|
|
603
|
+
}
|
|
604
|
+
return true;
|
|
605
|
+
}
|
|
606
|
+
catch {
|
|
607
|
+
// If we can't check, assume OK
|
|
608
|
+
return true;
|
|
609
|
+
}
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Cleanup old Selwise storage items to free up quota
|
|
613
|
+
*/
|
|
614
|
+
cleanupOldStorage() {
|
|
615
|
+
try {
|
|
616
|
+
// Clean up expired journey state
|
|
617
|
+
const journeyKey = 'selwise_journey_state';
|
|
618
|
+
const journeyData = localStorage.getItem(journeyKey);
|
|
619
|
+
if (journeyData) {
|
|
620
|
+
try {
|
|
621
|
+
const journey = JSON.parse(journeyData);
|
|
622
|
+
const JOURNEY_TIMEOUT = 30 * 60 * 1000; // 30 minutes
|
|
623
|
+
if (journey.lastEventTime && (Date.now() - journey.lastEventTime) > JOURNEY_TIMEOUT) {
|
|
624
|
+
localStorage.removeItem(journeyKey);
|
|
625
|
+
if (this.debugMode) {
|
|
626
|
+
testModeLog('EventBatcher', 'Cleaned up expired journey state');
|
|
627
|
+
}
|
|
628
|
+
}
|
|
629
|
+
}
|
|
630
|
+
catch {
|
|
631
|
+
localStorage.removeItem(journeyKey);
|
|
632
|
+
}
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
catch {
|
|
636
|
+
// Ignore cleanup errors
|
|
637
|
+
}
|
|
638
|
+
}
|
|
639
|
+
/**
|
|
640
|
+
* Load queue from localStorage
|
|
641
|
+
*/
|
|
642
|
+
loadFromStorage() {
|
|
643
|
+
try {
|
|
644
|
+
const stored = localStorage.getItem(STORAGE_KEY);
|
|
645
|
+
if (!stored)
|
|
646
|
+
return;
|
|
647
|
+
const parsed = JSON.parse(stored);
|
|
648
|
+
// Check version compatibility
|
|
649
|
+
if (parsed.version === STORAGE_VERSION && Array.isArray(parsed.queue)) {
|
|
650
|
+
// Filter out events older than 24 hours
|
|
651
|
+
const cutoffTime = Date.now() - (24 * 60 * 60 * 1000);
|
|
652
|
+
this.queue = parsed.queue
|
|
653
|
+
.filter(e => e.timestamp > cutoffTime)
|
|
654
|
+
.map((event) => ({
|
|
655
|
+
...event,
|
|
656
|
+
metadata: sanitizeTrackingMetadata(event.metadata),
|
|
657
|
+
}));
|
|
658
|
+
}
|
|
659
|
+
// Clear storage after loading
|
|
660
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
661
|
+
}
|
|
662
|
+
catch {
|
|
663
|
+
// Invalid storage, clear it
|
|
664
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Get queue size
|
|
669
|
+
*/
|
|
670
|
+
getQueueSize() {
|
|
671
|
+
return this.queue.length;
|
|
672
|
+
}
|
|
673
|
+
/**
|
|
674
|
+
* Get processed events count
|
|
675
|
+
*/
|
|
676
|
+
getProcessedCount() {
|
|
677
|
+
return this.processedEvents.size;
|
|
678
|
+
}
|
|
679
|
+
/**
|
|
680
|
+
* Clear all queued events
|
|
681
|
+
*/
|
|
682
|
+
clear() {
|
|
683
|
+
this.queue = [];
|
|
684
|
+
this.consentQueue = []; // Also clear consent queue
|
|
685
|
+
this.processedEvents.clear();
|
|
686
|
+
if (this.flushTimeout) {
|
|
687
|
+
clearTimeout(this.flushTimeout);
|
|
688
|
+
this.flushTimeout = null;
|
|
689
|
+
}
|
|
690
|
+
if (this.config.enablePersistence) {
|
|
691
|
+
localStorage.removeItem(STORAGE_KEY);
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Reset correlation tracking for new attribution journey
|
|
696
|
+
* Call this after a conversion event to start fresh attribution
|
|
697
|
+
*/
|
|
698
|
+
resetCorrelation() {
|
|
699
|
+
this.lastEventId = null;
|
|
700
|
+
if (this.debugMode) {
|
|
701
|
+
testModeLog('EventBatcher', 'Correlation tracking reset');
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
/**
|
|
705
|
+
* Get consent queue size
|
|
706
|
+
*/
|
|
707
|
+
getConsentQueueSize() {
|
|
708
|
+
return this.consentQueue.length;
|
|
709
|
+
}
|
|
710
|
+
/**
|
|
711
|
+
* Flush consent queue - called when user grants consent
|
|
712
|
+
* Re-evaluates each queued event and adds to main queue if consented
|
|
713
|
+
*/
|
|
714
|
+
flushConsentQueue() {
|
|
715
|
+
if (this.consentQueue.length === 0) {
|
|
716
|
+
return;
|
|
717
|
+
}
|
|
718
|
+
if (this.debugMode) {
|
|
719
|
+
testModeLog('EventBatcher', 'Flushing consent queue:', this.consentQueue.length, 'events');
|
|
720
|
+
}
|
|
721
|
+
// Process each queued event
|
|
722
|
+
const eventsToFlush = this.consentQueue.splice(0);
|
|
723
|
+
for (const event of eventsToFlush) {
|
|
724
|
+
// Re-check consent for each event
|
|
725
|
+
const canSend = this.checkConsentForEvent(event);
|
|
726
|
+
if (canSend) {
|
|
727
|
+
// Add to main queue for sending
|
|
728
|
+
this.queue.push(event);
|
|
729
|
+
this.processedEvents.add(event.eventId);
|
|
730
|
+
}
|
|
731
|
+
else {
|
|
732
|
+
// Still no consent for this category - keep in consent queue
|
|
733
|
+
this.consentQueue.push(event);
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
// Save state and schedule flush if events were added
|
|
737
|
+
if (this.queue.length > 0) {
|
|
738
|
+
if (this.config.enablePersistence) {
|
|
739
|
+
this.saveToStorage();
|
|
740
|
+
}
|
|
741
|
+
// Check if we should flush immediately
|
|
742
|
+
if (this.queue.length >= this.config.batchSize) {
|
|
743
|
+
this.flush();
|
|
744
|
+
}
|
|
745
|
+
else {
|
|
746
|
+
this.scheduleFlush();
|
|
747
|
+
}
|
|
748
|
+
}
|
|
749
|
+
if (this.debugMode) {
|
|
750
|
+
testModeLog('EventBatcher', 'Consent queue flushed. Main queue:', this.queue.length, 'Remaining consent queue:', this.consentQueue.length);
|
|
751
|
+
}
|
|
752
|
+
}
|
|
753
|
+
/**
|
|
754
|
+
* Cleanup
|
|
755
|
+
*/
|
|
756
|
+
destroy() {
|
|
757
|
+
this.clear();
|
|
758
|
+
// Clear cleanup interval
|
|
759
|
+
if (this.cleanupInterval) {
|
|
760
|
+
clearInterval(this.cleanupInterval);
|
|
761
|
+
this.cleanupInterval = undefined;
|
|
762
|
+
}
|
|
763
|
+
if (this.pageHideHandler) {
|
|
764
|
+
document.removeEventListener('visibilitychange', this.pageHideHandler);
|
|
765
|
+
window.removeEventListener('beforeunload', this.pageHideHandler);
|
|
766
|
+
this.pageHideHandler = null;
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
/**
|
|
770
|
+
* Start periodic cleanup of processed events set
|
|
771
|
+
*/
|
|
772
|
+
startCleanupInterval() {
|
|
773
|
+
// Cleanup every 5 minutes to prevent memory growth
|
|
774
|
+
this.cleanupInterval = setInterval(() => {
|
|
775
|
+
this.cleanupProcessedEvents();
|
|
776
|
+
}, 5 * 60 * 1000);
|
|
777
|
+
if (this.debugMode) {
|
|
778
|
+
testModeLog('EventBatcher', 'Cleanup interval started');
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
/**
|
|
782
|
+
* Cleanup old processed event IDs to prevent memory growth
|
|
783
|
+
*
|
|
784
|
+
* NOTE: After 10000 events, old event IDs can be re-sent and may create duplicates.
|
|
785
|
+
* This is a known limitation - the deduplication Set is kept to a reasonable size
|
|
786
|
+
* to prevent memory issues while still providing good duplicate detection for
|
|
787
|
+
* typical user sessions.
|
|
788
|
+
*
|
|
789
|
+
* Server-side deduplication cache provides additional protection.
|
|
790
|
+
*/
|
|
791
|
+
cleanupProcessedEvents() {
|
|
792
|
+
// More aggressive cleanup - keep only last 5000 event IDs (reduced from 10000)
|
|
793
|
+
// This provides better memory management for long-running sessions
|
|
794
|
+
if (this.processedEvents.size > 5000) {
|
|
795
|
+
const entries = Array.from(this.processedEvents);
|
|
796
|
+
this.processedEvents = new Set(entries.slice(-5000));
|
|
797
|
+
if (this.debugMode) {
|
|
798
|
+
testModeLog('EventBatcher', 'Cleaned up processed events, kept last 5000');
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
// Also cleanup consent queue if it gets too large
|
|
802
|
+
if (this.consentQueue.length > 100) {
|
|
803
|
+
// Remove oldest consent-pending events (they likely expired)
|
|
804
|
+
this.consentQueue = this.consentQueue.slice(-50);
|
|
805
|
+
if (this.debugMode) {
|
|
806
|
+
testModeLog('EventBatcher', 'Cleaned up consent queue, kept last 50');
|
|
807
|
+
}
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
generateSessionId() {
|
|
811
|
+
return `sess_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
812
|
+
}
|
|
813
|
+
generateVisitorId() {
|
|
814
|
+
let visitorId = null;
|
|
815
|
+
try {
|
|
816
|
+
visitorId = localStorage.getItem('selwise_visitor_id');
|
|
817
|
+
if (!visitorId) {
|
|
818
|
+
visitorId = `vis_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
819
|
+
localStorage.setItem('selwise_visitor_id', visitorId);
|
|
820
|
+
}
|
|
821
|
+
}
|
|
822
|
+
catch {
|
|
823
|
+
visitorId = `vis_${Date.now()}_${Math.random().toString(36).substring(2, 15)}`;
|
|
824
|
+
}
|
|
825
|
+
return visitorId;
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Generate tab-specific correlation ID
|
|
829
|
+
* This prevents correlation ID collisions between multiple tabs
|
|
830
|
+
*/
|
|
831
|
+
generateTabCorrelationId() {
|
|
832
|
+
// Use a random string for tab identification
|
|
833
|
+
return `tab_${Math.random().toString(36).substring(2, 11)}`;
|
|
834
|
+
}
|
|
835
|
+
/**
|
|
836
|
+
* Store events in IndexedDB overflow storage
|
|
837
|
+
* Non-blocking - errors are logged but don't throw
|
|
838
|
+
*/
|
|
839
|
+
storeInOverflow(events) {
|
|
840
|
+
this.overflowStorage.store(events).then(() => {
|
|
841
|
+
this.metrics.overflowStored += events.length;
|
|
842
|
+
if (this.debugMode) {
|
|
843
|
+
testModeLog('EventBatcher', 'Stored', events.length, 'events in overflow storage');
|
|
844
|
+
}
|
|
845
|
+
}).catch(() => {
|
|
846
|
+
// Silently fail - overflow storage is best-effort
|
|
847
|
+
this.metrics.eventsDropped += events.length;
|
|
848
|
+
if (this.debugMode) {
|
|
849
|
+
testModeWarn('EventBatcher', 'Overflow storage unavailable, dropped', events.length, 'events');
|
|
850
|
+
}
|
|
851
|
+
});
|
|
852
|
+
}
|
|
853
|
+
/**
|
|
854
|
+
* Load events from IndexedDB overflow storage
|
|
855
|
+
* Called during initialization to recover persisted events
|
|
856
|
+
*/
|
|
857
|
+
async loadFromOverflowStorage() {
|
|
858
|
+
if (!this.overflowStorage.isAvailable()) {
|
|
859
|
+
return;
|
|
860
|
+
}
|
|
861
|
+
try {
|
|
862
|
+
const events = await this.overflowStorage.retrieve(this.maxQueueSize);
|
|
863
|
+
if (events.length > 0) {
|
|
864
|
+
if (this.debugMode) {
|
|
865
|
+
testModeLog('EventBatcher', 'Loaded', events.length, 'events from overflow storage');
|
|
866
|
+
}
|
|
867
|
+
// Add events to queue (up to max queue size)
|
|
868
|
+
for (const event of events) {
|
|
869
|
+
if (this.queue.length < this.maxQueueSize) {
|
|
870
|
+
// Re-assign journey sequence on recovery
|
|
871
|
+
this.queue.push({
|
|
872
|
+
...event,
|
|
873
|
+
metadata: sanitizeTrackingMetadata(event.metadata),
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
else {
|
|
877
|
+
// Queue full, remaining events stay in overflow
|
|
878
|
+
break;
|
|
879
|
+
}
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
catch (error) {
|
|
884
|
+
if (this.debugMode) {
|
|
885
|
+
testModeError('EventBatcher', 'Failed to load overflow storage:', error);
|
|
886
|
+
}
|
|
887
|
+
}
|
|
888
|
+
}
|
|
889
|
+
/**
|
|
890
|
+
* Get metrics for monitoring
|
|
891
|
+
*/
|
|
892
|
+
getMetrics() {
|
|
893
|
+
return {
|
|
894
|
+
...this.metrics,
|
|
895
|
+
queueSize: this.queue.length,
|
|
896
|
+
processedCount: this.processedEvents.size,
|
|
897
|
+
consentQueueSize: this.consentQueue.length,
|
|
898
|
+
overflowStats: {
|
|
899
|
+
count: 0, // Will be populated asynchronously
|
|
900
|
+
oldestTimestamp: null,
|
|
901
|
+
},
|
|
902
|
+
};
|
|
903
|
+
}
|
|
904
|
+
/**
|
|
905
|
+
* Get overflow storage stats (async)
|
|
906
|
+
*/
|
|
907
|
+
async getOverflowStats() {
|
|
908
|
+
return this.overflowStorage.getStats();
|
|
909
|
+
}
|
|
910
|
+
/**
|
|
911
|
+
* Report metric to monitoring endpoint
|
|
912
|
+
* Non-blocking - failures are ignored
|
|
913
|
+
*/
|
|
914
|
+
reportMetric(name, value) {
|
|
915
|
+
if (!this.config.siteKey) {
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
const url = `${this.config.apiUrl}/public/sites/${this.config.siteKey}/metrics`;
|
|
919
|
+
// Use sendBeacon if available for non-blocking delivery
|
|
920
|
+
if (navigator.sendBeacon) {
|
|
921
|
+
const data = new Blob([JSON.stringify({ name, value, timestamp: Date.now() })], { type: 'application/json' });
|
|
922
|
+
navigator.sendBeacon(url, data);
|
|
923
|
+
}
|
|
924
|
+
else {
|
|
925
|
+
// Fallback to fetch with keepalive
|
|
926
|
+
fetch(url, {
|
|
927
|
+
method: 'POST',
|
|
928
|
+
body: JSON.stringify({ name, value, timestamp: Date.now() }),
|
|
929
|
+
keepalive: true,
|
|
930
|
+
}).catch(() => {
|
|
931
|
+
// Ignore failures
|
|
932
|
+
});
|
|
933
|
+
}
|
|
934
|
+
}
|
|
935
|
+
/**
|
|
936
|
+
* Send all accumulated metrics to monitoring endpoint
|
|
937
|
+
*/
|
|
938
|
+
async flushMetrics() {
|
|
939
|
+
if (!this.config.siteKey) {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
const metrics = await this.getOverflowStats();
|
|
943
|
+
const payload = {
|
|
944
|
+
metrics: {
|
|
945
|
+
eventsDropped: this.metrics.eventsDropped,
|
|
946
|
+
batchesFailed: this.metrics.batchesFailed,
|
|
947
|
+
sendBeaconFailed: this.metrics.sendBeaconFailed,
|
|
948
|
+
overflowStored: this.metrics.overflowStored,
|
|
949
|
+
overflowCount: metrics.count,
|
|
950
|
+
},
|
|
951
|
+
timestamp: Date.now(),
|
|
952
|
+
};
|
|
953
|
+
try {
|
|
954
|
+
await fetch(`${this.config.apiUrl}/public/sites/${this.config.siteKey}/metrics`, {
|
|
955
|
+
method: 'POST',
|
|
956
|
+
headers: { 'Content-Type': 'application/json' },
|
|
957
|
+
body: JSON.stringify(payload),
|
|
958
|
+
keepalive: true,
|
|
959
|
+
});
|
|
960
|
+
// Reset metrics after successful send
|
|
961
|
+
this.metrics = {
|
|
962
|
+
eventsDropped: 0,
|
|
963
|
+
batchesFailed: 0,
|
|
964
|
+
sendBeaconFailed: 0,
|
|
965
|
+
overflowStored: 0,
|
|
966
|
+
};
|
|
967
|
+
}
|
|
968
|
+
catch {
|
|
969
|
+
// Silently fail - metrics are best-effort
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
}
|
|
973
|
+
//# sourceMappingURL=event-batcher.js.map
|