@chordcommerce/analytics 1.23.1 → 1.25.0-beta.2

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.
@@ -63,10 +63,7 @@ var persistence_js_1 = require("./consent/persistence.js");
63
63
  var index_js_2 = require("../validators/index.js");
64
64
  var utils_js_1 = require("../utils.js");
65
65
  var DEBUG_MODE_STORAGE_KEY = 'chordcommerce/analytics/debug';
66
- var CONSENT_QUEUE_STORAGE_KEY = 'chordcommerce/analytics/consent-queue';
67
- var CONSENT_QUEUE_TTL_MS = 5 * 60 * 1000; // 5 minutes
68
- var CONSENT_QUEUE_MAX_SIZE_BYTES = 100 * 1024; // 100KB max queue size
69
- var CONSENT_QUEUE_MAX_EVENTS = 50; // Max number of events to queue
66
+ var OPERATION_TIMEOUT_MS = 5000;
70
67
  var ChordAnalytics = /** @class */ (function () {
71
68
  function ChordAnalytics(options) {
72
69
  var _this = this;
@@ -88,138 +85,12 @@ var ChordAnalytics = /** @class */ (function () {
88
85
  */
89
86
  this.cdpReadyPromise = null;
90
87
  this.consentConfig = null;
91
- // Non-null while reset() is in flight. Acts as both the coalescing handle and
92
- // the gate that track/identify/page await before executing.
93
- this.resetCompletePromise = null;
94
- /**
95
- * Queue for events waiting for consent to be ready
96
- */
97
- this.consentEventQueue = [];
98
- /**
99
- * Whether we're currently waiting for consent and watching for it
100
- */
101
- this.consentWatcherActive = false;
102
- /**
103
- * Promise that resolves when consent is ready (shared across all queued events)
104
- */
88
+ // Serial queue — all outgoing operations run one at a time in call order.
89
+ this.operationQueue = Promise.resolve();
90
+ // True while a reset() operation is executing in the queue.
91
+ this.resetting = false;
92
+ // Shared promise for concurrent waitForConsent() callers.
105
93
  this.consentReadyPromise = null;
106
- /**
107
- * Persist the consent event queue to sessionStorage for recovery on instance recreation.
108
- * Validates queue size before storing to prevent QuotaExceededError.
109
- */
110
- this.persistQueueToStorage = function () {
111
- if (typeof window === 'undefined' || !window.sessionStorage)
112
- return;
113
- try {
114
- // Limit number of events to prevent unbounded queue growth
115
- var eventsToStore = _this.consentEventQueue.slice(-CONSENT_QUEUE_MAX_EVENTS);
116
- var persistedEvents = eventsToStore.map(function (item) { return ({
117
- type: item.type,
118
- args: item.args,
119
- timestamp: item.timestamp,
120
- }); });
121
- var serialized = JSON.stringify(persistedEvents);
122
- // Check serialized size before attempting to store
123
- var sizeInBytes = new Blob([serialized]).size;
124
- if (sizeInBytes > CONSENT_QUEUE_MAX_SIZE_BYTES) {
125
- _this.logger("[Chord Analytics]: Consent queue too large (".concat(Math.round(sizeInBytes / 1024), "KB), skipping persistence"));
126
- return;
127
- }
128
- window.sessionStorage.setItem(CONSENT_QUEUE_STORAGE_KEY, serialized);
129
- }
130
- catch (error) {
131
- _this.logger('[Chord Analytics]: Error persisting queue to storage:', error);
132
- }
133
- };
134
- /**
135
- * Restore pending events from sessionStorage (keeps events less than 5 min old).
136
- * Called on new instance initialization.
137
- */
138
- this.restoreQueueFromStorage = function () {
139
- if (typeof window === 'undefined' || !window.sessionStorage)
140
- return;
141
- try {
142
- var stored = window.sessionStorage.getItem(CONSENT_QUEUE_STORAGE_KEY);
143
- if (!stored)
144
- return;
145
- var persistedEvents = void 0;
146
- try {
147
- persistedEvents = JSON.parse(stored);
148
- }
149
- catch (parseError) {
150
- _this.logger('[Chord Analytics]: Invalid JSON in stored consent queue, clearing storage:', parseError);
151
- window.sessionStorage.removeItem(CONSENT_QUEUE_STORAGE_KEY);
152
- return;
153
- }
154
- // Validate that parsed data is an array
155
- if (!Array.isArray(persistedEvents)) {
156
- _this.logger('[Chord Analytics]: Stored consent queue is not an array, clearing storage');
157
- window.sessionStorage.removeItem(CONSENT_QUEUE_STORAGE_KEY);
158
- return;
159
- }
160
- var now_1 = Date.now();
161
- // Filter out expired events and events with invalid structure
162
- var validEvents = persistedEvents.filter(function (event) {
163
- // Validate event structure
164
- if (typeof event !== 'object' || event === null)
165
- return false;
166
- if (!['track', 'page', 'identify'].includes(event.type))
167
- return false;
168
- if (!Array.isArray(event.args))
169
- return false;
170
- if (typeof event.timestamp !== 'number')
171
- return false;
172
- // Filter out expired events
173
- return now_1 - event.timestamp < CONSENT_QUEUE_TTL_MS;
174
- });
175
- if (validEvents.length === 0) {
176
- window.sessionStorage.removeItem(CONSENT_QUEUE_STORAGE_KEY);
177
- return;
178
- }
179
- if (_this.options.enableLogging && _this.options.debug) {
180
- // eslint-disable-next-line no-console
181
- console.log("[Chord Analytics DEBUG]: Restoring ".concat(validEvents.length, " events from sessionStorage"));
182
- }
183
- // Add restored events to the queue
184
- validEvents.forEach(function (event) {
185
- _this.consentEventQueue.push({
186
- type: event.type,
187
- args: event.args,
188
- timestamp: event.timestamp,
189
- // eslint-disable-next-line @typescript-eslint/no-empty-function
190
- resolve: function () { },
191
- });
192
- });
193
- // Start watching for consent if we restored events
194
- if (validEvents.length > 0 && !_this.consentWatcherActive) {
195
- _this.consentWatcherActive = true;
196
- _this.watchForConsentAndFlush();
197
- }
198
- }
199
- catch (error) {
200
- _this.logger('[Chord Analytics]: Error restoring queue from storage:', error);
201
- // Clear corrupted storage
202
- try {
203
- window.sessionStorage.removeItem(CONSENT_QUEUE_STORAGE_KEY);
204
- }
205
- catch (_a) {
206
- // Ignore cleanup errors
207
- }
208
- }
209
- };
210
- /**
211
- * Clear the persisted queue from sessionStorage after successful flush.
212
- */
213
- this.clearQueueFromStorage = function () {
214
- if (typeof window === 'undefined' || !window.sessionStorage)
215
- return;
216
- try {
217
- window.sessionStorage.removeItem(CONSENT_QUEUE_STORAGE_KEY);
218
- }
219
- catch (error) {
220
- _this.logger('[Chord Analytics]: Error clearing queue from storage:', error);
221
- }
222
- };
223
94
  /**
224
95
  * Set debug mode for Chord Analytics
225
96
  * @param enabled - Whether debug mode should be enabled
@@ -249,6 +120,32 @@ var ChordAnalytics = /** @class */ (function () {
249
120
  return null;
250
121
  return (window === null || window === void 0 ? void 0 : window["_".concat(namespace)]) || (0, cdp_js_1.cdpQueue)(namespace);
251
122
  };
123
+ /*
124
+ * The current user and anonymous ids as the CDP knows them.
125
+ *
126
+ * Exposed to source middleware, which on Segment could read these from the
127
+ * event facade. Either CDP may be absent or may not implement `user()`, so
128
+ * every step is guarded and an unknown id is simply undefined.
129
+ */
130
+ this.currentUser = function () {
131
+ var read = function (analytics) {
132
+ var _a, _b, _c;
133
+ try {
134
+ var user = (_a = analytics === null || analytics === void 0 ? void 0 : analytics.user) === null || _a === void 0 ? void 0 : _a.call(analytics);
135
+ if (!user)
136
+ return undefined;
137
+ var userId = (_b = user.userId) !== null && _b !== void 0 ? _b : undefined;
138
+ var anonymousId = (_c = user.anonymousId) !== null && _c !== void 0 ? _c : undefined;
139
+ if (userId === undefined && anonymousId === undefined)
140
+ return undefined;
141
+ return { userId: userId, anonymousId: anonymousId };
142
+ }
143
+ catch (_d) {
144
+ return undefined;
145
+ }
146
+ };
147
+ return read(_this.ccdp()) || read(_this.cdp()) || {};
148
+ };
252
149
  /*
253
150
  * Log a message to the console if `options.enableLogging` is true.
254
151
  */
@@ -263,21 +160,56 @@ var ChordAnalytics = /** @class */ (function () {
263
160
  console.log.apply(console, __spreadArray([message], optionalParams, false));
264
161
  };
265
162
  this.isResetting = function () {
266
- return _this.resetCompletePromise !== null;
163
+ return _this.resetting;
164
+ };
165
+ // Enqueues an operation to run after all previously enqueued operations
166
+ // complete. Errors are swallowed on the internal chain so a failed operation
167
+ // doesn't stall the queue.
168
+ this.enqueue = function (operation) {
169
+ var queued = _this.operationQueue.then(function () { return operation(); });
170
+ _this.operationQueue = queued.catch(function (err) { return _this.logger(err); });
171
+ return queued;
172
+ };
173
+ // Wraps a CDP call with a timeout so a stuck or unresponsive CDP cannot
174
+ // block the queue indefinitely.
175
+ // eslint-disable-next-line class-methods-use-this
176
+ this.withTimeout = function (promise) {
177
+ return Promise.race([
178
+ promise,
179
+ new Promise(function (_, reject) {
180
+ setTimeout(function () {
181
+ return reject(new Error("CDP call timed out after ".concat(OPERATION_TIMEOUT_MS, "ms")));
182
+ }, OPERATION_TIMEOUT_MS);
183
+ }),
184
+ ]);
267
185
  };
268
- // Single gate for all outgoing operations. Returns a Promise only when there
269
- // is actually something to wait for, preserving synchronous execution in the
270
- // common case where CDP is ready and no reset is in flight.
186
+ // Gate for CDP readiness and consent. Reset ordering is handled by the queue.
187
+ // Returns undefined when nothing needs to be awaited (common case) to avoid
188
+ // an unnecessary microtask yield.
271
189
  this.waitForReadyState = function () {
272
190
  var cdp = _this.cdpReadyPromise;
273
- var reset = _this.resetCompletePromise;
274
- if (!cdp && !reset)
191
+ var consentPending = _this.consentConfig && !_this.consentConfig.isConsentReady();
192
+ if (!cdp && !consentPending)
275
193
  return undefined;
276
- // eslint-disable-next-line @typescript-eslint/no-empty-function
277
- if (cdp && reset)
278
- return Promise.all([cdp, reset]).then(function () { });
279
- // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
280
- return cdp !== null && cdp !== void 0 ? cdp : reset;
194
+ return (function () { return __awaiter(_this, void 0, void 0, function () {
195
+ return __generator(this, function (_a) {
196
+ switch (_a.label) {
197
+ case 0:
198
+ if (!cdp) return [3 /*break*/, 2];
199
+ return [4 /*yield*/, cdp];
200
+ case 1:
201
+ _a.sent();
202
+ _a.label = 2;
203
+ case 2:
204
+ if (!consentPending) return [3 /*break*/, 4];
205
+ return [4 /*yield*/, this.waitForConsent()];
206
+ case 3:
207
+ _a.sent();
208
+ _a.label = 4;
209
+ case 4: return [2 /*return*/];
210
+ }
211
+ });
212
+ }); })();
281
213
  };
282
214
  /**
283
215
  * Wait for consent manager to be ready (no timeout - queues indefinitely).
@@ -316,107 +248,6 @@ var ChordAnalytics = /** @class */ (function () {
316
248
  }
317
249
  });
318
250
  }); };
319
- /**
320
- * Queue an event to be sent when consent is ready.
321
- * Persists to sessionStorage for recovery on instance recreation.
322
- * Starts a consent watcher if not already active.
323
- */
324
- this.queueEventForConsent = function (type, args) {
325
- return new Promise(function (resolve) {
326
- _this.consentEventQueue.push({
327
- type: type,
328
- args: args,
329
- timestamp: Date.now(),
330
- resolve: resolve,
331
- });
332
- // Persist queue to sessionStorage for recovery on instance recreation
333
- _this.persistQueueToStorage();
334
- // Start watching for consent if not already
335
- if (!_this.consentWatcherActive) {
336
- _this.consentWatcherActive = true;
337
- _this.watchForConsentAndFlush();
338
- }
339
- });
340
- };
341
- /**
342
- * Watch for consent to become ready, then flush the queue.
343
- */
344
- this.watchForConsentAndFlush = function () { return __awaiter(_this, void 0, void 0, function () {
345
- return __generator(this, function (_a) {
346
- switch (_a.label) {
347
- case 0: return [4 /*yield*/, this.waitForConsent()];
348
- case 1:
349
- _a.sent();
350
- return [4 /*yield*/, this.flushConsentQueue()];
351
- case 2:
352
- _a.sent();
353
- this.consentWatcherActive = false;
354
- return [2 /*return*/];
355
- }
356
- });
357
- }); };
358
- /**
359
- * Flush all queued events now that consent is ready.
360
- * Clears sessionStorage after successful flush.
361
- */
362
- this.flushConsentQueue = function () { return __awaiter(_this, void 0, void 0, function () {
363
- var queue, sendPromises;
364
- var _this = this;
365
- return __generator(this, function (_a) {
366
- switch (_a.label) {
367
- case 0:
368
- queue = __spreadArray([], this.consentEventQueue, true);
369
- this.consentEventQueue = [];
370
- // Clear persisted queue from storage since we're processing them now
371
- this.clearQueueFromStorage();
372
- if (this.options.enableLogging && this.options.debug) {
373
- // eslint-disable-next-line no-console
374
- console.log("[Chord Analytics DEBUG]: Flushing ".concat(queue.length, " queued events"));
375
- }
376
- sendPromises = queue.map(function (item) { return __awaiter(_this, void 0, void 0, function () {
377
- var _a, error_1;
378
- return __generator(this, function (_b) {
379
- switch (_b.label) {
380
- case 0:
381
- _b.trys.push([0, 9, , 10]);
382
- _a = item.type;
383
- switch (_a) {
384
- case 'track': return [3 /*break*/, 1];
385
- case 'page': return [3 /*break*/, 3];
386
- case 'identify': return [3 /*break*/, 5];
387
- }
388
- return [3 /*break*/, 7];
389
- case 1: return [4 /*yield*/, this.sendTrackEvent(item.args[0], item.args[1], item.args[2])];
390
- case 2:
391
- _b.sent();
392
- return [3 /*break*/, 8];
393
- case 3: return [4 /*yield*/, this.sendPageEvent(item.args[0], item.args[1])];
394
- case 4:
395
- _b.sent();
396
- return [3 /*break*/, 8];
397
- case 5: return [4 /*yield*/, this.sendIdentifyEvent(item.args[0], item.args[1], item.args[2])];
398
- case 6:
399
- _b.sent();
400
- return [3 /*break*/, 8];
401
- case 7: return [3 /*break*/, 8];
402
- case 8: return [3 /*break*/, 10];
403
- case 9:
404
- error_1 = _b.sent();
405
- this.logger('[Chord Analytics]: Error sending queued event:', error_1);
406
- return [3 /*break*/, 10];
407
- case 10:
408
- item.resolve();
409
- return [2 /*return*/];
410
- }
411
- });
412
- }); });
413
- return [4 /*yield*/, Promise.all(sendPromises)];
414
- case 1:
415
- _a.sent();
416
- return [2 /*return*/];
417
- }
418
- });
419
- }); };
420
251
  /**
421
252
  * Configure CDP with fresh consent categories before each event.
422
253
  * Uses Jitsu's configure() to update privacy.consentCategories.
@@ -469,85 +300,107 @@ var ChordAnalytics = /** @class */ (function () {
469
300
  * Send a `track` event to the CDP with any event name and properties.
470
301
  * If consent is not ready, the event is queued until consent is available.
471
302
  */
472
- this.track = function (event, props, options) { return __awaiter(_this, void 0, void 0, function () {
473
- var gate;
474
- return __generator(this, function (_a) {
475
- switch (_a.label) {
476
- case 0:
477
- if (this.options.enableLogging && this.options.debug) {
478
- // eslint-disable-next-line no-console
479
- console.log("[Chord Analytics DEBUG]: track() called with event: \"".concat(event, "\""));
480
- }
481
- if (!event)
482
- this.logger('No event name provided');
483
- gate = this.waitForReadyState();
484
- if (!gate) return [3 /*break*/, 2];
485
- return [4 /*yield*/, gate
486
- // If consent is configured but not ready, queue the event
487
- ];
488
- case 1:
489
- _a.sent();
490
- _a.label = 2;
491
- case 2:
492
- // If consent is configured but not ready, queue the event
493
- if (this.consentConfig && !this.consentConfig.isConsentReady()) {
494
- if (this.options.enableLogging && this.options.debug) {
495
- // eslint-disable-next-line no-console
496
- console.log("[Chord Analytics DEBUG]: track(\"".concat(event, "\") consent not ready, queuing event"));
497
- }
498
- return [2 /*return*/, this.queueEventForConsent('track', [event, props, options])];
499
- }
500
- if (this.options.enableLogging && this.options.debug) {
501
- // eslint-disable-next-line no-console
502
- console.log("[Chord Analytics DEBUG]: track(\"".concat(event, "\") sending event now"));
503
- }
504
- // Consent ready (or not configured) - send immediately
505
- return [2 /*return*/, this.sendTrackEvent(event, props, options)];
506
- }
507
- });
508
- }); };
303
+ this.track = function (event, props, options) {
304
+ if (_this.options.enableLogging && _this.options.debug) {
305
+ // eslint-disable-next-line no-console
306
+ console.log("[Chord Analytics DEBUG]: track() called with event: \"".concat(event, "\""));
307
+ }
308
+ if (!event)
309
+ _this.logger('No event name provided');
310
+ return _this.enqueue(function () { return __awaiter(_this, void 0, void 0, function () {
311
+ var gate;
312
+ return __generator(this, function (_a) {
313
+ switch (_a.label) {
314
+ case 0:
315
+ gate = this.waitForReadyState();
316
+ if (!gate) return [3 /*break*/, 2];
317
+ return [4 /*yield*/, gate];
318
+ case 1:
319
+ _a.sent();
320
+ _a.label = 2;
321
+ case 2: return [4 /*yield*/, this.sendTrackEvent(event, props, options)];
322
+ case 3:
323
+ _a.sent();
324
+ return [2 /*return*/];
325
+ }
326
+ });
327
+ }); });
328
+ };
509
329
  /**
510
330
  * Internal method to actually send a track event to CDP.
511
331
  */
512
332
  this.sendTrackEvent = function (event, props, options) { return __awaiter(_this, void 0, void 0, function () {
513
- var formattedProps, middlewareProps, searchParams_1, urlSearchParams, searchData_1, finalEventProps;
333
+ var formattedProps, middlewareProps, searchParams_1, urlSearchParams, searchData_1, _a, userId_1, anonymousId_1, invoke, settle, returned, results, _b, finalEventProps, cdpPromises, a_1, b_1;
514
334
  var _this = this;
515
- return __generator(this, function (_a) {
516
- switch (_a.label) {
335
+ return __generator(this, function (_c) {
336
+ switch (_c.label) {
517
337
  case 0:
518
338
  formattedProps = this.options.stripNull && props ? (0, utils_js_1.pruneNullValues)(props) : props;
519
339
  middlewareProps = {};
520
- if (this.middleware.length > 0) {
521
- searchParams_1 = typeof window !== 'undefined' ? window.location.search : '';
522
- urlSearchParams = new URLSearchParams(searchParams_1);
523
- searchData_1 = Object.fromEntries(urlSearchParams.entries());
524
- // process middleware synchronously
525
- this.middleware.forEach(function (func) {
526
- try {
527
- var result = func({
528
- payload: {
529
- obj: {
530
- properties: props,
531
- event: event,
532
- context: {
533
- page: {
534
- search: searchParams_1,
535
- searchParams: searchData_1,
536
- },
340
+ if (!(this.middleware.length > 0)) return [3 /*break*/, 4];
341
+ searchParams_1 = typeof window !== 'undefined' ? window.location.search : '';
342
+ urlSearchParams = new URLSearchParams(searchParams_1);
343
+ searchData_1 = Object.fromEntries(urlSearchParams.entries());
344
+ _a = this.currentUser(), userId_1 = _a.userId, anonymousId_1 = _a.anonymousId;
345
+ invoke = function (func) {
346
+ try {
347
+ return func({
348
+ payload: {
349
+ userId: userId_1,
350
+ anonymousId: anonymousId_1,
351
+ obj: {
352
+ properties: props,
353
+ event: event,
354
+ context: {
355
+ page: {
356
+ search: searchParams_1,
357
+ searchParams: searchData_1,
537
358
  },
538
359
  },
539
360
  },
540
- next: function (_) { return _; },
541
- });
542
- if (result && typeof result === 'object') {
543
- middlewareProps = __assign(__assign({}, middlewareProps), result);
544
- }
545
- }
546
- catch (error) {
547
- console.warn('Middleware error:', error);
361
+ },
362
+ next: function (_) { return _; },
363
+ });
364
+ }
365
+ catch (error) {
366
+ console.warn('Middleware error:', error);
367
+ return undefined;
368
+ }
369
+ };
370
+ settle = function (returned) {
371
+ if (!(0, utils_js_1.isPromise)(returned))
372
+ return returned;
373
+ return (0, utils_js_1.withTimeout)(returned, utils_js_1.MIDDLEWARE_TIMEOUT_MS)
374
+ .then(function (settled) {
375
+ if (settled === utils_js_1.TIMED_OUT) {
376
+ console.warn("Middleware timed out after ".concat(utils_js_1.MIDDLEWARE_TIMEOUT_MS, "ms; its properties were dropped"));
377
+ return undefined;
548
378
  }
379
+ return settled;
380
+ })
381
+ .catch(function (error) {
382
+ console.warn('Middleware error:', error);
383
+ return undefined;
549
384
  });
550
- }
385
+ };
386
+ returned = this.middleware.map(invoke);
387
+ if (!returned.some(utils_js_1.isPromise)) return [3 /*break*/, 2];
388
+ return [4 /*yield*/, Promise.all(returned.map(settle))];
389
+ case 1:
390
+ _b = _c.sent();
391
+ return [3 /*break*/, 3];
392
+ case 2:
393
+ _b = returned;
394
+ _c.label = 3;
395
+ case 3:
396
+ results = _b;
397
+ results.forEach(function (result) {
398
+ if (result && typeof result === 'object') {
399
+ middlewareProps = __assign(__assign({}, middlewareProps), result);
400
+ }
401
+ });
402
+ _c.label = 4;
403
+ case 4:
551
404
  finalEventProps = __assign(__assign(__assign({}, formattedProps), middlewareProps), { meta: this.meta() });
552
405
  if (this.options.debug) {
553
406
  this.validate(event, finalEventProps).forEach(function (valid) {
@@ -558,35 +411,27 @@ var ChordAnalytics = /** @class */ (function () {
558
411
  }
559
412
  // Configure CDP with fresh consent before sending
560
413
  this.configureConsentOnCdp();
561
- return [4 /*yield*/, new Promise(function (resolve) {
562
- var cdpPromises = [];
563
- try {
564
- var a_1 = _this.ccdp();
565
- if (a_1 && typeof a_1.track === 'function') {
566
- cdpPromises.push(new Promise(function (cdpResolve) {
567
- a_1.track(event, finalEventProps, options, function () { return cdpResolve(); });
568
- }));
569
- }
570
- var b_1 = _this.cdp();
571
- if (b_1 && typeof b_1.track === 'function') {
572
- cdpPromises.push(new Promise(function (cdpResolve) {
573
- b_1.track(event, finalEventProps, options, function () { return cdpResolve(); });
574
- }));
575
- }
576
- if (cdpPromises.length > 0) {
577
- Promise.allSettled(cdpPromises).then(function () { return resolve(); });
578
- }
579
- else {
580
- resolve();
581
- }
582
- }
583
- catch (error) {
584
- _this.logger(error);
585
- resolve();
586
- }
587
- })];
588
- case 1:
589
- _a.sent();
414
+ cdpPromises = [];
415
+ try {
416
+ a_1 = this.ccdp();
417
+ if (a_1 && typeof a_1.track === 'function') {
418
+ cdpPromises.push(this.withTimeout(new Promise(function (resolve) {
419
+ a_1.track(event, finalEventProps, options, function () { return resolve(); });
420
+ })));
421
+ }
422
+ b_1 = this.cdp();
423
+ if (b_1 && typeof b_1.track === 'function') {
424
+ cdpPromises.push(this.withTimeout(new Promise(function (resolve) {
425
+ b_1.track(event, finalEventProps, options, function () { return resolve(); });
426
+ })));
427
+ }
428
+ }
429
+ catch (error) {
430
+ this.logger(error);
431
+ }
432
+ return [4 /*yield*/, Promise.allSettled(cdpPromises)];
433
+ case 5:
434
+ _c.sent();
590
435
  return [2 /*return*/];
591
436
  }
592
437
  });
@@ -595,38 +440,31 @@ var ChordAnalytics = /** @class */ (function () {
595
440
  * Send an `identify` event to the CDP with user id and traits.
596
441
  * If consent is not ready, the event is queued until consent is available.
597
442
  */
598
- this.identify = function (userIdOrTraits, traitsOrOptions, options) { return __awaiter(_this, void 0, void 0, function () {
599
- var gate;
600
- return __generator(this, function (_a) {
601
- switch (_a.label) {
602
- case 0:
603
- gate = this.waitForReadyState();
604
- if (!gate) return [3 /*break*/, 2];
605
- return [4 /*yield*/, gate
606
- // If consent is configured but not ready, queue the event
607
- ];
608
- case 1:
609
- _a.sent();
610
- _a.label = 2;
611
- case 2:
612
- // If consent is configured but not ready, queue the event
613
- if (this.consentConfig && !this.consentConfig.isConsentReady()) {
614
- return [2 /*return*/, this.queueEventForConsent('identify', [
615
- userIdOrTraits,
616
- traitsOrOptions,
617
- options,
618
- ])];
619
- }
620
- // Consent ready (or not configured) - send immediately
621
- return [2 /*return*/, this.sendIdentifyEvent(userIdOrTraits, traitsOrOptions, options)];
622
- }
623
- });
624
- }); };
443
+ this.identify = function (userIdOrTraits, traitsOrOptions, options) {
444
+ return _this.enqueue(function () { return __awaiter(_this, void 0, void 0, function () {
445
+ var gate;
446
+ return __generator(this, function (_a) {
447
+ switch (_a.label) {
448
+ case 0:
449
+ gate = this.waitForReadyState();
450
+ if (!gate) return [3 /*break*/, 2];
451
+ return [4 /*yield*/, gate];
452
+ case 1:
453
+ _a.sent();
454
+ _a.label = 2;
455
+ case 2: return [4 /*yield*/, this.sendIdentifyEvent(userIdOrTraits, traitsOrOptions, options)];
456
+ case 3:
457
+ _a.sent();
458
+ return [2 /*return*/];
459
+ }
460
+ });
461
+ }); });
462
+ };
625
463
  /**
626
464
  * Internal method to actually send an identify event to CDP.
627
465
  */
628
466
  this.sendIdentifyEvent = function (userIdOrTraits, traitsOrOptions, options) { return __awaiter(_this, void 0, void 0, function () {
629
- var userId, traits, opts, identifyPromises, a_2, b_2, results, error_2;
467
+ var userId, traits, opts, identifyPromises, a_2, b_2, results, error_1;
630
468
  return __generator(this, function (_a) {
631
469
  switch (_a.label) {
632
470
  case 0:
@@ -647,25 +485,25 @@ var ChordAnalytics = /** @class */ (function () {
647
485
  identifyPromises = [];
648
486
  a_2 = this.ccdp();
649
487
  if (a_2 && typeof a_2.identify === 'function') {
650
- identifyPromises.push(new Promise(function (resolve) {
488
+ identifyPromises.push(this.withTimeout(new Promise(function (resolve) {
651
489
  if (userId) {
652
490
  a_2.identify(userId, traits, opts, function () { return resolve(); });
653
491
  }
654
492
  else {
655
493
  a_2.identify(traits, opts, function () { return resolve(); });
656
494
  }
657
- }));
495
+ })));
658
496
  }
659
497
  b_2 = this.cdp();
660
498
  if (b_2 && typeof b_2.identify === 'function') {
661
- identifyPromises.push(new Promise(function (resolve) {
499
+ identifyPromises.push(this.withTimeout(new Promise(function (resolve) {
662
500
  if (userId) {
663
501
  b_2.identify(userId, traits, opts, function () { return resolve(); });
664
502
  }
665
503
  else {
666
504
  b_2.identify(traits, opts, function () { return resolve(); });
667
505
  }
668
- }));
506
+ })));
669
507
  }
670
508
  return [4 /*yield*/, Promise.allSettled(identifyPromises)];
671
509
  case 2:
@@ -673,8 +511,8 @@ var ChordAnalytics = /** @class */ (function () {
673
511
  this.logger('identify event sent', results);
674
512
  return [3 /*break*/, 4];
675
513
  case 3:
676
- error_2 = _a.sent();
677
- this.logger(error_2);
514
+ error_1 = _a.sent();
515
+ this.logger(error_1);
678
516
  return [3 /*break*/, 4];
679
517
  case 4: return [2 /*return*/];
680
518
  }
@@ -686,29 +524,24 @@ var ChordAnalytics = /** @class */ (function () {
686
524
  */
687
525
  this.page = function (options, props) {
688
526
  if (props === void 0) { props = {}; }
689
- return __awaiter(_this, void 0, void 0, function () {
527
+ return _this.enqueue(function () { return __awaiter(_this, void 0, void 0, function () {
690
528
  var gate;
691
529
  return __generator(this, function (_a) {
692
530
  switch (_a.label) {
693
531
  case 0:
694
532
  gate = this.waitForReadyState();
695
533
  if (!gate) return [3 /*break*/, 2];
696
- return [4 /*yield*/, gate
697
- // If consent is configured but not ready, queue the event
698
- ];
534
+ return [4 /*yield*/, gate];
699
535
  case 1:
700
536
  _a.sent();
701
537
  _a.label = 2;
702
- case 2:
703
- // If consent is configured but not ready, queue the event
704
- if (this.consentConfig && !this.consentConfig.isConsentReady()) {
705
- return [2 /*return*/, this.queueEventForConsent('page', [options, props])];
706
- }
707
- // Consent ready (or not configured) - send immediately
708
- return [2 /*return*/, this.sendPageEvent(options, props)];
538
+ case 2: return [4 /*yield*/, this.sendPageEvent(options, props)];
539
+ case 3:
540
+ _a.sent();
541
+ return [2 /*return*/];
709
542
  }
710
543
  });
711
- });
544
+ }); });
712
545
  };
713
546
  /**
714
547
  * Internal method to actually send a page event to CDP.
@@ -716,112 +549,96 @@ var ChordAnalytics = /** @class */ (function () {
716
549
  this.sendPageEvent = function (options, props) {
717
550
  if (props === void 0) { props = {}; }
718
551
  return __awaiter(_this, void 0, void 0, function () {
552
+ var pagePromises, a_3, b_3, results;
719
553
  var _this = this;
720
554
  return __generator(this, function (_a) {
721
555
  switch (_a.label) {
722
556
  case 0:
723
557
  // Configure CDP with fresh consent before sending
724
558
  this.configureConsentOnCdp();
725
- return [4 /*yield*/, new Promise(function (resolve) {
726
- var pagePromises = [];
727
- try {
728
- var a_3 = _this.ccdp();
729
- if (a_3 && typeof a_3.page === 'function') {
730
- pagePromises.push(new Promise(function (cdpResolve) {
731
- a_3.page(__assign(__assign({}, props), { meta: _this.meta() }), options, function () {
732
- return cdpResolve();
733
- });
734
- }));
735
- }
736
- var b_3 = _this.cdp();
737
- if (b_3 && typeof b_3.page === 'function') {
738
- pagePromises.push(new Promise(function (cdpResolve) {
739
- b_3.page(__assign(__assign({}, props), { meta: _this.meta() }), options, function () {
740
- return cdpResolve();
741
- });
742
- }));
743
- }
744
- if (pagePromises.length > 0) {
745
- Promise.allSettled(pagePromises).then(function (results) {
746
- _this.logger('page event sent', results);
747
- resolve();
748
- });
749
- }
750
- else {
751
- resolve();
752
- }
753
- }
754
- catch (error) {
755
- _this.logger(error);
756
- resolve();
757
- }
758
- })];
559
+ pagePromises = [];
560
+ try {
561
+ // The new identity has no consent event of its own yet, so drop the
562
+ // stored signature and let the next init emit a fresh baseline.
563
+ (0, persistence_js_1.forgetConsent)(this.options.namespace);
564
+ a_3 = this.ccdp();
565
+ if (a_3 && typeof a_3.page === 'function') {
566
+ pagePromises.push(this.withTimeout(new Promise(function (resolve) {
567
+ a_3.page(__assign(__assign({}, props), { meta: _this.meta() }), options, function () { return resolve(); });
568
+ })));
569
+ }
570
+ b_3 = this.cdp();
571
+ if (b_3 && typeof b_3.page === 'function') {
572
+ pagePromises.push(this.withTimeout(new Promise(function (resolve) {
573
+ b_3.page(__assign(__assign({}, props), { meta: _this.meta() }), options, function () { return resolve(); });
574
+ })));
575
+ }
576
+ }
577
+ catch (error) {
578
+ this.logger(error);
579
+ }
580
+ return [4 /*yield*/, Promise.allSettled(pagePromises)];
759
581
  case 1:
760
- _a.sent();
582
+ results = _a.sent();
583
+ this.logger('page event sent', results);
761
584
  return [2 /*return*/];
762
585
  }
763
586
  });
764
587
  });
765
588
  };
766
- // Coalesces concurrent reset() calls — a second caller gets the same in-flight
767
- // Promise rather than triggering a duplicate reset.
768
589
  this.reset = function () {
769
- if (!_this.resetCompletePromise) {
770
- _this.resetCompletePromise = _this.performReset();
771
- }
772
- return _this.resetCompletePromise;
773
- };
774
- this.performReset = function () { return __awaiter(_this, void 0, void 0, function () {
775
- var a, b, maxAttempts, attempts, userState, error_3;
776
- var _a;
777
- return __generator(this, function (_b) {
778
- switch (_b.label) {
779
- case 0:
780
- _b.trys.push([0, 4, 5, 6]);
781
- // The new identity has no consent event of its own yet, so drop the
782
- // stored signature and let the next init emit a fresh baseline.
783
- (0, persistence_js_1.forgetConsent)(this.options.namespace);
784
- a = this.ccdp();
785
- if (a && typeof a.reset === 'function') {
786
- a.reset();
787
- }
788
- b = this.cdp();
789
- if (b && typeof b.reset === 'function') {
790
- b.reset();
791
- }
792
- maxAttempts = 50;
793
- attempts = 0;
794
- _b.label = 1;
795
- case 1:
796
- if (!(attempts < maxAttempts)) return [3 /*break*/, 3];
797
- // eslint-disable-next-line
798
- return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 10); })];
799
- case 2:
800
- // eslint-disable-next-line
801
- _b.sent();
802
- try {
803
- userState = ((_a = a === null || a === void 0 ? void 0 : a.user) === null || _a === void 0 ? void 0 : _a.call(a)) || {};
804
- if (userState.userId === null || userState.userId === undefined) {
805
- return [3 /*break*/, 3];
590
+ return _this.enqueue(function () { return __awaiter(_this, void 0, void 0, function () {
591
+ var a, b, maxAttempts, attempts, userState, error_2;
592
+ var _a;
593
+ return __generator(this, function (_b) {
594
+ switch (_b.label) {
595
+ case 0:
596
+ this.resetting = true;
597
+ _b.label = 1;
598
+ case 1:
599
+ _b.trys.push([1, 5, 6, 7]);
600
+ a = this.ccdp();
601
+ if (a && typeof a.reset === 'function') {
602
+ a.reset();
806
603
  }
807
- }
808
- catch (_c) {
809
- return [3 /*break*/, 3];
810
- }
811
- attempts++;
812
- return [3 /*break*/, 1];
813
- case 3: return [3 /*break*/, 6];
814
- case 4:
815
- error_3 = _b.sent();
816
- this.logger(error_3);
817
- return [3 /*break*/, 6];
818
- case 5:
819
- this.resetCompletePromise = null;
820
- return [7 /*endfinally*/];
821
- case 6: return [2 /*return*/];
822
- }
823
- });
824
- }); };
604
+ b = this.cdp();
605
+ if (b && typeof b.reset === 'function') {
606
+ b.reset();
607
+ }
608
+ maxAttempts = 50;
609
+ attempts = 0;
610
+ _b.label = 2;
611
+ case 2:
612
+ if (!(attempts < maxAttempts)) return [3 /*break*/, 4];
613
+ // eslint-disable-next-line
614
+ return [4 /*yield*/, new Promise(function (resolve) { return setTimeout(resolve, 10); })];
615
+ case 3:
616
+ // eslint-disable-next-line
617
+ _b.sent();
618
+ try {
619
+ userState = ((_a = a === null || a === void 0 ? void 0 : a.user) === null || _a === void 0 ? void 0 : _a.call(a)) || {};
620
+ if (userState.userId === null || userState.userId === undefined) {
621
+ return [3 /*break*/, 4];
622
+ }
623
+ }
624
+ catch (_c) {
625
+ return [3 /*break*/, 4];
626
+ }
627
+ attempts++;
628
+ return [3 /*break*/, 2];
629
+ case 4: return [3 /*break*/, 7];
630
+ case 5:
631
+ error_2 = _b.sent();
632
+ this.logger(error_2);
633
+ return [3 /*break*/, 7];
634
+ case 6:
635
+ this.resetting = false;
636
+ return [7 /*endfinally*/];
637
+ case 7: return [2 /*return*/];
638
+ }
639
+ });
640
+ }); });
641
+ };
825
642
  /**
826
643
  * Handler for postMessage events from the Shopify Web Pixel sandbox.
827
644
  * Bound to the instance for proper `this` context and cleanup.
@@ -1464,8 +1281,6 @@ var ChordAnalytics = /** @class */ (function () {
1464
1281
  var consentManager = (0, index_js_1.addCdpConsent)(this.options);
1465
1282
  this.consentConfig = consentManager.getConsentConfig() || null;
1466
1283
  }
1467
- // Restore any pending events from sessionStorage (survives instance recreation)
1468
- this.restoreQueueFromStorage();
1469
1284
  if (((_b = this.options) === null || _b === void 0 ? void 0 : _b.cdpDomain) && ((_c = this.options) === null || _c === void 0 ? void 0 : _c.cdpWriteKey) && window) {
1470
1285
  window[this.options.namespace] = (window === null || window === void 0 ? void 0 : window[this.options.namespace]) || this;
1471
1286
  this.initializeCdp();
@@ -1485,7 +1300,7 @@ var ChordAnalytics = /** @class */ (function () {
1485
1300
  */
1486
1301
  ChordAnalytics.prototype.initializeCdp = function () {
1487
1302
  return __awaiter(this, void 0, void 0, function () {
1488
- var snippetPromise, initialConsent, namespace, error_4, error_5;
1303
+ var snippetPromise, initialConsent, namespace, error_3, error_4;
1489
1304
  return __generator(this, function (_a) {
1490
1305
  switch (_a.label) {
1491
1306
  case 0:
@@ -1538,9 +1353,9 @@ var ChordAnalytics = /** @class */ (function () {
1538
1353
  (0, persistence_js_1.commitConsentEmit)(namespace, initialConsent);
1539
1354
  return [3 /*break*/, 6];
1540
1355
  case 5:
1541
- error_4 = _a.sent();
1356
+ error_3 = _a.sent();
1542
1357
  (0, persistence_js_1.releaseConsentEmit)(namespace, initialConsent);
1543
- throw error_4;
1358
+ throw error_3;
1544
1359
  case 6:
1545
1360
  if (this.options.enableLogging && this.options.debug) {
1546
1361
  // eslint-disable-next-line no-console
@@ -1555,9 +1370,9 @@ var ChordAnalytics = /** @class */ (function () {
1555
1370
  _a.label = 8;
1556
1371
  case 8: return [3 /*break*/, 10];
1557
1372
  case 9:
1558
- error_5 = _a.sent();
1373
+ error_4 = _a.sent();
1559
1374
  // eslint-disable-next-line no-console
1560
- console.error('CDP initialization failed:', error_5);
1375
+ console.error('CDP initialization failed:', error_4);
1561
1376
  return [3 /*break*/, 10];
1562
1377
  case 10: return [2 /*return*/];
1563
1378
  }