@salla.sa/analytics 2.14.548

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.
Files changed (77) hide show
  1. package/LICENSE.md +674 -0
  2. package/README.md +46 -0
  3. package/dist/cjs/checkout.js +2 -0
  4. package/dist/cjs/checkout.js.map +1 -0
  5. package/dist/cjs/chunks/uuid-DMRtGC8c.js +2 -0
  6. package/dist/cjs/chunks/uuid-DMRtGC8c.js.map +1 -0
  7. package/dist/cjs/index.js +2 -0
  8. package/dist/cjs/index.js.map +1 -0
  9. package/dist/cjs/storefront.js +2 -0
  10. package/dist/cjs/storefront.js.map +1 -0
  11. package/dist/esm/checkout.js +2 -0
  12. package/dist/esm/checkout.js.map +1 -0
  13. package/dist/esm/chunks/uuid-BSpqiCZm.js +2 -0
  14. package/dist/esm/chunks/uuid-BSpqiCZm.js.map +1 -0
  15. package/dist/esm/index.js +2 -0
  16. package/dist/esm/index.js.map +1 -0
  17. package/dist/esm/storefront.js +2 -0
  18. package/dist/esm/storefront.js.map +1 -0
  19. package/package.json +67 -0
  20. package/src/checkout/address-added.js +46 -0
  21. package/src/checkout/address-updated.js +46 -0
  22. package/src/checkout/checkout-started.js +34 -0
  23. package/src/checkout/checkout-step-completed.js +18 -0
  24. package/src/checkout/checkout-step-viewed.js +17 -0
  25. package/src/checkout/coupon-applied.js +22 -0
  26. package/src/checkout/coupon-denied.js +20 -0
  27. package/src/checkout/coupon-entered.js +21 -0
  28. package/src/checkout/coupon-removed.js +21 -0
  29. package/src/checkout/event-transformer.js +53 -0
  30. package/src/checkout/index.js +197 -0
  31. package/src/checkout/initial-data.js +23 -0
  32. package/src/checkout/map-clicked.js +27 -0
  33. package/src/checkout/payment-failed.js +28 -0
  34. package/src/checkout/payment-info-entered.js +17 -0
  35. package/src/checkout/payment-pending.js +21 -0
  36. package/src/checkout/payment-submitted.js +22 -0
  37. package/src/checkout/payment-succeeded.js +23 -0
  38. package/src/checkout/sanitizers.js +21 -0
  39. package/src/checkout/session.js +80 -0
  40. package/src/index.js +7 -0
  41. package/src/shared/cookie-capture.js +17 -0
  42. package/src/shared/extra-context.js +23 -0
  43. package/src/shared/host-resolver.js +58 -0
  44. package/src/shared/tracker-registry.js +55 -0
  45. package/src/shared/uuid.js +10 -0
  46. package/src/storefront/basket-gap-product-added.js +29 -0
  47. package/src/storefront/cart-updated.js +41 -0
  48. package/src/storefront/cart-viewed.js +30 -0
  49. package/src/storefront/coupon-applied.js +26 -0
  50. package/src/storefront/coupon-denied.js +35 -0
  51. package/src/storefront/coupon-removed.js +27 -0
  52. package/src/storefront/event-transformer.js +90 -0
  53. package/src/storefront/fbt-product-added.js +29 -0
  54. package/src/storefront/index.js +524 -0
  55. package/src/storefront/market-selected.js +29 -0
  56. package/src/storefront/order-completed.js +12 -0
  57. package/src/storefront/product-added-to-wishlist.js +28 -0
  58. package/src/storefront/product-added.js +51 -0
  59. package/src/storefront/product-clicked.js +22 -0
  60. package/src/storefront/product-fields.js +15 -0
  61. package/src/storefront/product-list-filtered.js +56 -0
  62. package/src/storefront/product-list-viewed.js +80 -0
  63. package/src/storefront/product-removed-from-wishlist.js +29 -0
  64. package/src/storefront/product-removed.js +27 -0
  65. package/src/storefront/product-reordered.js +19 -0
  66. package/src/storefront/product-reviewed.js +28 -0
  67. package/src/storefront/product-shared.js +18 -0
  68. package/src/storefront/product-viewed.js +44 -0
  69. package/src/storefront/products-searched.js +14 -0
  70. package/src/storefront/promotion-clicked.js +18 -0
  71. package/src/storefront/promotion-viewed.js +18 -0
  72. package/src/storefront/recommendation-list-viewed.js +22 -0
  73. package/src/storefront/signed-in.js +29 -0
  74. package/src/storefront/signed-out.js +10 -0
  75. package/src/storefront/signed-up.js +21 -0
  76. package/src/storefront/user-profile-updated.js +44 -0
  77. package/src/storefront/wishlist-product-added-to-cart.js +32 -0
@@ -0,0 +1,56 @@
1
+ import ProductListViewed from './product-list-viewed';
2
+
3
+ export default class ProductListFiltered extends ProductListViewed {
4
+
5
+ constructor(details) {
6
+ super(details);
7
+ const nextPageUrl = details?.nextPage || '';
8
+ this.filters = this.extractFilters(nextPageUrl);
9
+ }
10
+
11
+ extractFilters(url) {
12
+ if (!url) return null;
13
+
14
+ try {
15
+ const urlObj = new URL(url);
16
+ const filters = [];
17
+
18
+ Array.from(urlObj.searchParams.entries()).forEach(([param, value]) => {
19
+ if (param.startsWith('filters[')) {
20
+ const match = param.match(/^filters\[(.*?)\]/);
21
+ const type = match ? match[1] : param;
22
+
23
+ filters.push({
24
+ type: type,
25
+ value: value
26
+ });
27
+ }
28
+ });
29
+
30
+ return filters.length > 0 ? filters : null;
31
+ } catch (e) {
32
+ console.warn('Error extracting filters from URL:', e);
33
+ return null;
34
+ }
35
+ }
36
+
37
+ toSegment() {
38
+ const base = super.toSegment();
39
+ return {
40
+ event: 'Product List Filtered',
41
+ properties: {
42
+ ...base?.properties,
43
+ filters: this.filters
44
+ }
45
+ };
46
+ }
47
+
48
+ static shouldSend(details){
49
+ const nextPageUrl = details?.nextPage || '';
50
+ // On the search page, filtering is reported as "Product List Viewed" instead.
51
+ if (salla.config.get('page.slug') === 'product.index.search') {
52
+ return false;
53
+ }
54
+ return nextPageUrl.includes('filters');
55
+ }
56
+ }
@@ -0,0 +1,80 @@
1
+ export default class ProductListViewed {
2
+ constructor(details) {
3
+ const nextPageUrl = details?.nextPage;
4
+ this.listId = salla.config.get('page.slug');
5
+
6
+ if (Salla.config.get('page.id')) {
7
+ this.listId += `_${salla.config.get('page.id')}`;
8
+ }
9
+
10
+ this.sorts = null;
11
+ try {
12
+ if (nextPageUrl) {
13
+ const url = new URL(nextPageUrl);
14
+ const sortValue = url.searchParams.get('sort');
15
+ if (sortValue) {
16
+ this.sorts = [
17
+ {
18
+ type: 'sort',
19
+ value: sortValue
20
+ }
21
+ ];
22
+ }
23
+ }
24
+
25
+ if (details.source) {
26
+ this.listId = `${this.listId}_${details.source}`;
27
+ }
28
+
29
+ if (details.sourceValue) {
30
+ this.listId = `${this.listId}_${Array.isArray(details.sourceValue) ? details.sourceValue.join('_') : details.sourceValue}`;
31
+ }
32
+ } catch (error) {
33
+ salla.logger.error(error);
34
+ }
35
+
36
+ this.products = details?.data;
37
+ }
38
+
39
+ toSegment() {
40
+ const pageTitle = salla.config.get('page.title') || '';
41
+ const parts = pageTitle.split('|').map(s => s.trim());
42
+ const category = parts[0];
43
+ const sub_category = parts[1] || null;
44
+
45
+ return {
46
+ event: 'Product List Viewed',
47
+ properties: {
48
+ list_id: this.listId,
49
+ category,
50
+ sub_category,
51
+ sorts: this.sorts,
52
+ products: this.products.map((product) => ({
53
+ product_id: product.id,
54
+ category: product.category?.name,
55
+ currency: Salla.config.currency()?.code,
56
+ name: product.name,
57
+ brand: product.brand?.name,
58
+ variant: product.variant?.toString(),
59
+ coupon: product.coupon?.toString(),
60
+ price: product.price,
61
+ quantity: Number(product.quantity),
62
+ sku: product.sku,
63
+ position: product.position,
64
+ url: product.url,
65
+ image_url: product.image?.url
66
+ }))
67
+ }
68
+ };
69
+ }
70
+
71
+ static shouldSend(details){
72
+ const nextPageUrl = details?.nextPage || '';
73
+ const pageSlug = salla.config.get('page.slug');
74
+ // On the search page, always report as "Product List Viewed", even when filters are applied.
75
+ if (pageSlug === 'product.index.search') {
76
+ return true;
77
+ }
78
+ return !nextPageUrl.includes('filters') && !['index', 'customer.wishlist'].includes(pageSlug);
79
+ }
80
+ }
@@ -0,0 +1,29 @@
1
+ export default class ProductRemovedFromWishlist {
2
+ constructor(details) {
3
+ this.product = details.data.product;
4
+ }
5
+
6
+ toSegment() {
7
+ return {
8
+ event: 'Product Removed from Wishlist',
9
+ properties: {
10
+ wishlist_id : salla.config.get('store.id') + '_' + salla.config.get('user.id'),
11
+ wishlist_name : salla.config.get('store.name'),
12
+ product_id : this.product.id?.toString(),
13
+ name: this.product.name,
14
+ brand: this.product.brand,
15
+ variant: this.product.variant,
16
+ price: this.product.price,
17
+ quantity: Math.max(1, Number(this.product.quantity)),
18
+ sku: this.product.sku || null,
19
+ coupon: this.product.coupon || null,
20
+ position: Number(this.product.position || 0),
21
+ currency: Salla.config.currency()?.code,
22
+ url: this.product.url,
23
+ image_url: this.product?.image || null,
24
+ category: this.product.category,
25
+ brand: this.product.brand,
26
+ }
27
+ };
28
+ }
29
+ }
@@ -0,0 +1,27 @@
1
+ export default class ProductRemoved {
2
+ constructor(details) {
3
+ this.item = details[0];
4
+ }
5
+
6
+ toSegment() {
7
+ return {
8
+ event: 'Product Removed',
9
+ properties: {
10
+ cart_id: this.item?.cart_id?.toString(),
11
+ product_id: this.item?.id?.toString(),
12
+ category: this.item?.category,
13
+ currency: Salla.config.currency()?.code,
14
+ name: this.item?.name,
15
+ brand: this.item?.brand,
16
+ variant: this.item?.variant,
17
+ price: this.item?.price,
18
+ quantity: this.item?.quantity,
19
+ sku: this.item?.sku,
20
+ coupon: this.item?.coupon,
21
+ position: this.item?.position,
22
+ url: this.item?.url,
23
+ image_url: this.item?.image_url
24
+ }
25
+ };
26
+ }
27
+ }
@@ -0,0 +1,19 @@
1
+ export default class ProductReordered {
2
+ constructor(details) {
3
+ this.details = details;
4
+ }
5
+
6
+ toSegment() {
7
+ return {
8
+ event: 'Product Reordered',
9
+ properties: {
10
+ product_id: this.details?.data?.id?.toString(),
11
+ }
12
+ };
13
+ }
14
+
15
+ static shouldSend(details) {
16
+ const data = details?.data;
17
+ return data?.id && !isNaN(data?.id) && data?.type === 'reorder';
18
+ }
19
+ }
@@ -0,0 +1,28 @@
1
+ class ProductReviewed {
2
+ constructor(details) {
3
+ this.details = details;
4
+ }
5
+
6
+ toSegment() {
7
+ return {
8
+ event: 'Product Reviewed',
9
+ properties: this.details,
10
+ }
11
+ }
12
+
13
+ static registerEvent(){
14
+ salla.event.rating.onProductsRated(function (response, data) {
15
+ const { products, order_id } = data;
16
+ Object.values(products).forEach(product => {
17
+ Salla.event.emit('Product Reviewed', {
18
+ product_id: product.product_id?.toString(),
19
+ review_id: order_id?.toString(),
20
+ review_body: product.comment,
21
+ rating: product.rating
22
+ });
23
+ });
24
+ });
25
+ }
26
+ }
27
+
28
+ export default ProductReviewed;
@@ -0,0 +1,18 @@
1
+ export default class ProductShared {
2
+ constructor(details) {
3
+ this.data = details;
4
+ }
5
+
6
+ toSegment() {
7
+ const product = Salla.analytics.history.find(({ eventName }) => eventName === 'Product Viewed')?.payload || {};
8
+ product.share_via = this.data.share_via;
9
+ return {
10
+ event: 'Product Shared',
11
+ properties: product
12
+ };
13
+ }
14
+
15
+ static shouldSend() {
16
+ return salla.config.get('page.slug') === 'product.single';
17
+ }
18
+ }
@@ -0,0 +1,44 @@
1
+ export default class ProductViewed {
2
+ constructor(details) {
3
+ this.product = details[0];
4
+ // Read source from session storage
5
+ try {
6
+ this.source = sessionStorage.getItem('s-viewed-source') || null;
7
+ sessionStorage.removeItem('s-viewed-source');
8
+ } catch {
9
+ this.source = null;
10
+ }
11
+ // Read the clicked list position from session storage (same passing logic as
12
+ // s-viewed-source), falling back to any position carried on the product itself.
13
+ try {
14
+ const position = sessionStorage.getItem('s-viewed-position');
15
+ this.position = position !== null ? Number(position) : (this.product.position ?? null);
16
+ sessionStorage.removeItem('s-viewed-position');
17
+ } catch {
18
+ this.position = this.product.position ?? null;
19
+ }
20
+ }
21
+
22
+ toSegment() {
23
+ return {
24
+ event: 'Product Viewed',
25
+ properties: {
26
+ product_id: this.product.id?.toString(),
27
+ category: this.product.category,
28
+ name: this.product.name,
29
+ brand: this.product.brand,
30
+ variant: this.product.variant,
31
+ price: this.product.price,
32
+ quantity: this.product.quantity,
33
+ currency: this.product.currency,
34
+ coupon: this.product.coupon,
35
+ position: this.position,
36
+ value: this.product.value,
37
+ sku: this.product.sku,
38
+ url: this.product.url,
39
+ image_url: this.product.image_url,
40
+ source: this.source
41
+ }
42
+ };
43
+ }
44
+ }
@@ -0,0 +1,14 @@
1
+ export default class ProductSearched {
2
+ constructor(details) {
3
+ this.query = details;
4
+ }
5
+
6
+ toSegment() {
7
+ return {
8
+ event: 'Products Searched',
9
+ properties: {
10
+ query: this.query
11
+ }
12
+ };
13
+ }
14
+ }
@@ -0,0 +1,18 @@
1
+ export default class PromotionClicked {
2
+ constructor(details) {
3
+ this.promotion = details[0];
4
+ }
5
+
6
+ toSegment() {
7
+ return {
8
+ event: 'Promotion Clicked',
9
+ properties: {
10
+ promotion_id: this.promotion.id?.toString(),
11
+ creative: this.promotion.creative,
12
+ name: this.promotion.name,
13
+ position: this.promotion.position
14
+ }
15
+ };
16
+ }
17
+
18
+ }
@@ -0,0 +1,18 @@
1
+ export default class PromotionViewed {
2
+ constructor(details) {
3
+ this.promotion = details[0];
4
+ }
5
+
6
+ toSegment() {
7
+ return {
8
+ event: 'Promotion Viewed',
9
+ properties: {
10
+ promotion_id: this.promotion.id?.toString(),
11
+ creative: this.promotion.creative,
12
+ name: this.promotion.name,
13
+ position: this.promotion.position
14
+ }
15
+ };
16
+ }
17
+
18
+ }
@@ -0,0 +1,22 @@
1
+ import { mapProductFields } from './product-fields.js';
2
+
3
+ export default class RecommendationListViewed {
4
+ constructor(details) {
5
+ this.products = details?.products ?? [];
6
+ }
7
+
8
+ toSegment() {
9
+ const currency = Salla.config.currency()?.code;
10
+ return {
11
+ event: 'Recommendation List Viewed',
12
+ properties: {
13
+ recommendation_type: 'fbt',
14
+ source: 'pdp_fbt',
15
+ products: this.products.map((p, i) => ({
16
+ ...mapProductFields(p, i + 1, currency),
17
+ variant: 'product',
18
+ })),
19
+ },
20
+ };
21
+ }
22
+ }
@@ -0,0 +1,29 @@
1
+ export default class SignedIn {
2
+ constructor() {
3
+ this.user = JSON.parse(localStorage.getItem('user'));
4
+ }
5
+
6
+ toSegment() {
7
+ return {
8
+ event: 'Signed In',
9
+ properties: {
10
+ 'first_name' : this.user?.first_name,
11
+ 'last_name' : this.user?.last_name,
12
+ 'type' : this.user?.type,
13
+ 'mobile' : this.user?.mobile,
14
+ 'email' : this.user?.email,
15
+ }
16
+ };
17
+ }
18
+
19
+ static shouldSend(){
20
+ if (Salla.config.get('user.type') === 'user') {
21
+ //TODO:: It's only workaround
22
+ return !Salla.analytics.history.find(({ eventName }) => eventName === 'Signed In');
23
+ }
24
+ //this is dynamic check to make sure the next time the user is fetched, the event is sent
25
+ //this is bad workaround because it's too much magic, we need to find a better way to handle this
26
+ Salla.profile.event.onInfoFetched(() => Salla.event.emit('Signed In'));
27
+ return false;
28
+ }
29
+ }
@@ -0,0 +1,10 @@
1
+ export default class SignedOut {
2
+
3
+ toSegment() {
4
+ return {
5
+ event: 'Signed Out',
6
+ properties: {
7
+ }
8
+ };
9
+ }
10
+ }
@@ -0,0 +1,21 @@
1
+ import SignedIn from './signed-in';
2
+ export default class SignedUp extends SignedIn {
3
+
4
+ toSegment() {
5
+ const base = super.toSegment();
6
+ return {
7
+ event: 'Signed Up',
8
+ properties: base?.properties
9
+ };
10
+ }
11
+
12
+ static shouldSend() {
13
+ if (Salla.config.get('user.type') === 'user') {
14
+ return true;
15
+ }
16
+ //this is dynamic check to make sure the next time the user is fetched, the event is sent
17
+ //this is bad workaround because it's too much magic, we need to find a better way to handle this
18
+ Salla.profile.event.onInfoFetched(() => Salla.event.emit('Signed Up'));
19
+ return false;
20
+ }
21
+ }
@@ -0,0 +1,44 @@
1
+ export default class UserProfileUpdated {
2
+ static user = {};
3
+
4
+ constructor(details) {
5
+ this.details = details?.data;
6
+ }
7
+
8
+ toSegment() {
9
+ const fieldsToCheck = ['first_name', 'last_name', 'birthday', 'gender'];
10
+ const updateFields = fieldsToCheck.filter(key =>
11
+ UserProfileUpdated.user[key] !== this.details?.[key] &&
12
+ (UserProfileUpdated.user[key] !== undefined || this.details?.[key] !== undefined)
13
+ );
14
+ UserProfileUpdated.setUser(this.details);
15
+ return {
16
+ event: 'User Profile Updated',
17
+ properties: {
18
+ updated_fields: updateFields
19
+ }
20
+ };
21
+ }
22
+
23
+ static registerEvent() {
24
+ Salla.onReady(() => {
25
+ if(salla.url.is_page('customer.profile')) {
26
+ UserProfileUpdated.setUser(Salla.config.get('user'));
27
+ }
28
+ });
29
+ }
30
+
31
+ static setUser(user){
32
+ UserProfileUpdated.user = {
33
+ first_name: user?.first_name,
34
+ last_name: user?.last_name,
35
+ gender: user?.gender,
36
+ birthday: user?.birthday,
37
+ avatar: user?.avatar
38
+ };
39
+ }
40
+
41
+ static shouldSend(){
42
+ return Salla.config.get('user.type') === 'user';
43
+ }
44
+ }
@@ -0,0 +1,32 @@
1
+ export default class WishlistProductAddedToCart {
2
+ constructor(details) {
3
+ this.item = details[0];
4
+ }
5
+
6
+ toSegment() {
7
+ return {
8
+ event: 'Wishlist Product Added to Cart',
9
+ properties: {
10
+ wishlist_id : salla.config.get('store.id') + '_' + salla.config.get('user.id'),
11
+ wishlist_name : salla.config.get('store.name'),
12
+ cart_id: this.item.cart_id?.toString(),
13
+ product_id: this.item.id?.toString(),
14
+ category: this.item.category,
15
+ name: this.item.name,
16
+ brand: this.item.brand,
17
+ variant: this.item.variant,
18
+ price: this.item.price,
19
+ quantity: this.item.quantity,
20
+ sku: this.item.sku,
21
+ coupon: this.item.coupon,
22
+ position: this.item.position,
23
+ url: this.item.url,
24
+ image_url: this.item.image_url
25
+ }
26
+ };
27
+ }
28
+
29
+ static shouldSend() {
30
+ return salla.config.get('page.slug') === 'customer.wishlist';
31
+ }
32
+ }