@chordcommerce/analytics 1.21.2 → 1.22.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.
- package/dist/cjs/client/identifiers.d.ts +55 -0
- package/dist/cjs/client/identifiers.js +197 -0
- package/dist/cjs/client/index.d.ts +31 -0
- package/dist/cjs/client/index.js +115 -1
- package/dist/cjs/index.d.ts +1 -0
- package/dist/cjs/index.js +1 -0
- package/dist/cjs/types/client.d.ts +14 -0
- package/dist/client/identifiers.d.ts +55 -0
- package/dist/client/identifiers.js +135 -0
- package/dist/client/index.d.ts +31 -0
- package/dist/client/index.js +190 -83
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/types/client.d.ts +14 -0
- package/package.json +1 -1
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shopify Identifier Sync
|
|
3
|
+
*
|
|
4
|
+
* Syncs CDP anonymous ID and Google Client ID to Shopify cart attributes.
|
|
5
|
+
* This enables server-side events (webhooks) to correlate with client-side tracking.
|
|
6
|
+
*/
|
|
7
|
+
export interface ShopifyCart {
|
|
8
|
+
attributes?: Record<string, unknown>;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface IdentifierSyncOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Base URL for Shopify AJAX API calls. Defaults to window.location.origin
|
|
14
|
+
*/
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Locale path for Shopify routes. Defaults to window.Shopify.routes.root or '/'
|
|
18
|
+
*/
|
|
19
|
+
localePath?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Custom identifier getters. By default, reads CDP anonymous ID and Google Client ID.
|
|
22
|
+
*/
|
|
23
|
+
identifiers?: () => Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get a cookie value by name
|
|
27
|
+
*/
|
|
28
|
+
export declare const getCookie: (name: string) => string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Get default identifiers (CDP anonymous ID and Google Client ID)
|
|
31
|
+
*/
|
|
32
|
+
export declare const getDefaultIdentifiers: () => Record<string, string>;
|
|
33
|
+
/**
|
|
34
|
+
* Fetch the current Shopify cart
|
|
35
|
+
*/
|
|
36
|
+
export declare const getShopifyCart: (baseUrl?: string, localePath?: string) => Promise<ShopifyCart>;
|
|
37
|
+
/**
|
|
38
|
+
* Update Shopify cart attributes
|
|
39
|
+
*/
|
|
40
|
+
export declare const updateShopifyCartAttributes: (attributes: Record<string, string>, baseUrl?: string, localePath?: string) => Promise<ShopifyCart>;
|
|
41
|
+
/**
|
|
42
|
+
* Sync CDP and analytics identifiers to Shopify cart attributes.
|
|
43
|
+
*
|
|
44
|
+
* By default, syncs the Chord CDP anonymous ID (`__eventn_id` cookie) and
|
|
45
|
+
* Google Analytics client ID (`_ga` cookie) to the cart. These attributes
|
|
46
|
+
* are included in Shopify order webhooks, enabling server-side events to
|
|
47
|
+
* correlate with client-side tracking sessions.
|
|
48
|
+
*
|
|
49
|
+
* Smart deduplication: only updates the cart if identifiers are missing or
|
|
50
|
+
* have changed, avoiding unnecessary API calls on repeat page loads.
|
|
51
|
+
*
|
|
52
|
+
* @param options - Optional configuration for base URL, locale path, or custom identifier getter
|
|
53
|
+
* @returns The updated cart, or undefined if no update was needed or an error occurred
|
|
54
|
+
*/
|
|
55
|
+
export declare const syncIdentifiersToCart: (options?: IdentifierSyncOptions) => Promise<ShopifyCart | undefined>;
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Shopify Identifier Sync
|
|
4
|
+
*
|
|
5
|
+
* Syncs CDP anonymous ID and Google Client ID to Shopify cart attributes.
|
|
6
|
+
* This enables server-side events (webhooks) to correlate with client-side tracking.
|
|
7
|
+
*/
|
|
8
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
9
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
10
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
11
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
12
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
13
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
14
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
15
|
+
});
|
|
16
|
+
};
|
|
17
|
+
var __generator = (this && this.__generator) || function (thisArg, body) {
|
|
18
|
+
var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t, g;
|
|
19
|
+
return g = { next: verb(0), "throw": verb(1), "return": verb(2) }, typeof Symbol === "function" && (g[Symbol.iterator] = function() { return this; }), g;
|
|
20
|
+
function verb(n) { return function (v) { return step([n, v]); }; }
|
|
21
|
+
function step(op) {
|
|
22
|
+
if (f) throw new TypeError("Generator is already executing.");
|
|
23
|
+
while (g && (g = 0, op[0] && (_ = 0)), _) try {
|
|
24
|
+
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;
|
|
25
|
+
if (y = 0, t) op = [op[0] & 2, t.value];
|
|
26
|
+
switch (op[0]) {
|
|
27
|
+
case 0: case 1: t = op; break;
|
|
28
|
+
case 4: _.label++; return { value: op[1], done: false };
|
|
29
|
+
case 5: _.label++; y = op[1]; op = [0]; continue;
|
|
30
|
+
case 7: op = _.ops.pop(); _.trys.pop(); continue;
|
|
31
|
+
default:
|
|
32
|
+
if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) { _ = 0; continue; }
|
|
33
|
+
if (op[0] === 3 && (!t || (op[1] > t[0] && op[1] < t[3]))) { _.label = op[1]; break; }
|
|
34
|
+
if (op[0] === 6 && _.label < t[1]) { _.label = t[1]; t = op; break; }
|
|
35
|
+
if (t && _.label < t[2]) { _.label = t[2]; _.ops.push(op); break; }
|
|
36
|
+
if (t[2]) _.ops.pop();
|
|
37
|
+
_.trys.pop(); continue;
|
|
38
|
+
}
|
|
39
|
+
op = body.call(thisArg, _);
|
|
40
|
+
} catch (e) { op = [6, e]; y = 0; } finally { f = t = 0; }
|
|
41
|
+
if (op[0] & 5) throw op[1]; return { value: op[0] ? op[1] : void 0, done: true };
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
45
|
+
exports.syncIdentifiersToCart = exports.updateShopifyCartAttributes = exports.getShopifyCart = exports.getDefaultIdentifiers = exports.getCookie = void 0;
|
|
46
|
+
/**
|
|
47
|
+
* Get a cookie value by name
|
|
48
|
+
*/
|
|
49
|
+
var getCookie = function (name) {
|
|
50
|
+
var _a;
|
|
51
|
+
if (typeof document === 'undefined')
|
|
52
|
+
return undefined;
|
|
53
|
+
return (_a = document.cookie
|
|
54
|
+
.split('; ')
|
|
55
|
+
.find(function (row) { return row.startsWith("".concat(name, "=")); })) === null || _a === void 0 ? void 0 : _a.split('=')[1];
|
|
56
|
+
};
|
|
57
|
+
exports.getCookie = getCookie;
|
|
58
|
+
/**
|
|
59
|
+
* Get default identifiers (CDP anonymous ID and Google Client ID)
|
|
60
|
+
*/
|
|
61
|
+
var getDefaultIdentifiers = function () {
|
|
62
|
+
var attributes = {};
|
|
63
|
+
try {
|
|
64
|
+
// CDP anonymous ID (Jitsu/Chord CDP)
|
|
65
|
+
var anonymousId = (0, exports.getCookie)('__eventn_id');
|
|
66
|
+
if (anonymousId) {
|
|
67
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
68
|
+
attributes._cdpAnonymousId = anonymousId;
|
|
69
|
+
}
|
|
70
|
+
// Google Client ID (strip the "GA1.1." or similar prefix)
|
|
71
|
+
var gaCookie = (0, exports.getCookie)('_ga');
|
|
72
|
+
if (gaCookie) {
|
|
73
|
+
var googleClientId = gaCookie.substring(6);
|
|
74
|
+
if (googleClientId) {
|
|
75
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
76
|
+
attributes._googleClientId = googleClientId;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
// eslint-disable-next-line no-console
|
|
82
|
+
console.error('[Chord Analytics]: Error getting identifiers:', e);
|
|
83
|
+
}
|
|
84
|
+
return attributes;
|
|
85
|
+
};
|
|
86
|
+
exports.getDefaultIdentifiers = getDefaultIdentifiers;
|
|
87
|
+
/**
|
|
88
|
+
* Construct a URL for Shopify AJAX API calls
|
|
89
|
+
*/
|
|
90
|
+
var constructUrl = function (path, baseUrl, localePath) {
|
|
91
|
+
var _a, _b;
|
|
92
|
+
var defaultBaseUrl = typeof window !== 'undefined' ? (_a = window.location) === null || _a === void 0 ? void 0 : _a.origin : undefined;
|
|
93
|
+
// Access Shopify.routes.root for locale-aware URLs (cast needed as Window.Shopify type is partial)
|
|
94
|
+
var shopify = window === null || window === void 0 ? void 0 : window.Shopify;
|
|
95
|
+
var defaultLocalePath = ((_b = shopify === null || shopify === void 0 ? void 0 : shopify.routes) === null || _b === void 0 ? void 0 : _b.root) || '/';
|
|
96
|
+
var resolvedBaseUrl = baseUrl !== null && baseUrl !== void 0 ? baseUrl : defaultBaseUrl;
|
|
97
|
+
var resolvedLocalePath = localePath !== null && localePath !== void 0 ? localePath : defaultLocalePath;
|
|
98
|
+
var pathWithLocale = "".concat(resolvedLocalePath).concat(path);
|
|
99
|
+
if (!resolvedBaseUrl) {
|
|
100
|
+
return pathWithLocale.startsWith('/')
|
|
101
|
+
? pathWithLocale
|
|
102
|
+
: "/".concat(pathWithLocale);
|
|
103
|
+
}
|
|
104
|
+
return new URL(pathWithLocale, resolvedBaseUrl);
|
|
105
|
+
};
|
|
106
|
+
/**
|
|
107
|
+
* Fetch the current Shopify cart
|
|
108
|
+
*/
|
|
109
|
+
var getShopifyCart = function (baseUrl, localePath) { return __awaiter(void 0, void 0, void 0, function () {
|
|
110
|
+
var url, response;
|
|
111
|
+
return __generator(this, function (_a) {
|
|
112
|
+
switch (_a.label) {
|
|
113
|
+
case 0:
|
|
114
|
+
url = constructUrl('cart.js', baseUrl, localePath);
|
|
115
|
+
return [4 /*yield*/, fetch(url)];
|
|
116
|
+
case 1:
|
|
117
|
+
response = _a.sent();
|
|
118
|
+
if (!response.ok) {
|
|
119
|
+
throw new Error("Failed to fetch cart: ".concat(response.status));
|
|
120
|
+
}
|
|
121
|
+
return [2 /*return*/, response.json()];
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
}); };
|
|
125
|
+
exports.getShopifyCart = getShopifyCart;
|
|
126
|
+
/**
|
|
127
|
+
* Update Shopify cart attributes
|
|
128
|
+
*/
|
|
129
|
+
var updateShopifyCartAttributes = function (attributes, baseUrl, localePath) { return __awaiter(void 0, void 0, void 0, function () {
|
|
130
|
+
var url, response;
|
|
131
|
+
return __generator(this, function (_a) {
|
|
132
|
+
switch (_a.label) {
|
|
133
|
+
case 0:
|
|
134
|
+
url = constructUrl('cart/update.js', baseUrl, localePath);
|
|
135
|
+
return [4 /*yield*/, fetch(url, {
|
|
136
|
+
method: 'POST',
|
|
137
|
+
headers: {
|
|
138
|
+
'Content-Type': 'application/json',
|
|
139
|
+
},
|
|
140
|
+
body: JSON.stringify({ attributes: attributes }),
|
|
141
|
+
})];
|
|
142
|
+
case 1:
|
|
143
|
+
response = _a.sent();
|
|
144
|
+
if (!response.ok) {
|
|
145
|
+
throw new Error("Failed to update cart attributes: ".concat(response.status));
|
|
146
|
+
}
|
|
147
|
+
return [2 /*return*/, response.json()];
|
|
148
|
+
}
|
|
149
|
+
});
|
|
150
|
+
}); };
|
|
151
|
+
exports.updateShopifyCartAttributes = updateShopifyCartAttributes;
|
|
152
|
+
/**
|
|
153
|
+
* Sync CDP and analytics identifiers to Shopify cart attributes.
|
|
154
|
+
*
|
|
155
|
+
* By default, syncs the Chord CDP anonymous ID (`__eventn_id` cookie) and
|
|
156
|
+
* Google Analytics client ID (`_ga` cookie) to the cart. These attributes
|
|
157
|
+
* are included in Shopify order webhooks, enabling server-side events to
|
|
158
|
+
* correlate with client-side tracking sessions.
|
|
159
|
+
*
|
|
160
|
+
* Smart deduplication: only updates the cart if identifiers are missing or
|
|
161
|
+
* have changed, avoiding unnecessary API calls on repeat page loads.
|
|
162
|
+
*
|
|
163
|
+
* @param options - Optional configuration for base URL, locale path, or custom identifier getter
|
|
164
|
+
* @returns The updated cart, or undefined if no update was needed or an error occurred
|
|
165
|
+
*/
|
|
166
|
+
var syncIdentifiersToCart = function (options) {
|
|
167
|
+
if (options === void 0) { options = {}; }
|
|
168
|
+
return __awaiter(void 0, void 0, void 0, function () {
|
|
169
|
+
var baseUrl, localePath, _a, identifiers, cart_1, attributesToAdd_1, identifiersAlreadyExist, updatedCart, e_1;
|
|
170
|
+
return __generator(this, function (_b) {
|
|
171
|
+
switch (_b.label) {
|
|
172
|
+
case 0:
|
|
173
|
+
_b.trys.push([0, 3, , 4]);
|
|
174
|
+
baseUrl = options.baseUrl, localePath = options.localePath, _a = options.identifiers, identifiers = _a === void 0 ? exports.getDefaultIdentifiers : _a;
|
|
175
|
+
return [4 /*yield*/, (0, exports.getShopifyCart)(baseUrl, localePath)];
|
|
176
|
+
case 1:
|
|
177
|
+
cart_1 = _b.sent();
|
|
178
|
+
attributesToAdd_1 = identifiers();
|
|
179
|
+
identifiersAlreadyExist = Object.keys(attributesToAdd_1).every(function (key) { var _a; return ((_a = cart_1 === null || cart_1 === void 0 ? void 0 : cart_1.attributes) === null || _a === void 0 ? void 0 : _a[key]) === attributesToAdd_1[key]; });
|
|
180
|
+
if (identifiersAlreadyExist) {
|
|
181
|
+
return [2 /*return*/, undefined];
|
|
182
|
+
}
|
|
183
|
+
return [4 /*yield*/, (0, exports.updateShopifyCartAttributes)(attributesToAdd_1, baseUrl, localePath)];
|
|
184
|
+
case 2:
|
|
185
|
+
updatedCart = _b.sent();
|
|
186
|
+
return [2 /*return*/, updatedCart];
|
|
187
|
+
case 3:
|
|
188
|
+
e_1 = _b.sent();
|
|
189
|
+
// eslint-disable-next-line no-console
|
|
190
|
+
console.error('[Chord Analytics]: Error syncing identifiers to cart:', e_1);
|
|
191
|
+
return [2 /*return*/, undefined];
|
|
192
|
+
case 4: return [2 /*return*/];
|
|
193
|
+
}
|
|
194
|
+
});
|
|
195
|
+
});
|
|
196
|
+
};
|
|
197
|
+
exports.syncIdentifiersToCart = syncIdentifiersToCart;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { AnyAnalytics, EventProperties, Options, ValidateResult, ChordAnalyticsOptions, AnyOptions, CartViewedInput, IdentifyTraits, CheckoutStartedInput, CheckoutStepCompletedInput, CheckoutStepViewedInput, EmailCapturedInput, ProductListViewedInput, ProductListFilteredInput, ProductAddedInput, ProductClickedInput, VariantClickedInput, ProductRemovedInput, ProductViewedInput, ProductsSearchedInput, CouponAppliedInput, CouponDeniedInput, CouponEnteredInput, CouponRemovedInput, SignedInInput, SignedOutInput, SignedUpInput, LoginStartedInput, ObjectTypes, OrderCompletedInput, SubscriptionCancelledInput, NavigationClickedInput, PaymentInfoEnteredInput } from '../types/index.js';
|
|
2
2
|
import { AnalyticsMiddleware } from '../types/client.js';
|
|
3
|
+
import { type IdentifierSyncOptions, type ShopifyCart } from './identifiers.js';
|
|
3
4
|
export interface ChordAnalyticsSnippet {
|
|
4
5
|
_loadOptions: ChordAnalyticsOptions<ObjectTypes>;
|
|
5
6
|
invoked: boolean;
|
|
@@ -151,6 +152,36 @@ export declare class ChordAnalytics<T extends ObjectTypes = ObjectTypes> {
|
|
|
151
152
|
*/
|
|
152
153
|
private sendPageEvent;
|
|
153
154
|
reset: () => Promise<void>;
|
|
155
|
+
/**
|
|
156
|
+
* Handler for postMessage events from the Shopify Web Pixel sandbox.
|
|
157
|
+
* Bound to the instance for proper `this` context and cleanup.
|
|
158
|
+
*/
|
|
159
|
+
private webPixelMessageHandler;
|
|
160
|
+
/**
|
|
161
|
+
* Set up a listener for postMessage events from the Shopify Web Pixel sandbox.
|
|
162
|
+
* This allows the web pixel (running in Shopify's sandboxed iframe)
|
|
163
|
+
* to forward analytics events to this ChordAnalytics instance on the storefront.
|
|
164
|
+
*
|
|
165
|
+
* The web pixel sends messages in the format:
|
|
166
|
+
* - { source: 'chord-web-pixel', type: 'track', payload: [eventName, properties] }
|
|
167
|
+
* - { source: 'chord-web-pixel', type: 'identify', payload: [userId, traits] }
|
|
168
|
+
* - { source: 'chord-web-pixel', type: 'page', payload: [properties] }
|
|
169
|
+
*
|
|
170
|
+
* @returns A cleanup function to remove the listener
|
|
171
|
+
*/
|
|
172
|
+
setupWebPixelListener: () => (() => void);
|
|
173
|
+
/**
|
|
174
|
+
* Sync CDP anonymous ID and Google Client ID to Shopify cart attributes.
|
|
175
|
+
* This enables server-side events (webhooks) to correlate with client-side tracking.
|
|
176
|
+
*
|
|
177
|
+
* By default, syncs:
|
|
178
|
+
* - `_cdpAnonymousId`: from the `__eventn_id` cookie (Chord/Jitsu CDP)
|
|
179
|
+
* - `_googleClientId`: from the `_ga` cookie (Google Analytics)
|
|
180
|
+
*
|
|
181
|
+
* @param options - Optional configuration for the sync
|
|
182
|
+
* @returns The updated cart, or undefined if no update was needed or an error occurred
|
|
183
|
+
*/
|
|
184
|
+
syncIdentifiersToCart: (options?: IdentifierSyncOptions) => Promise<ShopifyCart | undefined>;
|
|
154
185
|
trackCartViewed: (props: CartViewedInput<T>, options?: AnyOptions) => Promise<void>;
|
|
155
186
|
trackCheckoutStarted: (props: CheckoutStartedInput<T>, options?: AnyOptions) => Promise<void>;
|
|
156
187
|
trackCheckoutStepCompleted: (props: CheckoutStepCompletedInput, options?: AnyOptions) => Promise<void>;
|
package/dist/cjs/client/index.js
CHANGED
|
@@ -61,6 +61,7 @@ var cdp_js_1 = require("./cdp.js");
|
|
|
61
61
|
var index_js_1 = require("./consent/index.js");
|
|
62
62
|
var index_js_2 = require("../validators/index.js");
|
|
63
63
|
var utils_js_1 = require("../utils.js");
|
|
64
|
+
var identifiers_js_1 = require("./identifiers.js");
|
|
64
65
|
var DEBUG_MODE_STORAGE_KEY = 'chordcommerce/analytics/debug';
|
|
65
66
|
var CONSENT_QUEUE_STORAGE_KEY = 'chordcommerce/analytics/consent-queue';
|
|
66
67
|
var CONSENT_QUEUE_TTL_MS = 5 * 60 * 1000; // 5 minutes
|
|
@@ -69,7 +70,7 @@ var CONSENT_QUEUE_MAX_EVENTS = 50; // Max number of events to queue
|
|
|
69
70
|
var ChordAnalytics = /** @class */ (function () {
|
|
70
71
|
function ChordAnalytics(options) {
|
|
71
72
|
var _this = this;
|
|
72
|
-
var _a, _b, _c;
|
|
73
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _j;
|
|
73
74
|
/**
|
|
74
75
|
* Allows snippet.js to detect whether this library has been initialized yet.
|
|
75
76
|
*/
|
|
@@ -791,6 +792,107 @@ var ChordAnalytics = /** @class */ (function () {
|
|
|
791
792
|
}
|
|
792
793
|
});
|
|
793
794
|
}); };
|
|
795
|
+
/**
|
|
796
|
+
* Handler for postMessage events from the Shopify Web Pixel sandbox.
|
|
797
|
+
* Bound to the instance for proper `this` context and cleanup.
|
|
798
|
+
*/
|
|
799
|
+
this.webPixelMessageHandler = null;
|
|
800
|
+
/**
|
|
801
|
+
* Set up a listener for postMessage events from the Shopify Web Pixel sandbox.
|
|
802
|
+
* This allows the web pixel (running in Shopify's sandboxed iframe)
|
|
803
|
+
* to forward analytics events to this ChordAnalytics instance on the storefront.
|
|
804
|
+
*
|
|
805
|
+
* The web pixel sends messages in the format:
|
|
806
|
+
* - { source: 'chord-web-pixel', type: 'track', payload: [eventName, properties] }
|
|
807
|
+
* - { source: 'chord-web-pixel', type: 'identify', payload: [userId, traits] }
|
|
808
|
+
* - { source: 'chord-web-pixel', type: 'page', payload: [properties] }
|
|
809
|
+
*
|
|
810
|
+
* @returns A cleanup function to remove the listener
|
|
811
|
+
*/
|
|
812
|
+
this.setupWebPixelListener = function () {
|
|
813
|
+
if (typeof window === 'undefined') {
|
|
814
|
+
_this.logger('[Chord Analytics]: setupWebPixelListener called in non-browser environment');
|
|
815
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-function
|
|
816
|
+
return function () { };
|
|
817
|
+
}
|
|
818
|
+
// Remove existing listener if one exists
|
|
819
|
+
if (_this.webPixelMessageHandler) {
|
|
820
|
+
window.removeEventListener('message', _this.webPixelMessageHandler);
|
|
821
|
+
}
|
|
822
|
+
_this.webPixelMessageHandler = function (event) {
|
|
823
|
+
// Validate message structure
|
|
824
|
+
if (!event.data || typeof event.data !== 'object')
|
|
825
|
+
return;
|
|
826
|
+
if (event.data.source !== 'chord-web-pixel')
|
|
827
|
+
return;
|
|
828
|
+
var message = event.data;
|
|
829
|
+
if (_this.options.enableLogging && _this.options.debug) {
|
|
830
|
+
// eslint-disable-next-line no-console
|
|
831
|
+
console.log('[Chord Analytics DEBUG]: Received web pixel message:', message.type, message.payload);
|
|
832
|
+
}
|
|
833
|
+
try {
|
|
834
|
+
switch (message.type) {
|
|
835
|
+
case 'track': {
|
|
836
|
+
var _a = message.payload, eventName = _a[0], properties = _a[1];
|
|
837
|
+
if (eventName) {
|
|
838
|
+
_this.track(eventName, properties);
|
|
839
|
+
}
|
|
840
|
+
break;
|
|
841
|
+
}
|
|
842
|
+
case 'identify': {
|
|
843
|
+
var _b = message.payload, userId = _b[0], traits = _b[1];
|
|
844
|
+
_this.identify(userId, traits);
|
|
845
|
+
break;
|
|
846
|
+
}
|
|
847
|
+
case 'page': {
|
|
848
|
+
var properties = message.payload[0];
|
|
849
|
+
_this.page(properties);
|
|
850
|
+
break;
|
|
851
|
+
}
|
|
852
|
+
default:
|
|
853
|
+
_this.logger("[Chord Analytics]: Unknown web pixel message type: ".concat(message.type));
|
|
854
|
+
}
|
|
855
|
+
}
|
|
856
|
+
catch (error) {
|
|
857
|
+
_this.logger('[Chord Analytics]: Error processing web pixel message:', error);
|
|
858
|
+
}
|
|
859
|
+
};
|
|
860
|
+
window.addEventListener('message', _this.webPixelMessageHandler);
|
|
861
|
+
_this.logger('[Chord Analytics]: Web pixel listener initialized');
|
|
862
|
+
// Return cleanup function
|
|
863
|
+
return function () {
|
|
864
|
+
if (_this.webPixelMessageHandler) {
|
|
865
|
+
window.removeEventListener('message', _this.webPixelMessageHandler);
|
|
866
|
+
_this.webPixelMessageHandler = null;
|
|
867
|
+
_this.logger('[Chord Analytics]: Web pixel listener removed');
|
|
868
|
+
}
|
|
869
|
+
};
|
|
870
|
+
};
|
|
871
|
+
/**
|
|
872
|
+
* Sync CDP anonymous ID and Google Client ID to Shopify cart attributes.
|
|
873
|
+
* This enables server-side events (webhooks) to correlate with client-side tracking.
|
|
874
|
+
*
|
|
875
|
+
* By default, syncs:
|
|
876
|
+
* - `_cdpAnonymousId`: from the `__eventn_id` cookie (Chord/Jitsu CDP)
|
|
877
|
+
* - `_googleClientId`: from the `_ga` cookie (Google Analytics)
|
|
878
|
+
*
|
|
879
|
+
* @param options - Optional configuration for the sync
|
|
880
|
+
* @returns The updated cart, or undefined if no update was needed or an error occurred
|
|
881
|
+
*/
|
|
882
|
+
this.syncIdentifiersToCart = function (options) { return __awaiter(_this, void 0, void 0, function () {
|
|
883
|
+
var result;
|
|
884
|
+
return __generator(this, function (_a) {
|
|
885
|
+
switch (_a.label) {
|
|
886
|
+
case 0: return [4 /*yield*/, (0, identifiers_js_1.syncIdentifiersToCart)(options)];
|
|
887
|
+
case 1:
|
|
888
|
+
result = _a.sent();
|
|
889
|
+
if (result) {
|
|
890
|
+
this.logger('[Chord Analytics]: Identifiers synced to cart');
|
|
891
|
+
}
|
|
892
|
+
return [2 /*return*/, result];
|
|
893
|
+
}
|
|
894
|
+
});
|
|
895
|
+
}); };
|
|
794
896
|
// TODO: Add support for props.products
|
|
795
897
|
this.trackCartViewed = function (props, options) { return __awaiter(_this, void 0, void 0, function () {
|
|
796
898
|
var cart, payload, formattedPayload;
|
|
@@ -1345,6 +1447,18 @@ var ChordAnalytics = /** @class */ (function () {
|
|
|
1345
1447
|
window[this.options.namespace] = (window === null || window === void 0 ? void 0 : window[this.options.namespace]) || this;
|
|
1346
1448
|
this.initializeCdp();
|
|
1347
1449
|
}
|
|
1450
|
+
// Set up web pixel listener: explicit true/false takes precedence,
|
|
1451
|
+
// otherwise auto-detect by checking for window.Shopify.shop (Liquid storefront)
|
|
1452
|
+
var shouldEnableWebPixel = (_e = (_d = this.options) === null || _d === void 0 ? void 0 : _d.enableWebPixelListener) !== null && _e !== void 0 ? _e : (typeof window !== 'undefined' && !!((_f = window.Shopify) === null || _f === void 0 ? void 0 : _f.shop));
|
|
1453
|
+
if (shouldEnableWebPixel) {
|
|
1454
|
+
this.setupWebPixelListener();
|
|
1455
|
+
}
|
|
1456
|
+
// Sync identifiers to cart: explicit true/false takes precedence,
|
|
1457
|
+
// otherwise auto-detect by checking for window.Shopify.shop (Liquid storefront)
|
|
1458
|
+
var shouldEnableIdentifierSync = (_h = (_g = this.options) === null || _g === void 0 ? void 0 : _g.enableIdentifierSync) !== null && _h !== void 0 ? _h : (typeof window !== 'undefined' && !!((_j = window.Shopify) === null || _j === void 0 ? void 0 : _j.shop));
|
|
1459
|
+
if (shouldEnableIdentifierSync) {
|
|
1460
|
+
this.syncIdentifiersToCart();
|
|
1461
|
+
}
|
|
1348
1462
|
}
|
|
1349
1463
|
/**
|
|
1350
1464
|
* Initialize CDP snippet and wait for it to be ready.
|
package/dist/cjs/index.d.ts
CHANGED
package/dist/cjs/index.js
CHANGED
|
@@ -15,4 +15,5 @@ var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
|
15
15
|
};
|
|
16
16
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
17
|
__exportStar(require("./client/index.js"), exports);
|
|
18
|
+
__exportStar(require("./client/identifiers.js"), exports);
|
|
18
19
|
__exportStar(require("./types/index.js"), exports);
|
|
@@ -34,6 +34,20 @@ export interface ChordAnalyticsOptions<T extends ObjectTypes = ObjectTypes> {
|
|
|
34
34
|
awaitConsent?: boolean;
|
|
35
35
|
debug?: boolean;
|
|
36
36
|
enableLogging?: boolean;
|
|
37
|
+
/**
|
|
38
|
+
* Controls the postMessage listener for events from the Shopify Custom Web
|
|
39
|
+
* Pixel sandbox. When true, always enables the listener. When false, always
|
|
40
|
+
* disables it. When undefined (default), auto-detects by checking for
|
|
41
|
+
* window.Shopify.shop which is present on Shopify Liquid storefronts.
|
|
42
|
+
*/
|
|
43
|
+
enableWebPixelListener?: boolean;
|
|
44
|
+
/**
|
|
45
|
+
* Controls syncing CDP anonymous ID and Google Client ID to Shopify cart
|
|
46
|
+
* attributes. When true, always enables sync. When false, always disables it.
|
|
47
|
+
* When undefined (default), auto-detects by checking for window.Shopify.shop
|
|
48
|
+
* which is present on Shopify Liquid storefronts.
|
|
49
|
+
*/
|
|
50
|
+
enableIdentifierSync?: boolean;
|
|
37
51
|
formatters: ChordAnalyticsFormatters<T>;
|
|
38
52
|
namespace?: string;
|
|
39
53
|
stripNull?: boolean;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shopify Identifier Sync
|
|
3
|
+
*
|
|
4
|
+
* Syncs CDP anonymous ID and Google Client ID to Shopify cart attributes.
|
|
5
|
+
* This enables server-side events (webhooks) to correlate with client-side tracking.
|
|
6
|
+
*/
|
|
7
|
+
export interface ShopifyCart {
|
|
8
|
+
attributes?: Record<string, unknown>;
|
|
9
|
+
[key: string]: unknown;
|
|
10
|
+
}
|
|
11
|
+
export interface IdentifierSyncOptions {
|
|
12
|
+
/**
|
|
13
|
+
* Base URL for Shopify AJAX API calls. Defaults to window.location.origin
|
|
14
|
+
*/
|
|
15
|
+
baseUrl?: string;
|
|
16
|
+
/**
|
|
17
|
+
* Locale path for Shopify routes. Defaults to window.Shopify.routes.root or '/'
|
|
18
|
+
*/
|
|
19
|
+
localePath?: string;
|
|
20
|
+
/**
|
|
21
|
+
* Custom identifier getters. By default, reads CDP anonymous ID and Google Client ID.
|
|
22
|
+
*/
|
|
23
|
+
identifiers?: () => Record<string, string>;
|
|
24
|
+
}
|
|
25
|
+
/**
|
|
26
|
+
* Get a cookie value by name
|
|
27
|
+
*/
|
|
28
|
+
export declare const getCookie: (name: string) => string | undefined;
|
|
29
|
+
/**
|
|
30
|
+
* Get default identifiers (CDP anonymous ID and Google Client ID)
|
|
31
|
+
*/
|
|
32
|
+
export declare const getDefaultIdentifiers: () => Record<string, string>;
|
|
33
|
+
/**
|
|
34
|
+
* Fetch the current Shopify cart
|
|
35
|
+
*/
|
|
36
|
+
export declare const getShopifyCart: (baseUrl?: string, localePath?: string) => Promise<ShopifyCart>;
|
|
37
|
+
/**
|
|
38
|
+
* Update Shopify cart attributes
|
|
39
|
+
*/
|
|
40
|
+
export declare const updateShopifyCartAttributes: (attributes: Record<string, string>, baseUrl?: string, localePath?: string) => Promise<ShopifyCart>;
|
|
41
|
+
/**
|
|
42
|
+
* Sync CDP and analytics identifiers to Shopify cart attributes.
|
|
43
|
+
*
|
|
44
|
+
* By default, syncs the Chord CDP anonymous ID (`__eventn_id` cookie) and
|
|
45
|
+
* Google Analytics client ID (`_ga` cookie) to the cart. These attributes
|
|
46
|
+
* are included in Shopify order webhooks, enabling server-side events to
|
|
47
|
+
* correlate with client-side tracking sessions.
|
|
48
|
+
*
|
|
49
|
+
* Smart deduplication: only updates the cart if identifiers are missing or
|
|
50
|
+
* have changed, avoiding unnecessary API calls on repeat page loads.
|
|
51
|
+
*
|
|
52
|
+
* @param options - Optional configuration for base URL, locale path, or custom identifier getter
|
|
53
|
+
* @returns The updated cart, or undefined if no update was needed or an error occurred
|
|
54
|
+
*/
|
|
55
|
+
export declare const syncIdentifiersToCart: (options?: IdentifierSyncOptions) => Promise<ShopifyCart | undefined>;
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Shopify Identifier Sync
|
|
3
|
+
*
|
|
4
|
+
* Syncs CDP anonymous ID and Google Client ID to Shopify cart attributes.
|
|
5
|
+
* This enables server-side events (webhooks) to correlate with client-side tracking.
|
|
6
|
+
*/
|
|
7
|
+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
|
|
8
|
+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
|
|
9
|
+
return new (P || (P = Promise))(function (resolve, reject) {
|
|
10
|
+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
|
|
11
|
+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
|
|
12
|
+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
|
|
13
|
+
step((generator = generator.apply(thisArg, _arguments || [])).next());
|
|
14
|
+
});
|
|
15
|
+
};
|
|
16
|
+
/**
|
|
17
|
+
* Get a cookie value by name
|
|
18
|
+
*/
|
|
19
|
+
export const getCookie = (name) => {
|
|
20
|
+
var _a;
|
|
21
|
+
if (typeof document === 'undefined')
|
|
22
|
+
return undefined;
|
|
23
|
+
return (_a = document.cookie
|
|
24
|
+
.split('; ')
|
|
25
|
+
.find((row) => row.startsWith(`${name}=`))) === null || _a === void 0 ? void 0 : _a.split('=')[1];
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Get default identifiers (CDP anonymous ID and Google Client ID)
|
|
29
|
+
*/
|
|
30
|
+
export const getDefaultIdentifiers = () => {
|
|
31
|
+
const attributes = {};
|
|
32
|
+
try {
|
|
33
|
+
// CDP anonymous ID (Jitsu/Chord CDP)
|
|
34
|
+
const anonymousId = getCookie('__eventn_id');
|
|
35
|
+
if (anonymousId) {
|
|
36
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
37
|
+
attributes._cdpAnonymousId = anonymousId;
|
|
38
|
+
}
|
|
39
|
+
// Google Client ID (strip the "GA1.1." or similar prefix)
|
|
40
|
+
const gaCookie = getCookie('_ga');
|
|
41
|
+
if (gaCookie) {
|
|
42
|
+
const googleClientId = gaCookie.substring(6);
|
|
43
|
+
if (googleClientId) {
|
|
44
|
+
// eslint-disable-next-line no-underscore-dangle
|
|
45
|
+
attributes._googleClientId = googleClientId;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
catch (e) {
|
|
50
|
+
// eslint-disable-next-line no-console
|
|
51
|
+
console.error('[Chord Analytics]: Error getting identifiers:', e);
|
|
52
|
+
}
|
|
53
|
+
return attributes;
|
|
54
|
+
};
|
|
55
|
+
/**
|
|
56
|
+
* Construct a URL for Shopify AJAX API calls
|
|
57
|
+
*/
|
|
58
|
+
const constructUrl = (path, baseUrl, localePath) => {
|
|
59
|
+
var _a, _b;
|
|
60
|
+
const defaultBaseUrl = typeof window !== 'undefined' ? (_a = window.location) === null || _a === void 0 ? void 0 : _a.origin : undefined;
|
|
61
|
+
// Access Shopify.routes.root for locale-aware URLs (cast needed as Window.Shopify type is partial)
|
|
62
|
+
const shopify = window === null || window === void 0 ? void 0 : window.Shopify;
|
|
63
|
+
const defaultLocalePath = ((_b = shopify === null || shopify === void 0 ? void 0 : shopify.routes) === null || _b === void 0 ? void 0 : _b.root) || '/';
|
|
64
|
+
const resolvedBaseUrl = baseUrl !== null && baseUrl !== void 0 ? baseUrl : defaultBaseUrl;
|
|
65
|
+
const resolvedLocalePath = localePath !== null && localePath !== void 0 ? localePath : defaultLocalePath;
|
|
66
|
+
const pathWithLocale = `${resolvedLocalePath}${path}`;
|
|
67
|
+
if (!resolvedBaseUrl) {
|
|
68
|
+
return pathWithLocale.startsWith('/')
|
|
69
|
+
? pathWithLocale
|
|
70
|
+
: `/${pathWithLocale}`;
|
|
71
|
+
}
|
|
72
|
+
return new URL(pathWithLocale, resolvedBaseUrl);
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* Fetch the current Shopify cart
|
|
76
|
+
*/
|
|
77
|
+
export const getShopifyCart = (baseUrl, localePath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
78
|
+
const url = constructUrl('cart.js', baseUrl, localePath);
|
|
79
|
+
const response = yield fetch(url);
|
|
80
|
+
if (!response.ok) {
|
|
81
|
+
throw new Error(`Failed to fetch cart: ${response.status}`);
|
|
82
|
+
}
|
|
83
|
+
return response.json();
|
|
84
|
+
});
|
|
85
|
+
/**
|
|
86
|
+
* Update Shopify cart attributes
|
|
87
|
+
*/
|
|
88
|
+
export const updateShopifyCartAttributes = (attributes, baseUrl, localePath) => __awaiter(void 0, void 0, void 0, function* () {
|
|
89
|
+
const url = constructUrl('cart/update.js', baseUrl, localePath);
|
|
90
|
+
const response = yield fetch(url, {
|
|
91
|
+
method: 'POST',
|
|
92
|
+
headers: {
|
|
93
|
+
'Content-Type': 'application/json',
|
|
94
|
+
},
|
|
95
|
+
body: JSON.stringify({ attributes }),
|
|
96
|
+
});
|
|
97
|
+
if (!response.ok) {
|
|
98
|
+
throw new Error(`Failed to update cart attributes: ${response.status}`);
|
|
99
|
+
}
|
|
100
|
+
return response.json();
|
|
101
|
+
});
|
|
102
|
+
/**
|
|
103
|
+
* Sync CDP and analytics identifiers to Shopify cart attributes.
|
|
104
|
+
*
|
|
105
|
+
* By default, syncs the Chord CDP anonymous ID (`__eventn_id` cookie) and
|
|
106
|
+
* Google Analytics client ID (`_ga` cookie) to the cart. These attributes
|
|
107
|
+
* are included in Shopify order webhooks, enabling server-side events to
|
|
108
|
+
* correlate with client-side tracking sessions.
|
|
109
|
+
*
|
|
110
|
+
* Smart deduplication: only updates the cart if identifiers are missing or
|
|
111
|
+
* have changed, avoiding unnecessary API calls on repeat page loads.
|
|
112
|
+
*
|
|
113
|
+
* @param options - Optional configuration for base URL, locale path, or custom identifier getter
|
|
114
|
+
* @returns The updated cart, or undefined if no update was needed or an error occurred
|
|
115
|
+
*/
|
|
116
|
+
export const syncIdentifiersToCart = (options = {}) => __awaiter(void 0, void 0, void 0, function* () {
|
|
117
|
+
try {
|
|
118
|
+
const { baseUrl, localePath, identifiers = getDefaultIdentifiers } = options;
|
|
119
|
+
const cart = yield getShopifyCart(baseUrl, localePath);
|
|
120
|
+
const attributesToAdd = identifiers();
|
|
121
|
+
// Check if all identifiers already exist with correct values
|
|
122
|
+
const identifiersAlreadyExist = Object.keys(attributesToAdd).every((key) => { var _a; return ((_a = cart === null || cart === void 0 ? void 0 : cart.attributes) === null || _a === void 0 ? void 0 : _a[key]) === attributesToAdd[key]; });
|
|
123
|
+
if (identifiersAlreadyExist) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
// Update cart with new identifiers
|
|
127
|
+
const updatedCart = yield updateShopifyCartAttributes(attributesToAdd, baseUrl, localePath);
|
|
128
|
+
return updatedCart;
|
|
129
|
+
}
|
|
130
|
+
catch (e) {
|
|
131
|
+
// eslint-disable-next-line no-console
|
|
132
|
+
console.error('[Chord Analytics]: Error syncing identifiers to cart:', e);
|
|
133
|
+
return undefined;
|
|
134
|
+
}
|
|
135
|
+
});
|