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