@chordcommerce/analytics 1.23.1 → 1.24.1
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/README.md +28 -2
- package/dist/cjs/client/index.d.ts +1 -0
- package/dist/cjs/client/index.js +90 -31
- package/dist/cjs/types/client.d.ts +14 -1
- package/dist/cjs/utils.d.ts +19 -0
- package/dist/cjs/utils.js +79 -1
- package/dist/client/index.d.ts +1 -0
- package/dist/client/index.js +67 -7
- package/dist/types/client.d.ts +14 -1
- package/dist/utils.d.ts +19 -0
- package/dist/utils.js +39 -0
- package/package.json +11 -1
package/README.md
CHANGED
|
@@ -121,8 +121,16 @@ export const cartFormatter: CartFormatter<StorefrontCart> = (props) => {
|
|
|
121
121
|
addSourceMiddleware(func: AnalyticsMiddleware)
|
|
122
122
|
|
|
123
123
|
Registers a middleware function to modify event properties before they are
|
|
124
|
-
sent to analytics providers. Middleware functions are
|
|
125
|
-
|
|
124
|
+
sent to analytics providers. Middleware functions are invoked in the order they
|
|
125
|
+
were added during event tracking, and each one's properties are merged into the
|
|
126
|
+
event in that order.
|
|
127
|
+
|
|
128
|
+
A middleware may be synchronous or asynchronous. Synchronous middleware is never
|
|
129
|
+
awaited, so it adds no delay to event delivery. Asynchronous middleware is
|
|
130
|
+
awaited before the event is sent, capped at one second — a middleware that has
|
|
131
|
+
not resolved by then is abandoned, a warning is logged, and the event is sent
|
|
132
|
+
without its properties. A middleware that throws or rejects is logged and
|
|
133
|
+
skipped. A middleware never prevents an event from being sent.
|
|
126
134
|
|
|
127
135
|
Parameters
|
|
128
136
|
|
|
@@ -135,6 +143,15 @@ The middleware function can return:
|
|
|
135
143
|
|
|
136
144
|
- Partial<EventProperties> - Properties to merge into the event payload
|
|
137
145
|
- void - No additional properties to add
|
|
146
|
+
- A Promise of either of the above - Awaited before the event is sent
|
|
147
|
+
|
|
148
|
+
The `payload` argument carries `payload.obj` (the event's properties, name and
|
|
149
|
+
page context) plus `payload.userId` and `payload.anonymousId` as the CDP
|
|
150
|
+
currently knows them — either may be undefined.
|
|
151
|
+
|
|
152
|
+
Note that middleware contributes properties by **returning** them. Mutating the
|
|
153
|
+
`payload` argument has no effect; `next` is accepted for signature
|
|
154
|
+
compatibility and does nothing.
|
|
138
155
|
|
|
139
156
|
Useage Example
|
|
140
157
|
|
|
@@ -156,6 +173,15 @@ Useage Example
|
|
|
156
173
|
timestamp: Date.now()
|
|
157
174
|
};
|
|
158
175
|
});
|
|
176
|
+
|
|
177
|
+
// Asynchronous middleware: the event waits for the lookup, up to one second.
|
|
178
|
+
analytics.addSourceMiddleware(async ({ payload }) => {
|
|
179
|
+
if (payload.obj.event !== 'Order Completed') return;
|
|
180
|
+
|
|
181
|
+
const isNewCustomer = await fetchIsNewCustomer();
|
|
182
|
+
|
|
183
|
+
return { is_new_customer: isNewCustomer ? 1 : 0 };
|
|
184
|
+
});
|
|
159
185
|
```
|
|
160
186
|
|
|
161
187
|
## API Reference
|
|
@@ -71,6 +71,7 @@ export declare class ChordAnalytics<T extends ObjectTypes = ObjectTypes> {
|
|
|
71
71
|
setDebugMode: (enabled: boolean) => void;
|
|
72
72
|
cdp: () => AnyAnalytics;
|
|
73
73
|
ccdp: () => AnyAnalytics;
|
|
74
|
+
private currentUser;
|
|
74
75
|
logger: (message?: any, ...optionalParams: any[]) => void;
|
|
75
76
|
isResetting: () => boolean;
|
|
76
77
|
private waitForReadyState;
|
package/dist/cjs/client/index.js
CHANGED
|
@@ -249,6 +249,32 @@ var ChordAnalytics = /** @class */ (function () {
|
|
|
249
249
|
return null;
|
|
250
250
|
return (window === null || window === void 0 ? void 0 : window["_".concat(namespace)]) || (0, cdp_js_1.cdpQueue)(namespace);
|
|
251
251
|
};
|
|
252
|
+
/*
|
|
253
|
+
* The current user and anonymous ids as the CDP knows them.
|
|
254
|
+
*
|
|
255
|
+
* Exposed to source middleware, which on Segment could read these from the
|
|
256
|
+
* event facade. Either CDP may be absent or may not implement `user()`, so
|
|
257
|
+
* every step is guarded and an unknown id is simply undefined.
|
|
258
|
+
*/
|
|
259
|
+
this.currentUser = function () {
|
|
260
|
+
var read = function (analytics) {
|
|
261
|
+
var _a, _b, _c;
|
|
262
|
+
try {
|
|
263
|
+
var user = (_a = analytics === null || analytics === void 0 ? void 0 : analytics.user) === null || _a === void 0 ? void 0 : _a.call(analytics);
|
|
264
|
+
if (!user)
|
|
265
|
+
return undefined;
|
|
266
|
+
var userId = (_b = user.userId) !== null && _b !== void 0 ? _b : undefined;
|
|
267
|
+
var anonymousId = (_c = user.anonymousId) !== null && _c !== void 0 ? _c : undefined;
|
|
268
|
+
if (userId === undefined && anonymousId === undefined)
|
|
269
|
+
return undefined;
|
|
270
|
+
return { userId: userId, anonymousId: anonymousId };
|
|
271
|
+
}
|
|
272
|
+
catch (_d) {
|
|
273
|
+
return undefined;
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
return read(_this.ccdp()) || read(_this.cdp()) || {};
|
|
277
|
+
};
|
|
252
278
|
/*
|
|
253
279
|
* Log a message to the console if `options.enableLogging` is true.
|
|
254
280
|
*/
|
|
@@ -510,44 +536,77 @@ var ChordAnalytics = /** @class */ (function () {
|
|
|
510
536
|
* Internal method to actually send a track event to CDP.
|
|
511
537
|
*/
|
|
512
538
|
this.sendTrackEvent = function (event, props, options) { return __awaiter(_this, void 0, void 0, function () {
|
|
513
|
-
var formattedProps, middlewareProps, searchParams_1, urlSearchParams, searchData_1, finalEventProps;
|
|
539
|
+
var formattedProps, middlewareProps, searchParams_1, urlSearchParams, searchData_1, _a, userId_1, anonymousId_1, invoke, settle, returned, results, _b, finalEventProps;
|
|
514
540
|
var _this = this;
|
|
515
|
-
return __generator(this, function (
|
|
516
|
-
switch (
|
|
541
|
+
return __generator(this, function (_c) {
|
|
542
|
+
switch (_c.label) {
|
|
517
543
|
case 0:
|
|
518
544
|
formattedProps = this.options.stripNull && props ? (0, utils_js_1.pruneNullValues)(props) : props;
|
|
519
545
|
middlewareProps = {};
|
|
520
|
-
if (this.middleware.length > 0)
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
546
|
+
if (!(this.middleware.length > 0)) return [3 /*break*/, 4];
|
|
547
|
+
searchParams_1 = typeof window !== 'undefined' ? window.location.search : '';
|
|
548
|
+
urlSearchParams = new URLSearchParams(searchParams_1);
|
|
549
|
+
searchData_1 = Object.fromEntries(urlSearchParams.entries());
|
|
550
|
+
_a = this.currentUser(), userId_1 = _a.userId, anonymousId_1 = _a.anonymousId;
|
|
551
|
+
invoke = function (func) {
|
|
552
|
+
try {
|
|
553
|
+
return func({
|
|
554
|
+
payload: {
|
|
555
|
+
userId: userId_1,
|
|
556
|
+
anonymousId: anonymousId_1,
|
|
557
|
+
obj: {
|
|
558
|
+
properties: props,
|
|
559
|
+
event: event,
|
|
560
|
+
context: {
|
|
561
|
+
page: {
|
|
562
|
+
search: searchParams_1,
|
|
563
|
+
searchParams: searchData_1,
|
|
537
564
|
},
|
|
538
565
|
},
|
|
539
566
|
},
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
567
|
+
},
|
|
568
|
+
next: function (_) { return _; },
|
|
569
|
+
});
|
|
570
|
+
}
|
|
571
|
+
catch (error) {
|
|
572
|
+
console.warn('Middleware error:', error);
|
|
573
|
+
return undefined;
|
|
574
|
+
}
|
|
575
|
+
};
|
|
576
|
+
settle = function (returned) {
|
|
577
|
+
if (!(0, utils_js_1.isPromise)(returned))
|
|
578
|
+
return returned;
|
|
579
|
+
return (0, utils_js_1.withTimeout)(returned, utils_js_1.MIDDLEWARE_TIMEOUT_MS)
|
|
580
|
+
.then(function (settled) {
|
|
581
|
+
if (settled === utils_js_1.TIMED_OUT) {
|
|
582
|
+
console.warn("Middleware timed out after ".concat(utils_js_1.MIDDLEWARE_TIMEOUT_MS, "ms; its properties were dropped"));
|
|
583
|
+
return undefined;
|
|
548
584
|
}
|
|
585
|
+
return settled;
|
|
586
|
+
})
|
|
587
|
+
.catch(function (error) {
|
|
588
|
+
console.warn('Middleware error:', error);
|
|
589
|
+
return undefined;
|
|
549
590
|
});
|
|
550
|
-
}
|
|
591
|
+
};
|
|
592
|
+
returned = this.middleware.map(invoke);
|
|
593
|
+
if (!returned.some(utils_js_1.isPromise)) return [3 /*break*/, 2];
|
|
594
|
+
return [4 /*yield*/, Promise.all(returned.map(settle))];
|
|
595
|
+
case 1:
|
|
596
|
+
_b = _c.sent();
|
|
597
|
+
return [3 /*break*/, 3];
|
|
598
|
+
case 2:
|
|
599
|
+
_b = returned;
|
|
600
|
+
_c.label = 3;
|
|
601
|
+
case 3:
|
|
602
|
+
results = _b;
|
|
603
|
+
results.forEach(function (result) {
|
|
604
|
+
if (result && typeof result === 'object') {
|
|
605
|
+
middlewareProps = __assign(__assign({}, middlewareProps), result);
|
|
606
|
+
}
|
|
607
|
+
});
|
|
608
|
+
_c.label = 4;
|
|
609
|
+
case 4:
|
|
551
610
|
finalEventProps = __assign(__assign(__assign({}, formattedProps), middlewareProps), { meta: this.meta() });
|
|
552
611
|
if (this.options.debug) {
|
|
553
612
|
this.validate(event, finalEventProps).forEach(function (valid) {
|
|
@@ -585,8 +644,8 @@ var ChordAnalytics = /** @class */ (function () {
|
|
|
585
644
|
resolve();
|
|
586
645
|
}
|
|
587
646
|
})];
|
|
588
|
-
case
|
|
589
|
-
|
|
647
|
+
case 5:
|
|
648
|
+
_c.sent();
|
|
590
649
|
return [2 /*return*/];
|
|
591
650
|
}
|
|
592
651
|
});
|
|
@@ -88,10 +88,23 @@ type MiddlewarePayload = {
|
|
|
88
88
|
page: PageContext;
|
|
89
89
|
};
|
|
90
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* The current user and anonymous ids, read from the CDP at send time.
|
|
93
|
+
* Segment's middleware payload exposes these as `userId()`/`anonymousId()`
|
|
94
|
+
* methods on a facade; they are plain values here.
|
|
95
|
+
*/
|
|
96
|
+
userId?: string;
|
|
97
|
+
anonymousId?: string;
|
|
91
98
|
};
|
|
92
99
|
type MiddlewareArgs = {
|
|
93
100
|
payload: MiddlewarePayload;
|
|
94
101
|
next: <T>(arg: T) => T;
|
|
95
102
|
};
|
|
96
|
-
export type
|
|
103
|
+
export type MiddlewareResult = Partial<EventProperties> | void;
|
|
104
|
+
/**
|
|
105
|
+
* A source middleware may be synchronous or asynchronous. An asynchronous one
|
|
106
|
+
* is awaited before the event is dispatched, bounded by a timeout — see
|
|
107
|
+
* `addSourceMiddleware` in the README.
|
|
108
|
+
*/
|
|
109
|
+
export type AnalyticsMiddleware = (args: MiddlewareArgs) => MiddlewareResult | Promise<MiddlewareResult>;
|
|
97
110
|
export {};
|
package/dist/cjs/utils.d.ts
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
1
1
|
import type { EventProperties } from './types/index.js';
|
|
2
2
|
export declare const pruneNullValues: (props: EventProperties) => EventProperties;
|
|
3
|
+
/**
|
|
4
|
+
* How long a single asynchronous source middleware may take before its
|
|
5
|
+
* contribution is abandoned.
|
|
6
|
+
*
|
|
7
|
+
* Middleware runs on the path to the CDP, so an unbounded wait would let one
|
|
8
|
+
* slow lookup hold up delivery indefinitely. `Order Completed` in particular
|
|
9
|
+
* fires on a confirmation page shoppers close, so a late event is a lost one.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MIDDLEWARE_TIMEOUT_MS = 1000;
|
|
12
|
+
/** Returned by {@link withTimeout} when the promise did not settle in time. */
|
|
13
|
+
export declare const TIMED_OUT: unique symbol;
|
|
14
|
+
export declare const isPromise: (value: unknown) => value is Promise<unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve `promise`, or {@link TIMED_OUT} if it has not settled within `ms`.
|
|
17
|
+
*
|
|
18
|
+
* The timer is cleared either way, so a promise that settles quickly does not
|
|
19
|
+
* leave a pending timeout behind.
|
|
20
|
+
*/
|
|
21
|
+
export declare const withTimeout: <T>(promise: Promise<T>, ms: number) => Promise<typeof TIMED_OUT | T>;
|
package/dist/cjs/utils.js
CHANGED
|
@@ -1,6 +1,42 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
3
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
4
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
5
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
6
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
7
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
8
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
9
|
+
});
|
|
10
|
+
};
|
|
11
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
12
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
13
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
14
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
15
|
+
function step(op) {
|
|
16
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
17
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
18
|
+
if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
|
|
19
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
20
|
+
switch (op[0]) {
|
|
21
|
+
case 0: case 1: t = op; break;
|
|
22
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
23
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
24
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
25
|
+
default:
|
|
26
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
27
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
28
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
29
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
30
|
+
if (t[2]) _.ops.pop();
|
|
31
|
+
_.trys.pop(); continue;
|
|
32
|
+
}
|
|
33
|
+
op = body.call(thisArg, _);
|
|
34
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
35
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
36
|
+
}
|
|
37
|
+
};
|
|
2
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.pruneNullValues = void 0;
|
|
39
|
+
exports.withTimeout = exports.isPromise = exports.TIMED_OUT = exports.MIDDLEWARE_TIMEOUT_MS = exports.pruneNullValues = void 0;
|
|
4
40
|
var pruneNullValues = function (props) {
|
|
5
41
|
if (typeof props !== 'object' || props === null)
|
|
6
42
|
return props;
|
|
@@ -23,3 +59,45 @@ var pruneNullValues = function (props) {
|
|
|
23
59
|
return props;
|
|
24
60
|
};
|
|
25
61
|
exports.pruneNullValues = pruneNullValues;
|
|
62
|
+
/**
|
|
63
|
+
* How long a single asynchronous source middleware may take before its
|
|
64
|
+
* contribution is abandoned.
|
|
65
|
+
*
|
|
66
|
+
* Middleware runs on the path to the CDP, so an unbounded wait would let one
|
|
67
|
+
* slow lookup hold up delivery indefinitely. `Order Completed` in particular
|
|
68
|
+
* fires on a confirmation page shoppers close, so a late event is a lost one.
|
|
69
|
+
*/
|
|
70
|
+
exports.MIDDLEWARE_TIMEOUT_MS = 1000;
|
|
71
|
+
/** Returned by {@link withTimeout} when the promise did not settle in time. */
|
|
72
|
+
exports.TIMED_OUT = Symbol('timed-out');
|
|
73
|
+
var isPromise = function (value) {
|
|
74
|
+
return typeof (value === null || value === void 0 ? void 0 : value.then) === 'function';
|
|
75
|
+
};
|
|
76
|
+
exports.isPromise = isPromise;
|
|
77
|
+
/**
|
|
78
|
+
* Resolve `promise`, or {@link TIMED_OUT} if it has not settled within `ms`.
|
|
79
|
+
*
|
|
80
|
+
* The timer is cleared either way, so a promise that settles quickly does not
|
|
81
|
+
* leave a pending timeout behind.
|
|
82
|
+
*/
|
|
83
|
+
var withTimeout = function (promise, ms) { return __awaiter(void 0, void 0, void 0, function () {
|
|
84
|
+
var timer, timeout;
|
|
85
|
+
return __generator(this, function (_a) {
|
|
86
|
+
switch (_a.label) {
|
|
87
|
+
case 0:
|
|
88
|
+
timeout = new Promise(function (resolve) {
|
|
89
|
+
timer = setTimeout(function () { return resolve(exports.TIMED_OUT); }, ms);
|
|
90
|
+
});
|
|
91
|
+
_a.label = 1;
|
|
92
|
+
case 1:
|
|
93
|
+
_a.trys.push([1, , 3, 4]);
|
|
94
|
+
return [4 /*yield*/, Promise.race([promise, timeout])];
|
|
95
|
+
case 2: return [2 /*return*/, _a.sent()];
|
|
96
|
+
case 3:
|
|
97
|
+
clearTimeout(timer);
|
|
98
|
+
return [7 /*endfinally*/];
|
|
99
|
+
case 4: return [2 /*return*/];
|
|
100
|
+
}
|
|
101
|
+
});
|
|
102
|
+
}); };
|
|
103
|
+
exports.withTimeout = withTimeout;
|
package/dist/client/index.d.ts
CHANGED
|
@@ -71,6 +71,7 @@ export declare class ChordAnalytics<T extends ObjectTypes = ObjectTypes> {
|
|
|
71
71
|
setDebugMode: (enabled: boolean) => void;
|
|
72
72
|
cdp: () => AnyAnalytics;
|
|
73
73
|
ccdp: () => AnyAnalytics;
|
|
74
|
+
private currentUser;
|
|
74
75
|
logger: (message?: any, ...optionalParams: any[]) => void;
|
|
75
76
|
isResetting: () => boolean;
|
|
76
77
|
private waitForReadyState;
|
package/dist/client/index.js
CHANGED
|
@@ -11,7 +11,7 @@ import { addCdpSnippet, cdpQueue } from './cdp.js';
|
|
|
11
11
|
import { addCdpConsent } from './consent/index.js';
|
|
12
12
|
import { claimConsentEmit, commitConsentEmit, forgetConsent, releaseConsentEmit, } from './consent/persistence.js';
|
|
13
13
|
import { eventSchemas } from '../validators/index.js';
|
|
14
|
-
import { pruneNullValues } from '../utils.js';
|
|
14
|
+
import { isPromise, MIDDLEWARE_TIMEOUT_MS, pruneNullValues, TIMED_OUT, withTimeout, } from '../utils.js';
|
|
15
15
|
const DEBUG_MODE_STORAGE_KEY = 'chordcommerce/analytics/debug';
|
|
16
16
|
const CONSENT_QUEUE_STORAGE_KEY = 'chordcommerce/analytics/consent-queue';
|
|
17
17
|
const CONSENT_QUEUE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
@@ -198,6 +198,32 @@ export class ChordAnalytics {
|
|
|
198
198
|
return null;
|
|
199
199
|
return (window === null || window === void 0 ? void 0 : window[`_${namespace}`]) || cdpQueue(namespace);
|
|
200
200
|
};
|
|
201
|
+
/*
|
|
202
|
+
* The current user and anonymous ids as the CDP knows them.
|
|
203
|
+
*
|
|
204
|
+
* Exposed to source middleware, which on Segment could read these from the
|
|
205
|
+
* event facade. Either CDP may be absent or may not implement `user()`, so
|
|
206
|
+
* every step is guarded and an unknown id is simply undefined.
|
|
207
|
+
*/
|
|
208
|
+
this.currentUser = () => {
|
|
209
|
+
const read = (analytics) => {
|
|
210
|
+
var _a, _b, _c;
|
|
211
|
+
try {
|
|
212
|
+
const user = (_a = analytics === null || analytics === void 0 ? void 0 : analytics.user) === null || _a === void 0 ? void 0 : _a.call(analytics);
|
|
213
|
+
if (!user)
|
|
214
|
+
return undefined;
|
|
215
|
+
const userId = (_b = user.userId) !== null && _b !== void 0 ? _b : undefined;
|
|
216
|
+
const anonymousId = (_c = user.anonymousId) !== null && _c !== void 0 ? _c : undefined;
|
|
217
|
+
if (userId === undefined && anonymousId === undefined)
|
|
218
|
+
return undefined;
|
|
219
|
+
return { userId, anonymousId };
|
|
220
|
+
}
|
|
221
|
+
catch (_d) {
|
|
222
|
+
return undefined;
|
|
223
|
+
}
|
|
224
|
+
};
|
|
225
|
+
return read(this.ccdp()) || read(this.cdp()) || {};
|
|
226
|
+
};
|
|
201
227
|
/*
|
|
202
228
|
* Log a message to the console if `options.enableLogging` is true.
|
|
203
229
|
*/
|
|
@@ -404,11 +430,13 @@ export class ChordAnalytics {
|
|
|
404
430
|
const searchParams = typeof window !== 'undefined' ? window.location.search : '';
|
|
405
431
|
const urlSearchParams = new URLSearchParams(searchParams);
|
|
406
432
|
const searchData = Object.fromEntries(urlSearchParams.entries());
|
|
407
|
-
|
|
408
|
-
|
|
433
|
+
const { userId, anonymousId } = this.currentUser();
|
|
434
|
+
const invoke = (func) => {
|
|
409
435
|
try {
|
|
410
|
-
|
|
436
|
+
return func({
|
|
411
437
|
payload: {
|
|
438
|
+
userId,
|
|
439
|
+
anonymousId,
|
|
412
440
|
obj: {
|
|
413
441
|
properties: props,
|
|
414
442
|
event,
|
|
@@ -422,12 +450,44 @@ export class ChordAnalytics {
|
|
|
422
450
|
},
|
|
423
451
|
next: (_) => _,
|
|
424
452
|
});
|
|
425
|
-
if (result && typeof result === 'object') {
|
|
426
|
-
middlewareProps = Object.assign(Object.assign({}, middlewareProps), result);
|
|
427
|
-
}
|
|
428
453
|
}
|
|
429
454
|
catch (error) {
|
|
430
455
|
console.warn('Middleware error:', error);
|
|
456
|
+
return undefined;
|
|
457
|
+
}
|
|
458
|
+
};
|
|
459
|
+
/**
|
|
460
|
+
* Give an asynchronous middleware until MIDDLEWARE_TIMEOUT_MS to produce
|
|
461
|
+
* its properties. Neither a timeout nor a rejection is allowed to reach
|
|
462
|
+
* the caller: a middleware must never cost the event.
|
|
463
|
+
*/
|
|
464
|
+
const settle = (returned) => {
|
|
465
|
+
if (!isPromise(returned))
|
|
466
|
+
return returned;
|
|
467
|
+
return withTimeout(returned, MIDDLEWARE_TIMEOUT_MS)
|
|
468
|
+
.then((settled) => {
|
|
469
|
+
if (settled === TIMED_OUT) {
|
|
470
|
+
console.warn(`Middleware timed out after ${MIDDLEWARE_TIMEOUT_MS}ms; its properties were dropped`);
|
|
471
|
+
return undefined;
|
|
472
|
+
}
|
|
473
|
+
return settled;
|
|
474
|
+
})
|
|
475
|
+
.catch((error) => {
|
|
476
|
+
console.warn('Middleware error:', error);
|
|
477
|
+
return undefined;
|
|
478
|
+
});
|
|
479
|
+
};
|
|
480
|
+
// Every middleware is invoked in registration order, as before. Only if
|
|
481
|
+
// one actually returned a promise is anything awaited -- so a set of
|
|
482
|
+
// purely synchronous middleware reaches the CDP on the same tick it
|
|
483
|
+
// always has, and callers that never await `track()` are unaffected.
|
|
484
|
+
const returned = this.middleware.map(invoke);
|
|
485
|
+
const results = returned.some(isPromise)
|
|
486
|
+
? yield Promise.all(returned.map(settle))
|
|
487
|
+
: returned;
|
|
488
|
+
results.forEach((result) => {
|
|
489
|
+
if (result && typeof result === 'object') {
|
|
490
|
+
middlewareProps = Object.assign(Object.assign({}, middlewareProps), result);
|
|
431
491
|
}
|
|
432
492
|
});
|
|
433
493
|
}
|
package/dist/types/client.d.ts
CHANGED
|
@@ -88,10 +88,23 @@ type MiddlewarePayload = {
|
|
|
88
88
|
page: PageContext;
|
|
89
89
|
};
|
|
90
90
|
};
|
|
91
|
+
/**
|
|
92
|
+
* The current user and anonymous ids, read from the CDP at send time.
|
|
93
|
+
* Segment's middleware payload exposes these as `userId()`/`anonymousId()`
|
|
94
|
+
* methods on a facade; they are plain values here.
|
|
95
|
+
*/
|
|
96
|
+
userId?: string;
|
|
97
|
+
anonymousId?: string;
|
|
91
98
|
};
|
|
92
99
|
type MiddlewareArgs = {
|
|
93
100
|
payload: MiddlewarePayload;
|
|
94
101
|
next: <T>(arg: T) => T;
|
|
95
102
|
};
|
|
96
|
-
export type
|
|
103
|
+
export type MiddlewareResult = Partial<EventProperties> | void;
|
|
104
|
+
/**
|
|
105
|
+
* A source middleware may be synchronous or asynchronous. An asynchronous one
|
|
106
|
+
* is awaited before the event is dispatched, bounded by a timeout — see
|
|
107
|
+
* `addSourceMiddleware` in the README.
|
|
108
|
+
*/
|
|
109
|
+
export type AnalyticsMiddleware = (args: MiddlewareArgs) => MiddlewareResult | Promise<MiddlewareResult>;
|
|
97
110
|
export {};
|
package/dist/utils.d.ts
CHANGED
|
@@ -1,2 +1,21 @@
|
|
|
1
1
|
import type { EventProperties } from './types/index.js';
|
|
2
2
|
export declare const pruneNullValues: (props: EventProperties) => EventProperties;
|
|
3
|
+
/**
|
|
4
|
+
* How long a single asynchronous source middleware may take before its
|
|
5
|
+
* contribution is abandoned.
|
|
6
|
+
*
|
|
7
|
+
* Middleware runs on the path to the CDP, so an unbounded wait would let one
|
|
8
|
+
* slow lookup hold up delivery indefinitely. `Order Completed` in particular
|
|
9
|
+
* fires on a confirmation page shoppers close, so a late event is a lost one.
|
|
10
|
+
*/
|
|
11
|
+
export declare const MIDDLEWARE_TIMEOUT_MS = 1000;
|
|
12
|
+
/** Returned by {@link withTimeout} when the promise did not settle in time. */
|
|
13
|
+
export declare const TIMED_OUT: unique symbol;
|
|
14
|
+
export declare const isPromise: (value: unknown) => value is Promise<unknown>;
|
|
15
|
+
/**
|
|
16
|
+
* Resolve `promise`, or {@link TIMED_OUT} if it has not settled within `ms`.
|
|
17
|
+
*
|
|
18
|
+
* The timer is cleared either way, so a promise that settles quickly does not
|
|
19
|
+
* leave a pending timeout behind.
|
|
20
|
+
*/
|
|
21
|
+
export declare const withTimeout: <T>(promise: Promise<T>, ms: number) => Promise<typeof TIMED_OUT | T>;
|
package/dist/utils.js
CHANGED
|
@@ -1,3 +1,12 @@
|
|
|
1
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
2
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
3
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
4
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
5
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
6
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
7
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
8
|
+
});
|
|
9
|
+
};
|
|
1
10
|
export const pruneNullValues = (props) => {
|
|
2
11
|
if (typeof props !== 'object' || props === null)
|
|
3
12
|
return props;
|
|
@@ -19,3 +28,33 @@ export const pruneNullValues = (props) => {
|
|
|
19
28
|
}
|
|
20
29
|
return props;
|
|
21
30
|
};
|
|
31
|
+
/**
|
|
32
|
+
* How long a single asynchronous source middleware may take before its
|
|
33
|
+
* contribution is abandoned.
|
|
34
|
+
*
|
|
35
|
+
* Middleware runs on the path to the CDP, so an unbounded wait would let one
|
|
36
|
+
* slow lookup hold up delivery indefinitely. `Order Completed` in particular
|
|
37
|
+
* fires on a confirmation page shoppers close, so a late event is a lost one.
|
|
38
|
+
*/
|
|
39
|
+
export const MIDDLEWARE_TIMEOUT_MS = 1000;
|
|
40
|
+
/** Returned by {@link withTimeout} when the promise did not settle in time. */
|
|
41
|
+
export const TIMED_OUT = Symbol('timed-out');
|
|
42
|
+
export const isPromise = (value) => typeof (value === null || value === void 0 ? void 0 : value.then) === 'function';
|
|
43
|
+
/**
|
|
44
|
+
* Resolve `promise`, or {@link TIMED_OUT} if it has not settled within `ms`.
|
|
45
|
+
*
|
|
46
|
+
* The timer is cleared either way, so a promise that settles quickly does not
|
|
47
|
+
* leave a pending timeout behind.
|
|
48
|
+
*/
|
|
49
|
+
export const withTimeout = (promise, ms) => __awaiter(void 0, void 0, void 0, function* () {
|
|
50
|
+
let timer;
|
|
51
|
+
const timeout = new Promise((resolve) => {
|
|
52
|
+
timer = setTimeout(() => resolve(TIMED_OUT), ms);
|
|
53
|
+
});
|
|
54
|
+
try {
|
|
55
|
+
return yield Promise.race([promise, timeout]);
|
|
56
|
+
}
|
|
57
|
+
finally {
|
|
58
|
+
clearTimeout(timer);
|
|
59
|
+
}
|
|
60
|
+
});
|
package/package.json
CHANGED
|
@@ -1,8 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@chordcommerce/analytics",
|
|
3
3
|
"description": "Chord Commerce event tracking",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.24.1",
|
|
5
5
|
"author": "Chord Commerce <help@chord.co> (https://www.chord.co)",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/chordcommerce/chord-pixel.git",
|
|
9
|
+
"directory": "packages/core"
|
|
10
|
+
},
|
|
11
|
+
"publishConfig": {
|
|
12
|
+
"registry": "https://registry.npmjs.org/",
|
|
13
|
+
"@chordcommerce:registry": "https://registry.npmjs.org/",
|
|
14
|
+
"provenance": false
|
|
15
|
+
},
|
|
6
16
|
"type": "module",
|
|
7
17
|
"main": "dist/index.js",
|
|
8
18
|
"types": "dist/index.d.ts",
|