choreograph-create-pixel 1.1.0 β 1.3.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 +120 -129
- package/dist/bundle.cjs.js +146 -96
- package/dist/bundle.esm.js +146 -96
- package/dist/bundle.iife.min.js +2 -2
- package/package.json +14 -7
package/README.md
CHANGED
|
@@ -1,175 +1,166 @@
|
|
|
1
1
|
# Choreograph Create Pixel
|
|
2
2
|
|
|
3
|
-
This library lets you apply best practises to [
|
|
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
|
|
8
|
-
- [x] Supports dynamic page
|
|
7
|
+
- [x] Supports [scraper](#scraper-pixels), [segment](#segment-pixels) and [conversion](#conversion-pixels) (coming soon) pixels
|
|
8
|
+
- [x] Supports dynamic page content updates
|
|
9
|
+
- [x] Prevents scraping browser-translated content
|
|
9
10
|
- [x] Continuous URL validation
|
|
10
|
-
- [x]
|
|
11
|
-
- [x]
|
|
12
|
-
- [x] Console debugger
|
|
11
|
+
- [x] User interaction triggers
|
|
12
|
+
- [x] [Console debugger](#debugging)
|
|
13
13
|
|
|
14
|
-
|
|
14
|
+
### Scraper pixels
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
<small>Type: `scraper`</small>
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
Scraper pixels are used to scrape (read) data from websites, and store that data as products within an advertiser's product store.
|
|
19
19
|
|
|
20
|
-
|
|
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
|
+
### Segment pixels
|
|
50
21
|
|
|
51
|
-
|
|
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
|
+
<small>Types: `viewed`, `basketed` and `purchased`</small>
|
|
63
23
|
|
|
64
|
-
|
|
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
|
+
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.
|
|
86
25
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
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
|
+
### Conversion pixels
|
|
27
|
+
|
|
28
|
+
_(coming soon)_
|
|
29
|
+
|
|
30
|
+
## Quickstart
|
|
112
31
|
|
|
113
|
-
|
|
32
|
+
The following theoretical snippet includes all pixel types, can be embedded on every page of _example.com_, and will automatically determine which pixel to enable and disable through continuous URL validation.
|
|
33
|
+
|
|
34
|
+
### ES module
|
|
114
35
|
|
|
115
36
|
```js
|
|
116
37
|
import Pixel from "choreograph-create-pixel";
|
|
117
38
|
|
|
39
|
+
// Scraper pixel
|
|
40
|
+
new Pixel({
|
|
41
|
+
advertiser: 0,
|
|
42
|
+
type: "scraper",
|
|
43
|
+
|
|
44
|
+
// Where on the website should this pixel be enabled?
|
|
45
|
+
url: /example\.com\/products\/\d/,
|
|
46
|
+
|
|
47
|
+
// Functions here are continuously being evaluated for value changes
|
|
48
|
+
scrape: {
|
|
49
|
+
// Data layer example
|
|
50
|
+
sku: () => window.dataLayer[0].product.sku,
|
|
51
|
+
|
|
52
|
+
// DOM example
|
|
53
|
+
name: () => document.querySelector("#product-name").innerText,
|
|
54
|
+
|
|
55
|
+
// Helper example
|
|
56
|
+
url: Pixel.getUrl,
|
|
57
|
+
},
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
// Viewed segment pixel
|
|
118
61
|
new Pixel({
|
|
119
|
-
|
|
62
|
+
advertiser: 0,
|
|
63
|
+
type: "viewed",
|
|
64
|
+
url: /example\.com\/products\/\d/,
|
|
65
|
+
|
|
66
|
+
// Segment pixels only require an SKU to be scraped
|
|
67
|
+
scrape: () => window.dataLayer[0].product.sku,
|
|
120
68
|
});
|
|
121
69
|
|
|
70
|
+
// Basketed segment pixel
|
|
122
71
|
new Pixel({
|
|
123
|
-
|
|
72
|
+
advertiser: 0,
|
|
73
|
+
type: "basketed",
|
|
74
|
+
url: /example\.com/,
|
|
75
|
+
|
|
76
|
+
// [Optional] Trigger by DOM events, rather than scrape content updates
|
|
77
|
+
trigger: {
|
|
78
|
+
event: "click",
|
|
79
|
+
elements: () => document.querySelectorAll("button.basket[data-sku]"),
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
// The "element" parameter only becomes available when using a trigger
|
|
83
|
+
scrape: (element) => element.getAttribute("data-sku"),
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// Purchased segment pixel
|
|
87
|
+
new Pixel({
|
|
88
|
+
advertiser: 0,
|
|
89
|
+
type: "purchased",
|
|
90
|
+
url: /example\.com\/cart/,
|
|
91
|
+
|
|
92
|
+
trigger: {
|
|
93
|
+
event: "click",
|
|
94
|
+
elements: () => document.querySelectorAll("button.checkout"),
|
|
95
|
+
},
|
|
96
|
+
|
|
97
|
+
// Segment pixels support multiple SKUs
|
|
98
|
+
scrape: () => window.dataLayer[0].cart.map((product) => product.sku),
|
|
124
99
|
});
|
|
125
100
|
```
|
|
126
101
|
|
|
127
|
-
> ES modules require manual bundling and transpiling before they can be safely
|
|
102
|
+
> ES modules require manual bundling and transpiling before they can be safely embedded on websites.
|
|
103
|
+
|
|
104
|
+
### CDN library
|
|
105
|
+
|
|
106
|
+
This implementation method allows for direct embedding on pages without the need for prior bundling or transpiling.
|
|
107
|
+
|
|
108
|
+
```html
|
|
109
|
+
<script src="https://cdn.jsdelivr.net/npm/choreograph-create-pixel@1"></script>
|
|
110
|
+
<script>
|
|
111
|
+
new ChoreographCreatePixel({
|
|
112
|
+
// ...
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
new ChoreographCreatePixel({
|
|
116
|
+
// ...
|
|
117
|
+
});
|
|
118
|
+
</script>
|
|
119
|
+
```
|
|
128
120
|
|
|
129
121
|
## Debugging
|
|
130
122
|
|
|
131
|
-
Enable
|
|
123
|
+
Enable browser console debugging by adding `?pixel_debug` or `#pixel_debug` to the page's URL.
|
|
132
124
|
|
|
133
125
|
## Configuration
|
|
134
126
|
|
|
135
|
-
| Property
|
|
136
|
-
|
|
|
137
|
-
| `
|
|
138
|
-
| `
|
|
139
|
-
| `
|
|
140
|
-
| `
|
|
141
|
-
| `
|
|
142
|
-
| `
|
|
143
|
-
| `
|
|
144
|
-
| `
|
|
145
|
-
| `
|
|
146
|
-
| `
|
|
147
|
-
| `
|
|
148
|
-
| `after` | Function | Lifecycle function that's executed just after the pixel's data is sent. | `function (data) {}` |
|
|
127
|
+
| Property | Type | Description | Default |
|
|
128
|
+
| ------------------------------- | ----------------------------------- | ---------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------- |
|
|
129
|
+
| `advertiser` | Number | You can retrieve the advertiser ID from the URL in Create: _manage.lemonpi.io/r/AGENCY_ID/advertiser/`ADVERTISER_ID`_. | _Required_ |
|
|
130
|
+
| `type` | String | Pixel type. One of: `"scraper"`, `"viewed"`, `"basketed"` or `"purchased"`. | _Required_ |
|
|
131
|
+
| `url` | RegExp | Only enables the pixel on page URLs that are matched by this pattern. | _Required_ |
|
|
132
|
+
| `scrape`<br>_(segments only)_ | String, Array or Function | Scrapes the product's SKU for segments. | _Required_ |
|
|
133
|
+
| `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_ |
|
|
134
|
+
| `trigger` | Object | Adds an event listener to enable the pixel only on a specific DOM event, instead of on scrape content updates. | `undefined` |
|
|
135
|
+
| `trigger.event` | String | DOM event type. [List of supported values.](https://www.w3schools.com/jsref/dom_obj_event.asp) | _Required_ |
|
|
136
|
+
| `trigger.elements` | Function | This function should return DOM element(s) to apply the event listener to. | _Required_ |
|
|
137
|
+
| `optional`<br>_(scrapers only)_ | Array | An array of field names (as used in `scrape.*`) that are allowed to have empty values. | `[]` |
|
|
138
|
+
| `before` | Function | A custom function that's executed just before the pixel is executed. | `(scrapedData, callback) => { callback(scrapedData); }` |
|
|
139
|
+
| `after` | Function | A custom function that's executed just after the pixel has been executed. | `(scrapedData) => {}` |
|
|
149
140
|
|
|
150
141
|
## Static methods (helpers)
|
|
151
142
|
|
|
152
|
-
###
|
|
143
|
+
### `.getUrl({ allowedQueryParameters, allowHash })`
|
|
153
144
|
|
|
154
|
-
Returns the bare page URL without query parameters or location hash. This is recommended to
|
|
145
|
+
Returns the bare page URL without query parameters or location hash. This is highly recommended to exclude (UTM) tagging, or other unwanted side effects.
|
|
155
146
|
|
|
156
|
-
| Property | Type | Description
|
|
157
|
-
| ------------------------ | ------- |
|
|
158
|
-
| `allowedQueryParameters` | Array | Explicitly allow query parameters in the
|
|
159
|
-
| `allowHash` | Boolean |
|
|
147
|
+
| Property | Type | Description | Default |
|
|
148
|
+
| ------------------------ | ------- | ----------------------------------------------------------------- | ------- |
|
|
149
|
+
| `allowedQueryParameters` | Array | Explicitly allow query parameters in the URL. | `[]` |
|
|
150
|
+
| `allowHash` | Boolean | Explicitly allow including the location hash (`#foo`) in the URL. | `false` |
|
|
160
151
|
|
|
161
|
-
###
|
|
152
|
+
### `.getAllPathSegments()`
|
|
162
153
|
|
|
163
154
|
Retrieves all path segments from the URL as an array. E.g. _http://www.example.com/foo/bar_ returns `["foo", "bar"]`.
|
|
164
155
|
|
|
165
|
-
###
|
|
156
|
+
### `.getPathSegment(index)`
|
|
166
157
|
|
|
167
|
-
Retrieves a specific segment from the URL. E.g. `getPathSegment(0)`
|
|
158
|
+
Retrieves a specific segment from the URL. E.g. `getPathSegment(0)` on _http://www.example.com/foo/bar_ returns `"foo"`.
|
|
168
159
|
|
|
169
|
-
###
|
|
160
|
+
### `.getAllQueryParameters()`
|
|
170
161
|
|
|
171
162
|
Retrieves all query string parameters from the URL as an object. E.g. _http://www.example.com/?foo=bar_ returns `{ foo: "bar" }`.
|
|
172
163
|
|
|
173
|
-
###
|
|
164
|
+
### `.getQueryParameter(key)`
|
|
174
165
|
|
|
175
|
-
Retrieves a specific query parameter from the URL. E.g. `getQueryParameter('foo')`
|
|
166
|
+
Retrieves a specific query parameter from the URL. E.g. `getQueryParameter('foo')` on _http://www.example.com/?foo=bar_ returns `"bar"`.
|
package/dist/bundle.cjs.js
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
|
-
/*! choreograph-create-pixel v1.
|
|
1
|
+
/*! choreograph-create-pixel v1.3.0 2022/10/28 */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
class ChoreographCreatePixel {
|
|
5
5
|
constructor(userConfig) {
|
|
6
|
-
this.
|
|
6
|
+
this.previouslyScrapedHash = null;
|
|
7
7
|
this.logs = [];
|
|
8
8
|
|
|
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
9
|
this.config = {
|
|
10
|
+
colors: { error: "red", warn: "orange", success: "green" },
|
|
11
|
+
icons: {
|
|
12
|
+
scraper: "π",
|
|
13
|
+
viewed: "π",
|
|
14
|
+
basketed: "π",
|
|
15
|
+
purchased: "π§Ύ",
|
|
16
|
+
attribution: "π",
|
|
17
|
+
conversion: "π΅",
|
|
18
|
+
},
|
|
22
19
|
debug: /(pixel|lemonpi)_debug/i.test(location.href),
|
|
23
20
|
before: (data, callback) => callback(data),
|
|
24
21
|
after: () => {},
|
|
@@ -88,14 +85,12 @@ class ChoreographCreatePixel {
|
|
|
88
85
|
if (!this.config.debug || this.logs.includes(message)) return;
|
|
89
86
|
|
|
90
87
|
const args = [
|
|
91
|
-
`%c${
|
|
92
|
-
this.
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
"",
|
|
96
|
-
this.
|
|
97
|
-
? `${this.settings.messageCss};color:${this.settings.colors[level]}`
|
|
98
|
-
: this.settings.messageCss,
|
|
88
|
+
`%cβΈ¬ create%c ${this.config.type} ${
|
|
89
|
+
this.config.icons[this.config.type]
|
|
90
|
+
} %c${message}`,
|
|
91
|
+
"background:black;color:white;border-radius:3px;padding:3px 6px",
|
|
92
|
+
"font-weight:bold",
|
|
93
|
+
`color:${this.config.colors[level]}`,
|
|
99
94
|
];
|
|
100
95
|
|
|
101
96
|
if (data) args.push(data);
|
|
@@ -104,58 +99,59 @@ class ChoreographCreatePixel {
|
|
|
104
99
|
}
|
|
105
100
|
|
|
106
101
|
validateConfig() {
|
|
107
|
-
if (typeof this.config.
|
|
108
|
-
this.log("error", "Please use a number", {
|
|
102
|
+
if (typeof this.config.advertiser !== "number")
|
|
103
|
+
this.log("error", "Please use a number", {
|
|
104
|
+
advertiser: this.config.advertiser,
|
|
105
|
+
});
|
|
109
106
|
else if (
|
|
110
|
-
!["
|
|
107
|
+
!["scraper", "viewed", "basketed", "purchased"].includes(this.config.type)
|
|
111
108
|
)
|
|
112
|
-
this.log("error", "Please use
|
|
113
|
-
|
|
109
|
+
this.log("error", "Please use scraper, viewed, basketed or purchased", {
|
|
110
|
+
type: this.config.type,
|
|
114
111
|
});
|
|
115
|
-
else if (!(this.config.
|
|
112
|
+
else if (!(this.config.url instanceof RegExp))
|
|
116
113
|
this.log("error", "Please use a regular expression", {
|
|
117
|
-
|
|
114
|
+
url: this.config.url,
|
|
115
|
+
});
|
|
116
|
+
else if (!this.config.scrape)
|
|
117
|
+
this.log("error", "Please provide something to scrape", {
|
|
118
|
+
scrape: this.config.scrape,
|
|
118
119
|
});
|
|
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
120
|
else return true;
|
|
122
121
|
}
|
|
123
122
|
|
|
124
123
|
cycle() {
|
|
125
|
-
if (!this.config.
|
|
126
|
-
this.log("warn", `
|
|
127
|
-
|
|
124
|
+
if (!this.config.url.test(location.href))
|
|
125
|
+
this.log("warn", `URL pattern does not match "${location.href}"`, {
|
|
126
|
+
url: this.config.url,
|
|
128
127
|
});
|
|
129
|
-
|
|
130
|
-
this.config.
|
|
128
|
+
else if (
|
|
129
|
+
this.config.type === "scraper" &&
|
|
131
130
|
document.querySelector('html[class*="translated-"]')
|
|
132
|
-
)
|
|
131
|
+
)
|
|
133
132
|
this.log(
|
|
134
133
|
"warn",
|
|
135
|
-
"This page has been translated by the browser, and
|
|
134
|
+
"This page has been translated by the browser, and won't be scraped"
|
|
136
135
|
);
|
|
137
|
-
|
|
136
|
+
else if (this.config.trigger) {
|
|
137
|
+
const attribute = `create-${this.config.type}-${this.config.trigger.event}`;
|
|
138
|
+
|
|
138
139
|
try {
|
|
139
|
-
let elements = this.config.
|
|
140
|
+
let elements = this.config.trigger.elements();
|
|
140
141
|
if (!elements.forEach) elements = [elements];
|
|
141
142
|
|
|
142
143
|
elements.forEach((element) => {
|
|
143
|
-
if (
|
|
144
|
-
|
|
145
|
-
) {
|
|
146
|
-
element.addEventListener(this.config.when.listener, () =>
|
|
144
|
+
if (!element.hasAttribute(attribute)) {
|
|
145
|
+
element.addEventListener(this.config.trigger.event, () =>
|
|
147
146
|
this.scrape(element)
|
|
148
147
|
);
|
|
149
148
|
|
|
150
|
-
element.setAttribute(
|
|
151
|
-
`choreograph-${this.config.when.listener}`,
|
|
152
|
-
""
|
|
153
|
-
);
|
|
149
|
+
element.setAttribute(attribute, "");
|
|
154
150
|
}
|
|
155
151
|
});
|
|
156
152
|
} catch (error) {
|
|
157
153
|
this.log("error", error.message, {
|
|
158
|
-
|
|
154
|
+
"trigger.elements": this.config.trigger.elements,
|
|
159
155
|
});
|
|
160
156
|
}
|
|
161
157
|
} else {
|
|
@@ -166,46 +162,86 @@ class ChoreographCreatePixel {
|
|
|
166
162
|
}
|
|
167
163
|
|
|
168
164
|
scrape(element) {
|
|
169
|
-
const data = {};
|
|
170
165
|
let hasErrors = false;
|
|
166
|
+
let data;
|
|
171
167
|
|
|
172
|
-
|
|
173
|
-
|
|
168
|
+
const handleField = (field, fieldName) => {
|
|
169
|
+
let result = field;
|
|
174
170
|
|
|
175
|
-
if (typeof
|
|
171
|
+
if (typeof result === "function") {
|
|
176
172
|
try {
|
|
177
|
-
|
|
173
|
+
result = result(element);
|
|
178
174
|
} catch (error) {
|
|
179
|
-
if (
|
|
180
|
-
this.log("error", error.message, {
|
|
181
|
-
which: { [fieldName]: this.config.which[fieldName] },
|
|
182
|
-
});
|
|
175
|
+
if (this.config.optional.includes(fieldName)) return null;
|
|
183
176
|
|
|
184
|
-
|
|
185
|
-
|
|
177
|
+
this.log(
|
|
178
|
+
this.config.type === "attribution" ? "warn" : "error",
|
|
179
|
+
error.message,
|
|
180
|
+
{ [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
hasErrors = true;
|
|
186
184
|
}
|
|
187
185
|
}
|
|
188
186
|
|
|
189
|
-
if (typeof
|
|
190
|
-
|
|
187
|
+
if (typeof result === "string")
|
|
188
|
+
result = result.replace(/\s+/g, " ").trim();
|
|
191
189
|
|
|
192
|
-
if (
|
|
193
|
-
if (
|
|
194
|
-
this.log("error", "This required field's value is empty", {
|
|
195
|
-
which: { [fieldName]: data[fieldName] },
|
|
196
|
-
});
|
|
190
|
+
if (result == null || result === "") {
|
|
191
|
+
if (this.config.optional.includes(fieldName)) return null;
|
|
197
192
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
193
|
+
this.log(
|
|
194
|
+
this.config.type === "attribution" ? "warn" : "error",
|
|
195
|
+
"Value is empty",
|
|
196
|
+
{ [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
|
|
197
|
+
);
|
|
198
|
+
|
|
199
|
+
hasErrors = true;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
return result;
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
if (this.config.type === "conversion") {
|
|
206
|
+
data = {
|
|
207
|
+
conversion: handleField(this.config.scrape),
|
|
208
|
+
attribution: localStorage.getItem("create-attribution-id"),
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
if (typeof data.attribution !== "string") {
|
|
212
|
+
this.log("warn", "There's no attribution ID stored yet");
|
|
213
|
+
hasErrors = true;
|
|
214
|
+
}
|
|
215
|
+
} else if (this.config.type !== "scraper") {
|
|
216
|
+
data = handleField(this.config.scrape);
|
|
217
|
+
} else {
|
|
218
|
+
data = Object.keys(this.config.scrape).reduce(
|
|
219
|
+
(acc, fieldName) => ({
|
|
220
|
+
...acc,
|
|
221
|
+
[fieldName]: handleField(this.config.scrape[fieldName], fieldName),
|
|
222
|
+
}),
|
|
223
|
+
{}
|
|
224
|
+
);
|
|
225
|
+
}
|
|
201
226
|
|
|
202
|
-
const
|
|
227
|
+
const dataHash = JSON.stringify(data);
|
|
203
228
|
|
|
204
|
-
if (!hasErrors && this.
|
|
229
|
+
if (!hasErrors && this.previouslyScrapedHash !== dataHash) {
|
|
205
230
|
try {
|
|
206
231
|
this.config.before(data, (newData) => {
|
|
207
|
-
if (this.config.
|
|
208
|
-
|
|
232
|
+
if (this.config.type === "attribution") {
|
|
233
|
+
localStorage.setItem("create-attribution-id", data);
|
|
234
|
+
this.log("success", "Successful!", { attribution: data });
|
|
235
|
+
|
|
236
|
+
try {
|
|
237
|
+
this.config.after(data);
|
|
238
|
+
} catch (error) {
|
|
239
|
+
this.log("error", error.message, {
|
|
240
|
+
after: this.config.after,
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
} else if (Array.isArray(newData))
|
|
244
|
+
newData.forEach((id) => this.send(id));
|
|
209
245
|
else this.send(newData);
|
|
210
246
|
});
|
|
211
247
|
} catch (error) {
|
|
@@ -214,42 +250,50 @@ class ChoreographCreatePixel {
|
|
|
214
250
|
});
|
|
215
251
|
}
|
|
216
252
|
|
|
217
|
-
this.
|
|
253
|
+
this.previouslyScrapedHash = dataHash;
|
|
218
254
|
this.logs.length = 0;
|
|
219
255
|
}
|
|
220
256
|
}
|
|
221
257
|
|
|
222
258
|
send(data) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
259
|
+
let url;
|
|
260
|
+
|
|
261
|
+
switch (this.config.type) {
|
|
262
|
+
case "scraper":
|
|
263
|
+
url = `https://d.lemonpi.io/scrapes${
|
|
264
|
+
this.config.debug ? "?validate=true" : ""
|
|
265
|
+
}`;
|
|
266
|
+
break;
|
|
267
|
+
|
|
268
|
+
case "conversion":
|
|
269
|
+
url = "https://lemonpi.io/"; // TO-DO
|
|
270
|
+
break;
|
|
271
|
+
|
|
272
|
+
default:
|
|
273
|
+
url = `https://d.lemonpi.io/a/${
|
|
274
|
+
this.config.advertiser
|
|
275
|
+
}/product/event?e=${encodeURIComponent(
|
|
276
|
+
JSON.stringify({
|
|
277
|
+
"event-type": `product-${this.config.type}`,
|
|
278
|
+
sku: data,
|
|
279
|
+
})
|
|
280
|
+
)}`;
|
|
281
|
+
break;
|
|
282
|
+
}
|
|
239
283
|
|
|
240
|
-
if (this.config.
|
|
284
|
+
if (this.config.type !== "scraper" && !this.config.debug)
|
|
241
285
|
new Image().src = url;
|
|
242
286
|
else
|
|
243
287
|
fetch(
|
|
244
288
|
url,
|
|
245
|
-
this.config.
|
|
289
|
+
this.config.type === "scraper"
|
|
246
290
|
? {
|
|
247
291
|
method: "POST",
|
|
248
292
|
headers: { "Content-Type": "application/json" },
|
|
249
293
|
body: JSON.stringify({
|
|
250
|
-
"advertiser-id": this.config.
|
|
294
|
+
"advertiser-id": this.config.advertiser,
|
|
251
295
|
sku: data.sku,
|
|
252
|
-
fields,
|
|
296
|
+
fields: { ...data, sku: undefined },
|
|
253
297
|
}),
|
|
254
298
|
}
|
|
255
299
|
: null
|
|
@@ -279,7 +323,13 @@ class ChoreographCreatePixel {
|
|
|
279
323
|
})
|
|
280
324
|
.catch(() => {
|
|
281
325
|
if (response.ok) {
|
|
282
|
-
this.log(
|
|
326
|
+
this.log(
|
|
327
|
+
"success",
|
|
328
|
+
"Successful!",
|
|
329
|
+
["viewed", "basketed", "purchased"].includes(this.config.type)
|
|
330
|
+
? { sku: data }
|
|
331
|
+
: data
|
|
332
|
+
);
|
|
283
333
|
|
|
284
334
|
try {
|
|
285
335
|
this.config.after(data);
|
package/dist/bundle.esm.js
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
|
-
/*! choreograph-create-pixel v1.
|
|
1
|
+
/*! choreograph-create-pixel v1.3.0 2022/10/28 */
|
|
2
2
|
class ChoreographCreatePixel {
|
|
3
3
|
constructor(userConfig) {
|
|
4
|
-
this.
|
|
4
|
+
this.previouslyScrapedHash = null;
|
|
5
5
|
this.logs = [];
|
|
6
6
|
|
|
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
7
|
this.config = {
|
|
8
|
+
colors: { error: "red", warn: "orange", success: "green" },
|
|
9
|
+
icons: {
|
|
10
|
+
scraper: "π",
|
|
11
|
+
viewed: "π",
|
|
12
|
+
basketed: "π",
|
|
13
|
+
purchased: "π§Ύ",
|
|
14
|
+
attribution: "π",
|
|
15
|
+
conversion: "π΅",
|
|
16
|
+
},
|
|
20
17
|
debug: /(pixel|lemonpi)_debug/i.test(location.href),
|
|
21
18
|
before: (data, callback) => callback(data),
|
|
22
19
|
after: () => {},
|
|
@@ -86,14 +83,12 @@ class ChoreographCreatePixel {
|
|
|
86
83
|
if (!this.config.debug || this.logs.includes(message)) return;
|
|
87
84
|
|
|
88
85
|
const args = [
|
|
89
|
-
`%c${
|
|
90
|
-
this.
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
"",
|
|
94
|
-
this.
|
|
95
|
-
? `${this.settings.messageCss};color:${this.settings.colors[level]}`
|
|
96
|
-
: this.settings.messageCss,
|
|
86
|
+
`%cβΈ¬ create%c ${this.config.type} ${
|
|
87
|
+
this.config.icons[this.config.type]
|
|
88
|
+
} %c${message}`,
|
|
89
|
+
"background:black;color:white;border-radius:3px;padding:3px 6px",
|
|
90
|
+
"font-weight:bold",
|
|
91
|
+
`color:${this.config.colors[level]}`,
|
|
97
92
|
];
|
|
98
93
|
|
|
99
94
|
if (data) args.push(data);
|
|
@@ -102,58 +97,59 @@ class ChoreographCreatePixel {
|
|
|
102
97
|
}
|
|
103
98
|
|
|
104
99
|
validateConfig() {
|
|
105
|
-
if (typeof this.config.
|
|
106
|
-
this.log("error", "Please use a number", {
|
|
100
|
+
if (typeof this.config.advertiser !== "number")
|
|
101
|
+
this.log("error", "Please use a number", {
|
|
102
|
+
advertiser: this.config.advertiser,
|
|
103
|
+
});
|
|
107
104
|
else if (
|
|
108
|
-
!["
|
|
105
|
+
!["scraper", "viewed", "basketed", "purchased"].includes(this.config.type)
|
|
109
106
|
)
|
|
110
|
-
this.log("error", "Please use
|
|
111
|
-
|
|
107
|
+
this.log("error", "Please use scraper, viewed, basketed or purchased", {
|
|
108
|
+
type: this.config.type,
|
|
112
109
|
});
|
|
113
|
-
else if (!(this.config.
|
|
110
|
+
else if (!(this.config.url instanceof RegExp))
|
|
114
111
|
this.log("error", "Please use a regular expression", {
|
|
115
|
-
|
|
112
|
+
url: this.config.url,
|
|
113
|
+
});
|
|
114
|
+
else if (!this.config.scrape)
|
|
115
|
+
this.log("error", "Please provide something to scrape", {
|
|
116
|
+
scrape: this.config.scrape,
|
|
116
117
|
});
|
|
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
118
|
else return true;
|
|
120
119
|
}
|
|
121
120
|
|
|
122
121
|
cycle() {
|
|
123
|
-
if (!this.config.
|
|
124
|
-
this.log("warn", `
|
|
125
|
-
|
|
122
|
+
if (!this.config.url.test(location.href))
|
|
123
|
+
this.log("warn", `URL pattern does not match "${location.href}"`, {
|
|
124
|
+
url: this.config.url,
|
|
126
125
|
});
|
|
127
|
-
|
|
128
|
-
this.config.
|
|
126
|
+
else if (
|
|
127
|
+
this.config.type === "scraper" &&
|
|
129
128
|
document.querySelector('html[class*="translated-"]')
|
|
130
|
-
)
|
|
129
|
+
)
|
|
131
130
|
this.log(
|
|
132
131
|
"warn",
|
|
133
|
-
"This page has been translated by the browser, and
|
|
132
|
+
"This page has been translated by the browser, and won't be scraped"
|
|
134
133
|
);
|
|
135
|
-
|
|
134
|
+
else if (this.config.trigger) {
|
|
135
|
+
const attribute = `create-${this.config.type}-${this.config.trigger.event}`;
|
|
136
|
+
|
|
136
137
|
try {
|
|
137
|
-
let elements = this.config.
|
|
138
|
+
let elements = this.config.trigger.elements();
|
|
138
139
|
if (!elements.forEach) elements = [elements];
|
|
139
140
|
|
|
140
141
|
elements.forEach((element) => {
|
|
141
|
-
if (
|
|
142
|
-
|
|
143
|
-
) {
|
|
144
|
-
element.addEventListener(this.config.when.listener, () =>
|
|
142
|
+
if (!element.hasAttribute(attribute)) {
|
|
143
|
+
element.addEventListener(this.config.trigger.event, () =>
|
|
145
144
|
this.scrape(element)
|
|
146
145
|
);
|
|
147
146
|
|
|
148
|
-
element.setAttribute(
|
|
149
|
-
`choreograph-${this.config.when.listener}`,
|
|
150
|
-
""
|
|
151
|
-
);
|
|
147
|
+
element.setAttribute(attribute, "");
|
|
152
148
|
}
|
|
153
149
|
});
|
|
154
150
|
} catch (error) {
|
|
155
151
|
this.log("error", error.message, {
|
|
156
|
-
|
|
152
|
+
"trigger.elements": this.config.trigger.elements,
|
|
157
153
|
});
|
|
158
154
|
}
|
|
159
155
|
} else {
|
|
@@ -164,46 +160,86 @@ class ChoreographCreatePixel {
|
|
|
164
160
|
}
|
|
165
161
|
|
|
166
162
|
scrape(element) {
|
|
167
|
-
const data = {};
|
|
168
163
|
let hasErrors = false;
|
|
164
|
+
let data;
|
|
169
165
|
|
|
170
|
-
|
|
171
|
-
|
|
166
|
+
const handleField = (field, fieldName) => {
|
|
167
|
+
let result = field;
|
|
172
168
|
|
|
173
|
-
if (typeof
|
|
169
|
+
if (typeof result === "function") {
|
|
174
170
|
try {
|
|
175
|
-
|
|
171
|
+
result = result(element);
|
|
176
172
|
} catch (error) {
|
|
177
|
-
if (
|
|
178
|
-
this.log("error", error.message, {
|
|
179
|
-
which: { [fieldName]: this.config.which[fieldName] },
|
|
180
|
-
});
|
|
173
|
+
if (this.config.optional.includes(fieldName)) return null;
|
|
181
174
|
|
|
182
|
-
|
|
183
|
-
|
|
175
|
+
this.log(
|
|
176
|
+
this.config.type === "attribution" ? "warn" : "error",
|
|
177
|
+
error.message,
|
|
178
|
+
{ [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
|
|
179
|
+
);
|
|
180
|
+
|
|
181
|
+
hasErrors = true;
|
|
184
182
|
}
|
|
185
183
|
}
|
|
186
184
|
|
|
187
|
-
if (typeof
|
|
188
|
-
|
|
185
|
+
if (typeof result === "string")
|
|
186
|
+
result = result.replace(/\s+/g, " ").trim();
|
|
189
187
|
|
|
190
|
-
if (
|
|
191
|
-
if (
|
|
192
|
-
this.log("error", "This required field's value is empty", {
|
|
193
|
-
which: { [fieldName]: data[fieldName] },
|
|
194
|
-
});
|
|
188
|
+
if (result == null || result === "") {
|
|
189
|
+
if (this.config.optional.includes(fieldName)) return null;
|
|
195
190
|
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
191
|
+
this.log(
|
|
192
|
+
this.config.type === "attribution" ? "warn" : "error",
|
|
193
|
+
"Value is empty",
|
|
194
|
+
{ [fieldName ? `scrape.${fieldName}` : "scrape"]: result }
|
|
195
|
+
);
|
|
196
|
+
|
|
197
|
+
hasErrors = true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return result;
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
if (this.config.type === "conversion") {
|
|
204
|
+
data = {
|
|
205
|
+
conversion: handleField(this.config.scrape),
|
|
206
|
+
attribution: localStorage.getItem("create-attribution-id"),
|
|
207
|
+
};
|
|
208
|
+
|
|
209
|
+
if (typeof data.attribution !== "string") {
|
|
210
|
+
this.log("warn", "There's no attribution ID stored yet");
|
|
211
|
+
hasErrors = true;
|
|
212
|
+
}
|
|
213
|
+
} else if (this.config.type !== "scraper") {
|
|
214
|
+
data = handleField(this.config.scrape);
|
|
215
|
+
} else {
|
|
216
|
+
data = Object.keys(this.config.scrape).reduce(
|
|
217
|
+
(acc, fieldName) => ({
|
|
218
|
+
...acc,
|
|
219
|
+
[fieldName]: handleField(this.config.scrape[fieldName], fieldName),
|
|
220
|
+
}),
|
|
221
|
+
{}
|
|
222
|
+
);
|
|
223
|
+
}
|
|
199
224
|
|
|
200
|
-
const
|
|
225
|
+
const dataHash = JSON.stringify(data);
|
|
201
226
|
|
|
202
|
-
if (!hasErrors && this.
|
|
227
|
+
if (!hasErrors && this.previouslyScrapedHash !== dataHash) {
|
|
203
228
|
try {
|
|
204
229
|
this.config.before(data, (newData) => {
|
|
205
|
-
if (this.config.
|
|
206
|
-
|
|
230
|
+
if (this.config.type === "attribution") {
|
|
231
|
+
localStorage.setItem("create-attribution-id", data);
|
|
232
|
+
this.log("success", "Successful!", { attribution: data });
|
|
233
|
+
|
|
234
|
+
try {
|
|
235
|
+
this.config.after(data);
|
|
236
|
+
} catch (error) {
|
|
237
|
+
this.log("error", error.message, {
|
|
238
|
+
after: this.config.after,
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
} else if (Array.isArray(newData))
|
|
242
|
+
newData.forEach((id) => this.send(id));
|
|
207
243
|
else this.send(newData);
|
|
208
244
|
});
|
|
209
245
|
} catch (error) {
|
|
@@ -212,42 +248,50 @@ class ChoreographCreatePixel {
|
|
|
212
248
|
});
|
|
213
249
|
}
|
|
214
250
|
|
|
215
|
-
this.
|
|
251
|
+
this.previouslyScrapedHash = dataHash;
|
|
216
252
|
this.logs.length = 0;
|
|
217
253
|
}
|
|
218
254
|
}
|
|
219
255
|
|
|
220
256
|
send(data) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
257
|
+
let url;
|
|
258
|
+
|
|
259
|
+
switch (this.config.type) {
|
|
260
|
+
case "scraper":
|
|
261
|
+
url = `https://d.lemonpi.io/scrapes${
|
|
262
|
+
this.config.debug ? "?validate=true" : ""
|
|
263
|
+
}`;
|
|
264
|
+
break;
|
|
265
|
+
|
|
266
|
+
case "conversion":
|
|
267
|
+
url = "https://lemonpi.io/"; // TO-DO
|
|
268
|
+
break;
|
|
269
|
+
|
|
270
|
+
default:
|
|
271
|
+
url = `https://d.lemonpi.io/a/${
|
|
272
|
+
this.config.advertiser
|
|
273
|
+
}/product/event?e=${encodeURIComponent(
|
|
274
|
+
JSON.stringify({
|
|
275
|
+
"event-type": `product-${this.config.type}`,
|
|
276
|
+
sku: data,
|
|
277
|
+
})
|
|
278
|
+
)}`;
|
|
279
|
+
break;
|
|
280
|
+
}
|
|
237
281
|
|
|
238
|
-
if (this.config.
|
|
282
|
+
if (this.config.type !== "scraper" && !this.config.debug)
|
|
239
283
|
new Image().src = url;
|
|
240
284
|
else
|
|
241
285
|
fetch(
|
|
242
286
|
url,
|
|
243
|
-
this.config.
|
|
287
|
+
this.config.type === "scraper"
|
|
244
288
|
? {
|
|
245
289
|
method: "POST",
|
|
246
290
|
headers: { "Content-Type": "application/json" },
|
|
247
291
|
body: JSON.stringify({
|
|
248
|
-
"advertiser-id": this.config.
|
|
292
|
+
"advertiser-id": this.config.advertiser,
|
|
249
293
|
sku: data.sku,
|
|
250
|
-
fields,
|
|
294
|
+
fields: { ...data, sku: undefined },
|
|
251
295
|
}),
|
|
252
296
|
}
|
|
253
297
|
: null
|
|
@@ -277,7 +321,13 @@ class ChoreographCreatePixel {
|
|
|
277
321
|
})
|
|
278
322
|
.catch(() => {
|
|
279
323
|
if (response.ok) {
|
|
280
|
-
this.log(
|
|
324
|
+
this.log(
|
|
325
|
+
"success",
|
|
326
|
+
"Successful!",
|
|
327
|
+
["viewed", "basketed", "purchased"].includes(this.config.type)
|
|
328
|
+
? { sku: data }
|
|
329
|
+
: data
|
|
330
|
+
);
|
|
281
331
|
|
|
282
332
|
try {
|
|
283
333
|
this.config.after(data);
|
package/dist/bundle.iife.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
/*! choreograph-create-pixel v1.
|
|
2
|
-
var ChoreographCreatePixel=function(){"use strict";function e(e,t){var
|
|
1
|
+
/*! choreograph-create-pixel v1.3.0 2022/10/28 */
|
|
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=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 o,i,c;return o=e,i=[{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 o=["%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&&o.push(n),(r=console).info.apply(r,o),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=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,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}}],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,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]}}],i&&r(o.prototype,i),c&&r(o,c),Object.defineProperty(o,"prototype",{writable:!1}),e}();return o}();
|
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.
|
|
4
|
+
"version": "1.3.0",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"wpp",
|
|
7
|
+
"groupm",
|
|
8
|
+
"lemonpi",
|
|
9
|
+
"odc",
|
|
10
|
+
"opendc"
|
|
11
|
+
],
|
|
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
|
-
"
|
|
28
|
+
"prepublishOnly": "npm run build"
|
|
22
29
|
},
|
|
23
30
|
"devDependencies": {
|
|
24
|
-
"@babel/core": "^7.19.
|
|
25
|
-
"@babel/preset-env": "^7.19.
|
|
26
|
-
"@rollup/plugin-babel": "^
|
|
27
|
-
"@rollup/plugin-eslint": "^
|
|
28
|
-
"eslint": "^8.
|
|
31
|
+
"@babel/core": "^7.19.6",
|
|
32
|
+
"@babel/preset-env": "^7.19.4",
|
|
33
|
+
"@rollup/plugin-babel": "^6.0.2",
|
|
34
|
+
"@rollup/plugin-eslint": "^9.0.1",
|
|
35
|
+
"eslint": "^8.26.0",
|
|
29
36
|
"eslint-config-prettier": "^8.5.0",
|
|
30
37
|
"eslint-plugin-prettier": "^4.2.1",
|
|
31
38
|
"moment": "^2.29.4",
|