@ordersune/crm-web-sdk 1.0.6 → 1.0.7

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.
@@ -1,7 +1,5 @@
1
1
  export interface UserProfile {
2
2
  id: string;
3
- user_type: string;
4
- traits: Record<string, any>;
5
3
  }
6
4
  export interface EventOptions {
7
5
  timestamp?: string;
@@ -11,4 +9,30 @@ export interface SDKConfig {
11
9
  apiKey: string;
12
10
  endpoint: string;
13
11
  debug?: boolean;
12
+ batchSize?: number;
13
+ batchInterval?: number;
14
+ }
15
+ export interface PurchasePayload {
16
+ externalId: string;
17
+ itemId: string;
18
+ itemName: string;
19
+ unitPrice: number;
20
+ quantity: number;
21
+ source: string;
22
+ channel: string;
23
+ itemCategory?: string;
24
+ }
25
+ export interface DeviceDetails {
26
+ deviceId: string;
27
+ deviceModel: string;
28
+ browser: string;
29
+ browserVersion: string;
30
+ language: string;
31
+ screenResolution: string;
32
+ userAgent: string;
33
+ platform: string;
34
+ }
35
+ export interface BrowserInfo {
36
+ browserType: string;
37
+ browserVersion: string;
14
38
  }
package/dist/web-sdk.d.ts CHANGED
@@ -10,18 +10,30 @@ export declare class WebSDK {
10
10
  private deviceDetails?;
11
11
  private currentUser?;
12
12
  private eventQueue;
13
+ private purchaseBatch;
14
+ private attributeQueue;
13
15
  private isProcessingQueue;
14
- private customAttributes;
16
+ private batchProcessingInterval?;
15
17
  constructor(config: SDKConfig);
16
- identify(userId: string, traits?: Record<string, any>): void;
18
+ private initialize;
19
+ identify(userId: string): void;
20
+ clearUserData(): void;
17
21
  trackEvent(eventName: string, properties?: Record<string, any>, options?: EventOptions): void;
18
22
  trackCustomAttribute(attributeName: string, attributeValue: any): void;
23
+ logProductPurchase(orderId: string, itemId: string, itemName: string, unitPrice: number, quantity: number, source: string, channel: string, itemCategory?: string): void;
19
24
  getUserProfile(): UserProfile | undefined;
20
- private initialize;
25
+ forceSend(): Promise<void>;
26
+ dispose(): void;
21
27
  private getStoredDeviceId;
28
+ private getStoredUserProfile;
22
29
  private storeDeviceId;
30
+ private storeUserProfile;
31
+ private storeCustomAttributes;
32
+ private loadCustomAttributes;
23
33
  private getGuestUserId;
34
+ private buildPurchasePayload;
24
35
  private initializeDeviceDetails;
36
+ private getBrowserInfo;
25
37
  private detectDeviceType;
26
38
  private detectBrowserType;
27
39
  private detectBrowserVersion;
package/dist/web-sdk.js CHANGED
@@ -6,76 +6,229 @@ const uuid_1 = require("uuid");
6
6
  const STORAGE_PREFIX = "os.";
7
7
  const DEFAULT_BATCH_SIZE = 10;
8
8
  const DEFAULT_BATCH_INTERVAL = 10000;
9
- const SDK_VERSION = "1.0.6";
9
+ const SDK_VERSION = "1.0.7";
10
10
  class WebSDK {
11
11
  constructor(config) {
12
- var _a;
12
+ var _a, _b, _c;
13
13
  this.maxBatchSize = DEFAULT_BATCH_SIZE;
14
14
  this.batchInterval = DEFAULT_BATCH_INTERVAL;
15
15
  this.sdkVersion = SDK_VERSION;
16
16
  this.eventQueue = [];
17
+ this.purchaseBatch = [];
18
+ this.attributeQueue = {};
17
19
  this.isProcessingQueue = false;
18
- this.customAttributes = {};
20
+ if (!config.apiKey || !config.endpoint) {
21
+ throw new Error("API key and endpoint are required");
22
+ }
19
23
  this.apiKey = config.apiKey;
20
24
  this.endpoint = config.endpoint;
21
25
  this.debug = (_a = config.debug) !== null && _a !== void 0 ? _a : false;
26
+ this.maxBatchSize = (_b = config.batchSize) !== null && _b !== void 0 ? _b : DEFAULT_BATCH_SIZE;
27
+ this.batchInterval = (_c = config.batchInterval) !== null && _c !== void 0 ? _c : DEFAULT_BATCH_INTERVAL;
22
28
  this.deviceId = this.getStoredDeviceId() || (0, uuid_1.v4)();
23
- this.initialize();
29
+ // Initialize async
30
+ this.initialize().catch((error) => {
31
+ (0, utils_1.log)(this.debug, "Async initialization failed", error);
32
+ });
24
33
  }
25
- identify(userId, traits = {}) {
26
- var _a;
34
+ async initialize() {
35
+ try {
36
+ (0, utils_1.log)(this.debug, "SDK Initialized");
37
+ this.storeDeviceId();
38
+ await this.initializeDeviceDetails();
39
+ await this.loadCustomAttributes();
40
+ const storedUser = this.getStoredUserProfile();
41
+ if (storedUser) {
42
+ this.currentUser = storedUser;
43
+ (0, utils_1.log)(this.debug, "Loaded user profile", this.currentUser);
44
+ }
45
+ else {
46
+ this.identify("");
47
+ }
48
+ try {
49
+ const timezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
50
+ this.trackCustomAttribute("timezone", timezone);
51
+ }
52
+ catch (error) {
53
+ (0, utils_1.log)(this.debug, "Failed to detect timezone", error);
54
+ }
55
+ this.startBatchProcessing();
56
+ this.trackEvent("app_started", {});
57
+ }
58
+ catch (error) {
59
+ (0, utils_1.log)(this.debug, "Initialization failed", error);
60
+ throw error;
61
+ }
62
+ }
63
+ identify(userId) {
64
+ if (typeof userId !== "string") {
65
+ throw new Error("User ID must be a string");
66
+ }
67
+ const storedUser = this.getStoredUserProfile();
68
+ if ((storedUser === null || storedUser === void 0 ? void 0 : storedUser.id) === userId)
69
+ return;
70
+ (0, utils_1.log)(this.debug, "Identifying user", userId);
27
71
  this.currentUser = {
28
72
  id: userId || this.getGuestUserId(),
29
- user_type: userId ? "registered" : "guest",
30
- traits: { ...(_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.traits, ...traits },
31
73
  };
32
- this.trackEvent("user_identified", {
33
- user_id: this.currentUser.id,
34
- traits,
74
+ this.storeUserProfile();
75
+ }
76
+ clearUserData() {
77
+ this.currentUser = {
78
+ id: this.getGuestUserId(),
79
+ };
80
+ this.attributeQueue = {};
81
+ this.eventQueue = [];
82
+ this.purchaseBatch = [];
83
+ sessionStorage.setItem(`${STORAGE_PREFIX}${this.apiKey}.current_user`, JSON.stringify(this.currentUser));
84
+ sessionStorage.removeItem(`${STORAGE_PREFIX}${this.apiKey}.custom_attributes`);
85
+ this.trackEvent("user_logged_out", {
86
+ device_id: this.deviceId,
35
87
  });
88
+ if (this.debug) {
89
+ (0, utils_1.log)(this.debug, "User data cleared");
90
+ }
36
91
  }
37
92
  trackEvent(eventName, properties = {}, options = {}) {
93
+ if (!eventName || typeof eventName !== "string") {
94
+ throw new Error("Event name is required and must be a string");
95
+ }
38
96
  const payload = {
39
97
  deviceId: this.deviceId,
40
98
  eventName,
41
99
  timestamp: options.timestamp || Date.now(),
42
100
  requestId: options.requestId || (0, uuid_1.v4)(),
43
- properties,
101
+ properties: properties,
44
102
  };
45
103
  this.queueEvent(payload);
46
104
  }
47
105
  trackCustomAttribute(attributeName, attributeValue) {
48
- this.customAttributes[attributeName] = attributeValue;
106
+ if (!attributeName || typeof attributeName !== "string") {
107
+ throw new Error("Attribute name is required and must be a string");
108
+ }
109
+ this.attributeQueue = {
110
+ ...this.attributeQueue,
111
+ [attributeName]: attributeValue,
112
+ };
113
+ (0, utils_1.log)(this.debug, "Custom attribute queued", attributeName, attributeValue);
114
+ this.storeCustomAttributes();
115
+ }
116
+ logProductPurchase(orderId, itemId, itemName, unitPrice, quantity, source, channel, itemCategory) {
117
+ const purchasePayload = this.buildPurchasePayload({
118
+ externalId: orderId,
119
+ itemId,
120
+ itemName,
121
+ unitPrice,
122
+ quantity,
123
+ source,
124
+ channel,
125
+ itemCategory,
126
+ });
127
+ this.purchaseBatch.push(purchasePayload);
128
+ if (this.debug) {
129
+ (0, utils_1.log)(this.debug, "Purchase event queued", purchasePayload);
130
+ }
49
131
  }
50
132
  getUserProfile() {
51
133
  return this.currentUser;
52
134
  }
53
- initialize() {
54
- this.storeDeviceId();
55
- this.initializeDeviceDetails();
56
- this.identify("");
57
- this.startBatchProcessing();
58
- (0, utils_1.log)(this.debug, "SDK Initialized");
135
+ async forceSend() {
136
+ if (this.eventQueue.length > 0 ||
137
+ this.purchaseBatch.length > 0 ||
138
+ this.attributeQueue.length > 0) {
139
+ await this.processBatch();
140
+ }
141
+ }
142
+ dispose() {
143
+ if (this.batchProcessingInterval) {
144
+ clearInterval(this.batchProcessingInterval);
145
+ }
146
+ if (this.eventQueue.length > 0 ||
147
+ this.purchaseBatch.length > 0 ||
148
+ this.attributeQueue.length > 0) {
149
+ this.processBatch().catch((error) => {
150
+ (0, utils_1.log)(this.debug, "Error during disposal", error);
151
+ });
152
+ }
59
153
  }
60
154
  getStoredDeviceId() {
61
155
  return localStorage.getItem(`${STORAGE_PREFIX}${this.apiKey}.device_id`);
62
156
  }
157
+ getStoredUserProfile() {
158
+ const storedUser = sessionStorage.getItem(`${STORAGE_PREFIX}${this.apiKey}.current_user`);
159
+ return storedUser ? JSON.parse(storedUser) : null;
160
+ }
63
161
  storeDeviceId() {
64
162
  localStorage.setItem(`${STORAGE_PREFIX}${this.apiKey}.device_id`, this.deviceId);
65
163
  }
164
+ storeUserProfile() {
165
+ if (this.currentUser) {
166
+ sessionStorage.setItem(`${STORAGE_PREFIX}${this.apiKey}.current_user`, JSON.stringify(this.currentUser));
167
+ }
168
+ }
169
+ storeCustomAttributes() {
170
+ const existingAttributes = sessionStorage.getItem(`${STORAGE_PREFIX}${this.apiKey}.custom_attributes`);
171
+ const existingParsed = existingAttributes
172
+ ? JSON.parse(existingAttributes)
173
+ : {};
174
+ const mergedAttributes = {
175
+ ...existingParsed,
176
+ ...this.attributeQueue,
177
+ };
178
+ sessionStorage.setItem(`${STORAGE_PREFIX}${this.apiKey}.custom_attributes`, JSON.stringify(mergedAttributes));
179
+ }
180
+ async loadCustomAttributes() {
181
+ const stored = sessionStorage.getItem(`${STORAGE_PREFIX}${this.apiKey}.custom_attributes`);
182
+ if (stored) {
183
+ this.attributeQueue = {
184
+ ...this.attributeQueue,
185
+ ...JSON.parse(stored),
186
+ };
187
+ }
188
+ }
66
189
  getGuestUserId() {
67
190
  return `guest_user_${this.deviceId}`;
68
191
  }
192
+ buildPurchasePayload({ externalId, itemId, itemName, unitPrice, quantity, source, channel, itemCategory, }) {
193
+ const timestamp = Date.now();
194
+ return {
195
+ external_id: externalId,
196
+ item_id: itemId,
197
+ item_name: itemName,
198
+ item_category: itemCategory,
199
+ quantity: quantity,
200
+ unit_price: unitPrice,
201
+ total_price: unitPrice * quantity,
202
+ source: source,
203
+ channel: channel,
204
+ purchased_at: timestamp,
205
+ };
206
+ }
69
207
  async initializeDeviceDetails() {
208
+ const deviceType = await this.detectDeviceType();
209
+ const browserInfo = await this.getBrowserInfo();
70
210
  this.deviceDetails = {
71
- deviceModel: await this.detectDeviceType(),
72
- browser: this.detectBrowserType(),
73
- browserVersion: this.detectBrowserVersion(),
211
+ deviceId: this.deviceId,
212
+ deviceModel: deviceType,
213
+ browser: browserInfo.browserType,
214
+ browserVersion: browserInfo.browserVersion,
215
+ language: navigator.language,
216
+ screenResolution: `${window.screen.width}x${window.screen.height}`,
217
+ userAgent: navigator.userAgent,
218
+ platform: navigator.platform,
219
+ };
220
+ }
221
+ async getBrowserInfo() {
222
+ const browserType = this.detectBrowserType();
223
+ const browserVersion = this.detectBrowserVersion();
224
+ return {
225
+ browserType,
226
+ browserVersion,
74
227
  };
75
228
  }
76
229
  async detectDeviceType() {
77
- if ("userAgentData" in navigator) {
78
- try {
230
+ try {
231
+ if ("userAgentData" in navigator) {
79
232
  const data = await navigator.userAgentData.getHighEntropyValues(["platform", "mobile"]);
80
233
  if (data.mobile) {
81
234
  return data.platform.toLowerCase().includes("ios")
@@ -90,9 +243,9 @@ class WebSDK {
90
243
  if (platform.includes("linux"))
91
244
  return "linux";
92
245
  }
93
- catch (error) {
94
- (0, utils_1.log)(this.debug, "Client Hints API failed", error);
95
- }
246
+ }
247
+ catch (error) {
248
+ (0, utils_1.log)(this.debug, "Client Hints API failed", error);
96
249
  }
97
250
  const userAgent = navigator.userAgent.toLowerCase();
98
251
  if (/iphone|ipad|ipod/.test(userAgent))
@@ -108,58 +261,64 @@ class WebSDK {
108
261
  return "unknown";
109
262
  }
110
263
  detectBrowserType() {
111
- if ("userAgentData" in navigator) {
112
- try {
264
+ try {
265
+ if ("userAgentData" in navigator) {
113
266
  const brands = navigator.userAgentData.brands;
114
267
  const brand = brands.find((b) => !["Chrome HTML", "Chromium", "Not A(Brand"].includes(b.brand));
115
268
  if (brand)
116
269
  return brand.brand.toLowerCase();
117
270
  }
118
- catch (error) {
119
- (0, utils_1.log)(this.debug, "Browser detection failed", error);
120
- }
271
+ const userAgent = navigator.userAgent.toLowerCase();
272
+ if (/edg\//.test(userAgent))
273
+ return "edge";
274
+ if (/chrome/.test(userAgent) && !/edg\//.test(userAgent))
275
+ return "chrome";
276
+ if (/firefox/.test(userAgent))
277
+ return "firefox";
278
+ if (/safari/.test(userAgent) && !/chrome/.test(userAgent))
279
+ return "safari";
280
+ return "unknown";
281
+ }
282
+ catch (error) {
283
+ (0, utils_1.log)(this.debug, "Browser detection failed", error);
284
+ return "unknown";
121
285
  }
122
- const userAgent = navigator.userAgent.toLowerCase();
123
- if (/edg\//.test(userAgent))
124
- return "edge";
125
- if (/chrome/.test(userAgent) && !/edg\//.test(userAgent))
126
- return "chrome";
127
- if (/firefox/.test(userAgent))
128
- return "firefox";
129
- if (/safari/.test(userAgent) && !/chrome/.test(userAgent))
130
- return "safari";
131
- return "unknown";
132
286
  }
133
287
  detectBrowserVersion() {
134
- if ("userAgentData" in navigator) {
135
- try {
288
+ try {
289
+ if ("userAgentData" in navigator) {
136
290
  const brands = navigator.userAgentData.brands;
137
291
  const brand = brands.find((b) => !["Chrome HTML", "Chromium", "Not A(Brand"].includes(b.brand));
138
292
  if (brand)
139
293
  return brand.version;
140
294
  }
141
- catch (error) {
142
- (0, utils_1.log)(this.debug, "Browser version detection failed", error);
143
- }
295
+ const userAgent = navigator.userAgent.toLowerCase();
296
+ const browserPatterns = {
297
+ chrome: /chrome\/(\d+)/,
298
+ firefox: /firefox\/(\d+)/,
299
+ safari: /version\/(\d+)/,
300
+ edge: /edg\/(\d+)/,
301
+ };
302
+ const browserType = this.detectBrowserType();
303
+ const pattern = browserPatterns[browserType];
304
+ if (!pattern)
305
+ return "unknown";
306
+ const match = userAgent.match(pattern);
307
+ return match ? match[1] : "unknown";
144
308
  }
145
- const userAgent = navigator.userAgent.toLowerCase();
146
- const browserPatterns = {
147
- chrome: /chrome\/(\d+)/,
148
- firefox: /firefox\/(\d+)/,
149
- safari: /version\/(\d+)/,
150
- edge: /edg\/(\d+)/,
151
- };
152
- const browserType = this.detectBrowserType();
153
- const pattern = browserPatterns[browserType];
154
- if (!pattern)
309
+ catch (error) {
310
+ (0, utils_1.log)(this.debug, "Browser version detection failed", error);
155
311
  return "unknown";
156
- const match = userAgent.match(pattern);
157
- return match ? match[1] : "unknown";
312
+ }
158
313
  }
159
314
  startBatchProcessing() {
160
- setInterval(() => {
161
- if (this.eventQueue.length > 0) {
162
- this.processBatch();
315
+ this.batchProcessingInterval = setInterval(() => {
316
+ if (this.eventQueue.length > 0 ||
317
+ this.purchaseBatch.length > 0 ||
318
+ this.attributeQueue.length > 0) {
319
+ this.processBatch().catch((error) => {
320
+ (0, utils_1.log)(this.debug, "Batch processing failed", error);
321
+ });
163
322
  }
164
323
  }, this.batchInterval);
165
324
  }
@@ -167,33 +326,48 @@ class WebSDK {
167
326
  this.eventQueue.push(payload);
168
327
  (0, utils_1.log)(this.debug, "Event queued", payload);
169
328
  if (this.eventQueue.length >= this.maxBatchSize) {
170
- this.processBatch();
329
+ this.processBatch().catch((error) => {
330
+ (0, utils_1.log)(this.debug, "Batch processing failed", error);
331
+ });
171
332
  }
172
333
  }
173
334
  async processBatch() {
174
335
  if (this.isProcessingQueue)
175
336
  return;
176
337
  this.isProcessingQueue = true;
177
- const batchToSend = this.eventQueue.splice(0, this.maxBatchSize);
338
+ const eventsToSend = [...this.eventQueue];
339
+ const purchasesToSend = [...this.purchaseBatch];
340
+ const attributesToSend = { ...this.attributeQueue };
341
+ this.eventQueue = [];
342
+ this.purchaseBatch = [];
343
+ this.attributeQueue = {};
178
344
  try {
179
- await this.sendBatchToServer(batchToSend);
345
+ await this.sendBatchToServer(eventsToSend, purchasesToSend, attributesToSend);
180
346
  }
181
347
  catch (error) {
182
- this.eventQueue.unshift(...batchToSend);
348
+ this.eventQueue.unshift(...eventsToSend);
349
+ this.purchaseBatch.unshift(...purchasesToSend);
350
+ this.attributeQueue = { ...this.attributeQueue, ...attributesToSend };
183
351
  (0, utils_1.log)(this.debug, "Batch processing failed", error);
352
+ throw error;
184
353
  }
185
354
  finally {
186
355
  this.isProcessingQueue = false;
187
356
  }
188
357
  }
189
- async sendBatchToServer(batch) {
358
+ async sendBatchToServer(events, purchases, currentAttributes) {
190
359
  var _a;
360
+ if (!this.deviceDetails) {
361
+ await this.initializeDeviceDetails();
362
+ }
191
363
  const payload = {
192
364
  sdkVersion: this.sdkVersion,
193
365
  deviceInfo: this.deviceDetails,
194
366
  userId: (_a = this.currentUser) === null || _a === void 0 ? void 0 : _a.id,
195
- events: batch,
196
- userAttributes: this.customAttributes,
367
+ time: Date.now(),
368
+ events: events,
369
+ purchases: purchases,
370
+ userAttributes: currentAttributes,
197
371
  };
198
372
  try {
199
373
  const response = await fetch(`${this.endpoint}/analytics/data`, {
@@ -207,10 +381,17 @@ class WebSDK {
207
381
  if (!response.ok) {
208
382
  throw new Error(`HTTP error! status: ${response.status}`);
209
383
  }
384
+ if (this.debug) {
385
+ (0, utils_1.log)(this.debug, `Batch dispatched successfully: ${payload.events.length} events, ${payload.purchases.length} purchases, ${Object.keys(payload.userAttributes).length} attributes`);
386
+ }
210
387
  await response.json();
388
+ if (Object.keys(currentAttributes).length > 0) {
389
+ this.attributeQueue = {};
390
+ sessionStorage.removeItem(`${STORAGE_PREFIX}${this.apiKey}.custom_attributes`);
391
+ }
211
392
  }
212
393
  catch (error) {
213
- (0, utils_1.log)(this.debug, "Failed to send batch to server", error);
394
+ (0, utils_1.log)(this.debug, "Failed to send payload to server", error);
214
395
  throw error;
215
396
  }
216
397
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ordersune/crm-web-sdk",
3
- "version": "1.0.6",
3
+ "version": "1.0.7",
4
4
  "main": "dist/index.js",
5
5
  "types": "dist/index.d.ts",
6
6
  "publishConfig": {