@cshah18/sdk 4.12.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 +719 -26
- package/dist/cobuy-sdk.esm.js.map +1 -1
- package/dist/cobuy-sdk.umd.js +719 -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 +47 -2
- package/dist/types/core/cobuy.d.ts +27 -1
- package/dist/types/core/endpoints.d.ts +2 -0
- package/dist/types/core/types.d.ts +121 -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.umd.js
CHANGED
|
@@ -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
|
-
//
|
|
3585
|
-
|
|
3586
|
-
|
|
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.
|
|
4441
|
+
if (!detail || !detail.product_id) {
|
|
4442
|
+
return;
|
|
4443
|
+
}
|
|
4444
|
+
if (this.currentProductId && detail.product_id !== this.currentProductId) {
|
|
4393
4445
|
return;
|
|
4394
4446
|
}
|
|
4395
|
-
|
|
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 ((
|
|
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
|
|
@@ -6001,6 +6122,8 @@ var CoBuySDK = (function (exports) {
|
|
|
6001
6122
|
GROUP_CHECKOUT_PREPARE: "/v1/sdk/groups/:groupId/checkout/prepare",
|
|
6002
6123
|
GROUP_CHECKOUT_VALIDATE: "/v1/sdk/groups/:groupId/checkout/validate",
|
|
6003
6124
|
GROUP_CHECKOUT_CONFIRM: "/v1/sdk/groups/:groupId/checkout/confirm",
|
|
6125
|
+
// Invite endpoints
|
|
6126
|
+
INVITE_RESOLVE: "/v1/sdk/invite/resolve",
|
|
6004
6127
|
};
|
|
6005
6128
|
/**
|
|
6006
6129
|
* Build full API URL from base URL and endpoint path
|
|
@@ -6039,8 +6162,11 @@ var CoBuySDK = (function (exports) {
|
|
|
6039
6162
|
var _a;
|
|
6040
6163
|
this.traceId = null;
|
|
6041
6164
|
this.rewardCache = new Map();
|
|
6165
|
+
this.productContextCache = new Map();
|
|
6042
6166
|
this.REWARD_CACHE_TTL = 60000; // 1 minute
|
|
6043
|
-
this.
|
|
6167
|
+
this.PRODUCT_CONTEXT_CACHE_TTL = 30000; // 30 seconds
|
|
6168
|
+
this.pendingRewardRequests = new Map();
|
|
6169
|
+
this.pendingContextRequests = new Map();
|
|
6044
6170
|
this.baseUrl = config.baseUrl;
|
|
6045
6171
|
this.authStrategy = config.authStrategy;
|
|
6046
6172
|
this.sessionId = config.sessionId;
|
|
@@ -6436,6 +6562,106 @@ var CoBuySDK = (function (exports) {
|
|
|
6436
6562
|
getTraceId() {
|
|
6437
6563
|
return this.traceId;
|
|
6438
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
|
+
}
|
|
6439
6665
|
/**
|
|
6440
6666
|
* Get product reward information
|
|
6441
6667
|
*
|
|
@@ -6480,13 +6706,31 @@ var CoBuySDK = (function (exports) {
|
|
|
6480
6706
|
data: cached.data,
|
|
6481
6707
|
};
|
|
6482
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
|
+
}
|
|
6483
6717
|
const cacheKey = `reward:${productId}`;
|
|
6484
|
-
const pending = this.
|
|
6718
|
+
const pending = this.pendingRewardRequests.get(cacheKey);
|
|
6485
6719
|
if (pending) {
|
|
6486
6720
|
this.logger.info(`Deduplicating request for product: ${productId}`);
|
|
6487
6721
|
return pending;
|
|
6488
6722
|
}
|
|
6489
|
-
const promise =
|
|
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) => {
|
|
6490
6734
|
if (response.success && response.data) {
|
|
6491
6735
|
this.rewardCache.set(productId, {
|
|
6492
6736
|
data: response.data,
|
|
@@ -6495,8 +6739,8 @@ var CoBuySDK = (function (exports) {
|
|
|
6495
6739
|
}
|
|
6496
6740
|
return response;
|
|
6497
6741
|
});
|
|
6498
|
-
this.
|
|
6499
|
-
promise.then(() => this.
|
|
6742
|
+
this.pendingRewardRequests.set(cacheKey, promise);
|
|
6743
|
+
promise.then(() => this.pendingRewardRequests.delete(cacheKey), () => this.pendingRewardRequests.delete(cacheKey));
|
|
6500
6744
|
return promise;
|
|
6501
6745
|
}
|
|
6502
6746
|
/**
|
|
@@ -6512,6 +6756,7 @@ var CoBuySDK = (function (exports) {
|
|
|
6512
6756
|
*/
|
|
6513
6757
|
clearRewardCache() {
|
|
6514
6758
|
this.rewardCache.clear();
|
|
6759
|
+
this.productContextCache.clear();
|
|
6515
6760
|
}
|
|
6516
6761
|
async fetchProductReward(productId) {
|
|
6517
6762
|
var _a;
|
|
@@ -6559,7 +6804,7 @@ var CoBuySDK = (function (exports) {
|
|
|
6559
6804
|
* ```
|
|
6560
6805
|
*/
|
|
6561
6806
|
async getProductPrimaryGroup(productId) {
|
|
6562
|
-
var _a;
|
|
6807
|
+
var _a, _b;
|
|
6563
6808
|
if (!validateProductId(productId)) {
|
|
6564
6809
|
return {
|
|
6565
6810
|
success: false,
|
|
@@ -6569,15 +6814,38 @@ var CoBuySDK = (function (exports) {
|
|
|
6569
6814
|
},
|
|
6570
6815
|
};
|
|
6571
6816
|
}
|
|
6572
|
-
const
|
|
6573
|
-
|
|
6574
|
-
|
|
6575
|
-
|
|
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) {
|
|
6576
6832
|
return {
|
|
6577
6833
|
success: true,
|
|
6578
|
-
data:
|
|
6834
|
+
data: Object.assign(Object.assign({}, contextGroup), { group: contextGroup }),
|
|
6579
6835
|
};
|
|
6580
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
|
+
}
|
|
6581
6849
|
return {
|
|
6582
6850
|
success: false,
|
|
6583
6851
|
error: response.error || {
|
|
@@ -6625,6 +6893,25 @@ var CoBuySDK = (function (exports) {
|
|
|
6625
6893
|
},
|
|
6626
6894
|
};
|
|
6627
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
|
+
}
|
|
6628
6915
|
const endpoint = buildApiUrl("", API_ENDPOINTS.PRODUCT_ACTIVE_GROUPS, { productId });
|
|
6629
6916
|
this.logger.info(`Fetching active groups for product: ${productId}`);
|
|
6630
6917
|
const response = await this.get(endpoint);
|
|
@@ -6811,6 +7098,58 @@ var CoBuySDK = (function (exports) {
|
|
|
6811
7098
|
},
|
|
6812
7099
|
};
|
|
6813
7100
|
}
|
|
7101
|
+
/**
|
|
7102
|
+
* Resolve an invite token to get product and group information
|
|
7103
|
+
*
|
|
7104
|
+
* Resolves an invite link token (e.g., from https://brand.cobuyza.co.za/i/QMeHD7ew)
|
|
7105
|
+
* to get the associated product, group, and invite metadata.
|
|
7106
|
+
*
|
|
7107
|
+
* @param {string} inviteToken - The invite token from the URL
|
|
7108
|
+
* @returns {Promise<ApiResponse<InviteResolveResponseData>>} Product and group information
|
|
7109
|
+
*
|
|
7110
|
+
* @description
|
|
7111
|
+
* Features:
|
|
7112
|
+
* - Resolves invite tokens to product/group details
|
|
7113
|
+
* - Tracks invite click counts
|
|
7114
|
+
* - Returns invite metadata (shared_via, click_count)
|
|
7115
|
+
* - Used for invite link handling in applications
|
|
7116
|
+
*
|
|
7117
|
+
* @example
|
|
7118
|
+
* ```typescript
|
|
7119
|
+
* // Resolve invite token from URL
|
|
7120
|
+
* const response = await client.resolveInvite('QMeHD7ew');
|
|
7121
|
+
*
|
|
7122
|
+
* if (response.success) {
|
|
7123
|
+
* const productId = response.data.product.id;
|
|
7124
|
+
* const groupId = response.data.group.id;
|
|
7125
|
+
* // Navigate to product page
|
|
7126
|
+
* window.location.href = `/product/${productId}`;
|
|
7127
|
+
* }
|
|
7128
|
+
* ```
|
|
7129
|
+
*/
|
|
7130
|
+
async resolveInvite(inviteToken) {
|
|
7131
|
+
const endpoint = buildApiUrl("", API_ENDPOINTS.INVITE_RESOLVE, undefined, {
|
|
7132
|
+
invite_token: inviteToken,
|
|
7133
|
+
});
|
|
7134
|
+
this.logger.info(`Resolving invite token: ${inviteToken}`);
|
|
7135
|
+
const response = await this.get(endpoint);
|
|
7136
|
+
if (response.success) {
|
|
7137
|
+
const payload = response.data;
|
|
7138
|
+
const inviteData = ((payload === null || payload === void 0 ? void 0 : payload.data) || response.data);
|
|
7139
|
+
this.logger.info(`Invite resolved successfully for product: ${inviteData.product.id}`);
|
|
7140
|
+
return {
|
|
7141
|
+
success: true,
|
|
7142
|
+
data: inviteData,
|
|
7143
|
+
};
|
|
7144
|
+
}
|
|
7145
|
+
return {
|
|
7146
|
+
success: false,
|
|
7147
|
+
error: response.error || {
|
|
7148
|
+
message: "Failed to resolve invite",
|
|
7149
|
+
code: "INVITE_RESOLVE_ERROR",
|
|
7150
|
+
},
|
|
7151
|
+
};
|
|
7152
|
+
}
|
|
6814
7153
|
/**
|
|
6815
7154
|
* Set or update contact information for the current session
|
|
6816
7155
|
*
|
|
@@ -7077,6 +7416,7 @@ var CoBuySDK = (function (exports) {
|
|
|
7077
7416
|
const event = {
|
|
7078
7417
|
event: "CTA_CLICKED",
|
|
7079
7418
|
productId,
|
|
7419
|
+
sessionId: this.sessionId,
|
|
7080
7420
|
timestamp: new Date().toISOString(),
|
|
7081
7421
|
context: {
|
|
7082
7422
|
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
@@ -7129,12 +7469,32 @@ var CoBuySDK = (function (exports) {
|
|
|
7129
7469
|
throw new CoBuyApiError(error instanceof Error ? error.message : "Unknown analytics error", "ANALYTICS_ERROR", { originalError: error });
|
|
7130
7470
|
}
|
|
7131
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
|
+
}
|
|
7132
7491
|
/**
|
|
7133
7492
|
* Track page view event
|
|
7134
7493
|
*/
|
|
7135
7494
|
async trackPageView() {
|
|
7136
7495
|
const event = {
|
|
7137
7496
|
event: "PAGE_VIEW",
|
|
7497
|
+
sessionId: this.sessionId,
|
|
7138
7498
|
timestamp: new Date().toISOString(),
|
|
7139
7499
|
context: {
|
|
7140
7500
|
pageUrl: typeof window !== "undefined" ? window.location.href : undefined,
|
|
@@ -7151,6 +7511,273 @@ var CoBuySDK = (function (exports) {
|
|
|
7151
7511
|
this.logger.error("[Analytics] Failed to track page view", error);
|
|
7152
7512
|
}
|
|
7153
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
|
+
}
|
|
7154
7781
|
/**
|
|
7155
7782
|
* Track custom event (extensible for future use)
|
|
7156
7783
|
*/
|
|
@@ -12596,8 +13223,14 @@ var CoBuySDK = (function (exports) {
|
|
|
12596
13223
|
participantsCount !== undefined &&
|
|
12597
13224
|
participantsCount >= maxParticipants);
|
|
12598
13225
|
if (isFulfilled && productId && groupId) {
|
|
12599
|
-
//
|
|
12600
|
-
this.
|
|
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
|
+
}
|
|
12601
13234
|
}
|
|
12602
13235
|
// Emit user-defined callback if provided
|
|
12603
13236
|
if ((_a = config.events) === null || _a === void 0 ? void 0 : _a.onGroupMemberJoined) {
|
|
@@ -12610,8 +13243,11 @@ var CoBuySDK = (function (exports) {
|
|
|
12610
13243
|
this.logger.warn("[SDK] Failed to initialize sockets", e);
|
|
12611
13244
|
}
|
|
12612
13245
|
}
|
|
12613
|
-
// Track page view
|
|
13246
|
+
// Track session init + page view events after successful initialization
|
|
12614
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
|
+
});
|
|
12615
13251
|
this.analyticsClient.trackPageView().catch((error) => {
|
|
12616
13252
|
// Non-blocking: Analytics failure should not affect SDK initialization
|
|
12617
13253
|
this.logger.warn("[SDK] Failed to track page view", error);
|
|
@@ -12917,6 +13553,63 @@ var CoBuySDK = (function (exports) {
|
|
|
12917
13553
|
this.logger.error("Error setting contact information", error);
|
|
12918
13554
|
}
|
|
12919
13555
|
}
|
|
13556
|
+
/**
|
|
13557
|
+
* Resolve an invite token to get product and group information
|
|
13558
|
+
*
|
|
13559
|
+
* Resolves an invite link token (e.g., from https://brand.cobuyza.co.za/i/QMeHD7ew)
|
|
13560
|
+
* to get the associated product, group, and invite metadata. Use this to handle
|
|
13561
|
+
* invite links in your application routing.
|
|
13562
|
+
*
|
|
13563
|
+
* @param inviteToken - The invite token from the URL
|
|
13564
|
+
* @returns Promise with invite resolution data or null if failed
|
|
13565
|
+
*
|
|
13566
|
+
* @example
|
|
13567
|
+
* ```typescript
|
|
13568
|
+
* // Extract token from URL path like /i/QMeHD7ew
|
|
13569
|
+
* const pathParts = window.location.pathname.split('/i/');
|
|
13570
|
+
* if (pathParts.length > 1) {
|
|
13571
|
+
* const inviteToken = pathParts[1];
|
|
13572
|
+
* const inviteData = await CoBuy.resolveInvite(inviteToken);
|
|
13573
|
+
*
|
|
13574
|
+
* if (inviteData) {
|
|
13575
|
+
* // Navigate to product page
|
|
13576
|
+
* window.location.href = `/product/${inviteData.product.id}`;
|
|
13577
|
+
* }
|
|
13578
|
+
* }
|
|
13579
|
+
* ```
|
|
13580
|
+
*/
|
|
13581
|
+
async resolveInvite(inviteToken) {
|
|
13582
|
+
if (!this.configManager.isInitialized()) {
|
|
13583
|
+
this.logger.warn("SDK not initialized, cannot resolve invite");
|
|
13584
|
+
return null;
|
|
13585
|
+
}
|
|
13586
|
+
if (!this.apiClient) {
|
|
13587
|
+
this.logger.error("API client not available");
|
|
13588
|
+
return null;
|
|
13589
|
+
}
|
|
13590
|
+
if (!inviteToken || typeof inviteToken !== "string" || inviteToken.trim() === "") {
|
|
13591
|
+
this.logger.error("Invalid invite token provided");
|
|
13592
|
+
return null;
|
|
13593
|
+
}
|
|
13594
|
+
try {
|
|
13595
|
+
const response = await this.apiClient.resolveInvite(inviteToken.trim());
|
|
13596
|
+
if (response.success && response.data) {
|
|
13597
|
+
this.logger.info(`Invite resolved successfully: product=${response.data.product.id}, group=${response.data.group.id}`);
|
|
13598
|
+
return response.data;
|
|
13599
|
+
}
|
|
13600
|
+
else {
|
|
13601
|
+
if (this.handleFraudError(response.error, "resolveInvite")) {
|
|
13602
|
+
return null;
|
|
13603
|
+
}
|
|
13604
|
+
this.logger.error("Failed to resolve invite", response.error);
|
|
13605
|
+
return null;
|
|
13606
|
+
}
|
|
13607
|
+
}
|
|
13608
|
+
catch (error) {
|
|
13609
|
+
this.logger.error("Error resolving invite", error);
|
|
13610
|
+
return null;
|
|
13611
|
+
}
|
|
13612
|
+
}
|
|
12920
13613
|
/**
|
|
12921
13614
|
* Validate checkout for a group
|
|
12922
13615
|
*
|