choreograph-create-pixel 1.1.0 β†’ 1.2.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
@@ -1,151 +1,135 @@
1
1
  # Choreograph Create Pixel
2
2
 
3
- This library lets you apply best practises to [Choreograph Create](https://www.lemonpi.io/) pixel development and implementation.
3
+ This library lets you apply best practises to [Create](https://www.lemonpi.io/) pixel development and implementation.
4
4
 
5
5
  ## Features
6
6
 
7
- - [x] Supports **scrape**, **view**, **basket**, and **purchase** pixels
7
+ - [x] Supports scrapers and segments
8
+ - [x] Supports user interaction triggers
8
9
  - [x] Supports dynamic page changes
9
10
  - [x] Continuous URL validation
10
- - [x] Optionally trigger by user interaction
11
11
  - [x] Prevents scraping browser-translated pages
12
12
  - [x] Console debugger
13
13
 
14
- ## Quickstart
14
+ ## Pixel types
15
15
 
16
- The following theoretical snippet could be embedded on every page of _example.com_, and would automatically determine when to enable through URL validation.
16
+ ### Scrapers
17
17
 
18
- ### Browser example
18
+ Scraper pixels are used to scrape (read) data from websites, and store it as products within an advertiser's product store.
19
19
 
20
- ```html
21
- <script src="https://cdn.jsdelivr.net/npm/choreograph-create-pixel@1"></script>
22
- <script>
23
- // Scrape pixel
24
- new ChoreographCreatePixel({
25
- // Who is the client? (advertiser ID)
26
- who: 0,
27
-
28
- // What kind of pixel is this?
29
- what: "scrape",
30
-
31
- // Where should the pixel be enabled?
32
- where: /example\.com\/products\/\d/,
33
-
34
- // Which data should be scraped?
35
- which: {
36
- // Data layer example
37
- sku: function () {
38
- return window.dataLayer[0].product.sku;
39
- },
40
-
41
- // DOM example
42
- name: function () {
43
- return document.querySelector("#product-name").innerText;
44
- },
45
-
46
- // Helper example
47
- url: ChoreographCreatePixel.getUrl,
48
- },
49
- });
20
+ ### Segments <small>(`viewed`, `basketed` and `purchased`)</small>
50
21
 
51
- // View segment pixel
52
- new ChoreographCreatePixel({
53
- who: 0,
54
- what: "view",
55
- where: /example\.com\/products\/\d/,
56
-
57
- which: {
58
- sku: function () {
59
- return window.dataLayer[0].product.sku;
60
- },
61
- },
62
- });
22
+ Products in ads are shown by recommendation logic. Segment pixels determine this recommendation on a consumer level, by storing a cookie at different moments during the consumer's journey. Segments can be triggered by page views, or other user interactions within a page.
63
23
 
64
- // Basket segment pixel
65
- new ChoreographCreatePixel({
66
- who: 0,
67
- what: "basket",
68
- where: /example\.com/,
69
-
70
- // (Optional) When should the pixel be enabled?
71
- when: {
72
- listener: "click",
73
-
74
- elements: function () {
75
- return document.querySelectorAll("button.basket[data-sku]");
76
- },
77
- },
78
-
79
- which: {
80
- // The "element" parameter only exists while using a "when" condition
81
- sku: function (element) {
82
- return element.getAttribute("data-sku");
83
- },
84
- },
85
- });
24
+ ## Quickstart
86
25
 
87
- // Purchase segment pixel
88
- new ChoreographCreatePixel({
89
- who: 0,
90
- what: "purchase",
91
- where: /example\.com\/cart/,
92
-
93
- when: {
94
- listener: "click",
95
-
96
- elements: function () {
97
- return document.querySelectorAll("button.checkout");
98
- },
99
- },
100
-
101
- which: {
102
- // which.sku supports returning an array of strings
103
- sku: function () {
104
- return window.dataLayer[0].cart.map(function (product) {
105
- return product.sku;
106
- });
107
- },
108
- },
109
- });
110
- </script>
111
- ```
26
+ The following snippet of all possible pixel types could be embedded on every page of _example.com_, and would automatically determine which pixel to enable where, through URL validation.
112
27
 
113
- ### ES module example
28
+ ### ES module
114
29
 
115
30
  ```js
116
31
  import Pixel from "choreograph-create-pixel";
117
32
 
33
+ // Scraper pixel
118
34
  new Pixel({
119
- ...
35
+ advertiser: 0,
36
+ type: "scraper",
37
+
38
+ // Where should this pixel be enabled?
39
+ url: /example\.com\/products\/\d/,
40
+
41
+ scrape: {
42
+ // Data layer example
43
+ sku: () => window.dataLayer[0].product.sku,
44
+
45
+ // DOM example
46
+ name: () => document.querySelector("#product-name").innerText,
47
+
48
+ // Helper example
49
+ url: Pixel.getUrl,
50
+ },
120
51
  });
121
52
 
53
+ // Viewed segment pixel
122
54
  new Pixel({
123
- ...
55
+ advertiser: 0,
56
+ type: "viewed",
57
+ url: /example\.com\/products\/\d/,
58
+
59
+ // Segment pixels only require an SKU to be scraped
60
+ scrape: () => window.dataLayer[0].product.sku,
61
+ });
62
+
63
+ // Basketed segment pixel
64
+ new Pixel({
65
+ advertiser: 0,
66
+ type: "basketed",
67
+ url: /example\.com/,
68
+
69
+ // [Optional] When should this pixel be triggered?
70
+ listener: {
71
+ event: "click",
72
+ elements: () => document.querySelectorAll("button.basket[data-sku]"),
73
+ },
74
+
75
+ // The "element" parameter can be used when using an event listener
76
+ scrape: (element) => element.getAttribute("data-sku"),
77
+ });
78
+
79
+ // Purchased segment pixel
80
+ new Pixel({
81
+ advertiser: 0,
82
+ type: "purchased",
83
+ url: /example\.com\/cart/,
84
+
85
+ listener: {
86
+ event: "click",
87
+ elements: () => document.querySelectorAll("button.checkout"),
88
+ },
89
+
90
+ // Segment pixels support multiple SKUs
91
+ scrape: () => window.dataLayer[0].cart.map((product) => product.sku),
124
92
  });
125
93
  ```
126
94
 
127
95
  > ES modules require manual bundling and transpiling before they can be safely used as browser scripts.
128
96
 
97
+ ### CDN library
98
+
99
+ This implementation method allows for embeddeding on pages without the need for bundling or transpiling.
100
+
101
+ ```html
102
+ <script src="https://cdn.jsdelivr.net/npm/choreograph-create-pixel@1"></script>
103
+ <script>
104
+ new ChoreographCreatePixel({
105
+ // ...
106
+ });
107
+
108
+ new ChoreographCreatePixel({
109
+ // ...
110
+ });
111
+ </script>
112
+ ```
113
+
129
114
  ## Debugging
130
115
 
131
- Enable the console debugger by adding `?pixel_debug` or `#pixel_debug` to the page's URL.
116
+ Enable browser console debugging by adding `?pixel_debug` or `#pixel_debug` to the page's URL.
132
117
 
133
118
  ## Configuration
134
119
 
135
- | Property | Type | Description | Default |
136
- | --------------- | ------------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------- |
137
- | `who` | Number | You can retrieve the advertiser ID from the URL in the [Manage platform](https://manage.lemonpi.io/) _(manage.lemonpi.io/r/AGENCY_ID/advertiser/`ADVERTISER_ID`)_. | _Required_ |
138
- | `what` | String | Pixel type. One of: `"scrape"`, `"view"`, `"basket"`, or `"purchase"`. | _Required_ |
139
- | `where` | RegExp | Disables pixels on page URLs that are not matched by this pattern. | _Required_ |
140
- | `which` | Object | An object holding the data fields to be scraped. | _Required_ |
141
- | `which.sku` | String, Array, or Function | `sku` is the only required data field, as it's the product's unique identifier. In case of segment pixels (`what` equals `"view"`, `"basket"`, or `"purchase"`), an array of strings is allowed to trigger multiple pixel executions. | _Required_ |
142
- | `which.*` | String, Number, Boolean, or Function | `*` should always match with existing field names in the advertiser's product store. | `undefined` |
143
- | `when` | Object | Use this property when you want to enable the pixel only after a specific DOM event. | `undefined` |
144
- | `when.listener` | String | DOM event type. [List of supported values.](https://www.w3schools.com/jsref/dom_obj_event.asp) | _Required_ when using `when` |
145
- | `when.elements` | Function | This function should return DOM element(s) to apply the listener to. | _Required_ when using `when` |
146
- | `optional` | Array | An array of field names (strings) that are allowed to have empty values. | `[]` |
147
- | `before` | Function | Lifecycle function that's executed just before the pixel's data is sent. | `function (data, callback) { callback(data); }` |
148
- | `after` | Function | Lifecycle function that's executed just after the pixel's data is sent. | `function (data) {}` |
120
+ | Property | Type | Description | Default |
121
+ | ------------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ----------------------------------------- |
122
+ | `advertiser` | Number | You can retrieve the advertiser ID from the URL in Create: _manage.lemonpi.io/r/AGENCY_ID/advertiser/`ADVERTISER_ID`_. | _Required_ |
123
+ | `type` | String | Pixel type. One of: `"scraper"`, `"viewed"`, `"basketed"` or `"purchased"`. | _Required_ |
124
+ | `url` | RegExp | Only enables the pixel on page URLs that are matched by this pattern. | _Required_ |
125
+ | `scrape`<br>_(segments only)_ | String, Array or Function | Scrapes the product's SKU for segments. | _Required_ |
126
+ | `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_ |
127
+ | `listener` | Object | Use an event listener when you want to enable the pixel only after a specific DOM event, instead of on page change. | `undefined` |
128
+ | `listener.event` | String | DOM event type. [List of supported values.](https://www.w3schools.com/jsref/dom_obj_event.asp) | _Required_ |
129
+ | `listener.elements` | Function | This function should return DOM element(s) to apply the event listener to. | _Required_ |
130
+ | `optional`<br>_(scrapers only)_ | Array | An array of field names (as used in `scrape.*`) that are allowed to have empty values. | `[]` |
131
+ | `before` | Function | A custom function that's executed just before the pixel is executed. | `(data, callback) => { callback(data); }` |
132
+ | `after` | Function | A custom function that's executed just after the pixel has been executed. | `(data) => {}` |
149
133
 
150
134
  ## Static methods (helpers)
151
135
 
@@ -1,23 +1,23 @@
1
- /*! choreograph-create-pixel v1.1.0 2022/09/26 */
1
+ /*! choreograph-create-pixel v1.2.0 2022/10/10 */
2
2
  'use strict';
3
3
 
4
+ const CONFIG = {
5
+ colors: { error: "red", warn: "orange", success: "green" },
6
+ icons: {
7
+ scraper: "πŸ“š",
8
+ viewed: "πŸ‘€",
9
+ basketed: "πŸ›’",
10
+ purchased: "🧾",
11
+ attribution: "πŸ”–",
12
+ conversion: "πŸ’΅",
13
+ },
14
+ };
15
+
4
16
  class ChoreographCreatePixel {
5
17
  constructor(userConfig) {
6
- this.previouslyScrapedJsonString = null;
18
+ this.previouslyScrapedHash = null;
7
19
  this.logs = [];
8
20
 
9
- this.settings = {
10
- colors: { error: "#f44336", warn: "#ffa726", success: "#66bb6a" },
11
- icons: { scrape: "πŸ”", view: "πŸ‘€", basket: "πŸ›’", purchase: "🧾" },
12
- titleCss: "font:700 80% HelveticaNeue;margin:12px 0 8px",
13
- messageCss: "font:500 120% HelveticaNeue;margin-bottom:12px",
14
- eventTypes: {
15
- view: "product-viewed",
16
- basket: "product-basketed",
17
- purchase: "product-purchased",
18
- },
19
- };
20
-
21
21
  this.config = {
22
22
  debug: /(pixel|lemonpi)_debug/i.test(location.href),
23
23
  before: (data, callback) => callback(data),
@@ -88,14 +88,12 @@ class ChoreographCreatePixel {
88
88
  if (!this.config.debug || this.logs.includes(message)) return;
89
89
 
90
90
  const args = [
91
- `%c${
92
- this.settings.icons[this.config.what]
93
- } ${this.config.what.toUpperCase()} PIXEL DEBUG%c\n%c${message}`,
94
- this.settings.titleCss,
95
- "",
96
- this.settings.colors[level]
97
- ? `${this.settings.messageCss};color:${this.settings.colors[level]}`
98
- : this.settings.messageCss,
91
+ `%cβΈ¬ create%c ${this.config.type} ${
92
+ CONFIG.icons[this.config.type]
93
+ } %c${message}`,
94
+ "background:black;color:white;border-radius:3px;padding:3px 6px",
95
+ "font-weight:bold",
96
+ `color:${CONFIG.colors[level]}`,
99
97
  ];
100
98
 
101
99
  if (data) args.push(data);
@@ -104,58 +102,59 @@ class ChoreographCreatePixel {
104
102
  }
105
103
 
106
104
  validateConfig() {
107
- if (typeof this.config.who !== "number")
108
- this.log("error", "Please use a number", { who: this.config.who });
105
+ if (typeof this.config.advertiser !== "number")
106
+ this.log("error", "Please use a number", {
107
+ advertiser: this.config.advertiser,
108
+ });
109
109
  else if (
110
- !["scrape", "view", "basket", "purchase"].includes(this.config.what)
110
+ !["scraper", "viewed", "basketed", "purchased"].includes(this.config.type)
111
111
  )
112
- this.log("error", "Please use scrape, view, basket, or purchase", {
113
- what: this.config.what,
112
+ this.log("error", "Please use scraper, viewed, basketed or purchased", {
113
+ type: this.config.type,
114
114
  });
115
- else if (!(this.config.where instanceof RegExp))
115
+ else if (!(this.config.url instanceof RegExp))
116
116
  this.log("error", "Please use a regular expression", {
117
- where: this.config.where,
117
+ url: this.config.url,
118
+ });
119
+ else if (!this.config.scrape)
120
+ this.log("error", "Please provide something to scrape", {
121
+ scrape: this.config.scrape,
118
122
  });
119
- else if (typeof this.config.which !== "object" || !this.config.which.sku)
120
- this.log("error", "Please provide an SKU", { which: this.config.which });
121
123
  else return true;
122
124
  }
123
125
 
124
126
  cycle() {
125
- if (!this.config.where.test(location.href)) {
126
- this.log("warn", `Pattern does not match "${location.href}"`, {
127
- where: this.config.where,
127
+ if (!this.config.url.test(location.href))
128
+ this.log("warn", `URL pattern does not match "${location.href}"`, {
129
+ url: this.config.url,
128
130
  });
129
- } else if (
130
- this.config.what === "scrape" &&
131
+ else if (
132
+ this.config.type === "scraper" &&
131
133
  document.querySelector('html[class*="translated-"]')
132
- ) {
134
+ )
133
135
  this.log(
134
136
  "warn",
135
- "This page has been translated by the browser, and will be excluded"
137
+ "This page has been translated by the browser, and won't be scraped"
136
138
  );
137
- } else if (this.config.when) {
139
+ else if (this.config.listener) {
140
+ const attribute = `create-${this.config.type}-${this.config.listener.event}`;
141
+
138
142
  try {
139
- let elements = this.config.when.elements();
143
+ let elements = this.config.listener.elements();
140
144
  if (!elements.forEach) elements = [elements];
141
145
 
142
146
  elements.forEach((element) => {
143
- if (
144
- !element.hasAttribute(`choreograph-${this.config.when.listener}`)
145
- ) {
146
- element.addEventListener(this.config.when.listener, () =>
147
+ if (!element.hasAttribute(attribute)) {
148
+ element.addEventListener(this.config.listener.event, () =>
147
149
  this.scrape(element)
148
150
  );
149
151
 
150
- element.setAttribute(
151
- `choreograph-${this.config.when.listener}`,
152
- ""
153
- );
152
+ element.setAttribute(attribute, "");
154
153
  }
155
154
  });
156
155
  } catch (error) {
157
156
  this.log("error", error.message, {
158
- when: { elements: this.config.when.elements },
157
+ "listener.elements": this.config.listener.elements,
159
158
  });
160
159
  }
161
160
  } else {
@@ -166,46 +165,86 @@ class ChoreographCreatePixel {
166
165
  }
167
166
 
168
167
  scrape(element) {
169
- const data = {};
170
168
  let hasErrors = false;
169
+ let data;
171
170
 
172
- Object.keys(this.config.which).forEach((fieldName) => {
173
- data[fieldName] = this.config.which[fieldName];
171
+ const handleField = (field, fieldName) => {
172
+ let result = field;
174
173
 
175
- if (typeof data[fieldName] === "function") {
174
+ if (typeof result === "function") {
176
175
  try {
177
- data[fieldName] = data[fieldName](element);
176
+ result = result(element);
178
177
  } catch (error) {
179
- if (!this.config.optional.includes(fieldName)) {
180
- this.log("error", error.message, {
181
- which: { [fieldName]: this.config.which[fieldName] },
182
- });
178
+ if (this.config.optional.includes(fieldName)) return null;
179
+
180
+ this.log(
181
+ this.config.type === "attribution" ? "warn" : "error",
182
+ error.message,
183
+ { [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
184
+ );
183
185
 
184
- hasErrors = true;
185
- } else delete data[fieldName];
186
+ hasErrors = true;
186
187
  }
187
188
  }
188
189
 
189
- if (typeof data[fieldName] === "string")
190
- data[fieldName] = data[fieldName].replace(/\s+/g, " ").trim();
190
+ if (typeof result === "string")
191
+ result = result.replace(/\s+/g, " ").trim();
191
192
 
192
- if (data[fieldName] == null || data[fieldName] === "")
193
- if (!this.config.optional.includes(fieldName)) {
194
- this.log("error", "This required field's value is empty", {
195
- which: { [fieldName]: data[fieldName] },
196
- });
193
+ if (result == null || result === "") {
194
+ if (this.config.optional.includes(fieldName)) return null;
197
195
 
198
- hasErrors = true;
199
- } else data[fieldName] = null;
200
- });
196
+ this.log(
197
+ this.config.type === "attribution" ? "warn" : "error",
198
+ "Value is empty",
199
+ { [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
200
+ );
201
+
202
+ hasErrors = true;
203
+ }
204
+
205
+ return result;
206
+ };
201
207
 
202
- const jsonString = JSON.stringify(data);
208
+ if (this.config.type === "conversion") {
209
+ data = {
210
+ conversion: handleField(this.config.scrape),
211
+ attribution: localStorage.getItem("create-attribution-id"),
212
+ };
203
213
 
204
- if (!hasErrors && this.previouslyScrapedJsonString !== jsonString) {
214
+ if (typeof data.attribution !== "string") {
215
+ this.log("warn", "There's no attribution ID stored yet");
216
+ hasErrors = true;
217
+ }
218
+ } else if (this.config.type !== "scraper") {
219
+ data = handleField(this.config.scrape);
220
+ } else {
221
+ data = Object.keys(this.config.scrape).reduce(
222
+ (acc, fieldName) => ({
223
+ ...acc,
224
+ [fieldName]: handleField(this.config.scrape[fieldName], fieldName),
225
+ }),
226
+ {}
227
+ );
228
+ }
229
+
230
+ const dataHash = JSON.stringify(data);
231
+
232
+ if (!hasErrors && this.previouslyScrapedHash !== dataHash) {
205
233
  try {
206
234
  this.config.before(data, (newData) => {
207
- if (this.config.what !== "scrape" && Array.isArray(newData.sku))
208
- newData.sku.forEach((sku) => this.send({ ...newData, sku }));
235
+ if (this.config.type === "attribution") {
236
+ localStorage.setItem("create-attribution-id", data);
237
+ this.log("success", "Successful!", { attribution: data });
238
+
239
+ try {
240
+ this.config.after(data);
241
+ } catch (error) {
242
+ this.log("error", error.message, {
243
+ after: this.config.after,
244
+ });
245
+ }
246
+ } else if (Array.isArray(newData))
247
+ newData.forEach((id) => this.send(id));
209
248
  else this.send(newData);
210
249
  });
211
250
  } catch (error) {
@@ -214,42 +253,50 @@ class ChoreographCreatePixel {
214
253
  });
215
254
  }
216
255
 
217
- this.previouslyScrapedJsonString = jsonString;
256
+ this.previouslyScrapedHash = dataHash;
218
257
  this.logs.length = 0;
219
258
  }
220
259
  }
221
260
 
222
261
  send(data) {
223
- const fields = { ...data };
224
- delete fields.sku;
225
-
226
- const url =
227
- this.config.what === "scrape"
228
- ? `https://d.lemonpi.io/scrapes${
229
- this.config.debug ? "?validate=true" : ""
230
- }`
231
- : `https://d.lemonpi.io/a/${
232
- this.config.who
233
- }/product/event?e=${encodeURIComponent(
234
- JSON.stringify({
235
- "event-type": this.settings.eventTypes[this.config.what],
236
- sku: data.sku,
237
- })
238
- )}`;
262
+ let url;
239
263
 
240
- if (this.config.what !== "scrape" && !this.config.debug)
264
+ switch (this.config.type) {
265
+ case "scraper":
266
+ url = `https://d.lemonpi.io/scrapes${
267
+ this.config.debug ? "?validate=true" : ""
268
+ }`;
269
+ break;
270
+
271
+ case "conversion":
272
+ url = "https://lemonpi.io/"; // TO-DO
273
+ break;
274
+
275
+ default:
276
+ url = `https://d.lemonpi.io/a/${
277
+ this.config.advertiser
278
+ }/product/event?e=${encodeURIComponent(
279
+ JSON.stringify({
280
+ "event-type": `product-${this.config.type}`,
281
+ sku: data,
282
+ })
283
+ )}`;
284
+ break;
285
+ }
286
+
287
+ if (this.config.type !== "scraper" && !this.config.debug)
241
288
  new Image().src = url;
242
289
  else
243
290
  fetch(
244
291
  url,
245
- this.config.what === "scrape"
292
+ this.config.type === "scraper"
246
293
  ? {
247
294
  method: "POST",
248
295
  headers: { "Content-Type": "application/json" },
249
296
  body: JSON.stringify({
250
- "advertiser-id": this.config.who,
297
+ "advertiser-id": this.config.advertiser,
251
298
  sku: data.sku,
252
- fields,
299
+ fields: { ...data, sku: undefined },
253
300
  }),
254
301
  }
255
302
  : null
@@ -279,7 +326,13 @@ class ChoreographCreatePixel {
279
326
  })
280
327
  .catch(() => {
281
328
  if (response.ok) {
282
- this.log("success", "Successful!", data);
329
+ this.log(
330
+ "success",
331
+ "Successful!",
332
+ ["viewed", "basketed", "purchased"].includes(this.config.type)
333
+ ? { sku: data }
334
+ : data
335
+ );
283
336
 
284
337
  try {
285
338
  this.config.after(data);
@@ -1,21 +1,21 @@
1
- /*! choreograph-create-pixel v1.1.0 2022/09/26 */
1
+ /*! choreograph-create-pixel v1.2.0 2022/10/10 */
2
+ const CONFIG = {
3
+ colors: { error: "red", warn: "orange", success: "green" },
4
+ icons: {
5
+ scraper: "πŸ“š",
6
+ viewed: "πŸ‘€",
7
+ basketed: "πŸ›’",
8
+ purchased: "🧾",
9
+ attribution: "πŸ”–",
10
+ conversion: "πŸ’΅",
11
+ },
12
+ };
13
+
2
14
  class ChoreographCreatePixel {
3
15
  constructor(userConfig) {
4
- this.previouslyScrapedJsonString = null;
16
+ this.previouslyScrapedHash = null;
5
17
  this.logs = [];
6
18
 
7
- this.settings = {
8
- colors: { error: "#f44336", warn: "#ffa726", success: "#66bb6a" },
9
- icons: { scrape: "πŸ”", view: "πŸ‘€", basket: "πŸ›’", purchase: "🧾" },
10
- titleCss: "font:700 80% HelveticaNeue;margin:12px 0 8px",
11
- messageCss: "font:500 120% HelveticaNeue;margin-bottom:12px",
12
- eventTypes: {
13
- view: "product-viewed",
14
- basket: "product-basketed",
15
- purchase: "product-purchased",
16
- },
17
- };
18
-
19
19
  this.config = {
20
20
  debug: /(pixel|lemonpi)_debug/i.test(location.href),
21
21
  before: (data, callback) => callback(data),
@@ -86,14 +86,12 @@ class ChoreographCreatePixel {
86
86
  if (!this.config.debug || this.logs.includes(message)) return;
87
87
 
88
88
  const args = [
89
- `%c${
90
- this.settings.icons[this.config.what]
91
- } ${this.config.what.toUpperCase()} PIXEL DEBUG%c\n%c${message}`,
92
- this.settings.titleCss,
93
- "",
94
- this.settings.colors[level]
95
- ? `${this.settings.messageCss};color:${this.settings.colors[level]}`
96
- : this.settings.messageCss,
89
+ `%cβΈ¬ create%c ${this.config.type} ${
90
+ CONFIG.icons[this.config.type]
91
+ } %c${message}`,
92
+ "background:black;color:white;border-radius:3px;padding:3px 6px",
93
+ "font-weight:bold",
94
+ `color:${CONFIG.colors[level]}`,
97
95
  ];
98
96
 
99
97
  if (data) args.push(data);
@@ -102,58 +100,59 @@ class ChoreographCreatePixel {
102
100
  }
103
101
 
104
102
  validateConfig() {
105
- if (typeof this.config.who !== "number")
106
- this.log("error", "Please use a number", { who: this.config.who });
103
+ if (typeof this.config.advertiser !== "number")
104
+ this.log("error", "Please use a number", {
105
+ advertiser: this.config.advertiser,
106
+ });
107
107
  else if (
108
- !["scrape", "view", "basket", "purchase"].includes(this.config.what)
108
+ !["scraper", "viewed", "basketed", "purchased"].includes(this.config.type)
109
109
  )
110
- this.log("error", "Please use scrape, view, basket, or purchase", {
111
- what: this.config.what,
110
+ this.log("error", "Please use scraper, viewed, basketed or purchased", {
111
+ type: this.config.type,
112
112
  });
113
- else if (!(this.config.where instanceof RegExp))
113
+ else if (!(this.config.url instanceof RegExp))
114
114
  this.log("error", "Please use a regular expression", {
115
- where: this.config.where,
115
+ url: this.config.url,
116
+ });
117
+ else if (!this.config.scrape)
118
+ this.log("error", "Please provide something to scrape", {
119
+ scrape: this.config.scrape,
116
120
  });
117
- else if (typeof this.config.which !== "object" || !this.config.which.sku)
118
- this.log("error", "Please provide an SKU", { which: this.config.which });
119
121
  else return true;
120
122
  }
121
123
 
122
124
  cycle() {
123
- if (!this.config.where.test(location.href)) {
124
- this.log("warn", `Pattern does not match "${location.href}"`, {
125
- where: this.config.where,
125
+ if (!this.config.url.test(location.href))
126
+ this.log("warn", `URL pattern does not match "${location.href}"`, {
127
+ url: this.config.url,
126
128
  });
127
- } else if (
128
- this.config.what === "scrape" &&
129
+ else if (
130
+ this.config.type === "scraper" &&
129
131
  document.querySelector('html[class*="translated-"]')
130
- ) {
132
+ )
131
133
  this.log(
132
134
  "warn",
133
- "This page has been translated by the browser, and will be excluded"
135
+ "This page has been translated by the browser, and won't be scraped"
134
136
  );
135
- } else if (this.config.when) {
137
+ else if (this.config.listener) {
138
+ const attribute = `create-${this.config.type}-${this.config.listener.event}`;
139
+
136
140
  try {
137
- let elements = this.config.when.elements();
141
+ let elements = this.config.listener.elements();
138
142
  if (!elements.forEach) elements = [elements];
139
143
 
140
144
  elements.forEach((element) => {
141
- if (
142
- !element.hasAttribute(`choreograph-${this.config.when.listener}`)
143
- ) {
144
- element.addEventListener(this.config.when.listener, () =>
145
+ if (!element.hasAttribute(attribute)) {
146
+ element.addEventListener(this.config.listener.event, () =>
145
147
  this.scrape(element)
146
148
  );
147
149
 
148
- element.setAttribute(
149
- `choreograph-${this.config.when.listener}`,
150
- ""
151
- );
150
+ element.setAttribute(attribute, "");
152
151
  }
153
152
  });
154
153
  } catch (error) {
155
154
  this.log("error", error.message, {
156
- when: { elements: this.config.when.elements },
155
+ "listener.elements": this.config.listener.elements,
157
156
  });
158
157
  }
159
158
  } else {
@@ -164,46 +163,86 @@ class ChoreographCreatePixel {
164
163
  }
165
164
 
166
165
  scrape(element) {
167
- const data = {};
168
166
  let hasErrors = false;
167
+ let data;
169
168
 
170
- Object.keys(this.config.which).forEach((fieldName) => {
171
- data[fieldName] = this.config.which[fieldName];
169
+ const handleField = (field, fieldName) => {
170
+ let result = field;
172
171
 
173
- if (typeof data[fieldName] === "function") {
172
+ if (typeof result === "function") {
174
173
  try {
175
- data[fieldName] = data[fieldName](element);
174
+ result = result(element);
176
175
  } catch (error) {
177
- if (!this.config.optional.includes(fieldName)) {
178
- this.log("error", error.message, {
179
- which: { [fieldName]: this.config.which[fieldName] },
180
- });
176
+ if (this.config.optional.includes(fieldName)) return null;
177
+
178
+ this.log(
179
+ this.config.type === "attribution" ? "warn" : "error",
180
+ error.message,
181
+ { [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
182
+ );
181
183
 
182
- hasErrors = true;
183
- } else delete data[fieldName];
184
+ hasErrors = true;
184
185
  }
185
186
  }
186
187
 
187
- if (typeof data[fieldName] === "string")
188
- data[fieldName] = data[fieldName].replace(/\s+/g, " ").trim();
188
+ if (typeof result === "string")
189
+ result = result.replace(/\s+/g, " ").trim();
189
190
 
190
- if (data[fieldName] == null || data[fieldName] === "")
191
- if (!this.config.optional.includes(fieldName)) {
192
- this.log("error", "This required field's value is empty", {
193
- which: { [fieldName]: data[fieldName] },
194
- });
191
+ if (result == null || result === "") {
192
+ if (this.config.optional.includes(fieldName)) return null;
195
193
 
196
- hasErrors = true;
197
- } else data[fieldName] = null;
198
- });
194
+ this.log(
195
+ this.config.type === "attribution" ? "warn" : "error",
196
+ "Value is empty",
197
+ { [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
198
+ );
199
+
200
+ hasErrors = true;
201
+ }
202
+
203
+ return result;
204
+ };
199
205
 
200
- const jsonString = JSON.stringify(data);
206
+ if (this.config.type === "conversion") {
207
+ data = {
208
+ conversion: handleField(this.config.scrape),
209
+ attribution: localStorage.getItem("create-attribution-id"),
210
+ };
201
211
 
202
- if (!hasErrors && this.previouslyScrapedJsonString !== jsonString) {
212
+ if (typeof data.attribution !== "string") {
213
+ this.log("warn", "There's no attribution ID stored yet");
214
+ hasErrors = true;
215
+ }
216
+ } else if (this.config.type !== "scraper") {
217
+ data = handleField(this.config.scrape);
218
+ } else {
219
+ data = Object.keys(this.config.scrape).reduce(
220
+ (acc, fieldName) => ({
221
+ ...acc,
222
+ [fieldName]: handleField(this.config.scrape[fieldName], fieldName),
223
+ }),
224
+ {}
225
+ );
226
+ }
227
+
228
+ const dataHash = JSON.stringify(data);
229
+
230
+ if (!hasErrors && this.previouslyScrapedHash !== dataHash) {
203
231
  try {
204
232
  this.config.before(data, (newData) => {
205
- if (this.config.what !== "scrape" && Array.isArray(newData.sku))
206
- newData.sku.forEach((sku) => this.send({ ...newData, sku }));
233
+ if (this.config.type === "attribution") {
234
+ localStorage.setItem("create-attribution-id", data);
235
+ this.log("success", "Successful!", { attribution: data });
236
+
237
+ try {
238
+ this.config.after(data);
239
+ } catch (error) {
240
+ this.log("error", error.message, {
241
+ after: this.config.after,
242
+ });
243
+ }
244
+ } else if (Array.isArray(newData))
245
+ newData.forEach((id) => this.send(id));
207
246
  else this.send(newData);
208
247
  });
209
248
  } catch (error) {
@@ -212,42 +251,50 @@ class ChoreographCreatePixel {
212
251
  });
213
252
  }
214
253
 
215
- this.previouslyScrapedJsonString = jsonString;
254
+ this.previouslyScrapedHash = dataHash;
216
255
  this.logs.length = 0;
217
256
  }
218
257
  }
219
258
 
220
259
  send(data) {
221
- const fields = { ...data };
222
- delete fields.sku;
223
-
224
- const url =
225
- this.config.what === "scrape"
226
- ? `https://d.lemonpi.io/scrapes${
227
- this.config.debug ? "?validate=true" : ""
228
- }`
229
- : `https://d.lemonpi.io/a/${
230
- this.config.who
231
- }/product/event?e=${encodeURIComponent(
232
- JSON.stringify({
233
- "event-type": this.settings.eventTypes[this.config.what],
234
- sku: data.sku,
235
- })
236
- )}`;
260
+ let url;
237
261
 
238
- if (this.config.what !== "scrape" && !this.config.debug)
262
+ switch (this.config.type) {
263
+ case "scraper":
264
+ url = `https://d.lemonpi.io/scrapes${
265
+ this.config.debug ? "?validate=true" : ""
266
+ }`;
267
+ break;
268
+
269
+ case "conversion":
270
+ url = "https://lemonpi.io/"; // TO-DO
271
+ break;
272
+
273
+ default:
274
+ url = `https://d.lemonpi.io/a/${
275
+ this.config.advertiser
276
+ }/product/event?e=${encodeURIComponent(
277
+ JSON.stringify({
278
+ "event-type": `product-${this.config.type}`,
279
+ sku: data,
280
+ })
281
+ )}`;
282
+ break;
283
+ }
284
+
285
+ if (this.config.type !== "scraper" && !this.config.debug)
239
286
  new Image().src = url;
240
287
  else
241
288
  fetch(
242
289
  url,
243
- this.config.what === "scrape"
290
+ this.config.type === "scraper"
244
291
  ? {
245
292
  method: "POST",
246
293
  headers: { "Content-Type": "application/json" },
247
294
  body: JSON.stringify({
248
- "advertiser-id": this.config.who,
295
+ "advertiser-id": this.config.advertiser,
249
296
  sku: data.sku,
250
- fields,
297
+ fields: { ...data, sku: undefined },
251
298
  }),
252
299
  }
253
300
  : null
@@ -277,7 +324,13 @@ class ChoreographCreatePixel {
277
324
  })
278
325
  .catch(() => {
279
326
  if (response.ok) {
280
- this.log("success", "Successful!", data);
327
+ this.log(
328
+ "success",
329
+ "Successful!",
330
+ ["viewed", "basketed", "purchased"].includes(this.config.type)
331
+ ? { sku: data }
332
+ : data
333
+ );
281
334
 
282
335
  try {
283
336
  this.config.after(data);
@@ -1,2 +1,2 @@
1
- /*! choreograph-create-pixel v1.1.0 2022/09/26 */
2
- var ChoreographCreatePixel=function(){"use strict";function e(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var o=Object.getOwnPropertySymbols(e);t&&(o=o.filter((function(t){return Object.getOwnPropertyDescriptor(e,t).enumerable}))),n.push.apply(n,o)}return n}function t(t){for(var n=1;n<arguments.length;n++){var o=null!=arguments[n]?arguments[n]:{};n%2?e(Object(o),!0).forEach((function(e){r(t,e,o[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(o)):e(Object(o)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,e))}))}return t}function n(e){return n="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e},n(e)}function o(e,t){for(var n=0;n<t.length;n++){var o=t[n];o.enumerable=o.enumerable||!1,o.configurable=!0,"value"in o&&(o.writable=!0),Object.defineProperty(e,o.key,o)}}function r(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}var i=function(){function e(n){!function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,e),this.previouslyScrapedJsonString=null,this.logs=[],this.settings={colors:{error:"#f44336",warn:"#ffa726",success:"#66bb6a"},icons:{scrape:"πŸ”",view:"πŸ‘€",basket:"πŸ›’",purchase:"🧾"},titleCss:"font:700 80% HelveticaNeue;margin:12px 0 8px",messageCss:"font:500 120% HelveticaNeue;margin-bottom:12px",eventTypes:{view:"product-viewed",basket:"product-basketed",purchase:"product-purchased"}},this.config=t({debug:/(pixel|lemonpi)_debug/i.test(location.href),before:function(e,t){return t(e)},after:function(){},optional:[]},n),this.validateConfig()&&this.cycle()}var i,s,c;return i=e,s=[{key:"log",value:function(e,t){var n,o=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;if(this.config.debug&&!this.logs.includes(t)){var r=["%c".concat(this.settings.icons[this.config.what]," ").concat(this.config.what.toUpperCase()," PIXEL DEBUG%c\n%c").concat(t),this.settings.titleCss,"",this.settings.colors[e]?"".concat(this.settings.messageCss,";color:").concat(this.settings.colors[e]):this.settings.messageCss];o&&r.push(o),(n=console).info.apply(n,r),this.logs.push(t)}}},{key:"validateConfig",value:function(){if("number"!=typeof this.config.who)this.log("error","Please use a number",{who:this.config.who});else if(["scrape","view","basket","purchase"].includes(this.config.what))if(this.config.where instanceof RegExp){if("object"===n(this.config.which)&&this.config.which.sku)return!0;this.log("error","Please provide an SKU",{which:this.config.which})}else this.log("error","Please use a regular expression",{where:this.config.where});else this.log("error","Please use scrape, view, basket, or purchase",{what:this.config.what})}},{key:"cycle",value:function(){var e=this;if(this.config.where.test(location.href))if("scrape"===this.config.what&&document.querySelector('html[class*="translated-"]'))this.log("warn","This page has been translated by the browser, and will be excluded");else if(this.config.when)try{var t=this.config.when.elements();t.forEach||(t=[t]),t.forEach((function(t){t.hasAttribute("choreograph-".concat(e.config.when.listener))||(t.addEventListener(e.config.when.listener,(function(){return e.scrape(t)})),t.setAttribute("choreograph-".concat(e.config.when.listener),""))}))}catch(e){this.log("error",e.message,{when:{elements:this.config.when.elements}})}else this.scrape();else this.log("warn",'Pattern does not match "'.concat(location.href,'"'),{where:this.config.where});setTimeout((function(){return e.cycle()}),750)}},{key:"scrape",value:function(e){var n=this,o={},i=!1;Object.keys(this.config.which).forEach((function(t){if(o[t]=n.config.which[t],"function"==typeof o[t])try{o[t]=o[t](e)}catch(e){n.config.optional.includes(t)?delete o[t]:(n.log("error",e.message,{which:r({},t,n.config.which[t])}),i=!0)}"string"==typeof o[t]&&(o[t]=o[t].replace(/\s+/g," ").trim()),null!=o[t]&&""!==o[t]||(n.config.optional.includes(t)?o[t]=null:(n.log("error","This required field's value is empty",{which:r({},t,o[t])}),i=!0))}));var s=JSON.stringify(o);if(!i&&this.previouslyScrapedJsonString!==s){try{this.config.before(o,(function(e){"scrape"!==n.config.what&&Array.isArray(e.sku)?e.sku.forEach((function(o){return n.send(t(t({},e),{},{sku:o}))})):n.send(e)}))}catch(e){this.log("error",e.message,{before:this.config.before})}this.previouslyScrapedJsonString=s,this.logs.length=0}}},{key:"send",value:function(e){var n=this,o=t({},e);delete o.sku;var r="scrape"===this.config.what?"https://d.lemonpi.io/scrapes".concat(this.config.debug?"?validate=true":""):"https://d.lemonpi.io/a/".concat(this.config.who,"/product/event?e=").concat(encodeURIComponent(JSON.stringify({"event-type":this.settings.eventTypes[this.config.what],sku:e.sku})));"scrape"===this.config.what||this.config.debug?fetch(r,"scrape"===this.config.what?{method:"POST",headers:{"Content-Type":"application/json"},body:JSON.stringify({"advertiser-id":this.config.who,sku:e.sku,fields:o})}:null).then((function(t){return t.json().then((function(o){if(t.ok){n.log("warn","Successful, with warnings. Details:",o);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:"),o);n.logs.length=0})).catch((function(){if(t.ok){n.log("success","Successful!",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,n=void 0===t?[]:t,o=e.allowHash,r=void 0!==o&&o,i="".concat(location.protocol,"//").concat(location.host).concat(location.pathname),s=new URLSearchParams(location.search);return n.reduce((function(e,t){var n=e?"&":"?",o=encodeURI(t),r=s.get(t);return null!=r?(i+="".concat(n).concat(o).concat(""===r?"":"=".concat(encodeURI(r))),!0):e}),!1),r&&(i+=location.hash),i}},{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,n){return t(t({},e),{},r({},decodeURI(n.split("=")[0]),decodeURI(n.split("=")[1]||"")))}),{})}},{key:"getQueryParameter",value:function(e){return this.getAllQueryParameters()[e]}}],s&&o(i.prototype,s),c&&o(i,c),Object.defineProperty(i,"prototype",{writable:!1}),e}();return i}();
1
+ /*! choreograph-create-pixel v1.2.0 2022/10/10 */
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 o=null!=arguments[r]?arguments[r]:{};r%2?e(Object(o),!0).forEach((function(e){n(t,e,o[e])})):Object.getOwnPropertyDescriptors?Object.defineProperties(t,Object.getOwnPropertyDescriptors(o)):e(Object(o)).forEach((function(e){Object.defineProperty(t,e,Object.getOwnPropertyDescriptor(o,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,n.key,n)}}function n(e,t,r){return t in e?Object.defineProperty(e,t,{value:r,enumerable:!0,configurable:!0,writable:!0}):e[t]=r,e}var o={colors:{error:"red",warn:"orange",success:"green"},icons:{scraper:"πŸ“š",viewed:"πŸ‘€",basketed:"πŸ›’",purchased:"🧾",attribution:"πŸ”–",conversion:"πŸ’΅"}},i=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({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,c,s;return i=e,c=[{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(o.icons[this.config.type]," %c").concat(t),"background:black;color:white;border-radius:3px;padding:3px 6px","font-weight:bold","color:".concat(o.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.listener){var t="create-".concat(this.config.type,"-").concat(this.config.listener.event);try{var r=this.config.listener.elements();r.forEach||(r=[r]),r.forEach((function(r){r.hasAttribute(t)||(r.addEventListener(e.config.listener.event,(function(){return e.scrape(r)})),r.setAttribute(t,""))}))}catch(e){this.log("error",e.message,{"listener.elements":this.config.listener.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,o=this,i=!1,c=function(t,r){var c=t;if("function"==typeof c)try{c=c(e)}catch(e){if(o.config.optional.includes(r))return null;o.log("attribution"===o.config.type?"warn":"error",e.message,n({},r?"scrape.".concat(r):"scrape",c)),i=!0}if("string"==typeof c&&(c=c.replace(/\s+/g," ").trim()),null==c||""===c){if(o.config.optional.includes(r))return null;o.log("attribution"===o.config.type?"warn":"error","Value is empty",n({},r?"scrape.".concat(r):"scrape",c)),i=!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"),i=!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(o.config.scrape[r],r)))}),{});var s=JSON.stringify(r);if(!i&&this.previouslyScrapedHash!==s){try{this.config.before(r,(function(e){if("attribution"===o.config.type){localStorage.setItem("create-attribution-id",r),o.log("success","Successful!",{attribution:r});try{o.config.after(r)}catch(e){o.log("error",e.message,{after:o.config.after})}}else Array.isArray(e)?e.forEach((function(e){return o.send(e)})):o.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}}],s=[{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,o=void 0!==n&&n,i="".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),o=c.get(t);return null!=o?(i+="".concat(r).concat(n).concat(""===o?"":"=".concat(encodeURI(o))),!0):e}),!1),o&&(i+=location.hash),i}},{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]}}],c&&r(i.prototype,c),s&&r(i,s),Object.defineProperty(i,"prototype",{writable:!1}),e}();return i}();
package/package.json CHANGED
@@ -1,7 +1,14 @@
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.1.0",
4
+ "keywords": [
5
+ "wpp",
6
+ "groupm",
7
+ "lemonpi",
8
+ "odc",
9
+ "opendc"
10
+ ],
11
+ "version": "1.2.0",
5
12
  "author": "Rick Stevens <rick.stevens@choreograph.com> (https://lemonpi.io)",
6
13
  "repository": "gitlab:GreenhouseGroup/lemonpi/solutions/choreograph-create-pixel",
7
14
  "homepage": "https://lemonpi.io",
@@ -18,14 +25,14 @@
18
25
  "format": "prettier --ignore-path .gitignore --check . '!**/*.{js,jsx,vue}'",
19
26
  "build": "rollup -c",
20
27
  "dev": "rollup -cw",
21
- "prepare": "npm run build"
28
+ "prepublishOnly": "npm run build"
22
29
  },
23
30
  "devDependencies": {
24
- "@babel/core": "^7.19.1",
25
- "@babel/preset-env": "^7.19.1",
31
+ "@babel/core": "^7.19.3",
32
+ "@babel/preset-env": "^7.19.3",
26
33
  "@rollup/plugin-babel": "^5.3.1",
27
34
  "@rollup/plugin-eslint": "^8.0.2",
28
- "eslint": "^8.23.1",
35
+ "eslint": "^8.24.0",
29
36
  "eslint-config-prettier": "^8.5.0",
30
37
  "eslint-plugin-prettier": "^4.2.1",
31
38
  "moment": "^2.29.4",