choreograph-create-pixel 1.3.2 β†’ 1.4.0

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
@@ -4,7 +4,7 @@ This library lets you apply best practises to [Create](https://www.lemonpi.io/)
4
4
 
5
5
  ## Features
6
6
 
7
- - [x] Supports [scraper](#scraper-pixels), [segment](#segment-pixels) and [conversion](#conversion-pixels) (coming soon) pixels
7
+ - [x] Supports [scraper](#scraper-pixels), [segment](#segment-pixels) and [post-click](#post-click-pixels) pixels
8
8
  - [x] Supports dynamic page content updates
9
9
  - [x] Prevents scraping browser-translated content
10
10
  - [x] Continuous URL validation
@@ -15,7 +15,7 @@ This library lets you apply best practises to [Create](https://www.lemonpi.io/)
15
15
 
16
16
  <small>Type: `scraper`</small>
17
17
 
18
- Scraper pixels are used to scrape (read) data from websites, and store that data as products within an advertiser's product store.
18
+ Scraper pixels are used to scrape (collect) data from websites, and store that data as products within an advertiser's product store.
19
19
 
20
20
  ### Segment pixels
21
21
 
@@ -23,9 +23,11 @@ Scraper pixels are used to scrape (read) data from websites, and store that data
23
23
 
24
24
  Products in ads are shown by recommendation. Segment pixels determine this recommendation on a consumer level, by storing a **cookie** at different moments or events during the consumer's journey.
25
25
 
26
- ### Conversion pixels
26
+ ### Post-click pixels
27
27
 
28
- _(coming soon)_
28
+ <small>Types: `attribution` and `conversion`</small>
29
+
30
+ Post-click conversion attribution tracks which clicked creative variant attributed to a website conversion. The **attribution** pixel collects the creative's impression ID from the URL (`ccpid` query parameter) on the landing page, and the **conversion** pixel logs this ID as a performance metric in Create when a user performed a conversion, like a purchase.
29
31
 
30
32
  ## Quickstart
31
33
 
@@ -44,7 +46,7 @@ new Pixel({
44
46
  // Where on the website should this pixel be enabled?
45
47
  url: /example\.com\/products\/\d/,
46
48
 
47
- // Functions here are continuously being evaluated for value changes
49
+ // Scrape functions are continuously re-evaluated for value changes
48
50
  scrape: {
49
51
  // Data layer example
50
52
  sku: () => window.dataLayer[0].product.sku,
@@ -97,6 +99,33 @@ new Pixel({
97
99
  // Segment pixels support multiple SKUs
98
100
  scrape: () => window.dataLayer[0].cart.map((product) => product.sku),
99
101
  });
102
+
103
+ // Attribution post-click pixel
104
+ new Pixel({
105
+ advertiser: 0,
106
+ type: "attribution",
107
+
108
+ // Uniquely label each set of post-click pixels
109
+ label: "example",
110
+
111
+ // Match this URL to the landing page of your campaign
112
+ url: /example\.com\/products\/\d/,
113
+ });
114
+
115
+ // Conversion post-click pixel
116
+ new Pixel({
117
+ advertiser: 0,
118
+ type: "conversion",
119
+ label: "example",
120
+
121
+ // Match this URL to the conversion page of your campaign
122
+ url: /example\.com\/cart/,
123
+
124
+ trigger: {
125
+ event: "click",
126
+ elements: "button.checkout",
127
+ },
128
+ });
100
129
  ```
101
130
 
102
131
  > ES modules require manual bundling and transpiling before they can be safely embedded on websites.
@@ -128,6 +157,7 @@ Enable browser console debugging by adding `?pixel_debug` or `#pixel_debug` to t
128
157
  | ------------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
129
158
  | `advertiser` | Number | You can retrieve the advertiser ID from the URL in Create: _manage.lemonpi.io/r/AGENCY_ID/advertiser/`ADVERTISER_ID`_. | _Required_ |
130
159
  | `type` | String | Pixel type. One of: `"scraper"`, `"viewed"`, `"basketed"` or `"purchased"`. | _Required_ |
160
+ | `label`<br>_(post-click only)_ | String | Conversion label, used for aggregation in reporting. | _Required_ |
131
161
  | `url` | RegExp | Only enables the pixel on page URLs that are matched by this pattern. | _Required_ |
132
162
  | `scrape`<br>_(segments only)_ | String, Array or Function | Scrapes the product's SKU for segments. | _Required_ |
133
163
  | `scrape.*`<br>_(scrapers only)_ | String, Number, Boolean or Function | Scrapes arbitrary product data. `*` should always match with existing field names in the advertiser's product store. | _Required_ |
@@ -1,4 +1,4 @@
1
- /*! choreograph-create-pixel v1.3.2 2022/12/22 */
1
+ /*! choreograph-create-pixel v1.4.0 2023/02/01 */
2
2
  'use strict';
3
3
 
4
4
  class ChoreographCreatePixel {
@@ -14,7 +14,7 @@ class ChoreographCreatePixel {
14
14
  basketed: "πŸ›’",
15
15
  purchased: "🧾",
16
16
  attribution: "πŸ”–",
17
- conversion: "πŸ’΅",
17
+ conversion: "πŸ“ˆ",
18
18
  },
19
19
  debug: /(pixel|lemonpi)_debug/i.test(location.href),
20
20
  before: (data, callback) => callback(data),
@@ -85,8 +85,8 @@ class ChoreographCreatePixel {
85
85
  if (!this.config.debug || this.logs.includes(message)) return;
86
86
 
87
87
  const args = [
88
- `%cβΈ¬ create%c ${this.config.type} ${
89
- this.config.icons[this.config.type]
88
+ `%cβΈ¬ create%c ${this.config.icons[this.config.type]} ${this.config.type}${
89
+ this.config.label ? ` (${this.config.label})` : ""
90
90
  } %c${message}`,
91
91
  "background:black;color:white;border-radius:3px;padding:3px 6px",
92
92
  "font-weight:bold",
@@ -104,16 +104,35 @@ class ChoreographCreatePixel {
104
104
  advertiser: this.config.advertiser,
105
105
  });
106
106
  else if (
107
- !["scraper", "viewed", "basketed", "purchased"].includes(this.config.type)
107
+ ![
108
+ "scraper",
109
+ "viewed",
110
+ "basketed",
111
+ "purchased",
112
+ "attribution",
113
+ "conversion",
114
+ ].includes(this.config.type)
108
115
  )
109
- this.log("error", "Please use scraper, viewed, basketed or purchased", {
110
- type: this.config.type,
116
+ this.log(
117
+ "error",
118
+ "Please use scraper, viewed, basketed, purchased, attribution or conversion",
119
+ { type: this.config.type }
120
+ );
121
+ else if (
122
+ ["attribution", "conversion"].includes(this.config.type) &&
123
+ typeof this.config.label !== "string"
124
+ )
125
+ this.log("error", "Please use a string", {
126
+ label: this.config.label,
111
127
  });
112
128
  else if (!(this.config.url instanceof RegExp))
113
129
  this.log("error", "Please use a regular expression", {
114
130
  url: this.config.url,
115
131
  });
116
- else if (!this.config.scrape)
132
+ else if (
133
+ !["attribution", "conversion"].includes(this.config.type) &&
134
+ !this.config.scrape
135
+ )
117
136
  this.log("error", "Please provide something to scrape", {
118
137
  scrape: this.config.scrape,
119
138
  });
@@ -134,7 +153,8 @@ class ChoreographCreatePixel {
134
153
  "This page has been translated by the browser, and won't be scraped"
135
154
  );
136
155
  else if (this.config.trigger) {
137
- const attribute = `create-${this.config.type}-${this.config.trigger.event}`;
156
+ // TO-DO: replace this.config.type with a unique pixel instance ID
157
+ const elementAttribute = `create-${this.config.type}-${this.config.trigger.event}`;
138
158
 
139
159
  try {
140
160
  let elements =
@@ -145,12 +165,14 @@ class ChoreographCreatePixel {
145
165
  if (!elements.forEach) elements = [elements];
146
166
 
147
167
  elements.forEach((element) => {
148
- if (!element.hasAttribute(attribute)) {
168
+ if (!element.hasAttribute(elementAttribute)) {
149
169
  element.addEventListener(this.config.trigger.event, () =>
150
- this.scrape(element)
170
+ this.config.type === "conversion"
171
+ ? this.convert()
172
+ : this.scrape(element)
151
173
  );
152
174
 
153
- element.setAttribute(attribute, "");
175
+ element.setAttribute(elementAttribute, "");
154
176
  }
155
177
  });
156
178
  } catch (error) {
@@ -159,12 +181,53 @@ class ChoreographCreatePixel {
159
181
  });
160
182
  }
161
183
  } else {
162
- this.scrape();
184
+ switch (this.config.type) {
185
+ case "attribution":
186
+ this.attribute();
187
+ break;
188
+
189
+ case "conversion":
190
+ this.convert();
191
+ break;
192
+
193
+ default:
194
+ this.scrape();
195
+ break;
196
+ }
163
197
  }
164
198
 
165
199
  setTimeout(() => this.cycle(), 750);
166
200
  }
167
201
 
202
+ attribute() {
203
+ const ccpid = this.constructor.getQueryParameter("ccpid");
204
+ if (!ccpid) return this.log("warn", "ccpid query parameter not present");
205
+
206
+ if (
207
+ !/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/.test(
208
+ ccpid
209
+ )
210
+ )
211
+ return this.log(
212
+ "error",
213
+ "ccpid query parameter is not formatted correctly"
214
+ );
215
+
216
+ const storageItemLabel = `choreograph-${this.config.label}`;
217
+ const storedCcpid = localStorage.getItem(storageItemLabel);
218
+ if (storedCcpid !== ccpid) localStorage.setItem(storageItemLabel, ccpid);
219
+ this.log("success", "Successful!", ccpid);
220
+ }
221
+
222
+ convert() {
223
+ const ccpid = localStorage.getItem(`choreograph-${this.config.label}`);
224
+
225
+ if (!ccpid)
226
+ return this.log("warn", `"${this.config.label}" attribution not present`);
227
+
228
+ this.send(ccpid);
229
+ }
230
+
168
231
  scrape(element) {
169
232
  let hasErrors = false;
170
233
  let data;
@@ -178,11 +241,9 @@ class ChoreographCreatePixel {
178
241
  } catch (error) {
179
242
  if (this.config.optional.includes(fieldName)) return undefined;
180
243
 
181
- this.log(
182
- this.config.type === "attribution" ? "warn" : "error",
183
- error.message,
184
- { [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
185
- );
244
+ this.log("error", error.message, {
245
+ [fieldName ? `scrape.${fieldName}` : "scrape"]: result,
246
+ });
186
247
 
187
248
  hasErrors = true;
188
249
  }
@@ -191,14 +252,13 @@ class ChoreographCreatePixel {
191
252
  if (typeof result === "string")
192
253
  result = result.replace(/\s+/g, " ").trim();
193
254
 
194
- if (result == null || result === "") {
195
- if (this.config.optional.includes(fieldName)) return null;
196
-
197
- this.log(
198
- this.config.type === "attribution" ? "warn" : "error",
199
- "Value is empty",
200
- { [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
201
- );
255
+ if (
256
+ (result == null || result === "") &&
257
+ !this.config.optional.includes(fieldName)
258
+ ) {
259
+ this.log("error", "Value is empty", {
260
+ [fieldName ? `scrape.${fieldName}` : "scrape"]: result,
261
+ });
202
262
 
203
263
  hasErrors = true;
204
264
  }
@@ -206,17 +266,7 @@ class ChoreographCreatePixel {
206
266
  return result;
207
267
  };
208
268
 
209
- if (this.config.type === "conversion") {
210
- data = {
211
- conversion: handleField(this.config.scrape),
212
- attribution: localStorage.getItem("create-attribution-id"),
213
- };
214
-
215
- if (typeof data.attribution !== "string") {
216
- this.log("warn", "There's no attribution ID stored yet");
217
- hasErrors = true;
218
- }
219
- } else if (this.config.type !== "scraper") {
269
+ if (this.config.type !== "scraper") {
220
270
  data = handleField(this.config.scrape);
221
271
  } else {
222
272
  data = Object.keys(this.config.scrape).reduce(
@@ -233,19 +283,7 @@ class ChoreographCreatePixel {
233
283
  if (!hasErrors && this.previouslyScrapedHash !== dataHash) {
234
284
  try {
235
285
  this.config.before(data, (newData) => {
236
- if (this.config.type === "attribution") {
237
- localStorage.setItem("create-attribution-id", data);
238
- this.log("success", "Successful!", { attribution: data });
239
-
240
- try {
241
- this.config.after(data);
242
- } catch (error) {
243
- this.log("error", error.message, {
244
- after: this.config.after,
245
- });
246
- }
247
- } else if (Array.isArray(newData))
248
- newData.forEach((id) => this.send(id));
286
+ if (Array.isArray(newData)) newData.forEach((id) => this.send(id));
249
287
  else this.send(newData);
250
288
  });
251
289
  } catch (error) {
@@ -267,10 +305,20 @@ class ChoreographCreatePixel {
267
305
  url = `https://d.lemonpi.io/scrapes${
268
306
  this.config.debug ? "?validate=true" : ""
269
307
  }`;
308
+
270
309
  break;
271
310
 
272
311
  case "conversion":
273
- url = "https://lemonpi.io/"; // TO-DO
312
+ url = `https://content.lemonpi.io/track/event?e=${encodeURIComponent(
313
+ JSON.stringify({
314
+ version: 1,
315
+ type: "conversion",
316
+ name: this.config.label,
317
+ "conversion-attribution-id": data,
318
+ "advertiser-id": this.config.advertiser,
319
+ })
320
+ )}`;
321
+
274
322
  break;
275
323
 
276
324
  default:
@@ -282,6 +330,7 @@ class ChoreographCreatePixel {
282
330
  sku: data,
283
331
  })
284
332
  )}`;
333
+
285
334
  break;
286
335
  }
287
336
 
@@ -1,4 +1,4 @@
1
- /*! choreograph-create-pixel v1.3.2 2022/12/22 */
1
+ /*! choreograph-create-pixel v1.4.0 2023/02/01 */
2
2
  class ChoreographCreatePixel {
3
3
  constructor(userConfig) {
4
4
  this.previouslyScrapedHash = null;
@@ -12,7 +12,7 @@ class ChoreographCreatePixel {
12
12
  basketed: "πŸ›’",
13
13
  purchased: "🧾",
14
14
  attribution: "πŸ”–",
15
- conversion: "πŸ’΅",
15
+ conversion: "πŸ“ˆ",
16
16
  },
17
17
  debug: /(pixel|lemonpi)_debug/i.test(location.href),
18
18
  before: (data, callback) => callback(data),
@@ -83,8 +83,8 @@ class ChoreographCreatePixel {
83
83
  if (!this.config.debug || this.logs.includes(message)) return;
84
84
 
85
85
  const args = [
86
- `%cβΈ¬ create%c ${this.config.type} ${
87
- this.config.icons[this.config.type]
86
+ `%cβΈ¬ create%c ${this.config.icons[this.config.type]} ${this.config.type}${
87
+ this.config.label ? ` (${this.config.label})` : ""
88
88
  } %c${message}`,
89
89
  "background:black;color:white;border-radius:3px;padding:3px 6px",
90
90
  "font-weight:bold",
@@ -102,16 +102,35 @@ class ChoreographCreatePixel {
102
102
  advertiser: this.config.advertiser,
103
103
  });
104
104
  else if (
105
- !["scraper", "viewed", "basketed", "purchased"].includes(this.config.type)
105
+ ![
106
+ "scraper",
107
+ "viewed",
108
+ "basketed",
109
+ "purchased",
110
+ "attribution",
111
+ "conversion",
112
+ ].includes(this.config.type)
106
113
  )
107
- this.log("error", "Please use scraper, viewed, basketed or purchased", {
108
- type: this.config.type,
114
+ this.log(
115
+ "error",
116
+ "Please use scraper, viewed, basketed, purchased, attribution or conversion",
117
+ { type: this.config.type }
118
+ );
119
+ else if (
120
+ ["attribution", "conversion"].includes(this.config.type) &&
121
+ typeof this.config.label !== "string"
122
+ )
123
+ this.log("error", "Please use a string", {
124
+ label: this.config.label,
109
125
  });
110
126
  else if (!(this.config.url instanceof RegExp))
111
127
  this.log("error", "Please use a regular expression", {
112
128
  url: this.config.url,
113
129
  });
114
- else if (!this.config.scrape)
130
+ else if (
131
+ !["attribution", "conversion"].includes(this.config.type) &&
132
+ !this.config.scrape
133
+ )
115
134
  this.log("error", "Please provide something to scrape", {
116
135
  scrape: this.config.scrape,
117
136
  });
@@ -132,7 +151,8 @@ class ChoreographCreatePixel {
132
151
  "This page has been translated by the browser, and won't be scraped"
133
152
  );
134
153
  else if (this.config.trigger) {
135
- const attribute = `create-${this.config.type}-${this.config.trigger.event}`;
154
+ // TO-DO: replace this.config.type with a unique pixel instance ID
155
+ const elementAttribute = `create-${this.config.type}-${this.config.trigger.event}`;
136
156
 
137
157
  try {
138
158
  let elements =
@@ -143,12 +163,14 @@ class ChoreographCreatePixel {
143
163
  if (!elements.forEach) elements = [elements];
144
164
 
145
165
  elements.forEach((element) => {
146
- if (!element.hasAttribute(attribute)) {
166
+ if (!element.hasAttribute(elementAttribute)) {
147
167
  element.addEventListener(this.config.trigger.event, () =>
148
- this.scrape(element)
168
+ this.config.type === "conversion"
169
+ ? this.convert()
170
+ : this.scrape(element)
149
171
  );
150
172
 
151
- element.setAttribute(attribute, "");
173
+ element.setAttribute(elementAttribute, "");
152
174
  }
153
175
  });
154
176
  } catch (error) {
@@ -157,12 +179,53 @@ class ChoreographCreatePixel {
157
179
  });
158
180
  }
159
181
  } else {
160
- this.scrape();
182
+ switch (this.config.type) {
183
+ case "attribution":
184
+ this.attribute();
185
+ break;
186
+
187
+ case "conversion":
188
+ this.convert();
189
+ break;
190
+
191
+ default:
192
+ this.scrape();
193
+ break;
194
+ }
161
195
  }
162
196
 
163
197
  setTimeout(() => this.cycle(), 750);
164
198
  }
165
199
 
200
+ attribute() {
201
+ const ccpid = this.constructor.getQueryParameter("ccpid");
202
+ if (!ccpid) return this.log("warn", "ccpid query parameter not present");
203
+
204
+ if (
205
+ !/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/.test(
206
+ ccpid
207
+ )
208
+ )
209
+ return this.log(
210
+ "error",
211
+ "ccpid query parameter is not formatted correctly"
212
+ );
213
+
214
+ const storageItemLabel = `choreograph-${this.config.label}`;
215
+ const storedCcpid = localStorage.getItem(storageItemLabel);
216
+ if (storedCcpid !== ccpid) localStorage.setItem(storageItemLabel, ccpid);
217
+ this.log("success", "Successful!", ccpid);
218
+ }
219
+
220
+ convert() {
221
+ const ccpid = localStorage.getItem(`choreograph-${this.config.label}`);
222
+
223
+ if (!ccpid)
224
+ return this.log("warn", `"${this.config.label}" attribution not present`);
225
+
226
+ this.send(ccpid);
227
+ }
228
+
166
229
  scrape(element) {
167
230
  let hasErrors = false;
168
231
  let data;
@@ -176,11 +239,9 @@ class ChoreographCreatePixel {
176
239
  } catch (error) {
177
240
  if (this.config.optional.includes(fieldName)) return undefined;
178
241
 
179
- this.log(
180
- this.config.type === "attribution" ? "warn" : "error",
181
- error.message,
182
- { [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
183
- );
242
+ this.log("error", error.message, {
243
+ [fieldName ? `scrape.${fieldName}` : "scrape"]: result,
244
+ });
184
245
 
185
246
  hasErrors = true;
186
247
  }
@@ -189,14 +250,13 @@ class ChoreographCreatePixel {
189
250
  if (typeof result === "string")
190
251
  result = result.replace(/\s+/g, " ").trim();
191
252
 
192
- if (result == null || result === "") {
193
- if (this.config.optional.includes(fieldName)) return null;
194
-
195
- this.log(
196
- this.config.type === "attribution" ? "warn" : "error",
197
- "Value is empty",
198
- { [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
199
- );
253
+ if (
254
+ (result == null || result === "") &&
255
+ !this.config.optional.includes(fieldName)
256
+ ) {
257
+ this.log("error", "Value is empty", {
258
+ [fieldName ? `scrape.${fieldName}` : "scrape"]: result,
259
+ });
200
260
 
201
261
  hasErrors = true;
202
262
  }
@@ -204,17 +264,7 @@ class ChoreographCreatePixel {
204
264
  return result;
205
265
  };
206
266
 
207
- if (this.config.type === "conversion") {
208
- data = {
209
- conversion: handleField(this.config.scrape),
210
- attribution: localStorage.getItem("create-attribution-id"),
211
- };
212
-
213
- if (typeof data.attribution !== "string") {
214
- this.log("warn", "There's no attribution ID stored yet");
215
- hasErrors = true;
216
- }
217
- } else if (this.config.type !== "scraper") {
267
+ if (this.config.type !== "scraper") {
218
268
  data = handleField(this.config.scrape);
219
269
  } else {
220
270
  data = Object.keys(this.config.scrape).reduce(
@@ -231,19 +281,7 @@ class ChoreographCreatePixel {
231
281
  if (!hasErrors && this.previouslyScrapedHash !== dataHash) {
232
282
  try {
233
283
  this.config.before(data, (newData) => {
234
- if (this.config.type === "attribution") {
235
- localStorage.setItem("create-attribution-id", data);
236
- this.log("success", "Successful!", { attribution: data });
237
-
238
- try {
239
- this.config.after(data);
240
- } catch (error) {
241
- this.log("error", error.message, {
242
- after: this.config.after,
243
- });
244
- }
245
- } else if (Array.isArray(newData))
246
- newData.forEach((id) => this.send(id));
284
+ if (Array.isArray(newData)) newData.forEach((id) => this.send(id));
247
285
  else this.send(newData);
248
286
  });
249
287
  } catch (error) {
@@ -265,10 +303,20 @@ class ChoreographCreatePixel {
265
303
  url = `https://d.lemonpi.io/scrapes${
266
304
  this.config.debug ? "?validate=true" : ""
267
305
  }`;
306
+
268
307
  break;
269
308
 
270
309
  case "conversion":
271
- url = "https://lemonpi.io/"; // TO-DO
310
+ url = `https://content.lemonpi.io/track/event?e=${encodeURIComponent(
311
+ JSON.stringify({
312
+ version: 1,
313
+ type: "conversion",
314
+ name: this.config.label,
315
+ "conversion-attribution-id": data,
316
+ "advertiser-id": this.config.advertiser,
317
+ })
318
+ )}`;
319
+
272
320
  break;
273
321
 
274
322
  default:
@@ -280,6 +328,7 @@ class ChoreographCreatePixel {
280
328
  sku: data,
281
329
  })
282
330
  )}`;
331
+
283
332
  break;
284
333
  }
285
334
 
@@ -1,2 +1,2 @@
1
- /*! choreograph-create-pixel v1.3.2 2022/12/22 */
2
- var ChoreographCreatePixel=function(){"use strict";function e(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function t(t){for(var r=1;r<arguments.length;r++){var i=null!=arguments[r]?arguments[r]:{};r%2?e(Object(i),!0).forEach((function(e){n(t,e,i[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(i)):e(Object(i)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(i,e))}))}return t}function r(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,i(n.key),n)}}function n(e,t,r){return(t=i(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e){var t=function(e,t){if("object"!=typeof e||null===e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var n=r.call(e,t||"default");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}var o=function(){function e(r){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.previouslyScrapedHash=null,this.logs=[],this.config=t({colors:{error:"red",warn:"orange",success:"green"},icons:{scraper:"πŸ“š",viewed:"πŸ‘€",basketed:"πŸ›’",purchased:"🧾",attribution:"πŸ”–",conversion:"πŸ’΅"},debug:/(pixel|lemonpi)_debug/i.test(location.href),before:function(e,t){return t(e)},after:function(){},optional:[]},r),this.validateConfig()&&this.cycle()}var i,o,c;return i=e,o=[{key:"log",value:function(e,t){var r,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;if(this.config.debug&&!this.logs.includes(t)){var i=["%cβΈ¬ create%c ".concat(this.config.type," ").concat(this.config.icons[this.config.type]," %c").concat(t),"background:black;color:white;border-radius:3px;padding:3px 6px","font-weight:bold","color:".concat(this.config.colors[e])];n&&i.push(n),(r=console).info.apply(r,i),this.logs.push(t)}}},{key:"validateConfig",value:function(){if("number"!=typeof this.config.advertiser)this.log("error","Please use a number",{advertiser:this.config.advertiser});else if(["scraper","viewed","basketed","purchased"].includes(this.config.type))if(this.config.url instanceof RegExp){if(this.config.scrape)return!0;this.log("error","Please provide something to scrape",{scrape:this.config.scrape})}else this.log("error","Please use a regular expression",{url:this.config.url});else this.log("error","Please use scraper, viewed, basketed or purchased",{type:this.config.type})}},{key:"cycle",value:function(){var e=this;if(this.config.url.test(location.href))if("scraper"===this.config.type&&document.querySelector('html[class*="translated-"]'))this.log("warn","This page has been translated by the browser, and won't be scraped");else if(this.config.trigger){var t="create-".concat(this.config.type,"-").concat(this.config.trigger.event);try{var r="string"==typeof this.config.trigger.elements?document.querySelectorAll(this.config.trigger.elements):this.config.trigger.elements();r.forEach||(r=[r]),r.forEach((function(r){r.hasAttribute(t)||(r.addEventListener(e.config.trigger.event,(function(){return e.scrape(r)})),r.setAttribute(t,""))}))}catch(e){this.log("error",e.message,{"trigger.elements":this.config.trigger.elements})}}else this.scrape();else this.log("warn",'URL pattern does not match "'.concat(location.href,'"'),{url:this.config.url});setTimeout((function(){return e.cycle()}),750)}},{key:"scrape",value:function(e){var r,i=this,o=!1,c=function(t,r){var c=t;if("function"==typeof c)try{c=c(e)}catch(e){if(i.config.optional.includes(r))return;i.log("attribution"===i.config.type?"warn":"error",e.message,n({},r?"scrape.".concat(r):"scrape",c)),o=!0}if("string"==typeof c&&(c=c.replace(/\s+/g," ").trim()),null==c||""===c){if(i.config.optional.includes(r))return null;i.log("attribution"===i.config.type?"warn":"error","Value is empty",n({},r?"scrape.".concat(r):"scrape",c)),o=!0}return c};"conversion"===this.config.type?"string"!=typeof(r={conversion:c(this.config.scrape),attribution:localStorage.getItem("create-attribution-id")}).attribution&&(this.log("warn","There's no attribution ID stored yet"),o=!0):r="scraper"!==this.config.type?c(this.config.scrape):Object.keys(this.config.scrape).reduce((function(e,r){return t(t({},e),{},n({},r,c(i.config.scrape[r],r)))}),{});var s=JSON.stringify(r);if(!o&&this.previouslyScrapedHash!==s){try{this.config.before(r,(function(e){if("attribution"===i.config.type){localStorage.setItem("create-attribution-id",r),i.log("success","Successful!",{attribution:r});try{i.config.after(r)}catch(e){i.log("error",e.message,{after:i.config.after})}}else Array.isArray(e)?e.forEach((function(e){return i.send(e)})):i.send(e)}))}catch(e){this.log("error",e.message,{before:this.config.before})}this.previouslyScrapedHash=s,this.logs.length=0}}},{key:"send",value:function(e){var r,n=this;switch(this.config.type){case"scraper":r="https://d.lemonpi.io/scrapes".concat(this.config.debug?"?validate=true":"");break;case"conversion":r="https://lemonpi.io/";break;default:r="https://d.lemonpi.io/a/".concat(this.config.advertiser,"/product/event?e=").concat(encodeURIComponent(JSON.stringify({"event-type":"product-".concat(this.config.type),sku:e})))}"scraper"===this.config.type||this.config.debug?fetch(r,"scraper"===this.config.type?{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({"advertiser-id":this.config.advertiser,sku:e.sku,fields:t(t({},e),{},{sku:void 0})})}:null).then((function(t){return t.json().then((function(r){if(t.ok){n.log("warn","Successful, with warnings. Details:",r);try{n.config.after(e)}catch(e){n.log("error",e.message,{after:n.config.after})}}else n.log("error","Failed: ".concat(t.status," (").concat(t.statusText,"). Details:"),r);n.logs.length=0})).catch((function(){if(t.ok){n.log("success","Successful!",["viewed","basketed","purchased"].includes(n.config.type)?{sku:e}:e);try{n.config.after(e)}catch(e){n.log("error",e.message,{after:n.config.after})}}else n.log("error","Failed: ".concat(t.status," (").concat(t.statusText,"). Details:"),t);n.logs.length=0}))})).catch((function(e){n.log("error","Failed: ".concat(e.message)),n.logs.length=0})):(new Image).src=r}}],c=[{key:"getUrl",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.allowedQueryParameters,r=void 0===t?[]:t,n=e.allowHash,i=void 0!==n&&n,o="".concat(location.protocol,"//").concat(location.host).concat(location.pathname),c=new URLSearchParams(location.search);return r.reduce((function(e,t){var r=e?"&":"?",n=encodeURI(t),i=c.get(t);return null!=i?(o+="".concat(r).concat(n).concat(""===i?"":"=".concat(encodeURI(i))),!0):e}),!1),i&&(o+=location.hash),o}},{key:"getAllPathSegments",value:function(){return location.pathname.split("/").filter((function(e){return e})).map((function(e){return decodeURI(e)}))}},{key:"getPathSegment",value:function(e){return this.getAllPathSegments()[e]}},{key:"getAllQueryParameters",value:function(){return location.search.replace(/^\?/,"").split("&").filter((function(e){return e})).reduce((function(e,r){return t(t({},e),{},n({},decodeURI(r.split("=")[0]),decodeURI(r.split("=")[1]||"")))}),{})}},{key:"getQueryParameter",value:function(e){return this.getAllQueryParameters()[e]}}],o&&r(i.prototype,o),c&&r(i,c),Object.defineProperty(i,"prototype",{writable:!1}),e}();return o}();
1
+ /*! choreograph-create-pixel v1.4.0 2023/02/01 */
2
+ var ChoreographCreatePixel=function(){"use strict";function e(e,t){var r=Object.keys(e);if(Object.getOwnPropertySymbols){var n=Object.getOwnPropertySymbols(e);t&&(n=n.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),r.push.apply(r,n)}return r}function t(t){for(var r=1;r<arguments.length;r++){var i=null!=arguments[r]?arguments[r]:{};r%2?e(Object(i),!0).forEach((function(e){n(t,e,i[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(i)):e(Object(i)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(i,e))}))}return t}function r(e,t){for(var r=0;r<t.length;r++){var n=t[r];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,i(n.key),n)}}function n(e,t,r){return(t=i(t))in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}function i(e){var t=function(e,t){if("object"!=typeof e||null===e)return e;var r=e[Symbol.toPrimitive];if(void 0!==r){var n=r.call(e,t||"default");if("object"!=typeof n)return n;throw new TypeError("@@toPrimitive must return a primitive value.")}return("string"===t?String:Number)(e)}(e,"string");return"symbol"==typeof t?t:String(t)}var o=function(){function e(r){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.previouslyScrapedHash=null,this.logs=[],this.config=t({colors:{error:"red",warn:"orange",success:"green"},icons:{scraper:"πŸ“š",viewed:"πŸ‘€",basketed:"πŸ›’",purchased:"🧾",attribution:"πŸ”–",conversion:"πŸ“ˆ"},debug:/(pixel|lemonpi)_debug/i.test(location.href),before:function(e,t){return t(e)},after:function(){},optional:[]},r),this.validateConfig()&&this.cycle()}var i,o,c;return i=e,o=[{key:"log",value:function(e,t){var r,n=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;if(this.config.debug&&!this.logs.includes(t)){var i=["%cβΈ¬ create%c ".concat(this.config.icons[this.config.type]," ").concat(this.config.type).concat(this.config.label?" (".concat(this.config.label,")"):""," %c").concat(t),"background:black;color:white;border-radius:3px;padding:3px 6px","font-weight:bold","color:".concat(this.config.colors[e])];n&&i.push(n),(r=console).info.apply(r,i),this.logs.push(t)}}},{key:"validateConfig",value:function(){if("number"!=typeof this.config.advertiser)this.log("error","Please use a number",{advertiser:this.config.advertiser});else if(["scraper","viewed","basketed","purchased","attribution","conversion"].includes(this.config.type))if(["attribution","conversion"].includes(this.config.type)&&"string"!=typeof this.config.label)this.log("error","Please use a string",{label:this.config.label});else if(this.config.url instanceof RegExp){if(["attribution","conversion"].includes(this.config.type)||this.config.scrape)return!0;this.log("error","Please provide something to scrape",{scrape:this.config.scrape})}else this.log("error","Please use a regular expression",{url:this.config.url});else this.log("error","Please use scraper, viewed, basketed, purchased, attribution or conversion",{type:this.config.type})}},{key:"cycle",value:function(){var e=this;if(this.config.url.test(location.href))if("scraper"===this.config.type&&document.querySelector('html[class*="translated-"]'))this.log("warn","This page has been translated by the browser, and won't be scraped");else if(this.config.trigger){var t="create-".concat(this.config.type,"-").concat(this.config.trigger.event);try{var r="string"==typeof this.config.trigger.elements?document.querySelectorAll(this.config.trigger.elements):this.config.trigger.elements();r.forEach||(r=[r]),r.forEach((function(r){r.hasAttribute(t)||(r.addEventListener(e.config.trigger.event,(function(){return"conversion"===e.config.type?e.convert():e.scrape(r)})),r.setAttribute(t,""))}))}catch(e){this.log("error",e.message,{"trigger.elements":this.config.trigger.elements})}}else switch(this.config.type){case"attribution":this.attribute();break;case"conversion":this.convert();break;default:this.scrape()}else this.log("warn",'URL pattern does not match "'.concat(location.href,'"'),{url:this.config.url});setTimeout((function(){return e.cycle()}),750)}},{key:"attribute",value:function(){var e=this.constructor.getQueryParameter("ccpid");if(!e)return this.log("warn","ccpid query parameter not present");if(!/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}/.test(e))return this.log("error","ccpid query parameter is not formatted correctly");var t="choreograph-".concat(this.config.label);localStorage.getItem(t)!==e&&localStorage.setItem(t,e),this.log("success","Successful!",e)}},{key:"convert",value:function(){var e=localStorage.getItem("choreograph-".concat(this.config.label));if(!e)return this.log("warn",'"'.concat(this.config.label,'" attribution not present'));this.send(e)}},{key:"scrape",value:function(e){var r,i=this,o=!1,c=function(t,r){var c=t;if("function"==typeof c)try{c=c(e)}catch(e){if(i.config.optional.includes(r))return;i.log("error",e.message,n({},r?"scrape.".concat(r):"scrape",c)),o=!0}return"string"==typeof c&&(c=c.replace(/\s+/g," ").trim()),null!=c&&""!==c||i.config.optional.includes(r)||(i.log("error","Value is empty",n({},r?"scrape.".concat(r):"scrape",c)),o=!0),c};r="scraper"!==this.config.type?c(this.config.scrape):Object.keys(this.config.scrape).reduce((function(e,r){return t(t({},e),{},n({},r,c(i.config.scrape[r],r)))}),{});var s=JSON.stringify(r);if(!o&&this.previouslyScrapedHash!==s){try{this.config.before(r,(function(e){Array.isArray(e)?e.forEach((function(e){return i.send(e)})):i.send(e)}))}catch(e){this.log("error",e.message,{before:this.config.before})}this.previouslyScrapedHash=s,this.logs.length=0}}},{key:"send",value:function(e){var r,n=this;switch(this.config.type){case"scraper":r="https://d.lemonpi.io/scrapes".concat(this.config.debug?"?validate=true":"");break;case"conversion":r="https://content.lemonpi.io/track/event?e=".concat(encodeURIComponent(JSON.stringify({version:1,type:"conversion",name:this.config.label,"conversion-attribution-id":e,"advertiser-id":this.config.advertiser})));break;default:r="https://d.lemonpi.io/a/".concat(this.config.advertiser,"/product/event?e=").concat(encodeURIComponent(JSON.stringify({"event-type":"product-".concat(this.config.type),sku:e})))}"scraper"===this.config.type||this.config.debug?fetch(r,"scraper"===this.config.type?{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({"advertiser-id":this.config.advertiser,sku:e.sku,fields:t(t({},e),{},{sku:void 0})})}:null).then((function(t){return t.json().then((function(r){if(t.ok){n.log("warn","Successful, with warnings. Details:",r);try{n.config.after(e)}catch(e){n.log("error",e.message,{after:n.config.after})}}else n.log("error","Failed: ".concat(t.status," (").concat(t.statusText,"). Details:"),r);n.logs.length=0})).catch((function(){if(t.ok){n.log("success","Successful!",["viewed","basketed","purchased"].includes(n.config.type)?{sku:e}:e);try{n.config.after(e)}catch(e){n.log("error",e.message,{after:n.config.after})}}else n.log("error","Failed: ".concat(t.status," (").concat(t.statusText,"). Details:"),t);n.logs.length=0}))})).catch((function(e){n.log("error","Failed: ".concat(e.message)),n.logs.length=0})):(new Image).src=r}}],c=[{key:"getUrl",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=e.allowedQueryParameters,r=void 0===t?[]:t,n=e.allowHash,i=void 0!==n&&n,o="".concat(location.protocol,"//").concat(location.host).concat(location.pathname),c=new URLSearchParams(location.search);return r.reduce((function(e,t){var r=e?"&":"?",n=encodeURI(t),i=c.get(t);return null!=i?(o+="".concat(r).concat(n).concat(""===i?"":"=".concat(encodeURI(i))),!0):e}),!1),i&&(o+=location.hash),o}},{key:"getAllPathSegments",value:function(){return location.pathname.split("/").filter((function(e){return e})).map((function(e){return decodeURI(e)}))}},{key:"getPathSegment",value:function(e){return this.getAllPathSegments()[e]}},{key:"getAllQueryParameters",value:function(){return location.search.replace(/^\?/,"").split("&").filter((function(e){return e})).reduce((function(e,r){return t(t({},e),{},n({},decodeURI(r.split("=")[0]),decodeURI(r.split("=")[1]||"")))}),{})}},{key:"getQueryParameter",value:function(e){return this.getAllQueryParameters()[e]}}],o&&r(i.prototype,o),c&&r(i,c),Object.defineProperty(i,"prototype",{writable:!1}),e}();return o}();
package/package.json CHANGED
@@ -1,13 +1,15 @@
1
1
  {
2
2
  "name": "choreograph-create-pixel",
3
3
  "description": "This library lets you apply best practises to Choreograph Create pixel development and implementation.",
4
- "version": "1.3.2",
4
+ "version": "1.4.0",
5
+ "type": "module",
5
6
  "keywords": [
6
7
  "wpp",
7
8
  "groupm",
8
9
  "lemonpi",
9
10
  "odc",
10
- "opendc"
11
+ "opendc",
12
+ "greenhouse"
11
13
  ],
12
14
  "author": "Rick Stevens <rick.stevens@choreograph.com> (https://lemonpi.io)",
13
15
  "repository": "gitlab:2sixty/choreograph-create/solutions/choreograph-create-pixel",
@@ -28,16 +30,18 @@
28
30
  "prepublishOnly": "npm run build"
29
31
  },
30
32
  "devDependencies": {
31
- "@babel/core": "^7.20.7",
33
+ "@babel/core": "^7.20.12",
34
+ "@babel/eslint-parser": "^7.19.1",
35
+ "@babel/plugin-syntax-import-assertions": "^7.20.0",
32
36
  "@babel/preset-env": "^7.20.2",
33
37
  "@rollup/plugin-babel": "^6.0.3",
34
- "@rollup/plugin-eslint": "^9.0.1",
35
- "@rollup/plugin-terser": "^0.2.1",
36
- "eslint": "^8.30.0",
37
- "eslint-config-prettier": "^8.5.0",
38
+ "@rollup/plugin-eslint": "^9.0.3",
39
+ "@rollup/plugin-terser": "^0.4.0",
40
+ "eslint": "^8.33.0",
41
+ "eslint-config-prettier": "^8.6.0",
38
42
  "eslint-plugin-prettier": "^4.2.1",
39
43
  "moment": "^2.29.4",
40
- "prettier": "^2.8.1",
41
- "rollup": "^2.79.1"
44
+ "prettier": "^2.8.3",
45
+ "rollup": "^3.12.0"
42
46
  }
43
47
  }