choreograph-create-pixel 1.4.3 → 1.4.4

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 CHANGED
@@ -1,6 +1,6 @@
1
- # Choreograph Create Pixel
1
+ # Creative Optimizations Pixel
2
2
 
3
- This library lets you apply best practises to [Create](https://create.choreograph.com/) pixel development and implementation.
3
+ This library lets you apply best practises to [Creative Optimizations](https://create.choreograph.com/) pixel development and implementation.
4
4
 
5
5
  ## Features
6
6
 
@@ -0,0 +1,370 @@
1
+ /*! choreograph-create-pixel v1.4.4 2026/03/14 */
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.js
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ default: () => ChoreographCreatePixel
24
+ });
25
+ module.exports = __toCommonJS(index_exports);
26
+
27
+ // src/helpers.js
28
+ function getUrl({ allowedQueryParameters = [], allowHash = false } = {}) {
29
+ let url = `${location.protocol}//${location.host}${location.pathname}`;
30
+ const parameters = new URLSearchParams(location.search);
31
+ allowedQueryParameters.reduce((paramAdded, parameter) => {
32
+ const separator = paramAdded ? "&" : "?";
33
+ const key = encodeURI(parameter);
34
+ const value = parameters.get(parameter);
35
+ if (value != null) {
36
+ url += `${separator}${key}${value === "" ? "" : `=${encodeURI(value)}`}`;
37
+ return true;
38
+ }
39
+ return paramAdded;
40
+ }, false);
41
+ if (allowHash) url += location.hash;
42
+ return url;
43
+ }
44
+ function getAllPathSegments() {
45
+ return location.pathname.split("/").filter((segment) => segment).map((segment) => decodeURI(segment));
46
+ }
47
+ function getPathSegment(index) {
48
+ return getAllPathSegments()[index];
49
+ }
50
+ function getAllQueryParameters() {
51
+ return location.search.replace(/^\?/, "").split("&").filter((parameter) => parameter).reduce(
52
+ (parameters, parameter) => ({
53
+ ...parameters,
54
+ [decodeURI(parameter.split("=")[0])]: decodeURI(
55
+ parameter.split("=")[1] || ""
56
+ )
57
+ }),
58
+ {}
59
+ );
60
+ }
61
+ function getQueryParameter(key) {
62
+ return getAllQueryParameters()[key];
63
+ }
64
+
65
+ // src/log.js
66
+ function log(level, message, data = null) {
67
+ if (!this.config.debug || this.logs.includes(message)) return;
68
+ const args = [
69
+ `%cCreative Optimizations%c ${this.config.icons[this.config.type]} ${this.config.type} %c${message}`,
70
+ "background:black;color:white;border-radius:3px;padding:3px 6px",
71
+ "font-weight:bold",
72
+ `color:${this.config.colors[level]}`
73
+ ];
74
+ if (data) args.push(data);
75
+ console.info(...args);
76
+ this.logs.push(message);
77
+ }
78
+
79
+ // src/validate.js
80
+ function validateConfig() {
81
+ if (typeof this.config.advertiser !== "number")
82
+ this.log("error", "Please use a number", {
83
+ advertiser: this.config.advertiser
84
+ });
85
+ else if (![
86
+ "scraper",
87
+ "viewed",
88
+ "basketed",
89
+ "purchased",
90
+ "attribution",
91
+ "conversion"
92
+ ].includes(this.config.type))
93
+ this.log(
94
+ "error",
95
+ "Please use scraper, viewed, basketed, purchased, attribution or conversion",
96
+ { type: this.config.type }
97
+ );
98
+ else if (["attribution", "conversion"].includes(this.config.type) && typeof this.config.label !== "string")
99
+ this.log("error", "Please use a string", {
100
+ label: this.config.label
101
+ });
102
+ else if (!(this.config.url instanceof RegExp))
103
+ this.log("error", "Please use a regular expression", {
104
+ url: this.config.url
105
+ });
106
+ else if (!["attribution", "conversion"].includes(this.config.type) && !this.config.scrape)
107
+ this.log("error", "Please provide something to scrape", {
108
+ scrape: this.config.scrape
109
+ });
110
+ else return true;
111
+ }
112
+
113
+ // src/attribute.js
114
+ function attribute() {
115
+ const ccpid = getQueryParameter("ccpid");
116
+ if (!ccpid) return this.log("warn", "ccpid query parameter not present");
117
+ if (!/^[0-9a-f]{20}$/.test(ccpid))
118
+ return this.log(
119
+ "error",
120
+ "ccpid query parameter is not formatted correctly"
121
+ );
122
+ const storageItemLabel = `choreograph-${this.config.label}`;
123
+ const storedCcpid = localStorage.getItem(storageItemLabel);
124
+ if (storedCcpid === "")
125
+ return this.log("warn", `"${this.config.label}" already converted`);
126
+ if (storedCcpid !== ccpid) localStorage.setItem(storageItemLabel, ccpid);
127
+ this.log("success", `Stored CCPID "${ccpid}" for "${this.config.label}"`);
128
+ }
129
+
130
+ // src/convert.js
131
+ function convert() {
132
+ const label = `choreograph-${this.config.label}`;
133
+ const ccpid = localStorage.getItem(label);
134
+ if (!ccpid)
135
+ return this.log("warn", `"${this.config.label}" attribution not present`);
136
+ this.send(ccpid);
137
+ localStorage.setItem(label, "");
138
+ }
139
+
140
+ // src/scrape.js
141
+ function scrape(element) {
142
+ let hasErrors = false;
143
+ let data;
144
+ const handleField = (field, fieldName) => {
145
+ let result = field;
146
+ if (typeof result === "function") {
147
+ try {
148
+ result = result(element);
149
+ } catch (error) {
150
+ if (this.config.optional.includes(fieldName)) return void 0;
151
+ this.log("error", error.message, {
152
+ [fieldName ? `scrape.${fieldName}` : "scrape"]: result
153
+ });
154
+ hasErrors = true;
155
+ }
156
+ }
157
+ if (typeof result === "string")
158
+ result = result.replace(/\s+/g, " ").trim();
159
+ if ((result == null || result === "") && !this.config.optional.includes(fieldName)) {
160
+ this.log("error", "Value is empty", {
161
+ [fieldName ? `scrape.${fieldName}` : "scrape"]: result
162
+ });
163
+ hasErrors = true;
164
+ }
165
+ return result;
166
+ };
167
+ if (this.config.type !== "scraper") {
168
+ data = handleField(this.config.scrape);
169
+ } else {
170
+ data = Object.keys(this.config.scrape).reduce(
171
+ (acc, fieldName) => ({
172
+ ...acc,
173
+ [fieldName]: handleField(this.config.scrape[fieldName], fieldName)
174
+ }),
175
+ {}
176
+ );
177
+ }
178
+ const dataHash = JSON.stringify(data);
179
+ if (!hasErrors && this.previouslyScrapedHash !== dataHash) {
180
+ try {
181
+ this.config.before(data, (newData) => {
182
+ if (Array.isArray(newData)) newData.forEach((id) => this.send(id));
183
+ else this.send(newData);
184
+ });
185
+ } catch (error) {
186
+ this.log("error", error.message, {
187
+ before: this.config.before
188
+ });
189
+ }
190
+ this.previouslyScrapedHash = dataHash;
191
+ this.logs.length = 0;
192
+ }
193
+ }
194
+
195
+ // src/send.js
196
+ function send(data) {
197
+ let payload;
198
+ let url;
199
+ let method = "GET";
200
+ switch (this.config.type) {
201
+ case "scraper": {
202
+ const { sku } = data;
203
+ delete data.sku;
204
+ payload = {
205
+ "advertiser-id": this.config.advertiser,
206
+ sku,
207
+ fields: data
208
+ };
209
+ url = `https://d.lemonpi.io/scrapes${this.config.debug ? "?validate=true" : ""}`;
210
+ method = "POST";
211
+ break;
212
+ }
213
+ case "viewed":
214
+ case "basketed":
215
+ case "purchased":
216
+ payload = {
217
+ "event-type": `product-${this.config.type}`,
218
+ sku: data
219
+ };
220
+ url = `https://d.lemonpi.io/a/${this.config.advertiser}/product/event?e=${encodeURIComponent(JSON.stringify(payload))}`;
221
+ break;
222
+ case "conversion":
223
+ payload = {
224
+ version: 1,
225
+ type: "conversion",
226
+ name: this.config.label,
227
+ "conversion-attribution-id": data,
228
+ "advertiser-id": this.config.advertiser
229
+ };
230
+ url = `https://content.lemonpi.io/track/event?e=${encodeURIComponent(
231
+ JSON.stringify(payload)
232
+ )}`;
233
+ break;
234
+ }
235
+ if (["viewed", "basketed", "purchased"].includes(this.config.type) && !this.config.debug) {
236
+ new Image().src = url;
237
+ return;
238
+ }
239
+ fetch(
240
+ url,
241
+ method === "POST" ? {
242
+ method: "POST",
243
+ headers: { "Content-Type": "application/json" },
244
+ body: JSON.stringify(payload)
245
+ } : null
246
+ ).then(
247
+ (response) => response.json().then((result) => {
248
+ if (response.ok) {
249
+ this.log("warn", "Successful, with warnings. Details:", {
250
+ payload,
251
+ result
252
+ });
253
+ try {
254
+ this.config.after(data);
255
+ } catch (error) {
256
+ this.log("error", error.message, {
257
+ after: this.config.after
258
+ });
259
+ }
260
+ } else
261
+ this.log(
262
+ "error",
263
+ `Failed: ${response.status} (${response.statusText}). Details:`,
264
+ { payload, result }
265
+ );
266
+ this.logs.length = 0;
267
+ }).catch(() => {
268
+ if (response.ok) {
269
+ this.log("success", "Successful!", { payload });
270
+ try {
271
+ this.config.after(data);
272
+ } catch (error) {
273
+ this.log("error", error.message, {
274
+ after: this.config.after
275
+ });
276
+ }
277
+ } else
278
+ this.log(
279
+ "error",
280
+ `Failed: ${response.status} (${response.statusText}). Details:`,
281
+ { payload, response }
282
+ );
283
+ this.logs.length = 0;
284
+ })
285
+ ).catch((error) => {
286
+ this.log("error", `Failed: ${error.message}`, { payload });
287
+ this.logs.length = 0;
288
+ });
289
+ }
290
+
291
+ // src/index.js
292
+ var ChoreographCreatePixel = class {
293
+ constructor(userConfig) {
294
+ this.previouslyScrapedHash = null;
295
+ this.logs = [];
296
+ this.config = {
297
+ colors: { error: "red", warn: "orange", success: "green" },
298
+ icons: {
299
+ scraper: "\u{1F4DA}",
300
+ viewed: "\u{1F440}",
301
+ basketed: "\u{1F6D2}",
302
+ purchased: "\u{1F9FE}",
303
+ attribution: "\u{1F516}",
304
+ conversion: "\u{1F4C8}"
305
+ },
306
+ debug: /(pixel|lemonpi)_debug/i.test(location.href),
307
+ before: (data, callback) => callback(data),
308
+ after: () => {
309
+ },
310
+ optional: [],
311
+ ...userConfig
312
+ };
313
+ if (this.validateConfig()) this.cycle();
314
+ }
315
+ cycle() {
316
+ if (!this.config.url.test(location.href))
317
+ this.log("warn", `URL pattern does not match "${location.href}"`, {
318
+ url: this.config.url
319
+ });
320
+ else if (this.config.type === "scraper" && document.querySelector('html[class*="translated-"]'))
321
+ this.log(
322
+ "warn",
323
+ "This page has been translated by the browser, and won't be scraped"
324
+ );
325
+ else if (this.config.trigger) {
326
+ const elementAttribute = `create-${this.config.type}-${this.config.trigger.event}`;
327
+ try {
328
+ let elements = typeof this.config.trigger.elements === "string" ? document.querySelectorAll(this.config.trigger.elements) : this.config.trigger.elements();
329
+ if (!elements.forEach) elements = [elements];
330
+ elements.forEach((element) => {
331
+ if (!element.hasAttribute(elementAttribute)) {
332
+ element.addEventListener(
333
+ this.config.trigger.event,
334
+ () => this.config.type === "conversion" ? this.convert() : this.scrape(element)
335
+ );
336
+ element.setAttribute(elementAttribute, "");
337
+ }
338
+ });
339
+ } catch (error) {
340
+ this.log("error", error.message, {
341
+ "trigger.elements": this.config.trigger.elements
342
+ });
343
+ }
344
+ } else {
345
+ switch (this.config.type) {
346
+ case "attribution":
347
+ this.attribute();
348
+ break;
349
+ case "conversion":
350
+ this.convert();
351
+ break;
352
+ default:
353
+ this.scrape();
354
+ break;
355
+ }
356
+ }
357
+ setTimeout(() => this.cycle(), 750);
358
+ }
359
+ };
360
+ ChoreographCreatePixel.getUrl = getUrl;
361
+ ChoreographCreatePixel.getAllPathSegments = getAllPathSegments;
362
+ ChoreographCreatePixel.getPathSegment = getPathSegment;
363
+ ChoreographCreatePixel.getAllQueryParameters = getAllQueryParameters;
364
+ ChoreographCreatePixel.getQueryParameter = getQueryParameter;
365
+ ChoreographCreatePixel.prototype.log = log;
366
+ ChoreographCreatePixel.prototype.validateConfig = validateConfig;
367
+ ChoreographCreatePixel.prototype.attribute = attribute;
368
+ ChoreographCreatePixel.prototype.convert = convert;
369
+ ChoreographCreatePixel.prototype.scrape = scrape;
370
+ ChoreographCreatePixel.prototype.send = send;