@mhubcloud/tms_core 3.4.0-beta.3 → 3.4.0-beta.4
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/dist/index.js +197 -156
- package/package.json +1 -1
- package/src/broker.ts +107 -109
- package/src/defaultPublisher.ts +12 -6
- package/src/publishers/googleAds.ts +113 -28
- package/src/utils/utils.ts +35 -16
- package/tests/AffiliatePort.js +2 -1
- package/tests/CJ.js +2 -1
- package/tests/Facebook.js +2 -1
- package/tests/GoogleAds.js +5 -5
- package/tests/GoogleAnalytics4.js +2 -1
- package/tests/Piano.js +2 -1
- package/tests/UniversalAnalytics.js +2 -1
- package/src/utils/tokens/googleAdsToken.ts +0 -66
package/dist/index.js
CHANGED
|
@@ -47,98 +47,6 @@ __export(broker_exports, {
|
|
|
47
47
|
});
|
|
48
48
|
var import_cosmos = require("@azure/cosmos");
|
|
49
49
|
var import_service_bus = require("@azure/service-bus");
|
|
50
|
-
var serviceBusConnectionString = process.env.ServiceBusConnectionString;
|
|
51
|
-
var cosmosConnectionString = process.env.CosmosDbConnectionString;
|
|
52
|
-
var cosmosDatabaseName = process.env.CosmosDbDatabaseName;
|
|
53
|
-
var cosmosContainerName = process.env.CosmosEventsContainerName;
|
|
54
|
-
var serviceBusClient;
|
|
55
|
-
var cosmosClient;
|
|
56
|
-
var senders = {};
|
|
57
|
-
function resolveEvent(eventCode, publisherConfig) {
|
|
58
|
-
if (!publisherConfig) {
|
|
59
|
-
const envConfig = process.env.Broker_PublisherConfig;
|
|
60
|
-
publisherConfig = envConfig ? JSON.parse(envConfig) : {};
|
|
61
|
-
}
|
|
62
|
-
const publishers = [];
|
|
63
|
-
for (const publisher2 of Object.keys(publisherConfig)) {
|
|
64
|
-
const publisherEvents = publisherConfig[publisher2];
|
|
65
|
-
if (publisherEvents[0] === "*") {
|
|
66
|
-
let matchesExcluded = false;
|
|
67
|
-
if (Array.isArray(publisherEvents[1]) && publisherEvents[1].length > 0) {
|
|
68
|
-
for (const event of publisherEvents[1]) {
|
|
69
|
-
if (matchesExcluded) {
|
|
70
|
-
break;
|
|
71
|
-
}
|
|
72
|
-
if (typeof event === "string") {
|
|
73
|
-
const regex = new RegExp(event);
|
|
74
|
-
matchesExcluded = regex.test(eventCode);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
if (!matchesExcluded) {
|
|
79
|
-
publishers.push(publisher2);
|
|
80
|
-
}
|
|
81
|
-
} else {
|
|
82
|
-
let match = false;
|
|
83
|
-
for (const event of publisherEvents) {
|
|
84
|
-
if (typeof event === "string") {
|
|
85
|
-
const regex = new RegExp(event);
|
|
86
|
-
match = regex.test(eventCode);
|
|
87
|
-
}
|
|
88
|
-
if (match) {
|
|
89
|
-
publishers.push(publisher2);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
if (publishers.length === 0) {
|
|
95
|
-
return null;
|
|
96
|
-
}
|
|
97
|
-
return publishers;
|
|
98
|
-
}
|
|
99
|
-
async function dispatchEvent(message, queues) {
|
|
100
|
-
if (!serviceBusClient) {
|
|
101
|
-
if (!serviceBusConnectionString) {
|
|
102
|
-
throw new Error("Service Bus connection string is missing.");
|
|
103
|
-
}
|
|
104
|
-
serviceBusClient = new import_service_bus.ServiceBusClient(serviceBusConnectionString);
|
|
105
|
-
}
|
|
106
|
-
await Promise.all(
|
|
107
|
-
queues.map(async (queue) => {
|
|
108
|
-
try {
|
|
109
|
-
let sender;
|
|
110
|
-
if (senders[queue]) {
|
|
111
|
-
sender = senders[queue];
|
|
112
|
-
} else {
|
|
113
|
-
senders[queue] = serviceBusClient.createSender(queue);
|
|
114
|
-
sender = senders[queue];
|
|
115
|
-
}
|
|
116
|
-
await sender.sendMessages({ body: message });
|
|
117
|
-
} catch (e) {
|
|
118
|
-
console.log(`Sending to queue ${queue} failed`);
|
|
119
|
-
}
|
|
120
|
-
})
|
|
121
|
-
);
|
|
122
|
-
}
|
|
123
|
-
async function logToCosmosDb(message, logger) {
|
|
124
|
-
if (!cosmosClient) {
|
|
125
|
-
if (!cosmosConnectionString) {
|
|
126
|
-
throw new Error("Cosmos DB connection string is missing.");
|
|
127
|
-
}
|
|
128
|
-
cosmosClient = new import_cosmos.CosmosClient(cosmosConnectionString);
|
|
129
|
-
}
|
|
130
|
-
if (!cosmosDatabaseName) {
|
|
131
|
-
throw new Error("Cosmos DB database name is missing.");
|
|
132
|
-
}
|
|
133
|
-
if (!cosmosContainerName) {
|
|
134
|
-
throw new Error("Cosmos DB events container name is missing.");
|
|
135
|
-
}
|
|
136
|
-
try {
|
|
137
|
-
await cosmosClient.database(cosmosDatabaseName).container(cosmosContainerName).items.create(message);
|
|
138
|
-
} catch (e) {
|
|
139
|
-
utils_exports.logException(logger, e);
|
|
140
|
-
}
|
|
141
|
-
}
|
|
142
50
|
|
|
143
51
|
// src/utils/utils.ts
|
|
144
52
|
var utils_exports = {};
|
|
@@ -147,19 +55,19 @@ __export(utils_exports, {
|
|
|
147
55
|
encodeEmail: () => encodeEmail,
|
|
148
56
|
extractAdCode: () => extractAdCode,
|
|
149
57
|
flatten: () => flatten,
|
|
58
|
+
formatError: () => formatError,
|
|
150
59
|
handlePlatformRequest: () => handlePlatformRequest,
|
|
151
60
|
handleResponse: () => handleResponse,
|
|
152
61
|
logEvent: () => logEvent,
|
|
153
62
|
logException: () => logException,
|
|
154
63
|
logMessage: () => logMessage,
|
|
155
|
-
parseErrorMessage: () => parseErrorMessage,
|
|
156
64
|
prepareTestRequestHeaders: () => prepareTestRequestHeaders,
|
|
157
65
|
removeUndefined: () => removeUndefined
|
|
158
66
|
});
|
|
159
67
|
var crypto = __toESM(require("crypto"));
|
|
160
68
|
var appInsights = __toESM(require("applicationinsights"));
|
|
161
69
|
var aiClient = null;
|
|
162
|
-
if (process.env.APPLICATIONINSIGHTS_CONNECTION_STRING
|
|
70
|
+
if (process.env.APPLICATIONINSIGHTS_CONNECTION_STRING) {
|
|
163
71
|
appInsights.setup().setAutoCollectDependencies(false).setAutoCollectConsole(true, false).setSendLiveMetrics(false).setAutoCollectRequests(false).setAutoCollectPerformance(false).setUseDiskRetryCaching(false).start();
|
|
164
72
|
aiClient = appInsights.defaultClient;
|
|
165
73
|
}
|
|
@@ -192,15 +100,17 @@ function logMessage(level, message, properties) {
|
|
|
192
100
|
properties
|
|
193
101
|
});
|
|
194
102
|
}
|
|
195
|
-
function logEvent(publisher2, event, region) {
|
|
103
|
+
function logEvent(publisher2, event, region, status, payload) {
|
|
196
104
|
const properties = {
|
|
197
105
|
publisher: publisher2,
|
|
198
106
|
event,
|
|
199
|
-
region
|
|
107
|
+
region,
|
|
108
|
+
status,
|
|
109
|
+
payload
|
|
200
110
|
};
|
|
201
111
|
logMessage(1, "Event dispatched to platform", properties);
|
|
202
112
|
}
|
|
203
|
-
function logException(
|
|
113
|
+
function logException(exception) {
|
|
204
114
|
aiClient?.trackException({ exception });
|
|
205
115
|
}
|
|
206
116
|
function removeUndefined(originalValue) {
|
|
@@ -314,7 +224,10 @@ async function handlePlatformRequest({
|
|
|
314
224
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
315
225
|
headers: { ...requestOptions.headers }
|
|
316
226
|
};
|
|
317
|
-
const rawPlatformResponse = await fetch(
|
|
227
|
+
const rawPlatformResponse = await fetch(
|
|
228
|
+
fullUrl,
|
|
229
|
+
fetchRequestOptions
|
|
230
|
+
);
|
|
318
231
|
if (process.env.PUBLISHER_TESTURL) {
|
|
319
232
|
await handleTestRequest({
|
|
320
233
|
fetchRequestOptions,
|
|
@@ -328,10 +241,22 @@ async function handlePlatformRequest({
|
|
|
328
241
|
rawPlatformResponse,
|
|
329
242
|
event.event
|
|
330
243
|
);
|
|
331
|
-
logEvent(
|
|
244
|
+
logEvent(
|
|
245
|
+
source,
|
|
246
|
+
event.event,
|
|
247
|
+
event.region,
|
|
248
|
+
rawPlatformResponse.status,
|
|
249
|
+
requestOptions.body ? JSON.stringify(requestOptions.body) : void 0
|
|
250
|
+
);
|
|
332
251
|
return processedPlatformResponse;
|
|
333
252
|
} else {
|
|
334
|
-
logEvent(
|
|
253
|
+
logEvent(
|
|
254
|
+
source,
|
|
255
|
+
event.event,
|
|
256
|
+
event.region,
|
|
257
|
+
rawPlatformResponse.status,
|
|
258
|
+
requestOptions.body ? JSON.stringify(requestOptions.body) : void 0
|
|
259
|
+
);
|
|
335
260
|
}
|
|
336
261
|
}
|
|
337
262
|
async function handleTestRequest(params) {
|
|
@@ -354,18 +279,115 @@ async function handleTestRequest(params) {
|
|
|
354
279
|
} catch {
|
|
355
280
|
}
|
|
356
281
|
}
|
|
357
|
-
function
|
|
358
|
-
let
|
|
282
|
+
function formatError(error) {
|
|
283
|
+
let formattedError;
|
|
359
284
|
if (error instanceof Error) {
|
|
285
|
+
formattedError = error;
|
|
360
286
|
if (error.cause) {
|
|
361
|
-
|
|
287
|
+
formattedError.message = `${error.message} Cause: ${error.cause.toString()}`;
|
|
362
288
|
} else {
|
|
363
|
-
|
|
289
|
+
formattedError.message = error.message;
|
|
364
290
|
}
|
|
365
291
|
} else {
|
|
366
|
-
|
|
292
|
+
formattedError = new Error(error);
|
|
293
|
+
}
|
|
294
|
+
return formattedError;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
// src/broker.ts
|
|
298
|
+
var serviceBusConnectionString = process.env.ServiceBusConnectionString;
|
|
299
|
+
var cosmosConnectionString = process.env.CosmosDbConnectionString;
|
|
300
|
+
var cosmosDatabaseName = process.env.CosmosDbDatabaseName;
|
|
301
|
+
var cosmosContainerName = process.env.CosmosDbEventsContainerName;
|
|
302
|
+
var serviceBusClient;
|
|
303
|
+
var cosmosClient;
|
|
304
|
+
var senders = {};
|
|
305
|
+
function resolveEvent(eventCode, publisherConfig) {
|
|
306
|
+
if (!publisherConfig) {
|
|
307
|
+
const envConfig = process.env.Broker_PublisherConfig;
|
|
308
|
+
publisherConfig = envConfig ? JSON.parse(envConfig) : {};
|
|
309
|
+
}
|
|
310
|
+
const publishers = [];
|
|
311
|
+
for (const publisher2 of Object.keys(publisherConfig)) {
|
|
312
|
+
const publisherEvents = publisherConfig[publisher2];
|
|
313
|
+
if (publisherEvents) {
|
|
314
|
+
if (publisherEvents[0] === "*") {
|
|
315
|
+
let matchesExcluded = false;
|
|
316
|
+
if (Array.isArray(publisherEvents[1]) && publisherEvents[1].length > 0) {
|
|
317
|
+
for (const event of publisherEvents[1]) {
|
|
318
|
+
if (matchesExcluded) {
|
|
319
|
+
break;
|
|
320
|
+
}
|
|
321
|
+
if (typeof event === "string") {
|
|
322
|
+
const regex = new RegExp(event);
|
|
323
|
+
matchesExcluded = regex.test(eventCode);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
}
|
|
327
|
+
if (!matchesExcluded) {
|
|
328
|
+
publishers.push(publisher2);
|
|
329
|
+
}
|
|
330
|
+
} else {
|
|
331
|
+
let match = false;
|
|
332
|
+
for (const event of publisherEvents) {
|
|
333
|
+
if (typeof event === "string") {
|
|
334
|
+
const regex = new RegExp(event);
|
|
335
|
+
match = regex.test(eventCode);
|
|
336
|
+
}
|
|
337
|
+
if (match) {
|
|
338
|
+
publishers.push(publisher2);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
if (publishers.length === 0) {
|
|
345
|
+
return null;
|
|
346
|
+
}
|
|
347
|
+
return publishers;
|
|
348
|
+
}
|
|
349
|
+
async function dispatchEvent(message, queues) {
|
|
350
|
+
if (!serviceBusClient) {
|
|
351
|
+
if (!serviceBusConnectionString) {
|
|
352
|
+
throw new Error("Service Bus connection string is missing.");
|
|
353
|
+
}
|
|
354
|
+
serviceBusClient = new import_service_bus.ServiceBusClient(serviceBusConnectionString);
|
|
355
|
+
}
|
|
356
|
+
await Promise.all(
|
|
357
|
+
queues.map(async (queue) => {
|
|
358
|
+
try {
|
|
359
|
+
let sender;
|
|
360
|
+
if (senders[queue]) {
|
|
361
|
+
sender = senders[queue];
|
|
362
|
+
} else {
|
|
363
|
+
senders[queue] = serviceBusClient.createSender(queue);
|
|
364
|
+
sender = senders[queue];
|
|
365
|
+
}
|
|
366
|
+
await sender.sendMessages({ body: message });
|
|
367
|
+
} catch (e) {
|
|
368
|
+
console.log(`Sending to queue ${queue} failed`);
|
|
369
|
+
}
|
|
370
|
+
})
|
|
371
|
+
);
|
|
372
|
+
}
|
|
373
|
+
async function logToCosmosDb(message) {
|
|
374
|
+
if (!cosmosClient) {
|
|
375
|
+
if (!cosmosConnectionString) {
|
|
376
|
+
throw new Error("Cosmos DB connection string is missing.");
|
|
377
|
+
}
|
|
378
|
+
cosmosClient = new import_cosmos.CosmosClient(cosmosConnectionString);
|
|
379
|
+
}
|
|
380
|
+
if (!cosmosDatabaseName) {
|
|
381
|
+
throw new Error("Cosmos DB database name is missing.");
|
|
382
|
+
}
|
|
383
|
+
if (!cosmosContainerName) {
|
|
384
|
+
throw new Error("Cosmos DB events container name is missing.");
|
|
385
|
+
}
|
|
386
|
+
try {
|
|
387
|
+
await cosmosClient.database(cosmosDatabaseName).container(cosmosContainerName).items.create(message);
|
|
388
|
+
} catch (e) {
|
|
389
|
+
logException(e);
|
|
367
390
|
}
|
|
368
|
-
return errorMessage;
|
|
369
391
|
}
|
|
370
392
|
|
|
371
393
|
// src/defaultPublisher.ts
|
|
@@ -386,15 +408,21 @@ async function defaultPublisher(azureFunctionContext, serviceBusMessage, source,
|
|
|
386
408
|
for (const result of results) {
|
|
387
409
|
if (result.status === "rejected") {
|
|
388
410
|
if (result.reason instanceof Error) {
|
|
389
|
-
const
|
|
390
|
-
logException(
|
|
391
|
-
|
|
411
|
+
const formattedError = formatError(result.reason);
|
|
412
|
+
logException(
|
|
413
|
+
azureFunctionContext.log,
|
|
414
|
+
new Error(formattedError.message)
|
|
415
|
+
);
|
|
416
|
+
throw formattedError;
|
|
392
417
|
} else {
|
|
393
|
-
const
|
|
418
|
+
const formattedError = formatError(
|
|
394
419
|
typeof result.reason === "object" ? JSON.stringify(result.reason) : result.reason
|
|
395
420
|
);
|
|
396
|
-
logException(
|
|
397
|
-
|
|
421
|
+
logException(
|
|
422
|
+
azureFunctionContext.log,
|
|
423
|
+
new Error(formattedError.message)
|
|
424
|
+
);
|
|
425
|
+
throw formattedError;
|
|
398
426
|
}
|
|
399
427
|
}
|
|
400
428
|
}
|
|
@@ -580,42 +608,9 @@ var facebook = {
|
|
|
580
608
|
}
|
|
581
609
|
};
|
|
582
610
|
|
|
583
|
-
// src/utils/tokens/googleAdsToken.ts
|
|
584
|
-
var tokenCache = null;
|
|
585
|
-
var tokenPromise = null;
|
|
586
|
-
async function fetchGoogleAdsToken(config) {
|
|
587
|
-
if (tokenCache && tokenCache.createdAt + 3500 * 1e3 > Date.now()) {
|
|
588
|
-
return tokenCache;
|
|
589
|
-
}
|
|
590
|
-
if (!tokenPromise) {
|
|
591
|
-
tokenPromise = (async () => {
|
|
592
|
-
const tokenResponse = await fetch(`https://oauth2.googleapis.com/token`, {
|
|
593
|
-
method: "POST",
|
|
594
|
-
headers: {
|
|
595
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
596
|
-
"Content-Type": "application/x-www-form-urlencoded"
|
|
597
|
-
},
|
|
598
|
-
body: new URLSearchParams({
|
|
599
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
600
|
-
client_id: config.client.id,
|
|
601
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
602
|
-
client_secret: config.client.secret,
|
|
603
|
-
refreshToken: config.refreshToken,
|
|
604
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
605
|
-
grant_type: "refresh_token"
|
|
606
|
-
})
|
|
607
|
-
});
|
|
608
|
-
const parsedTokenResponse = await tokenResponse.json();
|
|
609
|
-
const createdAt = Date.now();
|
|
610
|
-
tokenCache = { token: parsedTokenResponse.access_token, createdAt };
|
|
611
|
-
tokenPromise = null;
|
|
612
|
-
return tokenCache;
|
|
613
|
-
})();
|
|
614
|
-
}
|
|
615
|
-
return tokenPromise;
|
|
616
|
-
}
|
|
617
|
-
|
|
618
611
|
// src/publishers/googleAds.ts
|
|
612
|
+
var tokenCache;
|
|
613
|
+
var tokenPromise;
|
|
619
614
|
var googleAds = {
|
|
620
615
|
source: "GoogleAds",
|
|
621
616
|
/**
|
|
@@ -630,7 +625,7 @@ var googleAds = {
|
|
|
630
625
|
logMessage(1, `Excluded ${event.event} due to lack of identifiers`);
|
|
631
626
|
return;
|
|
632
627
|
}
|
|
633
|
-
const tokenData = await
|
|
628
|
+
const tokenData = await this.getToken(config);
|
|
634
629
|
data.conversionAction = `customers/${config.customerId}/conversionActions/${config.conversionId}`;
|
|
635
630
|
const body = {
|
|
636
631
|
conversions: [data],
|
|
@@ -643,6 +638,7 @@ var googleAds = {
|
|
|
643
638
|
headers: {
|
|
644
639
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
645
640
|
Authorization: `Bearer ${tokenData.token}`,
|
|
641
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
646
642
|
"developer-token": config.client.developerToken
|
|
647
643
|
},
|
|
648
644
|
body: JSON.stringify(body)
|
|
@@ -651,23 +647,68 @@ var googleAds = {
|
|
|
651
647
|
const response = await fetch(baseUrl, requestOptions);
|
|
652
648
|
const responseJson = await response.json();
|
|
653
649
|
const knownErrors = [
|
|
654
|
-
"The imported event has a conversion_date_time that precedes the click. Make sure your conversion_date_time is correct and try again
|
|
655
|
-
"The click ID or call is associated with an Ads account you don't have access to. Make sure you import conversions for accounts managed by your manager account
|
|
656
|
-
"The imported event can't be recorded because its click occurred before this conversion's click-through window. Make sure you import the most recent data
|
|
657
|
-
"The imported event includes an order ID that was previously recorded, so the event was not processed
|
|
658
|
-
"The imported event could not be attributed to a click. This may be because the event did not come from a Google Ads campaign
|
|
659
|
-
"The imported event can't be recorded because its click occurred before this conversion's click-through window. Make sure you import the most recent data
|
|
650
|
+
"The imported event has a conversion_date_time that precedes the click. Make sure your conversion_date_time is correct and try again.",
|
|
651
|
+
"The click ID or call is associated with an Ads account you don't have access to. Make sure you import conversions for accounts managed by your manager account.",
|
|
652
|
+
"The imported event can't be recorded because its click occurred before this conversion's click-through window. Make sure you import the most recent data.",
|
|
653
|
+
"The imported event includes an order ID that was previously recorded, so the event was not processed.",
|
|
654
|
+
"The imported event could not be attributed to a click. This may be because the event did not come from a Google Ads campaign.",
|
|
655
|
+
"The imported event can't be recorded because its click occurred before this conversion's click-through window. Make sure you import the most recent data."
|
|
660
656
|
];
|
|
661
657
|
if (responseJson.partialFailureError) {
|
|
662
658
|
logMessage(1, responseJson.partialFailureError.message);
|
|
663
|
-
|
|
659
|
+
const trimmedMessage = responseJson.partialFailureError.message.split("., at")[0] ?? "";
|
|
660
|
+
if (!knownErrors.includes(trimmedMessage)) {
|
|
664
661
|
throw new Error(responseJson.partialFailureError.message);
|
|
665
662
|
}
|
|
666
663
|
} else if (responseJson.error) {
|
|
667
664
|
throw new Error(responseJson.error.message);
|
|
668
665
|
} else {
|
|
669
|
-
logEvent(
|
|
666
|
+
logEvent(
|
|
667
|
+
this.source,
|
|
668
|
+
event.event,
|
|
669
|
+
event.region,
|
|
670
|
+
response.status,
|
|
671
|
+
requestOptions.body
|
|
672
|
+
);
|
|
673
|
+
}
|
|
674
|
+
},
|
|
675
|
+
async getToken(config) {
|
|
676
|
+
if (tokenCache && tokenCache.createdAt + 3500 * 1e3 > Date.now()) {
|
|
677
|
+
return tokenCache;
|
|
678
|
+
}
|
|
679
|
+
if (!tokenPromise) {
|
|
680
|
+
tokenPromise = (async () => {
|
|
681
|
+
const tokenResponse = await fetch(
|
|
682
|
+
`https://oauth2.googleapis.com/token`,
|
|
683
|
+
{
|
|
684
|
+
method: "POST",
|
|
685
|
+
headers: {
|
|
686
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
687
|
+
"Content-Type": "application/x-www-form-urlencoded"
|
|
688
|
+
},
|
|
689
|
+
body: new URLSearchParams({
|
|
690
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
691
|
+
client_id: config.client.id,
|
|
692
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
693
|
+
client_secret: config.client.secret,
|
|
694
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
695
|
+
refreshToken: config.refreshToken,
|
|
696
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
697
|
+
grant_type: "refresh_token"
|
|
698
|
+
})
|
|
699
|
+
}
|
|
700
|
+
);
|
|
701
|
+
if (!tokenResponse.ok) {
|
|
702
|
+
throw new Error("Access token fetch failed");
|
|
703
|
+
}
|
|
704
|
+
const parsedTokenResponse = await tokenResponse.json();
|
|
705
|
+
const createdAt = Date.now();
|
|
706
|
+
tokenCache = { token: parsedTokenResponse.access_token, createdAt };
|
|
707
|
+
tokenPromise = null;
|
|
708
|
+
return tokenCache;
|
|
709
|
+
})();
|
|
670
710
|
}
|
|
711
|
+
return tokenPromise;
|
|
671
712
|
}
|
|
672
713
|
};
|
|
673
714
|
|
package/package.json
CHANGED
package/src/broker.ts
CHANGED
|
@@ -5,140 +5,138 @@ import * as utils from './utils/utils';
|
|
|
5
5
|
const serviceBusConnectionString = process.env.ServiceBusConnectionString;
|
|
6
6
|
const cosmosConnectionString = process.env.CosmosDbConnectionString;
|
|
7
7
|
const cosmosDatabaseName = process.env.CosmosDbDatabaseName;
|
|
8
|
-
const cosmosContainerName = process.env.
|
|
8
|
+
const cosmosContainerName = process.env.CosmosDbEventsContainerName;
|
|
9
9
|
let serviceBusClient: ServiceBusClient | undefined;
|
|
10
10
|
let cosmosClient: CosmosClient | undefined;
|
|
11
11
|
const senders: Record<string, ServiceBusSender> = {};
|
|
12
12
|
|
|
13
|
-
type PublisherConfig = Record<string, string[] | string[][]>;
|
|
13
|
+
export type PublisherConfig = Record<string, string[] | string[][]>;
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
) {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
const publishers = [];
|
|
15
|
+
/**
|
|
16
|
+
* Evaluates an event against publisher configurations
|
|
17
|
+
* @param eventCode event.code parameter of the event
|
|
18
|
+
* @param publisherConfig object containing publisher event consumption configurations
|
|
19
|
+
* @returns list of publishers for which an event is relevant.
|
|
20
|
+
*/
|
|
21
|
+
export function resolveEvent(
|
|
22
|
+
eventCode: string,
|
|
23
|
+
publisherConfig?: PublisherConfig,
|
|
24
|
+
) {
|
|
25
|
+
if (!publisherConfig) {
|
|
26
|
+
const envConfig = process.env.Broker_PublisherConfig;
|
|
27
|
+
publisherConfig = envConfig ? JSON.parse(envConfig) : {};
|
|
28
|
+
}
|
|
32
29
|
|
|
33
|
-
|
|
34
|
-
const publisherEvents = publisherConfig[publisher];
|
|
35
|
-
if (publisherEvents) {
|
|
36
|
-
if (publisherEvents[0] === '*') {
|
|
37
|
-
let matchesExcluded = false;
|
|
38
|
-
// Checking whether event matches any of the excluded events
|
|
39
|
-
if (
|
|
40
|
-
Array.isArray(publisherEvents[1]) &&
|
|
41
|
-
publisherEvents[1].length > 0
|
|
42
|
-
) {
|
|
43
|
-
for (const event of publisherEvents[1]) {
|
|
44
|
-
if (matchesExcluded) {
|
|
45
|
-
break;
|
|
46
|
-
}
|
|
30
|
+
const publishers = [];
|
|
47
31
|
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
32
|
+
for (const publisher of Object.keys(publisherConfig)) {
|
|
33
|
+
const publisherEvents = publisherConfig[publisher];
|
|
34
|
+
if (publisherEvents) {
|
|
35
|
+
if (publisherEvents[0] === '*') {
|
|
36
|
+
let matchesExcluded = false;
|
|
37
|
+
// Checking whether event matches any of the excluded events
|
|
38
|
+
if (
|
|
39
|
+
Array.isArray(publisherEvents[1]) &&
|
|
40
|
+
publisherEvents[1].length > 0
|
|
41
|
+
) {
|
|
42
|
+
for (const event of publisherEvents[1]) {
|
|
43
|
+
if (matchesExcluded) {
|
|
44
|
+
break;
|
|
52
45
|
}
|
|
53
|
-
}
|
|
54
|
-
if (!matchesExcluded) {
|
|
55
|
-
publishers.push(publisher);
|
|
56
|
-
}
|
|
57
|
-
} else {
|
|
58
|
-
let match = false;
|
|
59
46
|
|
|
60
|
-
for (const event of publisherEvents) {
|
|
61
47
|
if (typeof event === 'string') {
|
|
62
48
|
const regex = new RegExp(event);
|
|
63
|
-
|
|
49
|
+
matchesExcluded = regex.test(eventCode);
|
|
64
50
|
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
if (!matchesExcluded) {
|
|
54
|
+
publishers.push(publisher);
|
|
55
|
+
}
|
|
56
|
+
} else {
|
|
57
|
+
let match = false;
|
|
65
58
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
59
|
+
for (const event of publisherEvents) {
|
|
60
|
+
if (typeof event === 'string') {
|
|
61
|
+
const regex = new RegExp(event);
|
|
62
|
+
match = regex.test(eventCode);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (match) {
|
|
66
|
+
publishers.push(publisher);
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
69
|
}
|
|
72
70
|
}
|
|
71
|
+
}
|
|
73
72
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
73
|
+
if (publishers.length === 0) {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
77
76
|
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
return publishers;
|
|
78
|
+
}
|
|
80
79
|
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
}
|
|
94
|
-
serviceBusClient = new ServiceBusClient(serviceBusConnectionString);
|
|
80
|
+
/**
|
|
81
|
+
* Sends an event to Service Bus
|
|
82
|
+
* @param message event to be sent
|
|
83
|
+
* @param queues list of Service Bus queues to send the event to
|
|
84
|
+
*/
|
|
85
|
+
export async function dispatchEvent(
|
|
86
|
+
message: Record<string, string>,
|
|
87
|
+
queues: string[],
|
|
88
|
+
) {
|
|
89
|
+
if (!serviceBusClient) {
|
|
90
|
+
if (!serviceBusConnectionString) {
|
|
91
|
+
throw new Error('Service Bus connection string is missing.');
|
|
95
92
|
}
|
|
93
|
+
serviceBusClient = new ServiceBusClient(serviceBusConnectionString);
|
|
94
|
+
}
|
|
96
95
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
await sender.sendMessages({ body: message });
|
|
108
|
-
} catch (e) {
|
|
109
|
-
console.log(`Sending to queue ${queue} failed`);
|
|
96
|
+
await Promise.all(
|
|
97
|
+
queues.map(async (queue) => {
|
|
98
|
+
try {
|
|
99
|
+
let sender;
|
|
100
|
+
if (senders[queue]) {
|
|
101
|
+
sender = senders[queue];
|
|
102
|
+
} else {
|
|
103
|
+
senders[queue] = serviceBusClient!.createSender(queue);
|
|
104
|
+
sender = senders[queue];
|
|
110
105
|
}
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Logs an event to Cosmos DB
|
|
117
|
-
* @param message event to be logged
|
|
118
|
-
*/
|
|
119
|
-
logToCosmosDb: async function (message: ServiceBusMessage) {
|
|
120
|
-
if (!cosmosClient) {
|
|
121
|
-
if (!cosmosConnectionString) {
|
|
122
|
-
throw new Error('Cosmos DB connection string is missing.');
|
|
106
|
+
await sender.sendMessages({ body: message });
|
|
107
|
+
} catch (e) {
|
|
108
|
+
console.log(`Sending to queue ${queue} failed`);
|
|
123
109
|
}
|
|
124
|
-
|
|
125
|
-
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
112
|
+
}
|
|
126
113
|
|
|
127
|
-
|
|
128
|
-
|
|
114
|
+
/**
|
|
115
|
+
* Logs an event to Cosmos DB
|
|
116
|
+
* @param message event to be logged
|
|
117
|
+
*/
|
|
118
|
+
export async function logToCosmosDb(message: ServiceBusMessage) {
|
|
119
|
+
if (!cosmosClient) {
|
|
120
|
+
if (!cosmosConnectionString) {
|
|
121
|
+
throw new Error('Cosmos DB connection string is missing.');
|
|
129
122
|
}
|
|
123
|
+
cosmosClient = new CosmosClient(cosmosConnectionString);
|
|
124
|
+
}
|
|
130
125
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
126
|
+
if (!cosmosDatabaseName) {
|
|
127
|
+
throw new Error('Cosmos DB database name is missing.');
|
|
128
|
+
}
|
|
134
129
|
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
}
|
|
130
|
+
if (!cosmosContainerName) {
|
|
131
|
+
throw new Error('Cosmos DB events container name is missing.');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
try {
|
|
135
|
+
await cosmosClient
|
|
136
|
+
.database(cosmosDatabaseName)
|
|
137
|
+
.container(cosmosContainerName)
|
|
138
|
+
.items.create(message);
|
|
139
|
+
} catch (e) {
|
|
140
|
+
utils.logException(e as Error);
|
|
141
|
+
}
|
|
142
|
+
}
|
package/src/defaultPublisher.ts
CHANGED
|
@@ -40,17 +40,23 @@ export async function defaultPublisher(
|
|
|
40
40
|
for (const result of results) {
|
|
41
41
|
if (result.status === 'rejected') {
|
|
42
42
|
if (result.reason instanceof Error) {
|
|
43
|
-
const
|
|
44
|
-
utils.logException(
|
|
45
|
-
|
|
43
|
+
const formattedError = utils.formatError(result.reason);
|
|
44
|
+
utils.logException(
|
|
45
|
+
azureFunctionContext.log,
|
|
46
|
+
new Error(formattedError.message),
|
|
47
|
+
);
|
|
48
|
+
throw formattedError;
|
|
46
49
|
} else {
|
|
47
|
-
const
|
|
50
|
+
const formattedError = utils.formatError(
|
|
48
51
|
typeof result.reason === 'object'
|
|
49
52
|
? JSON.stringify(result.reason)
|
|
50
53
|
: (result.reason as Error['message']),
|
|
51
54
|
);
|
|
52
|
-
utils.logException(
|
|
53
|
-
|
|
55
|
+
utils.logException(
|
|
56
|
+
azureFunctionContext.log,
|
|
57
|
+
new Error(formattedError.message),
|
|
58
|
+
);
|
|
59
|
+
throw formattedError;
|
|
54
60
|
}
|
|
55
61
|
}
|
|
56
62
|
}
|
|
@@ -1,24 +1,65 @@
|
|
|
1
|
+
import { Logger } from '@azure/functions';
|
|
1
2
|
import * as utils from './../utils/utils';
|
|
2
|
-
import {
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
3
|
+
import { EventToLog } from '../types/EventToLog';
|
|
4
|
+
import { TypeSafeObject } from '../types/UtilTypes';
|
|
5
|
+
|
|
6
|
+
interface GoogleAdsPlatformPublisher {
|
|
7
|
+
source: string;
|
|
8
|
+
publish: (
|
|
9
|
+
log: Logger,
|
|
10
|
+
config: GoogleAdsConfig,
|
|
11
|
+
data: TypeSafeObject,
|
|
12
|
+
event: EventToLog,
|
|
13
|
+
) => Promise<void>;
|
|
14
|
+
getToken: (config: GoogleAdsConfig) => Promise<TokenCache>;
|
|
15
|
+
}
|
|
10
16
|
|
|
11
17
|
interface GoogleAdsData {
|
|
12
18
|
gclid?: string;
|
|
13
19
|
wbraid?: string;
|
|
14
20
|
gbraid?: string;
|
|
15
21
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
16
|
-
user_identifiers
|
|
22
|
+
user_identifiers?: Record<string, unknown>[];
|
|
17
23
|
conversionAction?: string;
|
|
18
24
|
[x: string]: unknown;
|
|
19
25
|
}
|
|
20
26
|
|
|
21
|
-
|
|
27
|
+
interface GoogleAdsConfig extends TypeSafeObject {
|
|
28
|
+
client: {
|
|
29
|
+
id: string;
|
|
30
|
+
secret: string;
|
|
31
|
+
developerToken: string;
|
|
32
|
+
};
|
|
33
|
+
refreshToken: string;
|
|
34
|
+
customerId: string;
|
|
35
|
+
conversionId: string;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
interface GoogleAdsResponse {
|
|
39
|
+
partialFailureError?: {
|
|
40
|
+
message: string;
|
|
41
|
+
};
|
|
42
|
+
error?: {
|
|
43
|
+
message: string;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
interface TokenCache {
|
|
48
|
+
token: string;
|
|
49
|
+
createdAt: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface TokenResponse {
|
|
53
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
54
|
+
access_token: string;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
type TokenPromise = Promise<TokenCache> | null;
|
|
58
|
+
|
|
59
|
+
let tokenCache: TokenCache | null;
|
|
60
|
+
let tokenPromise: TokenPromise;
|
|
61
|
+
|
|
62
|
+
export const googleAds: GoogleAdsPlatformPublisher = {
|
|
22
63
|
source: 'GoogleAds',
|
|
23
64
|
/**
|
|
24
65
|
*Publishes data to Google Ads
|
|
@@ -38,7 +79,7 @@ export const googleAds: PlatformPublisher = {
|
|
|
38
79
|
return;
|
|
39
80
|
}
|
|
40
81
|
|
|
41
|
-
const tokenData = await
|
|
82
|
+
const tokenData = await this.getToken(config);
|
|
42
83
|
|
|
43
84
|
data.conversionAction = `customers/${config.customerId}/conversionActions/${config.conversionId}`;
|
|
44
85
|
|
|
@@ -54,6 +95,7 @@ export const googleAds: PlatformPublisher = {
|
|
|
54
95
|
headers: {
|
|
55
96
|
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
56
97
|
Authorization: `Bearer ${tokenData.token}`,
|
|
98
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
57
99
|
'developer-token': config.client.developerToken,
|
|
58
100
|
},
|
|
59
101
|
body: JSON.stringify(body),
|
|
@@ -65,32 +107,75 @@ export const googleAds: PlatformPublisher = {
|
|
|
65
107
|
const responseJson: GoogleAdsResponse = await response.json();
|
|
66
108
|
|
|
67
109
|
const knownErrors = [
|
|
68
|
-
'The imported event has a conversion_date_time that precedes the click. Make sure your conversion_date_time is correct and try again
|
|
69
|
-
"The click ID or call is associated with an Ads account you don't have access to. Make sure you import conversions for accounts managed by your manager account
|
|
70
|
-
"The imported event can't be recorded because its click occurred before this conversion's click-through window. Make sure you import the most recent data
|
|
71
|
-
'The imported event includes an order ID that was previously recorded, so the event was not processed
|
|
72
|
-
'The imported event could not be attributed to a click. This may be because the event did not come from a Google Ads campaign
|
|
73
|
-
"The imported event can't be recorded because its click occurred before this conversion's click-through window. Make sure you import the most recent data
|
|
110
|
+
'The imported event has a conversion_date_time that precedes the click. Make sure your conversion_date_time is correct and try again.',
|
|
111
|
+
"The click ID or call is associated with an Ads account you don't have access to. Make sure you import conversions for accounts managed by your manager account.",
|
|
112
|
+
"The imported event can't be recorded because its click occurred before this conversion's click-through window. Make sure you import the most recent data.",
|
|
113
|
+
'The imported event includes an order ID that was previously recorded, so the event was not processed.',
|
|
114
|
+
'The imported event could not be attributed to a click. This may be because the event did not come from a Google Ads campaign.',
|
|
115
|
+
"The imported event can't be recorded because its click occurred before this conversion's click-through window. Make sure you import the most recent data.",
|
|
74
116
|
];
|
|
75
117
|
|
|
76
118
|
if (responseJson.partialFailureError) {
|
|
77
119
|
utils.logMessage(1, responseJson.partialFailureError.message);
|
|
78
|
-
|
|
120
|
+
const trimmedMessage =
|
|
121
|
+
responseJson.partialFailureError.message.split('., at')[0] ?? '';
|
|
122
|
+
if (!knownErrors.includes(trimmedMessage)) {
|
|
79
123
|
throw new Error(responseJson.partialFailureError.message);
|
|
80
124
|
}
|
|
81
125
|
} else if (responseJson.error) {
|
|
82
126
|
throw new Error(responseJson.error.message);
|
|
83
127
|
} else {
|
|
84
|
-
utils.logEvent(
|
|
128
|
+
utils.logEvent(
|
|
129
|
+
this.source,
|
|
130
|
+
event.event,
|
|
131
|
+
event.region,
|
|
132
|
+
response.status,
|
|
133
|
+
requestOptions.body,
|
|
134
|
+
);
|
|
85
135
|
}
|
|
86
136
|
},
|
|
87
|
-
};
|
|
88
137
|
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
138
|
+
async getToken(config: GoogleAdsConfig) {
|
|
139
|
+
if (tokenCache && tokenCache.createdAt + 3500 * 1000 > Date.now()) {
|
|
140
|
+
return tokenCache;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
if (!tokenPromise) {
|
|
144
|
+
tokenPromise = (async () => {
|
|
145
|
+
const tokenResponse = await fetch(
|
|
146
|
+
`https://oauth2.googleapis.com/token`,
|
|
147
|
+
{
|
|
148
|
+
method: 'POST',
|
|
149
|
+
headers: {
|
|
150
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
151
|
+
'Content-Type': 'application/x-www-form-urlencoded',
|
|
152
|
+
},
|
|
153
|
+
body: new URLSearchParams({
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
155
|
+
client_id: config.client.id,
|
|
156
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
157
|
+
client_secret: config.client.secret,
|
|
158
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
159
|
+
refreshToken: config.refreshToken,
|
|
160
|
+
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
161
|
+
grant_type: 'refresh_token',
|
|
162
|
+
}),
|
|
163
|
+
},
|
|
164
|
+
);
|
|
165
|
+
|
|
166
|
+
if (!tokenResponse.ok) {
|
|
167
|
+
throw new Error('Access token fetch failed');
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
const parsedTokenResponse: TokenResponse = await tokenResponse.json();
|
|
171
|
+
const createdAt = Date.now();
|
|
172
|
+
|
|
173
|
+
tokenCache = { token: parsedTokenResponse.access_token, createdAt };
|
|
174
|
+
tokenPromise = null;
|
|
175
|
+
return tokenCache;
|
|
176
|
+
})();
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return tokenPromise;
|
|
180
|
+
},
|
|
181
|
+
};
|
package/src/utils/utils.ts
CHANGED
|
@@ -12,10 +12,7 @@ import { TypeSafeObject } from '../types/UtilTypes';
|
|
|
12
12
|
|
|
13
13
|
let aiClient: appInsights.TelemetryClient | null = null;
|
|
14
14
|
|
|
15
|
-
if (
|
|
16
|
-
process.env.APPLICATIONINSIGHTS_CONNECTION_STRING ||
|
|
17
|
-
process.env.APPINSIGHTS_INSTRUMENTATIONKEY
|
|
18
|
-
) {
|
|
15
|
+
if (process.env.APPLICATIONINSIGHTS_CONNECTION_STRING) {
|
|
19
16
|
appInsights
|
|
20
17
|
.setup() // Set environment variable APPLICATIONINSIGHTS_CONNECTION_STRING, so setup() can be called w/o parameters
|
|
21
18
|
.setAutoCollectDependencies(false)
|
|
@@ -76,11 +73,15 @@ export function logEvent(
|
|
|
76
73
|
publisher: string,
|
|
77
74
|
event: string,
|
|
78
75
|
region: string,
|
|
76
|
+
status: number,
|
|
77
|
+
payload?: string,
|
|
79
78
|
): void {
|
|
80
79
|
const properties: Telemetry['properties'] = {
|
|
81
|
-
publisher
|
|
82
|
-
event
|
|
83
|
-
region
|
|
80
|
+
publisher,
|
|
81
|
+
event,
|
|
82
|
+
region,
|
|
83
|
+
status,
|
|
84
|
+
payload,
|
|
84
85
|
};
|
|
85
86
|
logMessage(1, 'Event dispatched to platform', properties);
|
|
86
87
|
}
|
|
@@ -322,7 +323,10 @@ export async function handlePlatformRequest({
|
|
|
322
323
|
headers: { ...requestOptions.headers },
|
|
323
324
|
};
|
|
324
325
|
|
|
325
|
-
const rawPlatformResponse = await fetch(
|
|
326
|
+
const rawPlatformResponse: Response = await fetch(
|
|
327
|
+
fullUrl,
|
|
328
|
+
fetchRequestOptions,
|
|
329
|
+
);
|
|
326
330
|
|
|
327
331
|
if (process.env.PUBLISHER_TESTURL) {
|
|
328
332
|
await handleTestRequest({
|
|
@@ -339,11 +343,23 @@ export async function handlePlatformRequest({
|
|
|
339
343
|
event.event,
|
|
340
344
|
);
|
|
341
345
|
|
|
342
|
-
logEvent(
|
|
346
|
+
logEvent(
|
|
347
|
+
source,
|
|
348
|
+
event.event,
|
|
349
|
+
event.region,
|
|
350
|
+
rawPlatformResponse.status,
|
|
351
|
+
requestOptions.body ? JSON.stringify(requestOptions.body) : undefined,
|
|
352
|
+
);
|
|
343
353
|
|
|
344
354
|
return processedPlatformResponse;
|
|
345
355
|
} else {
|
|
346
|
-
logEvent(
|
|
356
|
+
logEvent(
|
|
357
|
+
source,
|
|
358
|
+
event.event,
|
|
359
|
+
event.region,
|
|
360
|
+
rawPlatformResponse.status,
|
|
361
|
+
requestOptions.body ? JSON.stringify(requestOptions.body) : undefined,
|
|
362
|
+
);
|
|
347
363
|
}
|
|
348
364
|
}
|
|
349
365
|
|
|
@@ -377,16 +393,19 @@ async function handleTestRequest(params: HandleTestRequestParams) {
|
|
|
377
393
|
}
|
|
378
394
|
}
|
|
379
395
|
|
|
380
|
-
export function
|
|
381
|
-
let
|
|
396
|
+
export function formatError(error: Error | Error['message']) {
|
|
397
|
+
let formattedError: Error;
|
|
382
398
|
if (error instanceof Error) {
|
|
399
|
+
formattedError = error;
|
|
383
400
|
if (error.cause) {
|
|
384
|
-
|
|
401
|
+
formattedError.message = `${
|
|
402
|
+
error.message
|
|
403
|
+
} Cause: ${error.cause.toString()}`;
|
|
385
404
|
} else {
|
|
386
|
-
|
|
405
|
+
formattedError.message = error.message;
|
|
387
406
|
}
|
|
388
407
|
} else {
|
|
389
|
-
|
|
408
|
+
formattedError = new Error(error);
|
|
390
409
|
}
|
|
391
|
-
return
|
|
410
|
+
return formattedError;
|
|
392
411
|
}
|
package/tests/AffiliatePort.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
const localSettings = require('../local.settings.json').Values;
|
|
2
|
+
process.env = { ...process.env, ...localSettings };
|
|
1
3
|
const publisher = require('../dist/index.js').publisher.AffiliatePort;
|
|
2
4
|
const defaultPublisher = require('../dist/index.js').DefaultPublisher;
|
|
3
5
|
const utils = require('../dist/index.js').utils;
|
|
4
|
-
var assert = require('assert');
|
|
5
6
|
const testUtils = require('../dist/index.js').testUtils;
|
|
6
7
|
|
|
7
8
|
describe('test AffiliatePort publisher', function () {
|
package/tests/CJ.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
const localSettings = require('../local.settings.json').Values;
|
|
2
|
+
process.env = { ...process.env, ...localSettings };
|
|
1
3
|
const publisher = require('../dist/index.js').publisher.CJ;
|
|
2
4
|
const defaultPublisher = require('../dist/index.js').DefaultPublisher;
|
|
3
5
|
const utils = require('../dist/index.js').utils;
|
|
4
|
-
var assert = require('assert');
|
|
5
6
|
const testUtils = require('../dist/index.js').testUtils;
|
|
6
7
|
|
|
7
8
|
describe('test CJ publisher', function () {
|
package/tests/Facebook.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
const localSettings = require('../local.settings.json').Values;
|
|
2
|
+
process.env = { ...process.env, ...localSettings };
|
|
1
3
|
const publisher = require('../dist/index.js').publisher.Facebook;
|
|
2
4
|
const defaultPublisher = require('../dist/index.js').DefaultPublisher;
|
|
3
5
|
const utils = require('../dist/index.js').utils;
|
|
4
|
-
var assert = require('assert');
|
|
5
6
|
const testUtils = require('../dist/index.js').testUtils;
|
|
6
7
|
|
|
7
8
|
describe('test Facebook publisher', function () {
|
package/tests/GoogleAds.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
+
const localSettings = require('../local.settings.json').Values;
|
|
2
|
+
process.env = { ...process.env, ...localSettings };
|
|
1
3
|
const publisher = require('../dist/index.js').publisher.GoogleAds;
|
|
2
4
|
const defaultPublisher = require('../dist/index.js').DefaultPublisher;
|
|
3
5
|
const utils = require('../dist/index.js').utils;
|
|
4
6
|
const testUtils = require('../dist/index.js').testUtils;
|
|
5
|
-
const localSettings = require('../local.settings.json');
|
|
6
|
-
process.env = { ...process.env, ...localSettings };
|
|
7
7
|
|
|
8
8
|
describe('test GoogleAds publisher', function () {
|
|
9
9
|
it('purchase', async function () {
|
|
@@ -84,11 +84,11 @@ function trigger(data) {
|
|
|
84
84
|
},
|
|
85
85
|
refreshToken:
|
|
86
86
|
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
87
|
-
process.env
|
|
87
|
+
process.env.Publisher_GoogleAds_RefreshToken,
|
|
88
88
|
customerId:
|
|
89
89
|
// eslint-disable-next-line @typescript-eslint/restrict-plus-operands
|
|
90
|
-
process.env
|
|
91
|
-
conversionId: process.env.
|
|
90
|
+
process.env.Publisher_GoogleAds_CustomerID,
|
|
91
|
+
conversionId: process.env.Publisher_GoogleAds_ConversionID_purchase,
|
|
92
92
|
};
|
|
93
93
|
|
|
94
94
|
switch (data.event?.code) {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
const localSettings = require('../local.settings.json').Values;
|
|
2
|
+
process.env = { ...process.env, ...localSettings };
|
|
1
3
|
const publisher = require('../dist/index.js').publisher.GoogleAnalytics4;
|
|
2
4
|
const defaultPublisher = require('../dist/index.js').DefaultPublisher;
|
|
3
5
|
const utils = require('../dist/index.js').utils;
|
|
4
|
-
var assert = require('assert');
|
|
5
6
|
const testUtils = require('../dist/index.js').testUtils;
|
|
6
7
|
|
|
7
8
|
describe('test GoogleAnalytics4 publisher', function () {
|
package/tests/Piano.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
+
const localSettings = require('../local.settings.json').Values;
|
|
2
|
+
process.env = { ...process.env, ...localSettings };
|
|
1
3
|
const publisher = require('../dist/index.js').publisher.Piano;
|
|
2
4
|
const defaultPublisher = require('../dist/index.js').DefaultPublisher;
|
|
3
5
|
const utils = require('../dist/index.js').utils;
|
|
4
6
|
const testUtils = require('../dist/index.js').testUtils;
|
|
5
|
-
var assert = require('assert');
|
|
6
7
|
|
|
7
8
|
describe('test Piano publisher', function () {
|
|
8
9
|
it('purchase', async function () {
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
+
const localSettings = require('../local.settings.json').Values;
|
|
2
|
+
process.env = { ...process.env, ...localSettings };
|
|
1
3
|
const publisher = require('../dist/index.js').publisher.UniversalAnalytics;
|
|
2
4
|
const defaultPublisher = require('../dist/index.js').DefaultPublisher;
|
|
3
5
|
const utils = require('../dist/index.js').utils;
|
|
4
|
-
var assert = require('assert');
|
|
5
6
|
const testUtils = require('../dist/index.js').testUtils;
|
|
6
7
|
|
|
7
8
|
describe('test UniversalAnalytics publisher', function () {
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
let tokenCache: TokenCache | null = null;
|
|
2
|
-
let tokenPromise: Promise<TokenCache> | null = null;
|
|
3
|
-
|
|
4
|
-
export async function fetchGoogleAdsToken(
|
|
5
|
-
config: GoogleAdsConfig,
|
|
6
|
-
): Promise<TokenCache> {
|
|
7
|
-
if (tokenCache && tokenCache.createdAt + 3500 * 1000 > Date.now()) {
|
|
8
|
-
return tokenCache;
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
if (!tokenPromise) {
|
|
12
|
-
tokenPromise = (async () => {
|
|
13
|
-
const tokenResponse = await fetch(`https://oauth2.googleapis.com/token`, {
|
|
14
|
-
method: 'POST',
|
|
15
|
-
headers: {
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
17
|
-
'Content-Type': 'application/x-www-form-urlencoded',
|
|
18
|
-
},
|
|
19
|
-
body: new URLSearchParams({
|
|
20
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
21
|
-
client_id: config.client.id,
|
|
22
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
23
|
-
client_secret: config.client.secret,
|
|
24
|
-
refreshToken: config.refreshToken,
|
|
25
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
26
|
-
grant_type: 'refresh_token',
|
|
27
|
-
}),
|
|
28
|
-
});
|
|
29
|
-
|
|
30
|
-
// if (!tokenResponse.ok) {
|
|
31
|
-
// throw new Error('Access token fetch failed')
|
|
32
|
-
// }
|
|
33
|
-
|
|
34
|
-
const parsedTokenResponse: TokenResponse = await tokenResponse.json();
|
|
35
|
-
const createdAt = Date.now();
|
|
36
|
-
|
|
37
|
-
tokenCache = { token: parsedTokenResponse.access_token, createdAt };
|
|
38
|
-
tokenPromise = null;
|
|
39
|
-
return tokenCache;
|
|
40
|
-
})();
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
return tokenPromise;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
interface TokenCache {
|
|
47
|
-
token: string;
|
|
48
|
-
createdAt: number;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
interface TokenResponse {
|
|
52
|
-
// eslint-disable-next-line @typescript-eslint/naming-convention
|
|
53
|
-
access_token: string;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
export interface GoogleAdsConfig {
|
|
57
|
-
client: {
|
|
58
|
-
id: string;
|
|
59
|
-
secret: string;
|
|
60
|
-
developerToken: string;
|
|
61
|
-
};
|
|
62
|
-
refreshToken: string;
|
|
63
|
-
customerId: string;
|
|
64
|
-
conversionId: string;
|
|
65
|
-
[x: string]: unknown;
|
|
66
|
-
}
|