choreograph-create-pixel 1.3.3 β†’ 1.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md 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.3 2022/12/22 */
1
+ /*! choreograph-create-pixel v1.4.1 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,49 @@ 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 (!/^[0-9a-f]{20}$/.test(ccpid))
207
+ return this.log(
208
+ "error",
209
+ "ccpid query parameter is not formatted correctly"
210
+ );
211
+
212
+ const storageItemLabel = `choreograph-${this.config.label}`;
213
+ const storedCcpid = localStorage.getItem(storageItemLabel);
214
+ if (storedCcpid !== ccpid) localStorage.setItem(storageItemLabel, ccpid);
215
+ this.log("success", "Successful!", ccpid);
216
+ }
217
+
218
+ convert() {
219
+ const ccpid = localStorage.getItem(`choreograph-${this.config.label}`);
220
+
221
+ if (!ccpid)
222
+ return this.log("warn", `"${this.config.label}" attribution not present`);
223
+
224
+ this.send(ccpid);
225
+ }
226
+
168
227
  scrape(element) {
169
228
  let hasErrors = false;
170
229
  let data;
@@ -178,11 +237,9 @@ class ChoreographCreatePixel {
178
237
  } catch (error) {
179
238
  if (this.config.optional.includes(fieldName)) return undefined;
180
239
 
181
- this.log(
182
- this.config.type === "attribution" ? "warn" : "error",
183
- error.message,
184
- { [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
185
- );
240
+ this.log("error", error.message, {
241
+ [fieldName ? `scrape.${fieldName}` : "scrape"]: result,
242
+ });
186
243
 
187
244
  hasErrors = true;
188
245
  }
@@ -195,11 +252,9 @@ class ChoreographCreatePixel {
195
252
  (result == null || result === "") &&
196
253
  !this.config.optional.includes(fieldName)
197
254
  ) {
198
- this.log(
199
- this.config.type === "attribution" ? "warn" : "error",
200
- "Value is empty",
201
- { [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
202
- );
255
+ this.log("error", "Value is empty", {
256
+ [fieldName ? `scrape.${fieldName}` : "scrape"]: result,
257
+ });
203
258
 
204
259
  hasErrors = true;
205
260
  }
@@ -207,17 +262,7 @@ class ChoreographCreatePixel {
207
262
  return result;
208
263
  };
209
264
 
210
- if (this.config.type === "conversion") {
211
- data = {
212
- conversion: handleField(this.config.scrape),
213
- attribution: localStorage.getItem("create-attribution-id"),
214
- };
215
-
216
- if (typeof data.attribution !== "string") {
217
- this.log("warn", "There's no attribution ID stored yet");
218
- hasErrors = true;
219
- }
220
- } else if (this.config.type !== "scraper") {
265
+ if (this.config.type !== "scraper") {
221
266
  data = handleField(this.config.scrape);
222
267
  } else {
223
268
  data = Object.keys(this.config.scrape).reduce(
@@ -234,19 +279,7 @@ class ChoreographCreatePixel {
234
279
  if (!hasErrors && this.previouslyScrapedHash !== dataHash) {
235
280
  try {
236
281
  this.config.before(data, (newData) => {
237
- if (this.config.type === "attribution") {
238
- localStorage.setItem("create-attribution-id", data);
239
- this.log("success", "Successful!", { attribution: data });
240
-
241
- try {
242
- this.config.after(data);
243
- } catch (error) {
244
- this.log("error", error.message, {
245
- after: this.config.after,
246
- });
247
- }
248
- } else if (Array.isArray(newData))
249
- newData.forEach((id) => this.send(id));
282
+ if (Array.isArray(newData)) newData.forEach((id) => this.send(id));
250
283
  else this.send(newData);
251
284
  });
252
285
  } catch (error) {
@@ -268,10 +301,20 @@ class ChoreographCreatePixel {
268
301
  url = `https://d.lemonpi.io/scrapes${
269
302
  this.config.debug ? "?validate=true" : ""
270
303
  }`;
304
+
271
305
  break;
272
306
 
273
307
  case "conversion":
274
- url = "https://lemonpi.io/"; // TO-DO
308
+ url = `https://content.lemonpi.io/track/event?e=${encodeURIComponent(
309
+ JSON.stringify({
310
+ version: 1,
311
+ type: "conversion",
312
+ name: this.config.label,
313
+ "conversion-attribution-id": data,
314
+ "advertiser-id": this.config.advertiser,
315
+ })
316
+ )}`;
317
+
275
318
  break;
276
319
 
277
320
  default:
@@ -283,6 +326,7 @@ class ChoreographCreatePixel {
283
326
  sku: data,
284
327
  })
285
328
  )}`;
329
+
286
330
  break;
287
331
  }
288
332
 
@@ -1,4 +1,4 @@
1
- /*! choreograph-create-pixel v1.3.3 2022/12/22 */
1
+ /*! choreograph-create-pixel v1.4.1 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,49 @@ 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 (!/^[0-9a-f]{20}$/.test(ccpid))
205
+ return this.log(
206
+ "error",
207
+ "ccpid query parameter is not formatted correctly"
208
+ );
209
+
210
+ const storageItemLabel = `choreograph-${this.config.label}`;
211
+ const storedCcpid = localStorage.getItem(storageItemLabel);
212
+ if (storedCcpid !== ccpid) localStorage.setItem(storageItemLabel, ccpid);
213
+ this.log("success", "Successful!", ccpid);
214
+ }
215
+
216
+ convert() {
217
+ const ccpid = localStorage.getItem(`choreograph-${this.config.label}`);
218
+
219
+ if (!ccpid)
220
+ return this.log("warn", `"${this.config.label}" attribution not present`);
221
+
222
+ this.send(ccpid);
223
+ }
224
+
166
225
  scrape(element) {
167
226
  let hasErrors = false;
168
227
  let data;
@@ -176,11 +235,9 @@ class ChoreographCreatePixel {
176
235
  } catch (error) {
177
236
  if (this.config.optional.includes(fieldName)) return undefined;
178
237
 
179
- this.log(
180
- this.config.type === "attribution" ? "warn" : "error",
181
- error.message,
182
- { [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
183
- );
238
+ this.log("error", error.message, {
239
+ [fieldName ? `scrape.${fieldName}` : "scrape"]: result,
240
+ });
184
241
 
185
242
  hasErrors = true;
186
243
  }
@@ -193,11 +250,9 @@ class ChoreographCreatePixel {
193
250
  (result == null || result === "") &&
194
251
  !this.config.optional.includes(fieldName)
195
252
  ) {
196
- this.log(
197
- this.config.type === "attribution" ? "warn" : "error",
198
- "Value is empty",
199
- { [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
200
- );
253
+ this.log("error", "Value is empty", {
254
+ [fieldName ? `scrape.${fieldName}` : "scrape"]: result,
255
+ });
201
256
 
202
257
  hasErrors = true;
203
258
  }
@@ -205,17 +260,7 @@ class ChoreographCreatePixel {
205
260
  return result;
206
261
  };
207
262
 
208
- if (this.config.type === "conversion") {
209
- data = {
210
- conversion: handleField(this.config.scrape),
211
- attribution: localStorage.getItem("create-attribution-id"),
212
- };
213
-
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") {
263
+ if (this.config.type !== "scraper") {
219
264
  data = handleField(this.config.scrape);
220
265
  } else {
221
266
  data = Object.keys(this.config.scrape).reduce(
@@ -232,19 +277,7 @@ class ChoreographCreatePixel {
232
277
  if (!hasErrors && this.previouslyScrapedHash !== dataHash) {
233
278
  try {
234
279
  this.config.before(data, (newData) => {
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));
280
+ if (Array.isArray(newData)) newData.forEach((id) => this.send(id));
248
281
  else this.send(newData);
249
282
  });
250
283
  } catch (error) {
@@ -266,10 +299,20 @@ class ChoreographCreatePixel {
266
299
  url = `https://d.lemonpi.io/scrapes${
267
300
  this.config.debug ? "?validate=true" : ""
268
301
  }`;
302
+
269
303
  break;
270
304
 
271
305
  case "conversion":
272
- url = "https://lemonpi.io/"; // TO-DO
306
+ url = `https://content.lemonpi.io/track/event?e=${encodeURIComponent(
307
+ JSON.stringify({
308
+ version: 1,
309
+ type: "conversion",
310
+ name: this.config.label,
311
+ "conversion-attribution-id": data,
312
+ "advertiser-id": this.config.advertiser,
313
+ })
314
+ )}`;
315
+
273
316
  break;
274
317
 
275
318
  default:
@@ -281,6 +324,7 @@ class ChoreographCreatePixel {
281
324
  sku: data,
282
325
  })
283
326
  )}`;
327
+
284
328
  break;
285
329
  }
286
330
 
@@ -1,2 +1,2 @@
1
- /*! choreograph-create-pixel v1.3.3 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}return"string"==typeof c&&(c=c.replace(/\s+/g," ").trim()),null!=c&&""!==c||i.config.optional.includes(r)||(i.log("attribution"===i.config.type?"warn":"error","Value is empty",n({},r?"scrape.".concat(r):"scrape",c)),o=!0),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.1 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]{20}$/.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.3",
4
+ "version": "1.4.1",
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
  }