@blotoutio/providers-awin-sdk 0.0.0-stage → 1.76.2
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 +10 -2
- package/index.cjs.js +373 -0
- package/index.js +376 -0
- package/index.mjs +371 -0
- package/package.json +24 -4
package/README.md
CHANGED
|
@@ -1,3 +1,11 @@
|
|
|
1
|
-
#
|
|
1
|
+
# providers-awin-sdk
|
|
2
2
|
|
|
3
|
-
This
|
|
3
|
+
This library was generated with [Nx](https://nx.dev).
|
|
4
|
+
|
|
5
|
+
## Building
|
|
6
|
+
|
|
7
|
+
Run `nx build providers-awin-sdk` to build the library.
|
|
8
|
+
|
|
9
|
+
## Running unit tests
|
|
10
|
+
|
|
11
|
+
Run `nx test providers-awin-sdk` to execute the unit tests via [Jest](https://jestjs.io).
|
package/index.cjs.js
ADDED
|
@@ -0,0 +1,373 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const packageName = 'awin';
|
|
4
|
+
const fallbackPixelUrl = 'https://www.awin1.com/sread.img';
|
|
5
|
+
const masterTagUrl = 'https://www.dwin1.com';
|
|
6
|
+
// Click checksum Awin appends to the landing page URL.
|
|
7
|
+
const clickIdQueryKey = 'awc';
|
|
8
|
+
const awinChannel = 'aw';
|
|
9
|
+
const defaultCommissionGroup = 'DEFAULT';
|
|
10
|
+
// Stores the last-click channel for Awin's deduplication.
|
|
11
|
+
const channelKey = 'channel';
|
|
12
|
+
// `aw` for an Awin click, otherwise the name of the other channel's click ID
|
|
13
|
+
// (e.g. `google` for gclid). Null when the landing URL carries no click.
|
|
14
|
+
const getLandingChannel = (search, clickIds) => {
|
|
15
|
+
const params = new URLSearchParams(search);
|
|
16
|
+
if (params.get(clickIdQueryKey)) {
|
|
17
|
+
return awinChannel;
|
|
18
|
+
}
|
|
19
|
+
const click = clickIds.find(({ param }) => params.get(param));
|
|
20
|
+
return click ? click.label.toLowerCase().replace(/\s+/g, '') : null;
|
|
21
|
+
};
|
|
22
|
+
// Purchase is always a conversion; any other event only when it is mapped.
|
|
23
|
+
// Returns null for events that must not be sent to Awin.
|
|
24
|
+
const getCommissionGroup = (eventName, conversions, defaultGroup) => {
|
|
25
|
+
var _a;
|
|
26
|
+
const fallback = (defaultGroup === null || defaultGroup === void 0 ? void 0 : defaultGroup.trim()) || defaultCommissionGroup;
|
|
27
|
+
const mapping = conversions.find((item) => item.event === eventName);
|
|
28
|
+
if (mapping) {
|
|
29
|
+
return ((_a = mapping.commissionGroup) === null || _a === void 0 ? void 0 : _a.trim()) || fallback;
|
|
30
|
+
}
|
|
31
|
+
return eventName === 'Purchase' ? fallback : null;
|
|
32
|
+
};
|
|
33
|
+
const toNumber = (value) => {
|
|
34
|
+
const number = Number(value);
|
|
35
|
+
return value === undefined || value === null || isNaN(number)
|
|
36
|
+
? undefined
|
|
37
|
+
: number;
|
|
38
|
+
};
|
|
39
|
+
// When the checkout total is in another currency than the line items, the
|
|
40
|
+
// Shopify pixel sends value = grossValue = Σ(line price × qty): no tax or
|
|
41
|
+
// shipping in it, while taxCost / shippingCost stay in the checkout currency.
|
|
42
|
+
// Matched with a small tolerance for the worker's per-field currency rounding.
|
|
43
|
+
const isLineSubtotal = (gross, contents) => {
|
|
44
|
+
var _a;
|
|
45
|
+
if (!(contents === null || contents === void 0 ? void 0 : contents.length)) {
|
|
46
|
+
return false;
|
|
47
|
+
}
|
|
48
|
+
let subtotal = 0;
|
|
49
|
+
let units = 0;
|
|
50
|
+
for (const content of contents) {
|
|
51
|
+
const price = toNumber(content.item_price);
|
|
52
|
+
if (price === undefined) {
|
|
53
|
+
return false;
|
|
54
|
+
}
|
|
55
|
+
const quantity = (_a = toNumber(content.quantity)) !== null && _a !== void 0 ? _a : 1;
|
|
56
|
+
subtotal += price * quantity;
|
|
57
|
+
units += quantity;
|
|
58
|
+
}
|
|
59
|
+
return Math.abs(gross - subtotal) <= 0.01 + units * 0.001;
|
|
60
|
+
};
|
|
61
|
+
// Awin expects the order subtotal excluding tax and shipping, or the number of
|
|
62
|
+
// leads for a valueless lead. Derived here from the gross breakdown when it is
|
|
63
|
+
// present, so the browser and server send the same amount whatever the
|
|
64
|
+
// channel's purchase value mode (which only the worker applies to `value`).
|
|
65
|
+
const getAmount = (eventName, data) => {
|
|
66
|
+
var _a, _b;
|
|
67
|
+
const gross = toNumber(data.grossValue);
|
|
68
|
+
let amount = toNumber(data.value);
|
|
69
|
+
if (gross !== undefined) {
|
|
70
|
+
// A line subtotal has nothing to subtract (and the costs may be in another
|
|
71
|
+
// currency); gross is used as the worker's Net mode rewrites `value`.
|
|
72
|
+
amount = isLineSubtotal(gross, data.contents)
|
|
73
|
+
? gross
|
|
74
|
+
: Math.max(0, gross -
|
|
75
|
+
((_a = toNumber(data.taxCost)) !== null && _a !== void 0 ? _a : 0) -
|
|
76
|
+
((_b = toNumber(data.shippingCost)) !== null && _b !== void 0 ? _b : 0));
|
|
77
|
+
}
|
|
78
|
+
if (amount === undefined) {
|
|
79
|
+
return eventName === 'Purchase' ? 0 : 1;
|
|
80
|
+
}
|
|
81
|
+
return Number(amount.toFixed(2));
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const keyPrefix = `_worker`;
|
|
85
|
+
|
|
86
|
+
const canLog = () => {
|
|
87
|
+
try {
|
|
88
|
+
return localStorage.getItem('edgeTagDebug') === '1';
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
const prefix = `[EdgeTag]`;
|
|
95
|
+
const logger = {
|
|
96
|
+
log: (...args) => {
|
|
97
|
+
if (canLog()) {
|
|
98
|
+
console.log(prefix, ...args);
|
|
99
|
+
}
|
|
100
|
+
},
|
|
101
|
+
error: (...args) => {
|
|
102
|
+
if (canLog()) {
|
|
103
|
+
console.error(prefix, ...args);
|
|
104
|
+
}
|
|
105
|
+
},
|
|
106
|
+
info: (...args) => {
|
|
107
|
+
if (canLog()) {
|
|
108
|
+
console.info(prefix, ...args);
|
|
109
|
+
}
|
|
110
|
+
},
|
|
111
|
+
trace: (...args) => {
|
|
112
|
+
if (canLog()) {
|
|
113
|
+
console.trace(prefix, ...args);
|
|
114
|
+
}
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
const initKey = `${keyPrefix}StoreMultiple`;
|
|
119
|
+
const saveDataPerKey = (destination, persistType, provider, value, key) => {
|
|
120
|
+
const storage = getData(destination, persistType);
|
|
121
|
+
if (!storage['data']) {
|
|
122
|
+
storage['data'] = {};
|
|
123
|
+
}
|
|
124
|
+
if (!storage['data'][provider]) {
|
|
125
|
+
storage['data'][provider] = {};
|
|
126
|
+
}
|
|
127
|
+
storage['data'][provider][key] = value;
|
|
128
|
+
saveData(destination, persistType, storage);
|
|
129
|
+
};
|
|
130
|
+
const saveData = (destination, persistType, value, key = initKey) => {
|
|
131
|
+
if (persistType === 'session') {
|
|
132
|
+
const data = getSession(key);
|
|
133
|
+
data[destination] = value;
|
|
134
|
+
saveSession(data, key);
|
|
135
|
+
return;
|
|
136
|
+
}
|
|
137
|
+
const data = getLocal(key);
|
|
138
|
+
data[destination] = value;
|
|
139
|
+
saveLocal(data, key);
|
|
140
|
+
};
|
|
141
|
+
const getData = (destination, persistType, key = initKey) => {
|
|
142
|
+
let data;
|
|
143
|
+
if (persistType === 'session') {
|
|
144
|
+
data = getSession(key);
|
|
145
|
+
}
|
|
146
|
+
else {
|
|
147
|
+
data = getLocal(key);
|
|
148
|
+
}
|
|
149
|
+
return (data === null || data === void 0 ? void 0 : data[destination]) || {};
|
|
150
|
+
};
|
|
151
|
+
const saveLocal = (value, key) => {
|
|
152
|
+
try {
|
|
153
|
+
if (!localStorage) {
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
157
|
+
}
|
|
158
|
+
catch {
|
|
159
|
+
logger.log('Local storage not supported.');
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
const getLocal = (key) => {
|
|
163
|
+
try {
|
|
164
|
+
if (!localStorage) {
|
|
165
|
+
return {};
|
|
166
|
+
}
|
|
167
|
+
const data = localStorage.getItem(key);
|
|
168
|
+
if (!data) {
|
|
169
|
+
return {};
|
|
170
|
+
}
|
|
171
|
+
return JSON.parse(data) || {};
|
|
172
|
+
}
|
|
173
|
+
catch {
|
|
174
|
+
return {};
|
|
175
|
+
}
|
|
176
|
+
};
|
|
177
|
+
const saveSession = (value, key) => {
|
|
178
|
+
try {
|
|
179
|
+
if (!sessionStorage) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
sessionStorage.setItem(key, JSON.stringify(value));
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
logger.log('Session storage not supported.');
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
const getSession = (key) => {
|
|
189
|
+
try {
|
|
190
|
+
if (!sessionStorage) {
|
|
191
|
+
return {};
|
|
192
|
+
}
|
|
193
|
+
const data = sessionStorage.getItem(key);
|
|
194
|
+
if (!data) {
|
|
195
|
+
return {};
|
|
196
|
+
}
|
|
197
|
+
return JSON.parse(data) || {};
|
|
198
|
+
}
|
|
199
|
+
catch {
|
|
200
|
+
return {};
|
|
201
|
+
}
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
const getUrl = (pageUrl) => {
|
|
205
|
+
try {
|
|
206
|
+
return new URL(pageUrl || window.location.href);
|
|
207
|
+
}
|
|
208
|
+
catch {
|
|
209
|
+
return null;
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
// Records the last-click channel so conversions carry `aw` only when Awin won.
|
|
213
|
+
// Kept on the edge for server-side calls and locally for the conversion tag.
|
|
214
|
+
const saveChannel = ({ destination, sendEdgeData, manifest }, url) => {
|
|
215
|
+
var _a;
|
|
216
|
+
let clickIds = [];
|
|
217
|
+
try {
|
|
218
|
+
clickIds = JSON.parse(((_a = manifest.variables) === null || _a === void 0 ? void 0 : _a['clickIds']) || '[]');
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
// keep detecting Awin clicks only
|
|
222
|
+
}
|
|
223
|
+
const channel = url && getLandingChannel(url.search, clickIds);
|
|
224
|
+
if (!channel) {
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
sendEdgeData({ [`${packageName}::${channelKey}`]: channel }, {});
|
|
228
|
+
saveDataPerKey(destination, 'local', packageName, channel, channelKey);
|
|
229
|
+
};
|
|
230
|
+
const isExcluded = (url, excludedPaths) => !!url &&
|
|
231
|
+
(excludedPaths || '')
|
|
232
|
+
.split(',')
|
|
233
|
+
.map((path) => path.trim())
|
|
234
|
+
.some((path) => path && url.pathname.startsWith(path));
|
|
235
|
+
// Loads the Awin MasterTag on every page except the excluded (payment / PII)
|
|
236
|
+
// paths.
|
|
237
|
+
const init = (params) => {
|
|
238
|
+
var _a, _b, _c;
|
|
239
|
+
const { manifest, pageUrl } = params;
|
|
240
|
+
const advertiserId = (_a = manifest.variables) === null || _a === void 0 ? void 0 : _a['advertiserId'];
|
|
241
|
+
if (!advertiserId || typeof document === 'undefined') {
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
const url = getUrl(pageUrl);
|
|
246
|
+
if (((_b = manifest.variables) === null || _b === void 0 ? void 0 : _b['deduplication']) === '1') {
|
|
247
|
+
saveChannel(params, url);
|
|
248
|
+
}
|
|
249
|
+
if (isExcluded(url, (_c = manifest.variables) === null || _c === void 0 ? void 0 : _c['excludedPaths'])) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
const scriptId = `awin-mastertag-${advertiserId}`;
|
|
253
|
+
if (document.getElementById(scriptId)) {
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
const script = document.createElement('script');
|
|
257
|
+
script.id = scriptId;
|
|
258
|
+
script.defer = true;
|
|
259
|
+
script.src = `${masterTagUrl}/${advertiserId}.js`;
|
|
260
|
+
(document.body || document.head).appendChild(script);
|
|
261
|
+
}
|
|
262
|
+
catch (e) {
|
|
263
|
+
logger.error(`[${packageName}] MasterTag injection failed`, e);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
const parseConversions = (value) => {
|
|
268
|
+
try {
|
|
269
|
+
const parsed = JSON.parse(value || '[]');
|
|
270
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
271
|
+
}
|
|
272
|
+
catch {
|
|
273
|
+
return [];
|
|
274
|
+
}
|
|
275
|
+
};
|
|
276
|
+
// Last-click channel saved on landing when deduplication is on, else `aw`.
|
|
277
|
+
const getChannel = (destination, deduplication) => {
|
|
278
|
+
var _a, _b;
|
|
279
|
+
if (deduplication !== '1') {
|
|
280
|
+
return awinChannel;
|
|
281
|
+
}
|
|
282
|
+
const storage = getData(destination, 'local');
|
|
283
|
+
return ((_b = (_a = storage.data) === null || _a === void 0 ? void 0 : _a[packageName]) === null || _b === void 0 ? void 0 : _b[channelKey]) || awinChannel;
|
|
284
|
+
};
|
|
285
|
+
// The pixel attributes the order from the awin1.com cookies, so they must be
|
|
286
|
+
// sent: an Image does that like Awin's own snippet, and fetch with credentials
|
|
287
|
+
// covers sandboxes without a DOM (Shopify custom pixel).
|
|
288
|
+
const sendPixel = (url) => {
|
|
289
|
+
if (typeof Image !== 'undefined') {
|
|
290
|
+
new Image(0, 0).src = url;
|
|
291
|
+
return;
|
|
292
|
+
}
|
|
293
|
+
fetch(url, {
|
|
294
|
+
mode: 'no-cors',
|
|
295
|
+
credentials: 'include',
|
|
296
|
+
keepalive: true,
|
|
297
|
+
}).catch((e) => logger.error(`[${packageName}] Pixel failed`, e));
|
|
298
|
+
};
|
|
299
|
+
// Declares the Awin Conversion Tag and fires the mandatory Fall-back
|
|
300
|
+
// Conversion Pixel. If the MasterTag is still loading it picks the sale up on
|
|
301
|
+
// load; if it already ran, AWIN.Tracking.run() re-triggers it.
|
|
302
|
+
const tag = ({ data, eventName, eventId, manifestVariables, destination, }) => {
|
|
303
|
+
var _a;
|
|
304
|
+
const payload = { sdkVersion: "1.76.2" };
|
|
305
|
+
const advertiserId = manifestVariables['advertiserId'];
|
|
306
|
+
if (typeof window === 'undefined' || !advertiserId) {
|
|
307
|
+
return payload;
|
|
308
|
+
}
|
|
309
|
+
const commissionGroup = getCommissionGroup(eventName, parseConversions(manifestVariables['conversions']), manifestVariables['commissionGroup']);
|
|
310
|
+
if (!commissionGroup) {
|
|
311
|
+
return payload;
|
|
312
|
+
}
|
|
313
|
+
try {
|
|
314
|
+
const amount = getAmount(eventName, data).toFixed(2);
|
|
315
|
+
const discounts = data['discounts'];
|
|
316
|
+
const orderRef = String(data['orderId'] || eventId);
|
|
317
|
+
const parts = `${commissionGroup}:${amount}`;
|
|
318
|
+
const currency = String(data['currency'] || 'USD');
|
|
319
|
+
const voucher = ((_a = discounts === null || discounts === void 0 ? void 0 : discounts[0]) === null || _a === void 0 ? void 0 : _a.code) || '';
|
|
320
|
+
const channel = getChannel(destination, manifestVariables['deduplication']);
|
|
321
|
+
const test = manifestVariables['testMode'] === '1' ? '1' : '0';
|
|
322
|
+
const pixel = new URL(fallbackPixelUrl);
|
|
323
|
+
pixel.searchParams.set('tt', 'ns');
|
|
324
|
+
pixel.searchParams.set('tv', '2');
|
|
325
|
+
pixel.searchParams.set('merchant', advertiserId);
|
|
326
|
+
pixel.searchParams.set('amount', amount);
|
|
327
|
+
pixel.searchParams.set('ch', channel);
|
|
328
|
+
pixel.searchParams.set('parts', parts);
|
|
329
|
+
pixel.searchParams.set('ref', orderRef);
|
|
330
|
+
pixel.searchParams.set('cr', currency);
|
|
331
|
+
pixel.searchParams.set('vc', voucher);
|
|
332
|
+
pixel.searchParams.set('testmode', test);
|
|
333
|
+
sendPixel(pixel.toString());
|
|
334
|
+
window.AWIN = window.AWIN || {};
|
|
335
|
+
window.AWIN.Tracking = window.AWIN.Tracking || {};
|
|
336
|
+
window.AWIN.Tracking.Sale = {
|
|
337
|
+
amount,
|
|
338
|
+
channel,
|
|
339
|
+
orderRef,
|
|
340
|
+
parts,
|
|
341
|
+
currency,
|
|
342
|
+
voucher,
|
|
343
|
+
test,
|
|
344
|
+
};
|
|
345
|
+
if (typeof window.AWIN.Tracking.run === 'function') {
|
|
346
|
+
window.AWIN.Tracking.run();
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
catch (e) {
|
|
350
|
+
logger.error(`[${packageName}] Conversion tag failed`, e);
|
|
351
|
+
}
|
|
352
|
+
return payload;
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
// eslint-disable-next-line @nx/enforce-module-boundaries
|
|
356
|
+
const data = {
|
|
357
|
+
name: packageName,
|
|
358
|
+
init,
|
|
359
|
+
tag,
|
|
360
|
+
};
|
|
361
|
+
try {
|
|
362
|
+
if (typeof window !== 'undefined') {
|
|
363
|
+
if (!window.edgetagProviders) {
|
|
364
|
+
window.edgetagProviders = [];
|
|
365
|
+
}
|
|
366
|
+
window.edgetagProviders.push(data);
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
catch {
|
|
370
|
+
// No window
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
module.exports = data;
|
package/index.js
ADDED
|
@@ -0,0 +1,376 @@
|
|
|
1
|
+
var ProvidersAwinSdk = (function () {
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const packageName = 'awin';
|
|
5
|
+
const fallbackPixelUrl = 'https://www.awin1.com/sread.img';
|
|
6
|
+
const masterTagUrl = 'https://www.dwin1.com';
|
|
7
|
+
// Click checksum Awin appends to the landing page URL.
|
|
8
|
+
const clickIdQueryKey = 'awc';
|
|
9
|
+
const awinChannel = 'aw';
|
|
10
|
+
const defaultCommissionGroup = 'DEFAULT';
|
|
11
|
+
// Stores the last-click channel for Awin's deduplication.
|
|
12
|
+
const channelKey = 'channel';
|
|
13
|
+
// `aw` for an Awin click, otherwise the name of the other channel's click ID
|
|
14
|
+
// (e.g. `google` for gclid). Null when the landing URL carries no click.
|
|
15
|
+
const getLandingChannel = (search, clickIds) => {
|
|
16
|
+
const params = new URLSearchParams(search);
|
|
17
|
+
if (params.get(clickIdQueryKey)) {
|
|
18
|
+
return awinChannel;
|
|
19
|
+
}
|
|
20
|
+
const click = clickIds.find(({ param }) => params.get(param));
|
|
21
|
+
return click ? click.label.toLowerCase().replace(/\s+/g, '') : null;
|
|
22
|
+
};
|
|
23
|
+
// Purchase is always a conversion; any other event only when it is mapped.
|
|
24
|
+
// Returns null for events that must not be sent to Awin.
|
|
25
|
+
const getCommissionGroup = (eventName, conversions, defaultGroup) => {
|
|
26
|
+
var _a;
|
|
27
|
+
const fallback = (defaultGroup === null || defaultGroup === void 0 ? void 0 : defaultGroup.trim()) || defaultCommissionGroup;
|
|
28
|
+
const mapping = conversions.find((item) => item.event === eventName);
|
|
29
|
+
if (mapping) {
|
|
30
|
+
return ((_a = mapping.commissionGroup) === null || _a === void 0 ? void 0 : _a.trim()) || fallback;
|
|
31
|
+
}
|
|
32
|
+
return eventName === 'Purchase' ? fallback : null;
|
|
33
|
+
};
|
|
34
|
+
const toNumber = (value) => {
|
|
35
|
+
const number = Number(value);
|
|
36
|
+
return value === undefined || value === null || isNaN(number)
|
|
37
|
+
? undefined
|
|
38
|
+
: number;
|
|
39
|
+
};
|
|
40
|
+
// When the checkout total is in another currency than the line items, the
|
|
41
|
+
// Shopify pixel sends value = grossValue = Σ(line price × qty): no tax or
|
|
42
|
+
// shipping in it, while taxCost / shippingCost stay in the checkout currency.
|
|
43
|
+
// Matched with a small tolerance for the worker's per-field currency rounding.
|
|
44
|
+
const isLineSubtotal = (gross, contents) => {
|
|
45
|
+
var _a;
|
|
46
|
+
if (!(contents === null || contents === void 0 ? void 0 : contents.length)) {
|
|
47
|
+
return false;
|
|
48
|
+
}
|
|
49
|
+
let subtotal = 0;
|
|
50
|
+
let units = 0;
|
|
51
|
+
for (const content of contents) {
|
|
52
|
+
const price = toNumber(content.item_price);
|
|
53
|
+
if (price === undefined) {
|
|
54
|
+
return false;
|
|
55
|
+
}
|
|
56
|
+
const quantity = (_a = toNumber(content.quantity)) !== null && _a !== void 0 ? _a : 1;
|
|
57
|
+
subtotal += price * quantity;
|
|
58
|
+
units += quantity;
|
|
59
|
+
}
|
|
60
|
+
return Math.abs(gross - subtotal) <= 0.01 + units * 0.001;
|
|
61
|
+
};
|
|
62
|
+
// Awin expects the order subtotal excluding tax and shipping, or the number of
|
|
63
|
+
// leads for a valueless lead. Derived here from the gross breakdown when it is
|
|
64
|
+
// present, so the browser and server send the same amount whatever the
|
|
65
|
+
// channel's purchase value mode (which only the worker applies to `value`).
|
|
66
|
+
const getAmount = (eventName, data) => {
|
|
67
|
+
var _a, _b;
|
|
68
|
+
const gross = toNumber(data.grossValue);
|
|
69
|
+
let amount = toNumber(data.value);
|
|
70
|
+
if (gross !== undefined) {
|
|
71
|
+
// A line subtotal has nothing to subtract (and the costs may be in another
|
|
72
|
+
// currency); gross is used as the worker's Net mode rewrites `value`.
|
|
73
|
+
amount = isLineSubtotal(gross, data.contents)
|
|
74
|
+
? gross
|
|
75
|
+
: Math.max(0, gross -
|
|
76
|
+
((_a = toNumber(data.taxCost)) !== null && _a !== void 0 ? _a : 0) -
|
|
77
|
+
((_b = toNumber(data.shippingCost)) !== null && _b !== void 0 ? _b : 0));
|
|
78
|
+
}
|
|
79
|
+
if (amount === undefined) {
|
|
80
|
+
return eventName === 'Purchase' ? 0 : 1;
|
|
81
|
+
}
|
|
82
|
+
return Number(amount.toFixed(2));
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
const keyPrefix = `_worker`;
|
|
86
|
+
|
|
87
|
+
const canLog = () => {
|
|
88
|
+
try {
|
|
89
|
+
return localStorage.getItem('edgeTagDebug') === '1';
|
|
90
|
+
}
|
|
91
|
+
catch {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
const prefix = `[EdgeTag]`;
|
|
96
|
+
const logger = {
|
|
97
|
+
log: (...args) => {
|
|
98
|
+
if (canLog()) {
|
|
99
|
+
console.log(prefix, ...args);
|
|
100
|
+
}
|
|
101
|
+
},
|
|
102
|
+
error: (...args) => {
|
|
103
|
+
if (canLog()) {
|
|
104
|
+
console.error(prefix, ...args);
|
|
105
|
+
}
|
|
106
|
+
},
|
|
107
|
+
info: (...args) => {
|
|
108
|
+
if (canLog()) {
|
|
109
|
+
console.info(prefix, ...args);
|
|
110
|
+
}
|
|
111
|
+
},
|
|
112
|
+
trace: (...args) => {
|
|
113
|
+
if (canLog()) {
|
|
114
|
+
console.trace(prefix, ...args);
|
|
115
|
+
}
|
|
116
|
+
},
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
const initKey = `${keyPrefix}StoreMultiple`;
|
|
120
|
+
const saveDataPerKey = (destination, persistType, provider, value, key) => {
|
|
121
|
+
const storage = getData(destination, persistType);
|
|
122
|
+
if (!storage['data']) {
|
|
123
|
+
storage['data'] = {};
|
|
124
|
+
}
|
|
125
|
+
if (!storage['data'][provider]) {
|
|
126
|
+
storage['data'][provider] = {};
|
|
127
|
+
}
|
|
128
|
+
storage['data'][provider][key] = value;
|
|
129
|
+
saveData(destination, persistType, storage);
|
|
130
|
+
};
|
|
131
|
+
const saveData = (destination, persistType, value, key = initKey) => {
|
|
132
|
+
if (persistType === 'session') {
|
|
133
|
+
const data = getSession(key);
|
|
134
|
+
data[destination] = value;
|
|
135
|
+
saveSession(data, key);
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
const data = getLocal(key);
|
|
139
|
+
data[destination] = value;
|
|
140
|
+
saveLocal(data, key);
|
|
141
|
+
};
|
|
142
|
+
const getData = (destination, persistType, key = initKey) => {
|
|
143
|
+
let data;
|
|
144
|
+
if (persistType === 'session') {
|
|
145
|
+
data = getSession(key);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
data = getLocal(key);
|
|
149
|
+
}
|
|
150
|
+
return (data === null || data === void 0 ? void 0 : data[destination]) || {};
|
|
151
|
+
};
|
|
152
|
+
const saveLocal = (value, key) => {
|
|
153
|
+
try {
|
|
154
|
+
if (!localStorage) {
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
158
|
+
}
|
|
159
|
+
catch {
|
|
160
|
+
logger.log('Local storage not supported.');
|
|
161
|
+
}
|
|
162
|
+
};
|
|
163
|
+
const getLocal = (key) => {
|
|
164
|
+
try {
|
|
165
|
+
if (!localStorage) {
|
|
166
|
+
return {};
|
|
167
|
+
}
|
|
168
|
+
const data = localStorage.getItem(key);
|
|
169
|
+
if (!data) {
|
|
170
|
+
return {};
|
|
171
|
+
}
|
|
172
|
+
return JSON.parse(data) || {};
|
|
173
|
+
}
|
|
174
|
+
catch {
|
|
175
|
+
return {};
|
|
176
|
+
}
|
|
177
|
+
};
|
|
178
|
+
const saveSession = (value, key) => {
|
|
179
|
+
try {
|
|
180
|
+
if (!sessionStorage) {
|
|
181
|
+
return;
|
|
182
|
+
}
|
|
183
|
+
sessionStorage.setItem(key, JSON.stringify(value));
|
|
184
|
+
}
|
|
185
|
+
catch {
|
|
186
|
+
logger.log('Session storage not supported.');
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
const getSession = (key) => {
|
|
190
|
+
try {
|
|
191
|
+
if (!sessionStorage) {
|
|
192
|
+
return {};
|
|
193
|
+
}
|
|
194
|
+
const data = sessionStorage.getItem(key);
|
|
195
|
+
if (!data) {
|
|
196
|
+
return {};
|
|
197
|
+
}
|
|
198
|
+
return JSON.parse(data) || {};
|
|
199
|
+
}
|
|
200
|
+
catch {
|
|
201
|
+
return {};
|
|
202
|
+
}
|
|
203
|
+
};
|
|
204
|
+
|
|
205
|
+
const getUrl = (pageUrl) => {
|
|
206
|
+
try {
|
|
207
|
+
return new URL(pageUrl || window.location.href);
|
|
208
|
+
}
|
|
209
|
+
catch {
|
|
210
|
+
return null;
|
|
211
|
+
}
|
|
212
|
+
};
|
|
213
|
+
// Records the last-click channel so conversions carry `aw` only when Awin won.
|
|
214
|
+
// Kept on the edge for server-side calls and locally for the conversion tag.
|
|
215
|
+
const saveChannel = ({ destination, sendEdgeData, manifest }, url) => {
|
|
216
|
+
var _a;
|
|
217
|
+
let clickIds = [];
|
|
218
|
+
try {
|
|
219
|
+
clickIds = JSON.parse(((_a = manifest.variables) === null || _a === void 0 ? void 0 : _a['clickIds']) || '[]');
|
|
220
|
+
}
|
|
221
|
+
catch {
|
|
222
|
+
// keep detecting Awin clicks only
|
|
223
|
+
}
|
|
224
|
+
const channel = url && getLandingChannel(url.search, clickIds);
|
|
225
|
+
if (!channel) {
|
|
226
|
+
return;
|
|
227
|
+
}
|
|
228
|
+
sendEdgeData({ [`${packageName}::${channelKey}`]: channel }, {});
|
|
229
|
+
saveDataPerKey(destination, 'local', packageName, channel, channelKey);
|
|
230
|
+
};
|
|
231
|
+
const isExcluded = (url, excludedPaths) => !!url &&
|
|
232
|
+
(excludedPaths || '')
|
|
233
|
+
.split(',')
|
|
234
|
+
.map((path) => path.trim())
|
|
235
|
+
.some((path) => path && url.pathname.startsWith(path));
|
|
236
|
+
// Loads the Awin MasterTag on every page except the excluded (payment / PII)
|
|
237
|
+
// paths.
|
|
238
|
+
const init = (params) => {
|
|
239
|
+
var _a, _b, _c;
|
|
240
|
+
const { manifest, pageUrl } = params;
|
|
241
|
+
const advertiserId = (_a = manifest.variables) === null || _a === void 0 ? void 0 : _a['advertiserId'];
|
|
242
|
+
if (!advertiserId || typeof document === 'undefined') {
|
|
243
|
+
return;
|
|
244
|
+
}
|
|
245
|
+
try {
|
|
246
|
+
const url = getUrl(pageUrl);
|
|
247
|
+
if (((_b = manifest.variables) === null || _b === void 0 ? void 0 : _b['deduplication']) === '1') {
|
|
248
|
+
saveChannel(params, url);
|
|
249
|
+
}
|
|
250
|
+
if (isExcluded(url, (_c = manifest.variables) === null || _c === void 0 ? void 0 : _c['excludedPaths'])) {
|
|
251
|
+
return;
|
|
252
|
+
}
|
|
253
|
+
const scriptId = `awin-mastertag-${advertiserId}`;
|
|
254
|
+
if (document.getElementById(scriptId)) {
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
const script = document.createElement('script');
|
|
258
|
+
script.id = scriptId;
|
|
259
|
+
script.defer = true;
|
|
260
|
+
script.src = `${masterTagUrl}/${advertiserId}.js`;
|
|
261
|
+
(document.body || document.head).appendChild(script);
|
|
262
|
+
}
|
|
263
|
+
catch (e) {
|
|
264
|
+
logger.error(`[${packageName}] MasterTag injection failed`, e);
|
|
265
|
+
}
|
|
266
|
+
};
|
|
267
|
+
|
|
268
|
+
const parseConversions = (value) => {
|
|
269
|
+
try {
|
|
270
|
+
const parsed = JSON.parse(value || '[]');
|
|
271
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
272
|
+
}
|
|
273
|
+
catch {
|
|
274
|
+
return [];
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
// Last-click channel saved on landing when deduplication is on, else `aw`.
|
|
278
|
+
const getChannel = (destination, deduplication) => {
|
|
279
|
+
var _a, _b;
|
|
280
|
+
if (deduplication !== '1') {
|
|
281
|
+
return awinChannel;
|
|
282
|
+
}
|
|
283
|
+
const storage = getData(destination, 'local');
|
|
284
|
+
return ((_b = (_a = storage.data) === null || _a === void 0 ? void 0 : _a[packageName]) === null || _b === void 0 ? void 0 : _b[channelKey]) || awinChannel;
|
|
285
|
+
};
|
|
286
|
+
// The pixel attributes the order from the awin1.com cookies, so they must be
|
|
287
|
+
// sent: an Image does that like Awin's own snippet, and fetch with credentials
|
|
288
|
+
// covers sandboxes without a DOM (Shopify custom pixel).
|
|
289
|
+
const sendPixel = (url) => {
|
|
290
|
+
if (typeof Image !== 'undefined') {
|
|
291
|
+
new Image(0, 0).src = url;
|
|
292
|
+
return;
|
|
293
|
+
}
|
|
294
|
+
fetch(url, {
|
|
295
|
+
mode: 'no-cors',
|
|
296
|
+
credentials: 'include',
|
|
297
|
+
keepalive: true,
|
|
298
|
+
}).catch((e) => logger.error(`[${packageName}] Pixel failed`, e));
|
|
299
|
+
};
|
|
300
|
+
// Declares the Awin Conversion Tag and fires the mandatory Fall-back
|
|
301
|
+
// Conversion Pixel. If the MasterTag is still loading it picks the sale up on
|
|
302
|
+
// load; if it already ran, AWIN.Tracking.run() re-triggers it.
|
|
303
|
+
const tag = ({ data, eventName, eventId, manifestVariables, destination, }) => {
|
|
304
|
+
var _a;
|
|
305
|
+
const payload = { sdkVersion: "1.76.2" };
|
|
306
|
+
const advertiserId = manifestVariables['advertiserId'];
|
|
307
|
+
if (typeof window === 'undefined' || !advertiserId) {
|
|
308
|
+
return payload;
|
|
309
|
+
}
|
|
310
|
+
const commissionGroup = getCommissionGroup(eventName, parseConversions(manifestVariables['conversions']), manifestVariables['commissionGroup']);
|
|
311
|
+
if (!commissionGroup) {
|
|
312
|
+
return payload;
|
|
313
|
+
}
|
|
314
|
+
try {
|
|
315
|
+
const amount = getAmount(eventName, data).toFixed(2);
|
|
316
|
+
const discounts = data['discounts'];
|
|
317
|
+
const orderRef = String(data['orderId'] || eventId);
|
|
318
|
+
const parts = `${commissionGroup}:${amount}`;
|
|
319
|
+
const currency = String(data['currency'] || 'USD');
|
|
320
|
+
const voucher = ((_a = discounts === null || discounts === void 0 ? void 0 : discounts[0]) === null || _a === void 0 ? void 0 : _a.code) || '';
|
|
321
|
+
const channel = getChannel(destination, manifestVariables['deduplication']);
|
|
322
|
+
const test = manifestVariables['testMode'] === '1' ? '1' : '0';
|
|
323
|
+
const pixel = new URL(fallbackPixelUrl);
|
|
324
|
+
pixel.searchParams.set('tt', 'ns');
|
|
325
|
+
pixel.searchParams.set('tv', '2');
|
|
326
|
+
pixel.searchParams.set('merchant', advertiserId);
|
|
327
|
+
pixel.searchParams.set('amount', amount);
|
|
328
|
+
pixel.searchParams.set('ch', channel);
|
|
329
|
+
pixel.searchParams.set('parts', parts);
|
|
330
|
+
pixel.searchParams.set('ref', orderRef);
|
|
331
|
+
pixel.searchParams.set('cr', currency);
|
|
332
|
+
pixel.searchParams.set('vc', voucher);
|
|
333
|
+
pixel.searchParams.set('testmode', test);
|
|
334
|
+
sendPixel(pixel.toString());
|
|
335
|
+
window.AWIN = window.AWIN || {};
|
|
336
|
+
window.AWIN.Tracking = window.AWIN.Tracking || {};
|
|
337
|
+
window.AWIN.Tracking.Sale = {
|
|
338
|
+
amount,
|
|
339
|
+
channel,
|
|
340
|
+
orderRef,
|
|
341
|
+
parts,
|
|
342
|
+
currency,
|
|
343
|
+
voucher,
|
|
344
|
+
test,
|
|
345
|
+
};
|
|
346
|
+
if (typeof window.AWIN.Tracking.run === 'function') {
|
|
347
|
+
window.AWIN.Tracking.run();
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
catch (e) {
|
|
351
|
+
logger.error(`[${packageName}] Conversion tag failed`, e);
|
|
352
|
+
}
|
|
353
|
+
return payload;
|
|
354
|
+
};
|
|
355
|
+
|
|
356
|
+
// eslint-disable-next-line @nx/enforce-module-boundaries
|
|
357
|
+
const data = {
|
|
358
|
+
name: packageName,
|
|
359
|
+
init,
|
|
360
|
+
tag,
|
|
361
|
+
};
|
|
362
|
+
try {
|
|
363
|
+
if (typeof window !== 'undefined') {
|
|
364
|
+
if (!window.edgetagProviders) {
|
|
365
|
+
window.edgetagProviders = [];
|
|
366
|
+
}
|
|
367
|
+
window.edgetagProviders.push(data);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
catch {
|
|
371
|
+
// No window
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
return data;
|
|
375
|
+
|
|
376
|
+
})();
|
package/index.mjs
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
const packageName = 'awin';
|
|
2
|
+
const fallbackPixelUrl = 'https://www.awin1.com/sread.img';
|
|
3
|
+
const masterTagUrl = 'https://www.dwin1.com';
|
|
4
|
+
// Click checksum Awin appends to the landing page URL.
|
|
5
|
+
const clickIdQueryKey = 'awc';
|
|
6
|
+
const awinChannel = 'aw';
|
|
7
|
+
const defaultCommissionGroup = 'DEFAULT';
|
|
8
|
+
// Stores the last-click channel for Awin's deduplication.
|
|
9
|
+
const channelKey = 'channel';
|
|
10
|
+
// `aw` for an Awin click, otherwise the name of the other channel's click ID
|
|
11
|
+
// (e.g. `google` for gclid). Null when the landing URL carries no click.
|
|
12
|
+
const getLandingChannel = (search, clickIds) => {
|
|
13
|
+
const params = new URLSearchParams(search);
|
|
14
|
+
if (params.get(clickIdQueryKey)) {
|
|
15
|
+
return awinChannel;
|
|
16
|
+
}
|
|
17
|
+
const click = clickIds.find(({ param }) => params.get(param));
|
|
18
|
+
return click ? click.label.toLowerCase().replace(/\s+/g, '') : null;
|
|
19
|
+
};
|
|
20
|
+
// Purchase is always a conversion; any other event only when it is mapped.
|
|
21
|
+
// Returns null for events that must not be sent to Awin.
|
|
22
|
+
const getCommissionGroup = (eventName, conversions, defaultGroup) => {
|
|
23
|
+
var _a;
|
|
24
|
+
const fallback = (defaultGroup === null || defaultGroup === void 0 ? void 0 : defaultGroup.trim()) || defaultCommissionGroup;
|
|
25
|
+
const mapping = conversions.find((item) => item.event === eventName);
|
|
26
|
+
if (mapping) {
|
|
27
|
+
return ((_a = mapping.commissionGroup) === null || _a === void 0 ? void 0 : _a.trim()) || fallback;
|
|
28
|
+
}
|
|
29
|
+
return eventName === 'Purchase' ? fallback : null;
|
|
30
|
+
};
|
|
31
|
+
const toNumber = (value) => {
|
|
32
|
+
const number = Number(value);
|
|
33
|
+
return value === undefined || value === null || isNaN(number)
|
|
34
|
+
? undefined
|
|
35
|
+
: number;
|
|
36
|
+
};
|
|
37
|
+
// When the checkout total is in another currency than the line items, the
|
|
38
|
+
// Shopify pixel sends value = grossValue = Σ(line price × qty): no tax or
|
|
39
|
+
// shipping in it, while taxCost / shippingCost stay in the checkout currency.
|
|
40
|
+
// Matched with a small tolerance for the worker's per-field currency rounding.
|
|
41
|
+
const isLineSubtotal = (gross, contents) => {
|
|
42
|
+
var _a;
|
|
43
|
+
if (!(contents === null || contents === void 0 ? void 0 : contents.length)) {
|
|
44
|
+
return false;
|
|
45
|
+
}
|
|
46
|
+
let subtotal = 0;
|
|
47
|
+
let units = 0;
|
|
48
|
+
for (const content of contents) {
|
|
49
|
+
const price = toNumber(content.item_price);
|
|
50
|
+
if (price === undefined) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
const quantity = (_a = toNumber(content.quantity)) !== null && _a !== void 0 ? _a : 1;
|
|
54
|
+
subtotal += price * quantity;
|
|
55
|
+
units += quantity;
|
|
56
|
+
}
|
|
57
|
+
return Math.abs(gross - subtotal) <= 0.01 + units * 0.001;
|
|
58
|
+
};
|
|
59
|
+
// Awin expects the order subtotal excluding tax and shipping, or the number of
|
|
60
|
+
// leads for a valueless lead. Derived here from the gross breakdown when it is
|
|
61
|
+
// present, so the browser and server send the same amount whatever the
|
|
62
|
+
// channel's purchase value mode (which only the worker applies to `value`).
|
|
63
|
+
const getAmount = (eventName, data) => {
|
|
64
|
+
var _a, _b;
|
|
65
|
+
const gross = toNumber(data.grossValue);
|
|
66
|
+
let amount = toNumber(data.value);
|
|
67
|
+
if (gross !== undefined) {
|
|
68
|
+
// A line subtotal has nothing to subtract (and the costs may be in another
|
|
69
|
+
// currency); gross is used as the worker's Net mode rewrites `value`.
|
|
70
|
+
amount = isLineSubtotal(gross, data.contents)
|
|
71
|
+
? gross
|
|
72
|
+
: Math.max(0, gross -
|
|
73
|
+
((_a = toNumber(data.taxCost)) !== null && _a !== void 0 ? _a : 0) -
|
|
74
|
+
((_b = toNumber(data.shippingCost)) !== null && _b !== void 0 ? _b : 0));
|
|
75
|
+
}
|
|
76
|
+
if (amount === undefined) {
|
|
77
|
+
return eventName === 'Purchase' ? 0 : 1;
|
|
78
|
+
}
|
|
79
|
+
return Number(amount.toFixed(2));
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
const keyPrefix = `_worker`;
|
|
83
|
+
|
|
84
|
+
const canLog = () => {
|
|
85
|
+
try {
|
|
86
|
+
return localStorage.getItem('edgeTagDebug') === '1';
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
};
|
|
92
|
+
const prefix = `[EdgeTag]`;
|
|
93
|
+
const logger = {
|
|
94
|
+
log: (...args) => {
|
|
95
|
+
if (canLog()) {
|
|
96
|
+
console.log(prefix, ...args);
|
|
97
|
+
}
|
|
98
|
+
},
|
|
99
|
+
error: (...args) => {
|
|
100
|
+
if (canLog()) {
|
|
101
|
+
console.error(prefix, ...args);
|
|
102
|
+
}
|
|
103
|
+
},
|
|
104
|
+
info: (...args) => {
|
|
105
|
+
if (canLog()) {
|
|
106
|
+
console.info(prefix, ...args);
|
|
107
|
+
}
|
|
108
|
+
},
|
|
109
|
+
trace: (...args) => {
|
|
110
|
+
if (canLog()) {
|
|
111
|
+
console.trace(prefix, ...args);
|
|
112
|
+
}
|
|
113
|
+
},
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const initKey = `${keyPrefix}StoreMultiple`;
|
|
117
|
+
const saveDataPerKey = (destination, persistType, provider, value, key) => {
|
|
118
|
+
const storage = getData(destination, persistType);
|
|
119
|
+
if (!storage['data']) {
|
|
120
|
+
storage['data'] = {};
|
|
121
|
+
}
|
|
122
|
+
if (!storage['data'][provider]) {
|
|
123
|
+
storage['data'][provider] = {};
|
|
124
|
+
}
|
|
125
|
+
storage['data'][provider][key] = value;
|
|
126
|
+
saveData(destination, persistType, storage);
|
|
127
|
+
};
|
|
128
|
+
const saveData = (destination, persistType, value, key = initKey) => {
|
|
129
|
+
if (persistType === 'session') {
|
|
130
|
+
const data = getSession(key);
|
|
131
|
+
data[destination] = value;
|
|
132
|
+
saveSession(data, key);
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
135
|
+
const data = getLocal(key);
|
|
136
|
+
data[destination] = value;
|
|
137
|
+
saveLocal(data, key);
|
|
138
|
+
};
|
|
139
|
+
const getData = (destination, persistType, key = initKey) => {
|
|
140
|
+
let data;
|
|
141
|
+
if (persistType === 'session') {
|
|
142
|
+
data = getSession(key);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
data = getLocal(key);
|
|
146
|
+
}
|
|
147
|
+
return (data === null || data === void 0 ? void 0 : data[destination]) || {};
|
|
148
|
+
};
|
|
149
|
+
const saveLocal = (value, key) => {
|
|
150
|
+
try {
|
|
151
|
+
if (!localStorage) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
localStorage.setItem(key, JSON.stringify(value));
|
|
155
|
+
}
|
|
156
|
+
catch {
|
|
157
|
+
logger.log('Local storage not supported.');
|
|
158
|
+
}
|
|
159
|
+
};
|
|
160
|
+
const getLocal = (key) => {
|
|
161
|
+
try {
|
|
162
|
+
if (!localStorage) {
|
|
163
|
+
return {};
|
|
164
|
+
}
|
|
165
|
+
const data = localStorage.getItem(key);
|
|
166
|
+
if (!data) {
|
|
167
|
+
return {};
|
|
168
|
+
}
|
|
169
|
+
return JSON.parse(data) || {};
|
|
170
|
+
}
|
|
171
|
+
catch {
|
|
172
|
+
return {};
|
|
173
|
+
}
|
|
174
|
+
};
|
|
175
|
+
const saveSession = (value, key) => {
|
|
176
|
+
try {
|
|
177
|
+
if (!sessionStorage) {
|
|
178
|
+
return;
|
|
179
|
+
}
|
|
180
|
+
sessionStorage.setItem(key, JSON.stringify(value));
|
|
181
|
+
}
|
|
182
|
+
catch {
|
|
183
|
+
logger.log('Session storage not supported.');
|
|
184
|
+
}
|
|
185
|
+
};
|
|
186
|
+
const getSession = (key) => {
|
|
187
|
+
try {
|
|
188
|
+
if (!sessionStorage) {
|
|
189
|
+
return {};
|
|
190
|
+
}
|
|
191
|
+
const data = sessionStorage.getItem(key);
|
|
192
|
+
if (!data) {
|
|
193
|
+
return {};
|
|
194
|
+
}
|
|
195
|
+
return JSON.parse(data) || {};
|
|
196
|
+
}
|
|
197
|
+
catch {
|
|
198
|
+
return {};
|
|
199
|
+
}
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const getUrl = (pageUrl) => {
|
|
203
|
+
try {
|
|
204
|
+
return new URL(pageUrl || window.location.href);
|
|
205
|
+
}
|
|
206
|
+
catch {
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
};
|
|
210
|
+
// Records the last-click channel so conversions carry `aw` only when Awin won.
|
|
211
|
+
// Kept on the edge for server-side calls and locally for the conversion tag.
|
|
212
|
+
const saveChannel = ({ destination, sendEdgeData, manifest }, url) => {
|
|
213
|
+
var _a;
|
|
214
|
+
let clickIds = [];
|
|
215
|
+
try {
|
|
216
|
+
clickIds = JSON.parse(((_a = manifest.variables) === null || _a === void 0 ? void 0 : _a['clickIds']) || '[]');
|
|
217
|
+
}
|
|
218
|
+
catch {
|
|
219
|
+
// keep detecting Awin clicks only
|
|
220
|
+
}
|
|
221
|
+
const channel = url && getLandingChannel(url.search, clickIds);
|
|
222
|
+
if (!channel) {
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
sendEdgeData({ [`${packageName}::${channelKey}`]: channel }, {});
|
|
226
|
+
saveDataPerKey(destination, 'local', packageName, channel, channelKey);
|
|
227
|
+
};
|
|
228
|
+
const isExcluded = (url, excludedPaths) => !!url &&
|
|
229
|
+
(excludedPaths || '')
|
|
230
|
+
.split(',')
|
|
231
|
+
.map((path) => path.trim())
|
|
232
|
+
.some((path) => path && url.pathname.startsWith(path));
|
|
233
|
+
// Loads the Awin MasterTag on every page except the excluded (payment / PII)
|
|
234
|
+
// paths.
|
|
235
|
+
const init = (params) => {
|
|
236
|
+
var _a, _b, _c;
|
|
237
|
+
const { manifest, pageUrl } = params;
|
|
238
|
+
const advertiserId = (_a = manifest.variables) === null || _a === void 0 ? void 0 : _a['advertiserId'];
|
|
239
|
+
if (!advertiserId || typeof document === 'undefined') {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
try {
|
|
243
|
+
const url = getUrl(pageUrl);
|
|
244
|
+
if (((_b = manifest.variables) === null || _b === void 0 ? void 0 : _b['deduplication']) === '1') {
|
|
245
|
+
saveChannel(params, url);
|
|
246
|
+
}
|
|
247
|
+
if (isExcluded(url, (_c = manifest.variables) === null || _c === void 0 ? void 0 : _c['excludedPaths'])) {
|
|
248
|
+
return;
|
|
249
|
+
}
|
|
250
|
+
const scriptId = `awin-mastertag-${advertiserId}`;
|
|
251
|
+
if (document.getElementById(scriptId)) {
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
const script = document.createElement('script');
|
|
255
|
+
script.id = scriptId;
|
|
256
|
+
script.defer = true;
|
|
257
|
+
script.src = `${masterTagUrl}/${advertiserId}.js`;
|
|
258
|
+
(document.body || document.head).appendChild(script);
|
|
259
|
+
}
|
|
260
|
+
catch (e) {
|
|
261
|
+
logger.error(`[${packageName}] MasterTag injection failed`, e);
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
const parseConversions = (value) => {
|
|
266
|
+
try {
|
|
267
|
+
const parsed = JSON.parse(value || '[]');
|
|
268
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
269
|
+
}
|
|
270
|
+
catch {
|
|
271
|
+
return [];
|
|
272
|
+
}
|
|
273
|
+
};
|
|
274
|
+
// Last-click channel saved on landing when deduplication is on, else `aw`.
|
|
275
|
+
const getChannel = (destination, deduplication) => {
|
|
276
|
+
var _a, _b;
|
|
277
|
+
if (deduplication !== '1') {
|
|
278
|
+
return awinChannel;
|
|
279
|
+
}
|
|
280
|
+
const storage = getData(destination, 'local');
|
|
281
|
+
return ((_b = (_a = storage.data) === null || _a === void 0 ? void 0 : _a[packageName]) === null || _b === void 0 ? void 0 : _b[channelKey]) || awinChannel;
|
|
282
|
+
};
|
|
283
|
+
// The pixel attributes the order from the awin1.com cookies, so they must be
|
|
284
|
+
// sent: an Image does that like Awin's own snippet, and fetch with credentials
|
|
285
|
+
// covers sandboxes without a DOM (Shopify custom pixel).
|
|
286
|
+
const sendPixel = (url) => {
|
|
287
|
+
if (typeof Image !== 'undefined') {
|
|
288
|
+
new Image(0, 0).src = url;
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
fetch(url, {
|
|
292
|
+
mode: 'no-cors',
|
|
293
|
+
credentials: 'include',
|
|
294
|
+
keepalive: true,
|
|
295
|
+
}).catch((e) => logger.error(`[${packageName}] Pixel failed`, e));
|
|
296
|
+
};
|
|
297
|
+
// Declares the Awin Conversion Tag and fires the mandatory Fall-back
|
|
298
|
+
// Conversion Pixel. If the MasterTag is still loading it picks the sale up on
|
|
299
|
+
// load; if it already ran, AWIN.Tracking.run() re-triggers it.
|
|
300
|
+
const tag = ({ data, eventName, eventId, manifestVariables, destination, }) => {
|
|
301
|
+
var _a;
|
|
302
|
+
const payload = { sdkVersion: "1.76.2" };
|
|
303
|
+
const advertiserId = manifestVariables['advertiserId'];
|
|
304
|
+
if (typeof window === 'undefined' || !advertiserId) {
|
|
305
|
+
return payload;
|
|
306
|
+
}
|
|
307
|
+
const commissionGroup = getCommissionGroup(eventName, parseConversions(manifestVariables['conversions']), manifestVariables['commissionGroup']);
|
|
308
|
+
if (!commissionGroup) {
|
|
309
|
+
return payload;
|
|
310
|
+
}
|
|
311
|
+
try {
|
|
312
|
+
const amount = getAmount(eventName, data).toFixed(2);
|
|
313
|
+
const discounts = data['discounts'];
|
|
314
|
+
const orderRef = String(data['orderId'] || eventId);
|
|
315
|
+
const parts = `${commissionGroup}:${amount}`;
|
|
316
|
+
const currency = String(data['currency'] || 'USD');
|
|
317
|
+
const voucher = ((_a = discounts === null || discounts === void 0 ? void 0 : discounts[0]) === null || _a === void 0 ? void 0 : _a.code) || '';
|
|
318
|
+
const channel = getChannel(destination, manifestVariables['deduplication']);
|
|
319
|
+
const test = manifestVariables['testMode'] === '1' ? '1' : '0';
|
|
320
|
+
const pixel = new URL(fallbackPixelUrl);
|
|
321
|
+
pixel.searchParams.set('tt', 'ns');
|
|
322
|
+
pixel.searchParams.set('tv', '2');
|
|
323
|
+
pixel.searchParams.set('merchant', advertiserId);
|
|
324
|
+
pixel.searchParams.set('amount', amount);
|
|
325
|
+
pixel.searchParams.set('ch', channel);
|
|
326
|
+
pixel.searchParams.set('parts', parts);
|
|
327
|
+
pixel.searchParams.set('ref', orderRef);
|
|
328
|
+
pixel.searchParams.set('cr', currency);
|
|
329
|
+
pixel.searchParams.set('vc', voucher);
|
|
330
|
+
pixel.searchParams.set('testmode', test);
|
|
331
|
+
sendPixel(pixel.toString());
|
|
332
|
+
window.AWIN = window.AWIN || {};
|
|
333
|
+
window.AWIN.Tracking = window.AWIN.Tracking || {};
|
|
334
|
+
window.AWIN.Tracking.Sale = {
|
|
335
|
+
amount,
|
|
336
|
+
channel,
|
|
337
|
+
orderRef,
|
|
338
|
+
parts,
|
|
339
|
+
currency,
|
|
340
|
+
voucher,
|
|
341
|
+
test,
|
|
342
|
+
};
|
|
343
|
+
if (typeof window.AWIN.Tracking.run === 'function') {
|
|
344
|
+
window.AWIN.Tracking.run();
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
catch (e) {
|
|
348
|
+
logger.error(`[${packageName}] Conversion tag failed`, e);
|
|
349
|
+
}
|
|
350
|
+
return payload;
|
|
351
|
+
};
|
|
352
|
+
|
|
353
|
+
// eslint-disable-next-line @nx/enforce-module-boundaries
|
|
354
|
+
const data = {
|
|
355
|
+
name: packageName,
|
|
356
|
+
init,
|
|
357
|
+
tag,
|
|
358
|
+
};
|
|
359
|
+
try {
|
|
360
|
+
if (typeof window !== 'undefined') {
|
|
361
|
+
if (!window.edgetagProviders) {
|
|
362
|
+
window.edgetagProviders = [];
|
|
363
|
+
}
|
|
364
|
+
window.edgetagProviders.push(data);
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
catch {
|
|
368
|
+
// No window
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
export { data as default };
|
package/package.json
CHANGED
|
@@ -1,6 +1,26 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@blotoutio/providers-awin-sdk",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
3
|
+
"version": "1.76.2",
|
|
4
|
+
"description": "Awin Browser SDK for EdgeTag",
|
|
5
|
+
"author": "Blotout",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"homepage": "https://github.com/blotoutio/edgetag-sdk",
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"main": "./index.cjs.js",
|
|
12
|
+
"module": "./index.mjs",
|
|
13
|
+
"types": "./index.d.ts",
|
|
14
|
+
"repository": {
|
|
15
|
+
"type": "git",
|
|
16
|
+
"url": "git+https://github.com/blotoutio/edgetag-sdk.git"
|
|
17
|
+
},
|
|
18
|
+
"files": [
|
|
19
|
+
"index.js",
|
|
20
|
+
"index.d.ts",
|
|
21
|
+
"index.cjs.js",
|
|
22
|
+
"index.mjs",
|
|
23
|
+
"package.json",
|
|
24
|
+
"README.md"
|
|
25
|
+
]
|
|
26
|
+
}
|