@cshah18/sdk 4.13.0 → 4.14.0

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.
@@ -455,6 +455,8 @@ var CoBuySDK = (function (exports) {
455
455
  debug,
456
456
  events: options.events,
457
457
  performance,
458
+ geo: options.geo,
459
+ device: options.device,
458
460
  };
459
461
  return this.config;
460
462
  }
@@ -822,6 +824,7 @@ var CoBuySDK = (function (exports) {
822
824
  this.currentJoinedGroupId = null;
823
825
  this.currentProductId = null;
824
826
  this.currentSessionId = null;
827
+ this.analyticsClient = null;
825
828
  this.onGroupJoined = null;
826
829
  this.onViewProgress = null;
827
830
  this.socketListenerRegistered = false;
@@ -836,6 +839,7 @@ var CoBuySDK = (function (exports) {
836
839
  /** Handle group member joined socket event and update groups list */
837
840
  this.onGroupMemberJoined = (event) => {
838
841
  const detail = event.detail || {};
842
+ console.log("eventttttt", detail);
839
843
  const productId = detail.product_id;
840
844
  const groupData = detail.group;
841
845
  // Only process if this is for the current product
@@ -868,6 +872,10 @@ var CoBuySDK = (function (exports) {
868
872
  this.apiClient = apiClient;
869
873
  this.injectStyles();
870
874
  }
875
+ /** Set the analytics client for event tracking */
876
+ setAnalyticsClient(client) {
877
+ this.analyticsClient = client;
878
+ }
871
879
  /** Set callback for when a group is joined successfully */
872
880
  setOnGroupJoined(callback) {
873
881
  this.onGroupJoined = callback;
@@ -1125,10 +1133,21 @@ var CoBuySDK = (function (exports) {
1125
1133
  buttonElement.disabled = true;
1126
1134
  const originalText = button.textContent;
1127
1135
  button.textContent = "Joining...";
1136
+ const productId = this.currentProductId || "";
1137
+ if (this.analyticsClient) {
1138
+ this.analyticsClient
1139
+ .trackJoinAttempt(productId, groupId)
1140
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1141
+ }
1128
1142
  try {
1129
1143
  const response = await this.apiClient.joinGroup(groupId);
1130
1144
  if (response.success && response.data) {
1131
1145
  this.logger.info(`[handleJoinGroup] Successfully joined group: ${groupId}`, response.data);
1146
+ if (this.analyticsClient) {
1147
+ this.analyticsClient
1148
+ .trackJoinSuccess(productId, groupId)
1149
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1150
+ }
1132
1151
  // Track the joined group
1133
1152
  this.currentJoinedGroupId = groupId;
1134
1153
  // Close the modal and trigger callback with the full join response data
@@ -1139,12 +1158,22 @@ var CoBuySDK = (function (exports) {
1139
1158
  }
1140
1159
  else {
1141
1160
  this.logger.error("Failed to join group: API response unsuccessful");
1161
+ if (this.analyticsClient) {
1162
+ this.analyticsClient
1163
+ .trackJoinFailure(productId, groupId)
1164
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1165
+ }
1142
1166
  button.textContent = originalText;
1143
1167
  buttonElement.disabled = false;
1144
1168
  }
1145
1169
  }
1146
1170
  catch (error) {
1147
1171
  this.logger.error("Error joining group", error);
1172
+ if (this.analyticsClient) {
1173
+ this.analyticsClient
1174
+ .trackJoinFailure(productId, groupId, "EXCEPTION", error instanceof Error ? error.message : String(error))
1175
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1176
+ }
1148
1177
  button.textContent = originalText;
1149
1178
  buttonElement.disabled = false;
1150
1179
  }
@@ -1165,10 +1194,21 @@ var CoBuySDK = (function (exports) {
1165
1194
  this.setJoinButtonsDisabled(true);
1166
1195
  const originalText = button.textContent;
1167
1196
  button.textContent = "Creating...";
1197
+ const productId = this.currentProductId;
1198
+ if (this.analyticsClient) {
1199
+ this.analyticsClient
1200
+ .trackGroupCreateAttempt(productId)
1201
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1202
+ }
1168
1203
  try {
1169
1204
  const response = await this.apiClient.createAndJoinGroup(this.currentProductId);
1170
1205
  if (response.success && response.data) {
1171
1206
  this.logger.info("Successfully created and joined new group", response.data);
1207
+ if (this.analyticsClient) {
1208
+ this.analyticsClient
1209
+ .trackGroupCreateSuccess(productId, response.data.group.id)
1210
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1211
+ }
1172
1212
  this.currentJoinedGroupId = response.data.group.id;
1173
1213
  this.close();
1174
1214
  if (this.onGroupJoined) {
@@ -1177,6 +1217,11 @@ var CoBuySDK = (function (exports) {
1177
1217
  }
1178
1218
  else {
1179
1219
  this.logger.error("Failed to create group: API response unsuccessful");
1220
+ if (this.analyticsClient) {
1221
+ this.analyticsClient
1222
+ .trackGroupCreateFailure(productId)
1223
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1224
+ }
1180
1225
  button.textContent = originalText;
1181
1226
  buttonElement.disabled = false;
1182
1227
  this.setJoinButtonsDisabled(false);
@@ -1184,6 +1229,11 @@ var CoBuySDK = (function (exports) {
1184
1229
  }
1185
1230
  catch (error) {
1186
1231
  this.logger.error("Error creating group", error);
1232
+ if (this.analyticsClient) {
1233
+ this.analyticsClient
1234
+ .trackGroupCreateFailure(productId, "EXCEPTION", error instanceof Error ? error.message : String(error))
1235
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
1236
+ }
1187
1237
  button.textContent = originalText;
1188
1238
  buttonElement.disabled = false;
1189
1239
  this.setJoinButtonsDisabled(false);
@@ -3581,14 +3631,13 @@ var CoBuySDK = (function (exports) {
3581
3631
  document.body.appendChild(this.modalElement);
3582
3632
  // Subscribe to realtime socket events
3583
3633
  this.subscribeToSocketEvents();
3584
- // If socket is available and connected, subscribe to this group
3585
- if (this.socketManager && this.socketManager.isConnected() && this.currentGroupId) {
3586
- this.logger.info(`[LobbyModal] Socket connected, subscribing to group ${this.currentGroupId}`);
3634
+ // Subscribe to the group room for targeted events.
3635
+ // Socket.IO client buffers emits until connected, so this is safe even
3636
+ // if the handshake hasn't completed yet.
3637
+ if (this.socketManager && this.currentGroupId) {
3638
+ this.logger.info(`[LobbyModal] Subscribing to group room ${this.currentGroupId}`);
3587
3639
  this.socketManager.subscribeToGroup(this.currentGroupId);
3588
3640
  }
3589
- else if (this.socketManager && !this.socketManager.isConnected()) {
3590
- this.logger.warn("[LobbyModal] Socket manager not connected yet");
3591
- }
3592
3641
  // Start timers and animations
3593
3642
  this.startTimer();
3594
3643
  this.startActivityAnimation();
@@ -4389,10 +4438,18 @@ var CoBuySDK = (function (exports) {
4389
4438
  /** Handle backend fulfillment notifications */
4390
4439
  this.handleGroupFulfilledEvent = (event) => {
4391
4440
  const detail = event.detail;
4392
- if (!detail || !detail.productId) {
4441
+ if (!detail || !detail.product_id) {
4442
+ return;
4443
+ }
4444
+ if (this.currentProductId && detail.product_id !== this.currentProductId) {
4393
4445
  return;
4394
4446
  }
4395
- if (this.currentProductId && detail.productId !== this.currentProductId) {
4447
+ // Only enter the fulfilled/checkout flow for users who have actively joined a group this
4448
+ // session. currentSessionId is set exclusively in the setOnGroupJoined callback (when the
4449
+ // user completes a join API call). Observers who never joined should re-fetch the primary
4450
+ // group API to display the updated group state instead.
4451
+ if (!this.currentSessionId) {
4452
+ void this.refreshGroupDataFromRealtime();
4396
4453
  return;
4397
4454
  }
4398
4455
  this.processGroupFulfilled(detail);
@@ -4449,12 +4506,21 @@ var CoBuySDK = (function (exports) {
4449
4506
  const groupData = await this.fetchPrimaryGroup(this.currentProductId);
4450
4507
  this.currentGroupData = groupData;
4451
4508
  this.currentGroupId = (groupData === null || groupData === void 0 ? void 0 : groupData.id) || this.currentGroupId;
4452
- // If backend signals completion via counts, reflect it
4509
+ // If backend signals completion via counts, reflect it — but only for actual members.
4510
+ // Observers may see a full group returned by fetchPrimaryGroup; they should NOT enter
4511
+ // the checkout flow. currentSessionId is the membership signal (non-null = joined).
4453
4512
  if (groupData) {
4454
4513
  const participants = Number(groupData.participants_count || 0);
4455
4514
  const max = Number(groupData.max_participants || 0);
4456
- if (max > 0 && participants >= max) {
4515
+ if (max > 0 && participants >= max && this.currentSessionId) {
4457
4516
  this.groupFulfilled = true;
4517
+ // The primary group API returns offline_redemption for members of fulfilled groups.
4518
+ // Extract it here so renderFulfilledSummary shows the "Redeem In-store" link
4519
+ // (matching the UI users see on a full page reload).
4520
+ if (groupData.offline_redemption &&
4521
+ isValidOfflineRedemption(groupData.offline_redemption)) {
4522
+ this.offlineRedemption = groupData.offline_redemption;
4523
+ }
4458
4524
  }
4459
4525
  }
4460
4526
  else {
@@ -4799,6 +4865,9 @@ var CoBuySDK = (function (exports) {
4799
4865
  getGroupListModal() {
4800
4866
  if (!this.groupListModal) {
4801
4867
  this.groupListModal = new GroupListModal(undefined, 8, this.config.debug, this.apiClient);
4868
+ if (this.analyticsClient) {
4869
+ this.groupListModal.setAnalyticsClient(this.analyticsClient);
4870
+ }
4802
4871
  // Set callback to open lobby when a group is successfully joined
4803
4872
  this.groupListModal.setOnGroupJoined((joinData) => {
4804
4873
  var _a;
@@ -5024,6 +5093,11 @@ var CoBuySDK = (function (exports) {
5024
5093
  if (max > 0 && participants >= max) {
5025
5094
  this.groupFulfilled = true;
5026
5095
  this.logger.info("Group is already fulfilled on initial load", { participants, max });
5096
+ if (this.analyticsClient) {
5097
+ this.analyticsClient
5098
+ .trackGroupFullView(options.productId, groupData.id, max)
5099
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5100
+ }
5027
5101
  }
5028
5102
  }
5029
5103
  }
@@ -5089,6 +5163,11 @@ var CoBuySDK = (function (exports) {
5089
5163
  // LOADED state - render widget only if we have group data
5090
5164
  this.createWidget(rewardData, container, options);
5091
5165
  this.logger.info(`Widget rendered for product: ${options.productId}`);
5166
+ if (this.analyticsClient) {
5167
+ this.analyticsClient
5168
+ .trackCreativeView(options.productId)
5169
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5170
+ }
5092
5171
  }
5093
5172
  else {
5094
5173
  // No group data available - hide widget
@@ -5740,7 +5819,7 @@ var CoBuySDK = (function (exports) {
5740
5819
  * Handle CTA button click with analytics and modal opening
5741
5820
  */
5742
5821
  async handleCTAClick(productId) {
5743
- var _a, _b, _c, _d;
5822
+ var _a, _b, _c, _d, _e, _f, _g;
5744
5823
  this.logger.info(`CTA clicked for product: ${productId}`);
5745
5824
  // Track analytics event asynchronously (fire-and-forget)
5746
5825
  if (this.analyticsClient) {
@@ -5758,14 +5837,31 @@ var CoBuySDK = (function (exports) {
5758
5837
  if (this.apiClient && this.currentGroupId) {
5759
5838
  try {
5760
5839
  this.logger.info(`Joining group: ${this.currentGroupId}`);
5840
+ if (this.analyticsClient) {
5841
+ this.analyticsClient
5842
+ .trackJoinAttempt(productId, this.currentGroupId)
5843
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5844
+ }
5761
5845
  const joinResponse = await this.apiClient.joinGroup(this.currentGroupId);
5762
5846
  if (!joinResponse.success) {
5763
5847
  this.logger.error("Failed to join group, modal will not open", joinResponse.error);
5848
+ if (this.analyticsClient) {
5849
+ const errCode = (_c = joinResponse.error) === null || _c === void 0 ? void 0 : _c.code;
5850
+ const errMsg = (_d = joinResponse.error) === null || _d === void 0 ? void 0 : _d.message;
5851
+ this.analyticsClient
5852
+ .trackJoinFailure(productId, this.currentGroupId, errCode, errMsg)
5853
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5854
+ }
5764
5855
  this.setButtonLoadingState(false);
5765
5856
  return; // Don't open modal on error
5766
5857
  }
5767
5858
  groupJoinData = joinResponse.data;
5768
5859
  this.logger.info("Successfully joined group", groupJoinData);
5860
+ if (this.analyticsClient && groupJoinData) {
5861
+ this.analyticsClient
5862
+ .trackJoinSuccess(productId, groupJoinData.group.id)
5863
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5864
+ }
5769
5865
  this.offlineRedemption =
5770
5866
  groupJoinData &&
5771
5867
  groupJoinData.offline_redemption &&
@@ -5789,6 +5885,11 @@ var CoBuySDK = (function (exports) {
5789
5885
  }
5790
5886
  catch (error) {
5791
5887
  this.logger.error("Group join failed, modal will not open", error);
5888
+ if (this.analyticsClient) {
5889
+ this.analyticsClient
5890
+ .trackJoinFailure(productId, (_e = this.currentGroupId) !== null && _e !== void 0 ? _e : undefined, "EXCEPTION", error instanceof Error ? error.message : String(error))
5891
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5892
+ }
5792
5893
  this.setButtonLoadingState(false);
5793
5894
  return; // Don't open modal on error
5794
5895
  }
@@ -5803,7 +5904,7 @@ var CoBuySDK = (function (exports) {
5803
5904
  const progress = Math.round((groupJoinData.group.participants_count / groupJoinData.group.max_participants) * 100);
5804
5905
  // Format discount based on reward type
5805
5906
  let discountText = "";
5806
- if ((_d = (_c = this.currentRewardData) === null || _c === void 0 ? void 0 : _c.reward) === null || _d === void 0 ? void 0 : _d.value) {
5907
+ if ((_g = (_f = this.currentRewardData) === null || _f === void 0 ? void 0 : _f.reward) === null || _g === void 0 ? void 0 : _g.value) {
5807
5908
  const rewardType = this.currentRewardData.reward.type;
5808
5909
  const rewardValue = this.currentRewardData.reward.value;
5809
5910
  if (rewardType === "percentage" || rewardType === "cashback") {
@@ -5843,6 +5944,11 @@ var CoBuySDK = (function (exports) {
5843
5944
  shareMessage: shareMessageFromInvite,
5844
5945
  isLocked: !isGroupFulfilled,
5845
5946
  offlineRedemption: offlineRedemptionFromJoin,
5947
+ onShare: this.analyticsClient
5948
+ ? () => {
5949
+ this.analyticsClient.trackShareClick(productId, groupJoinData.group.id, "other").catch((e) => this.logger.warn("Analytics tracking failed", e));
5950
+ }
5951
+ : undefined,
5846
5952
  activities: [
5847
5953
  {
5848
5954
  emoji: "👤",
@@ -5878,6 +5984,12 @@ var CoBuySDK = (function (exports) {
5878
5984
  },
5879
5985
  ], // Will be populated from real-time data later
5880
5986
  });
5987
+ // Track popup open after modal is launched
5988
+ if (this.analyticsClient) {
5989
+ this.analyticsClient
5990
+ .trackPopupOpen(productId, groupJoinData.group.id)
5991
+ .catch((e) => this.logger.warn("Analytics tracking failed", e));
5992
+ }
5881
5993
  // Remove loading state after modal opens
5882
5994
  this.setButtonLoadingState(false);
5883
5995
  }
@@ -5984,6 +6096,14 @@ var CoBuySDK = (function (exports) {
5984
6096
  getProductId() {
5985
6097
  return this.currentProductId;
5986
6098
  }
6099
+ /**
6100
+ * Returns the group ID that the current user has joined via this widget, or null if the user
6101
+ * is only an observer (has not actively joined a group this session).
6102
+ * Used by the CoBuy host class to check membership before preparing checkout.
6103
+ */
6104
+ getJoinedGroupId() {
6105
+ return this.currentSessionId ? this.currentGroupId : null;
6106
+ }
5987
6107
  }
5988
6108
 
5989
6109
  /**
@@ -5991,6 +6111,7 @@ var CoBuySDK = (function (exports) {
5991
6111
  */
5992
6112
  const API_ENDPOINTS = {
5993
6113
  // Product endpoints
6114
+ PRODUCT_CONTEXT: "/v1/sdk/products/:productId/context",
5994
6115
  PRODUCT_REWARD: "/v1/sdk/products/:productId/reward",
5995
6116
  PRODUCT_PRIMARY_GROUP: "/v1/sdk/products/:productId/group/primary",
5996
6117
  // Group endpoints
@@ -6041,8 +6162,11 @@ var CoBuySDK = (function (exports) {
6041
6162
  var _a;
6042
6163
  this.traceId = null;
6043
6164
  this.rewardCache = new Map();
6165
+ this.productContextCache = new Map();
6044
6166
  this.REWARD_CACHE_TTL = 60000; // 1 minute
6045
- this.pendingRequests = new Map();
6167
+ this.PRODUCT_CONTEXT_CACHE_TTL = 30000; // 30 seconds
6168
+ this.pendingRewardRequests = new Map();
6169
+ this.pendingContextRequests = new Map();
6046
6170
  this.baseUrl = config.baseUrl;
6047
6171
  this.authStrategy = config.authStrategy;
6048
6172
  this.sessionId = config.sessionId;
@@ -6438,6 +6562,106 @@ var CoBuySDK = (function (exports) {
6438
6562
  getTraceId() {
6439
6563
  return this.traceId;
6440
6564
  }
6565
+ getCachedProductContext(productId) {
6566
+ const cached = this.productContextCache.get(productId);
6567
+ if (cached && cached.expires > Date.now()) {
6568
+ return cached.data;
6569
+ }
6570
+ if (cached) {
6571
+ this.productContextCache.delete(productId);
6572
+ }
6573
+ return null;
6574
+ }
6575
+ normalizePrimaryGroup(groupData) {
6576
+ if (!groupData) {
6577
+ return null;
6578
+ }
6579
+ const nestedGroup = groupData.group;
6580
+ if (nestedGroup && typeof nestedGroup === "object" && "id" in nestedGroup) {
6581
+ return nestedGroup;
6582
+ }
6583
+ return groupData;
6584
+ }
6585
+ buildRewardDataFromContext(productId, context) {
6586
+ var _a, _b, _c, _d, _e;
6587
+ const primaryGroup = this.normalizePrimaryGroup(context.primary_group);
6588
+ return {
6589
+ productId,
6590
+ reward: (_a = context.reward) !== null && _a !== void 0 ? _a : null,
6591
+ campaign_mode: (_c = (_b = context.campaign) === null || _b === void 0 ? void 0 : _b.campaign_mode) !== null && _c !== void 0 ? _c : "group",
6592
+ campaign_creative_id: (_d = primaryGroup === null || primaryGroup === void 0 ? void 0 : primaryGroup.campaign_creative_id) !== null && _d !== void 0 ? _d : null,
6593
+ eligibility: (_e = context.eligibility) !== null && _e !== void 0 ? _e : { isEligible: false },
6594
+ };
6595
+ }
6596
+ setProductContextCache(productId, context) {
6597
+ this.productContextCache.set(productId, {
6598
+ data: context,
6599
+ expires: Date.now() + this.PRODUCT_CONTEXT_CACHE_TTL,
6600
+ });
6601
+ this.rewardCache.set(productId, {
6602
+ data: this.buildRewardDataFromContext(productId, context),
6603
+ expires: Date.now() + this.REWARD_CACHE_TTL,
6604
+ });
6605
+ }
6606
+ /**
6607
+ * Get product context information
6608
+ *
6609
+ * Uses the consolidated bootstrap endpoint so the SDK can resolve campaign,
6610
+ * reward, primary group, and active groups in a single request.
6611
+ */
6612
+ async getProductContext(productId) {
6613
+ if (!validateProductId(productId)) {
6614
+ return {
6615
+ success: false,
6616
+ error: {
6617
+ message: "Invalid productId format. Must be a non-empty string (max 200 chars).",
6618
+ code: "INVALID_PRODUCT_ID",
6619
+ },
6620
+ };
6621
+ }
6622
+ // const cached = this.getCachedProductContext(productId);
6623
+ // if (cached) {
6624
+ // this.logger.info(`Using cached product context for product: ${productId}`);
6625
+ // return {
6626
+ // success: true,
6627
+ // data: cached,
6628
+ // };
6629
+ // }
6630
+ const cacheKey = `context:${productId}`;
6631
+ const pending = this.pendingContextRequests.get(cacheKey);
6632
+ if (pending) {
6633
+ this.logger.info(`Deduplicating product context request for: ${productId}`);
6634
+ return pending;
6635
+ }
6636
+ const promise = this.fetchProductContext(productId).then((response) => {
6637
+ if (response.success && response.data) {
6638
+ this.setProductContextCache(productId, response.data);
6639
+ }
6640
+ return response;
6641
+ });
6642
+ this.pendingContextRequests.set(cacheKey, promise);
6643
+ promise.then(() => this.pendingContextRequests.delete(cacheKey), () => this.pendingContextRequests.delete(cacheKey));
6644
+ return promise;
6645
+ }
6646
+ async fetchProductContext(productId) {
6647
+ var _a;
6648
+ const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_CONTEXT, { productId });
6649
+ this.logger.info(`Fetching product context for product: ${productId}`);
6650
+ const response = await this.get(endpoint);
6651
+ if (response.success && ((_a = response.data) === null || _a === void 0 ? void 0 : _a.data)) {
6652
+ return {
6653
+ success: true,
6654
+ data: response.data.data,
6655
+ };
6656
+ }
6657
+ return {
6658
+ success: false,
6659
+ error: response.error || {
6660
+ message: "Failed to fetch product context data",
6661
+ code: "PRODUCT_CONTEXT_FETCH_ERROR",
6662
+ },
6663
+ };
6664
+ }
6441
6665
  /**
6442
6666
  * Get product reward information
6443
6667
  *
@@ -6482,13 +6706,31 @@ var CoBuySDK = (function (exports) {
6482
6706
  data: cached.data,
6483
6707
  };
6484
6708
  }
6709
+ const cachedContext = this.getCachedProductContext(productId);
6710
+ if (cachedContext) {
6711
+ this.logger.info(`Using cached product context reward for product: ${productId}`);
6712
+ return {
6713
+ success: true,
6714
+ data: this.buildRewardDataFromContext(productId, cachedContext),
6715
+ };
6716
+ }
6485
6717
  const cacheKey = `reward:${productId}`;
6486
- const pending = this.pendingRequests.get(cacheKey);
6718
+ const pending = this.pendingRewardRequests.get(cacheKey);
6487
6719
  if (pending) {
6488
6720
  this.logger.info(`Deduplicating request for product: ${productId}`);
6489
6721
  return pending;
6490
6722
  }
6491
- const promise = this.fetchProductReward(productId).then((response) => {
6723
+ const promise = (async () => {
6724
+ const contextResponse = await this.getProductContext(productId);
6725
+ if (contextResponse.success && contextResponse.data) {
6726
+ return {
6727
+ success: true,
6728
+ data: this.buildRewardDataFromContext(productId, contextResponse.data),
6729
+ };
6730
+ }
6731
+ this.logger.info(`Falling back to legacy reward endpoint for product: ${productId}`);
6732
+ return this.fetchProductReward(productId);
6733
+ })().then((response) => {
6492
6734
  if (response.success && response.data) {
6493
6735
  this.rewardCache.set(productId, {
6494
6736
  data: response.data,
@@ -6497,8 +6739,8 @@ var CoBuySDK = (function (exports) {
6497
6739
  }
6498
6740
  return response;
6499
6741
  });
6500
- this.pendingRequests.set(cacheKey, promise);
6501
- promise.then(() => this.pendingRequests.delete(cacheKey), () => this.pendingRequests.delete(cacheKey));
6742
+ this.pendingRewardRequests.set(cacheKey, promise);
6743
+ promise.then(() => this.pendingRewardRequests.delete(cacheKey), () => this.pendingRewardRequests.delete(cacheKey));
6502
6744
  return promise;
6503
6745
  }
6504
6746
  /**
@@ -6514,6 +6756,7 @@ var CoBuySDK = (function (exports) {
6514
6756
  */
6515
6757
  clearRewardCache() {
6516
6758
  this.rewardCache.clear();
6759
+ this.productContextCache.clear();
6517
6760
  }
6518
6761
  async fetchProductReward(productId) {
6519
6762
  var _a;
@@ -6561,7 +6804,7 @@ var CoBuySDK = (function (exports) {
6561
6804
  * ```
6562
6805
  */
6563
6806
  async getProductPrimaryGroup(productId) {
6564
- var _a;
6807
+ var _a, _b;
6565
6808
  if (!validateProductId(productId)) {
6566
6809
  return {
6567
6810
  success: false,
@@ -6571,15 +6814,38 @@ var CoBuySDK = (function (exports) {
6571
6814
  },
6572
6815
  };
6573
6816
  }
6574
- const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_PRIMARY_GROUP, { productId }, { allowAutoCreate: true });
6575
- this.logger.info(`Fetching primary group for product: ${productId}`);
6576
- const response = await this.get(endpoint);
6577
- if (response.success && ((_a = response.data) === null || _a === void 0 ? void 0 : _a.data)) {
6817
+ // const cachedContext = this.getCachedProductContext(productId);
6818
+ // const cachedGroup = this.normalizePrimaryGroup(cachedContext?.primary_group);
6819
+ // if (cachedGroup) {
6820
+ // this.logger.info(`Using cached primary group from product context for product: ${productId}`);
6821
+ // return {
6822
+ // success: true,
6823
+ // data: {
6824
+ // ...cachedGroup,
6825
+ // group: cachedGroup,
6826
+ // },
6827
+ // };
6828
+ // }
6829
+ const contextResponse = await this.getProductContext(productId);
6830
+ const contextGroup = this.normalizePrimaryGroup((_a = contextResponse.data) === null || _a === void 0 ? void 0 : _a.primary_group);
6831
+ if (contextResponse.success && contextGroup) {
6578
6832
  return {
6579
6833
  success: true,
6580
- data: response.data.data,
6834
+ data: Object.assign(Object.assign({}, contextGroup), { group: contextGroup }),
6581
6835
  };
6582
6836
  }
6837
+ const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_PRIMARY_GROUP, { productId }, { allowAutoCreate: true });
6838
+ this.logger.info(`Fetching primary group for product: ${productId}`);
6839
+ const response = await this.get(endpoint);
6840
+ if (response.success && ((_b = response.data) === null || _b === void 0 ? void 0 : _b.data)) {
6841
+ const normalizedGroup = this.normalizePrimaryGroup(response.data.data);
6842
+ if (normalizedGroup) {
6843
+ return {
6844
+ success: true,
6845
+ data: Object.assign(Object.assign({}, normalizedGroup), { group: normalizedGroup }),
6846
+ };
6847
+ }
6848
+ }
6583
6849
  return {
6584
6850
  success: false,
6585
6851
  error: response.error || {
@@ -6627,6 +6893,25 @@ var CoBuySDK = (function (exports) {
6627
6893
  },
6628
6894
  };
6629
6895
  }
6896
+ const cachedContext = this.getCachedProductContext(productId);
6897
+ if (cachedContext) {
6898
+ this.logger.info(`Using cached active groups from product context for product: ${productId}`);
6899
+ return {
6900
+ success: true,
6901
+ data: {
6902
+ groups: (cachedContext.active_groups || []),
6903
+ },
6904
+ };
6905
+ }
6906
+ const contextResponse = await this.getProductContext(productId);
6907
+ if (contextResponse.success && contextResponse.data) {
6908
+ return {
6909
+ success: true,
6910
+ data: {
6911
+ groups: (contextResponse.data.active_groups || []),
6912
+ },
6913
+ };
6914
+ }
6630
6915
  const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_ACTIVE_GROUPS, { productId });
6631
6916
  this.logger.info(`Fetching active groups for product: ${productId}`);
6632
6917
  const response = await this.get(endpoint);
@@ -7131,6 +7416,7 @@ var CoBuySDK = (function (exports) {
7131
7416
  const event = {
7132
7417
  event: "CTA_CLICKED",
7133
7418
  productId,
7419
+ sessionId: this.sessionId,
7134
7420
  timestamp: new Date().toISOString(),
7135
7421
  context: {
7136
7422
  pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
@@ -7183,12 +7469,32 @@ var CoBuySDK = (function (exports) {
7183
7469
  throw new CoBuyApiError(error instanceof Error ? error.message : "Unknown analytics error", "ANALYTICS_ERROR", { originalError: error });
7184
7470
  }
7185
7471
  }
7472
+ /**
7473
+ * Track session init event fired when the SDK initializes
7474
+ */
7475
+ async trackSessionInit(geo, device) {
7476
+ const event = {
7477
+ event: "SESSION_INIT",
7478
+ sessionId: this.sessionId,
7479
+ timestamp: new Date().toISOString(),
7480
+ context: Object.assign(Object.assign({ pageUrl: typeof window !== "undefined" ? window.location.href : undefined, userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined, sdkVersion: this.sdkVersion }, (geo ? { geo } : {})), (device ? { device } : {})),
7481
+ };
7482
+ try {
7483
+ await this.sendEvent(event);
7484
+ this.logger.info("[Analytics] Session init tracked");
7485
+ }
7486
+ catch (error) {
7487
+ // Non-blocking
7488
+ this.logger.error("[Analytics] Failed to track session init", error);
7489
+ }
7490
+ }
7186
7491
  /**
7187
7492
  * Track page view event
7188
7493
  */
7189
7494
  async trackPageView() {
7190
7495
  const event = {
7191
7496
  event: "PAGE_VIEW",
7497
+ sessionId: this.sessionId,
7192
7498
  timestamp: new Date().toISOString(),
7193
7499
  context: {
7194
7500
  pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
@@ -7205,6 +7511,273 @@ var CoBuySDK = (function (exports) {
7205
7511
  this.logger.error("[Analytics] Failed to track page view", error);
7206
7512
  }
7207
7513
  }
7514
+ /**
7515
+ * Track creative view event (widget rendered and visible)
7516
+ */
7517
+ async trackCreativeView(productId) {
7518
+ const event = {
7519
+ event: "CREATIVE_VIEW",
7520
+ productId,
7521
+ sessionId: this.sessionId,
7522
+ timestamp: new Date().toISOString(),
7523
+ context: {
7524
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7525
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7526
+ sdkVersion: this.sdkVersion,
7527
+ },
7528
+ };
7529
+ try {
7530
+ await this.sendEvent(event);
7531
+ this.logger.info(`[Analytics] Creative view tracked for product: ${productId}`);
7532
+ }
7533
+ catch (error) {
7534
+ this.logger.error("[Analytics] Failed to track creative view", error);
7535
+ }
7536
+ }
7537
+ /**
7538
+ * Track popup/lobby modal open event
7539
+ */
7540
+ async trackPopupOpen(productId, groupId) {
7541
+ const event = {
7542
+ event: "POPUP_OPEN",
7543
+ productId,
7544
+ sessionId: this.sessionId,
7545
+ timestamp: new Date().toISOString(),
7546
+ context: {
7547
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7548
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7549
+ sdkVersion: this.sdkVersion,
7550
+ groupId,
7551
+ },
7552
+ };
7553
+ try {
7554
+ await this.sendEvent(event);
7555
+ this.logger.info(`[Analytics] Popup open tracked for product: ${productId}`);
7556
+ }
7557
+ catch (error) {
7558
+ this.logger.error("[Analytics] Failed to track popup open", error);
7559
+ }
7560
+ }
7561
+ /**
7562
+ * Track join attempt event (before API call)
7563
+ */
7564
+ async trackJoinAttempt(productId, groupId) {
7565
+ const event = {
7566
+ event: "JOIN_ATTEMPT",
7567
+ productId,
7568
+ sessionId: this.sessionId,
7569
+ timestamp: new Date().toISOString(),
7570
+ context: {
7571
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7572
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7573
+ sdkVersion: this.sdkVersion,
7574
+ groupId,
7575
+ },
7576
+ };
7577
+ try {
7578
+ await this.sendEvent(event);
7579
+ this.logger.info(`[Analytics] Join attempt tracked for product: ${productId}`);
7580
+ }
7581
+ catch (error) {
7582
+ this.logger.error("[Analytics] Failed to track join attempt", error);
7583
+ }
7584
+ }
7585
+ /**
7586
+ * Track successful group join event
7587
+ */
7588
+ async trackJoinSuccess(productId, groupId) {
7589
+ const event = {
7590
+ event: "JOIN_SUCCESS",
7591
+ productId,
7592
+ sessionId: this.sessionId,
7593
+ timestamp: new Date().toISOString(),
7594
+ context: {
7595
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7596
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7597
+ sdkVersion: this.sdkVersion,
7598
+ groupId,
7599
+ },
7600
+ };
7601
+ try {
7602
+ await this.sendEvent(event);
7603
+ this.logger.info(`[Analytics] Join success tracked for product: ${productId}`);
7604
+ }
7605
+ catch (error) {
7606
+ this.logger.error("[Analytics] Failed to track join success", error);
7607
+ }
7608
+ }
7609
+ /**
7610
+ * Track join failure event
7611
+ */
7612
+ async trackJoinFailure(productId, groupId, errorCode, message) {
7613
+ const event = {
7614
+ event: "JOIN_FAILURE",
7615
+ productId,
7616
+ sessionId: this.sessionId,
7617
+ timestamp: new Date().toISOString(),
7618
+ context: {
7619
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7620
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7621
+ sdkVersion: this.sdkVersion,
7622
+ groupId,
7623
+ errorCode,
7624
+ errorMessage: message,
7625
+ },
7626
+ };
7627
+ try {
7628
+ await this.sendEvent(event);
7629
+ this.logger.info(`[Analytics] Join failure tracked for product: ${productId}`);
7630
+ }
7631
+ catch (error) {
7632
+ this.logger.error("[Analytics] Failed to track join failure", error);
7633
+ }
7634
+ }
7635
+ /**
7636
+ * Track already joined event (user attempts to join a group they're already in)
7637
+ */
7638
+ async trackAlreadyJoined(productId, groupId) {
7639
+ const event = {
7640
+ event: "ALREADY_JOINED",
7641
+ productId,
7642
+ sessionId: this.sessionId,
7643
+ timestamp: new Date().toISOString(),
7644
+ context: {
7645
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7646
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7647
+ sdkVersion: this.sdkVersion,
7648
+ groupId,
7649
+ },
7650
+ };
7651
+ try {
7652
+ await this.sendEvent(event);
7653
+ this.logger.info(`[Analytics] Already joined tracked for product: ${productId}`);
7654
+ }
7655
+ catch (error) {
7656
+ this.logger.error("[Analytics] Failed to track already joined", error);
7657
+ }
7658
+ }
7659
+ /**
7660
+ * Track group full view event (user sees a fulfilled/full group)
7661
+ */
7662
+ async trackGroupFullView(productId, groupId, totalMembers) {
7663
+ const event = {
7664
+ event: "GROUP_FULL_VIEW",
7665
+ productId,
7666
+ sessionId: this.sessionId,
7667
+ timestamp: new Date().toISOString(),
7668
+ context: {
7669
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7670
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7671
+ sdkVersion: this.sdkVersion,
7672
+ groupId,
7673
+ totalMembers,
7674
+ },
7675
+ };
7676
+ try {
7677
+ await this.sendEvent(event);
7678
+ this.logger.info(`[Analytics] Group full view tracked for product: ${productId}`);
7679
+ }
7680
+ catch (error) {
7681
+ this.logger.error("[Analytics] Failed to track group full view", error);
7682
+ }
7683
+ }
7684
+ /**
7685
+ * Track share click event
7686
+ */
7687
+ async trackShareClick(productId, groupId, channel) {
7688
+ const event = {
7689
+ event: "SHARE_CLICK",
7690
+ productId,
7691
+ sessionId: this.sessionId,
7692
+ timestamp: new Date().toISOString(),
7693
+ context: {
7694
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7695
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7696
+ sdkVersion: this.sdkVersion,
7697
+ groupId,
7698
+ channel,
7699
+ },
7700
+ };
7701
+ try {
7702
+ await this.sendEvent(event);
7703
+ this.logger.info(`[Analytics] Share click tracked for product: ${productId}`);
7704
+ }
7705
+ catch (error) {
7706
+ this.logger.error("[Analytics] Failed to track share click", error);
7707
+ }
7708
+ }
7709
+ /**
7710
+ * Track group creation attempt event
7711
+ */
7712
+ async trackGroupCreateAttempt(productId) {
7713
+ const event = {
7714
+ event: "GROUP_CREATE_ATTEMPT",
7715
+ productId,
7716
+ sessionId: this.sessionId,
7717
+ timestamp: new Date().toISOString(),
7718
+ context: {
7719
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7720
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7721
+ sdkVersion: this.sdkVersion,
7722
+ },
7723
+ };
7724
+ try {
7725
+ await this.sendEvent(event);
7726
+ this.logger.info(`[Analytics] Group create attempt tracked for product: ${productId}`);
7727
+ }
7728
+ catch (error) {
7729
+ this.logger.error("[Analytics] Failed to track group create attempt", error);
7730
+ }
7731
+ }
7732
+ /**
7733
+ * Track successful group creation event
7734
+ */
7735
+ async trackGroupCreateSuccess(productId, groupId) {
7736
+ const event = {
7737
+ event: "GROUP_CREATE_SUCCESS",
7738
+ productId,
7739
+ sessionId: this.sessionId,
7740
+ timestamp: new Date().toISOString(),
7741
+ context: {
7742
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7743
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7744
+ sdkVersion: this.sdkVersion,
7745
+ groupId,
7746
+ },
7747
+ };
7748
+ try {
7749
+ await this.sendEvent(event);
7750
+ this.logger.info(`[Analytics] Group create success tracked for product: ${productId}`);
7751
+ }
7752
+ catch (error) {
7753
+ this.logger.error("[Analytics] Failed to track group create success", error);
7754
+ }
7755
+ }
7756
+ /**
7757
+ * Track group creation failure event
7758
+ */
7759
+ async trackGroupCreateFailure(productId, errorCode, message) {
7760
+ const event = {
7761
+ event: "GROUP_CREATE_FAILURE",
7762
+ productId,
7763
+ sessionId: this.sessionId,
7764
+ timestamp: new Date().toISOString(),
7765
+ context: {
7766
+ pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
7767
+ userAgent: typeof navigator !== "undefined" ? navigator.userAgent : undefined,
7768
+ sdkVersion: this.sdkVersion,
7769
+ errorCode,
7770
+ errorMessage: message,
7771
+ },
7772
+ };
7773
+ try {
7774
+ await this.sendEvent(event);
7775
+ this.logger.info(`[Analytics] Group create failure tracked for product: ${productId}`);
7776
+ }
7777
+ catch (error) {
7778
+ this.logger.error("[Analytics] Failed to track group create failure", error);
7779
+ }
7780
+ }
7208
7781
  /**
7209
7782
  * Track custom event (extensible for future use)
7210
7783
  */
@@ -12650,8 +13223,14 @@ var CoBuySDK = (function (exports) {
12650
13223
  participantsCount !== undefined &&
12651
13224
  participantsCount >= maxParticipants);
12652
13225
  if (isFulfilled && productId && groupId) {
12653
- // Prepare checkout when group is fulfilled (if not already prepared)
12654
- this.prepareCheckoutIfNotDone(productId, groupId);
13226
+ // Only prepare checkout if the current user is actually a member of this group.
13227
+ // this.widgets tracks all WidgetRoot instances; getJoinedGroupId() returns the
13228
+ // joined group ID only when the user has completed a join this session (not for
13229
+ // observers). Calling prepareCheckout for non-members would generate spurious 403s.
13230
+ const userIsInGroup = [...this.widgets].some((w) => w.getJoinedGroupId() === groupId);
13231
+ if (userIsInGroup) {
13232
+ this.prepareCheckoutIfNotDone(productId, groupId);
13233
+ }
12655
13234
  }
12656
13235
  // Emit user-defined callback if provided
12657
13236
  if ((_a = config.events) === null || _a === void 0 ? void 0 : _a.onGroupMemberJoined) {
@@ -12664,8 +13243,11 @@ var CoBuySDK = (function (exports) {
12664
13243
  this.logger.warn("[SDK] Failed to initialize sockets", e);
12665
13244
  }
12666
13245
  }
12667
- // Track page view event after successful initialization
13246
+ // Track session init + page view events after successful initialization
12668
13247
  if (this.analyticsClient) {
13248
+ this.analyticsClient.trackSessionInit(config.geo, config.device).catch((error) => {
13249
+ this.logger.warn("[SDK] Failed to track session init", error);
13250
+ });
12669
13251
  this.analyticsClient.trackPageView().catch((error) => {
12670
13252
  // Non-blocking: Analytics failure should not affect SDK initialization
12671
13253
  this.logger.warn("[SDK] Failed to track page view", error);