@mhubcloud/tms_core 3.4.6 → 3.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -42,7 +42,6 @@
42
42
  "**/*.log": true,
43
43
  "**/*.log*": true,
44
44
  "**/build": true,
45
- "**/dist": true
46
45
  },
47
46
  "search.exclude": {
48
47
  "**/node_modules": false
package/dist/index.js CHANGED
@@ -54,10 +54,10 @@ __export(utils_exports, {
54
54
  EventPublisherBase: () => EventPublisherBase,
55
55
  encodeEmail: () => encodeEmail,
56
56
  extractAdCode: () => extractAdCode,
57
+ extractPlatformCodes: () => extractPlatformCodes,
57
58
  flatten: () => flatten,
58
59
  formatError: () => formatError,
59
60
  handlePlatformRequest: () => handlePlatformRequest,
60
- handleResponse: () => handleResponse,
61
61
  logEvent: () => logEvent,
62
62
  logException: () => logException,
63
63
  logMessage: () => logMessage,
@@ -100,17 +100,23 @@ function logMessage(level, message, properties) {
100
100
  properties
101
101
  });
102
102
  }
103
- function logEvent(publisher2, event, region, status, payload) {
103
+ function logEvent(publisher2, event, region, id, status, payload) {
104
+ const message = status >= 200 && status < 300 ? "Event dispatched to platform" : "Event dispatch failed";
104
105
  const properties = {
105
106
  publisher: publisher2,
106
107
  event,
107
108
  region,
109
+ id,
108
110
  status,
109
111
  payload
110
112
  };
111
- logMessage(1, "Event dispatched to platform", properties);
113
+ logMessage(1, message, properties);
112
114
  }
113
115
  function logException(exception) {
116
+ aiClient?.trackMetric({
117
+ name: `Error/${exception.name}`,
118
+ value: 1
119
+ });
114
120
  aiClient?.trackException({ exception });
115
121
  }
116
122
  function removeUndefined(originalValue) {
@@ -168,6 +174,19 @@ function extractAdCode(data, adPlatform, adCode, format = null) {
168
174
  }
169
175
  return null;
170
176
  }
177
+ function extractPlatformCodes(data, adPlatform) {
178
+ const platformCodes = data.ads.filter((x) => {
179
+ return x.adPlatform === adPlatform;
180
+ }).sort((a, b) => b.visitAt - a.visitAt)[0];
181
+ if (!platformCodes) {
182
+ return null;
183
+ } else {
184
+ return {
185
+ adCodes: platformCodes.adCodes,
186
+ visitAt: platformCodes.visitAt
187
+ };
188
+ }
189
+ }
171
190
  function flatten(event, separator = "_") {
172
191
  return Object.assign(
173
192
  {},
@@ -192,27 +211,12 @@ function prepareTestRequestHeaders(headers, origUrl, origRequest, origResponse)
192
211
  headers["x-mhub-orig-response-status-code"] = origResponse.status;
193
212
  headers["x-mhub-orig-response-status-message"] = origResponse.statusText;
194
213
  }
195
- async function handleResponse(response, event) {
196
- if (!response.ok) {
197
- throw new Error(
198
- `HTTP Error code: ${response.status}, event: ${event}, body: ${await response.text()}`
199
- );
200
- }
201
- const body = await response.text();
202
- try {
203
- return JSON.parse(body);
204
- } catch (ex) {
205
- return body;
206
- }
207
- }
208
214
  async function handlePlatformRequest({
209
215
  source,
210
216
  baseUrl,
211
217
  requestOptions,
212
218
  query = "",
213
- event,
214
- logger,
215
- handlePlatformResponse = true
219
+ event
216
220
  }) {
217
221
  if (!baseUrl || baseUrl.length < 3) {
218
222
  throw new Error("URL for request was not provided or was not valid");
@@ -224,39 +228,42 @@ async function handlePlatformRequest({
224
228
  // eslint-disable-next-line @typescript-eslint/naming-convention
225
229
  headers: { ...requestOptions.headers }
226
230
  };
227
- const rawPlatformResponse = await fetch(
228
- fullUrl,
229
- fetchRequestOptions
230
- );
231
- if (process.env.PUBLISHER_TESTURL) {
232
- await handleTestRequest({
233
- fetchRequestOptions,
234
- fullUrl,
235
- query,
236
- rawPlatformResponse
237
- });
238
- }
239
- if (handlePlatformResponse) {
240
- const processedPlatformResponse = await handleResponse(
241
- rawPlatformResponse,
242
- event.event
243
- );
231
+ let rawPlatformResponse;
232
+ try {
233
+ rawPlatformResponse = await fetch(fullUrl, fetchRequestOptions);
234
+ } catch (e) {
244
235
  logEvent(
245
236
  source,
246
237
  event.event,
247
238
  event.region,
248
- rawPlatformResponse.status,
239
+ event.id,
240
+ 0,
249
241
  requestOptions.body ? JSON.stringify(requestOptions.body) : void 0
250
242
  );
251
- return processedPlatformResponse;
252
- } else {
243
+ throw e;
244
+ }
245
+ if (rawPlatformResponse) {
246
+ if (process.env.PUBLISHER_TESTURL) {
247
+ await handleTestRequest({
248
+ fetchRequestOptions,
249
+ fullUrl,
250
+ query,
251
+ rawPlatformResponse
252
+ });
253
+ }
253
254
  logEvent(
254
255
  source,
255
256
  event.event,
256
257
  event.region,
258
+ event.id,
257
259
  rawPlatformResponse.status,
258
260
  requestOptions.body ? JSON.stringify(requestOptions.body) : void 0
259
261
  );
262
+ if (!rawPlatformResponse.ok) {
263
+ throw new Error(
264
+ `HTTP Error code: ${rawPlatformResponse.status}, event: ${event.event}, region: ${event.region}, body: ${await rawPlatformResponse.text()}`
265
+ );
266
+ }
260
267
  }
261
268
  }
262
269
  async function handleTestRequest(params) {
@@ -450,7 +457,7 @@ async function processMessage(message, log2, platformPublisher, trigger) {
450
457
  source: platformPublisher.source,
451
458
  data: JSON.stringify(message)
452
459
  };
453
- const publishers = trigger(message, log2);
460
+ const publishers = await trigger(message, log2);
454
461
  if (!publishers.length) {
455
462
  logMessage(
456
463
  1,
@@ -470,7 +477,8 @@ async function processMessage(message, log2, platformPublisher, trigger) {
470
477
  platformPublisher,
471
478
  {
472
479
  event: message.event.code + (message.event.name ? "/" + message.event.name : ""),
473
- region: message.event.region
480
+ region: message.event.region,
481
+ id: message.messageId
474
482
  }
475
483
  );
476
484
  if (res) {
@@ -598,6 +606,13 @@ var facebook = {
598
606
  */
599
607
  async publish(log2, config, data, event) {
600
608
  if (typeof config.id === "string" && typeof config.access_token === "string") {
609
+ if (!(data.user_data.fbp || data.user_data.fbc || data.user_data.client_user_agent && data.user_data.client_ip_address)) {
610
+ logMessage(
611
+ 1,
612
+ `Facebook: excluded ${event.event} due to a lack of user identifiers`
613
+ );
614
+ return;
615
+ }
601
616
  const requestOptions = {
602
617
  method: "POST",
603
618
  body: JSON.stringify({ data: [removeUndefined(data)] }),
@@ -670,22 +685,31 @@ var googleAds = {
670
685
  "The imported event could not be attributed to a click. This may be because the event did not come from a Google Ads campaign.",
671
686
  "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."
672
687
  ];
688
+ let status = 0;
689
+ let error;
673
690
  if (responseJson.partialFailureError) {
691
+ status = 500;
674
692
  logMessage(1, responseJson.partialFailureError.message);
675
693
  const trimmedMessage = responseJson.partialFailureError.message.split("., at")[0] ?? "";
676
694
  if (!knownErrors.includes(trimmedMessage)) {
677
- throw new Error(responseJson.partialFailureError.message);
695
+ error = responseJson.partialFailureError.message;
678
696
  }
679
697
  } else if (responseJson.error) {
680
- throw new Error(responseJson.error.message);
698
+ status = 500;
699
+ error = responseJson.error.message;
681
700
  } else {
682
- logEvent(
683
- this.source,
684
- event.event,
685
- event.region,
686
- response.status,
687
- requestOptions.body
688
- );
701
+ status = 200;
702
+ }
703
+ logEvent(
704
+ this.source,
705
+ event.event,
706
+ event.region,
707
+ event.id,
708
+ status,
709
+ requestOptions.body
710
+ );
711
+ if (error) {
712
+ throw new Error(error);
689
713
  }
690
714
  },
691
715
  async getToken(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhubcloud/tms_core",
3
- "version": "3.4.6",
3
+ "version": "3.5.0",
4
4
  "description": "Core function for mHub Cloud TMS publishers",
5
5
  "homepage": "https://mhubcloud.com/",
6
6
  "license": "Apache 2.0",
@@ -42,6 +42,6 @@
42
42
  "scripts": {
43
43
  "test": "mocha ./tests/*.js --local-settings=./local.settings.json --timeout 100000",
44
44
  "dev": "ts-node index.ts",
45
- "build": "tsup ./src/index.ts --format cjs --target node20"
45
+ "build": "tsup ./src/index.ts --format cjs --target node22"
46
46
  }
47
47
  }
@@ -21,7 +21,7 @@ export async function defaultPublisher(
21
21
  serviceBusMessage: ServiceBusMessage,
22
22
  source: string,
23
23
  platformPublisher: PlatformPublisher,
24
- trigger: (a: unknown) => EventPublisher[],
24
+ trigger: (a: unknown) => Promise<EventPublisher[]>,
25
25
  ) {
26
26
  if (serviceBusMessage.$schema === 'mhub/pingtest') {
27
27
  return;
@@ -69,14 +69,13 @@ async function processMessage(
69
69
  message: ServiceBusMessage,
70
70
  log: Logger,
71
71
  platformPublisher: PlatformPublisher,
72
- trigger: (a: object, log: Logger) => EventPublisher[],
72
+ trigger: (a: object, log: Logger) => Promise<EventPublisher[]>,
73
73
  ) {
74
- // TODO Je potreba otestovat, ze se to nerozbije, kdyz jsme prestali prepisovat enqueuedAt na Date.now()
75
74
  const logProperties: Telemetry['properties'] = {
76
75
  source: platformPublisher.source,
77
76
  data: JSON.stringify(message),
78
77
  };
79
- const publishers = trigger(message, log);
78
+ const publishers = await trigger(message, log);
80
79
  if (!publishers.length) {
81
80
  utils.logMessage(
82
81
  1,
@@ -100,6 +99,7 @@ async function processMessage(
100
99
  message.event.code +
101
100
  (message.event.name ? '/' + message.event.name : ''),
102
101
  region: message.event.region,
102
+ id: message.messageId,
103
103
  },
104
104
  );
105
105
  if (res) {
@@ -4,6 +4,36 @@ import {
4
4
  PlatformRequestOptions,
5
5
  } from '../types/PlatformPublisher';
6
6
 
7
+ /* eslint-disable @typescript-eslint/naming-convention */
8
+ interface FacebookData {
9
+ event_name: string;
10
+ event_time: number;
11
+ action_source: string;
12
+ event_id: string;
13
+ event_source_url: string;
14
+ opt_out: boolean;
15
+ user_data: {
16
+ client_user_agent: string;
17
+ fbc: string;
18
+ fbp: string;
19
+ client_ip_address: string;
20
+ };
21
+ custom_data: {
22
+ currency: string;
23
+ value: number;
24
+ contents: {
25
+ id: string;
26
+ quantity: number;
27
+ item_price: number;
28
+ }[];
29
+ content_type: string;
30
+ order_id: string;
31
+ content_category: string;
32
+ };
33
+ [x: string]: unknown;
34
+ }
35
+ /* eslint-enable @typescript-eslint/naming-convention */
36
+
7
37
  export const facebook: PlatformPublisher = {
8
38
  source: 'Facebook',
9
39
  /**
@@ -18,6 +48,20 @@ export const facebook: PlatformPublisher = {
18
48
  typeof config.id === 'string' &&
19
49
  typeof config.access_token === 'string'
20
50
  ) {
51
+ if (
52
+ !(
53
+ (data as FacebookData).user_data.fbp ||
54
+ (data as FacebookData).user_data.fbc ||
55
+ ((data as FacebookData).user_data.client_user_agent &&
56
+ (data as FacebookData).user_data.client_ip_address)
57
+ )
58
+ ) {
59
+ utils.logMessage(
60
+ 1,
61
+ `Facebook: excluded ${event.event} due to a lack of user identifiers`,
62
+ );
63
+ return;
64
+ }
21
65
  const requestOptions: PlatformRequestOptions = {
22
66
  method: 'POST',
23
67
  body: JSON.stringify({ data: [utils.removeUndefined(data)] }),
@@ -115,23 +115,35 @@ export const googleAds: GoogleAdsPlatformPublisher = {
115
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.",
116
116
  ];
117
117
 
118
+ let status = 0;
119
+ let error;
120
+
118
121
  if (responseJson.partialFailureError) {
122
+ status = 500;
119
123
  utils.logMessage(1, responseJson.partialFailureError.message);
120
124
  const trimmedMessage =
121
125
  responseJson.partialFailureError.message.split('., at')[0] ?? '';
122
126
  if (!knownErrors.includes(trimmedMessage)) {
123
- throw new Error(responseJson.partialFailureError.message);
127
+ error = responseJson.partialFailureError.message;
124
128
  }
125
129
  } else if (responseJson.error) {
126
- throw new Error(responseJson.error.message);
130
+ status = 500;
131
+ error = responseJson.error.message;
127
132
  } else {
128
- utils.logEvent(
129
- this.source,
130
- event.event,
131
- event.region,
132
- response.status,
133
- requestOptions.body,
134
- );
133
+ status = 200;
134
+ }
135
+
136
+ utils.logEvent(
137
+ this.source,
138
+ event.event,
139
+ event.region,
140
+ event.id,
141
+ status,
142
+ requestOptions.body,
143
+ );
144
+
145
+ if (error) {
146
+ throw new Error(error);
135
147
  }
136
148
  },
137
149
 
@@ -1,4 +1,5 @@
1
1
  export interface EventToLog {
2
2
  event: string;
3
3
  region: string;
4
+ id: string;
4
5
  }
@@ -5,5 +5,6 @@ export interface ServiceBusMessage {
5
5
  name?: string;
6
6
  region: string;
7
7
  };
8
+ messageId: string;
8
9
  [x: string]: unknown;
9
10
  }
@@ -73,17 +73,23 @@ export function logEvent(
73
73
  publisher: string,
74
74
  event: string,
75
75
  region: string,
76
+ id: string,
76
77
  status: number,
77
78
  payload?: string,
78
79
  ): void {
80
+ const message =
81
+ status >= 200 && status < 300
82
+ ? 'Event dispatched to platform'
83
+ : 'Event dispatch failed';
79
84
  const properties: Telemetry['properties'] = {
80
85
  publisher,
81
86
  event,
82
87
  region,
88
+ id,
83
89
  status,
84
90
  payload,
85
91
  };
86
- logMessage(1, 'Event dispatched to platform', properties);
92
+ logMessage(1, message, properties);
87
93
  }
88
94
 
89
95
  /**
@@ -91,6 +97,10 @@ export function logEvent(
91
97
  */
92
98
  export function logException(exception: Error) {
93
99
  // log.warn(exception.message);
100
+ aiClient?.trackMetric({
101
+ name: `Error/${exception.name}`,
102
+ value: 1,
103
+ });
94
104
  aiClient?.trackException({ exception: exception });
95
105
  }
96
106
 
@@ -207,6 +217,27 @@ export function extractAdCode(
207
217
  return null;
208
218
  }
209
219
 
220
+ export function extractPlatformCodes(
221
+ data: {
222
+ ads: Ad[];
223
+ },
224
+ adPlatform: string,
225
+ ) {
226
+ const platformCodes = data.ads
227
+ .filter((x) => {
228
+ return x.adPlatform === adPlatform;
229
+ })
230
+ .sort((a, b) => b.visitAt - a.visitAt)[0];
231
+ if (!platformCodes) {
232
+ return null;
233
+ } else {
234
+ return {
235
+ adCodes: platformCodes.adCodes,
236
+ visitAt: platformCodes.visitAt,
237
+ };
238
+ }
239
+ }
240
+
210
241
  /**
211
242
  * Transforms an object to be flat, for instance for GA4
212
243
  * @param event Event that is received
@@ -263,28 +294,6 @@ export function prepareTestRequestHeaders(
263
294
  headers['x-mhub-orig-response-status-message'] = origResponse.statusText;
264
295
  }
265
296
 
266
- /**
267
- * Handles HTTP response from node-fetch
268
- * @param response Node-fetch response object
269
- * @param event Event for which the response is
270
- * @returns Deserialized response JSON
271
- */
272
- export async function handleResponse(response: Response, event: string) {
273
- if (!response.ok) {
274
- throw new Error(
275
- `HTTP Error code: ${
276
- response.status
277
- }, event: ${event}, body: ${await response.text()}`,
278
- );
279
- }
280
- const body = await response.text();
281
- try {
282
- return JSON.parse(body) as object;
283
- } catch (ex) {
284
- return body;
285
- }
286
- }
287
-
288
297
  interface HandleRequestParams {
289
298
  source: string;
290
299
  baseUrl: string;
@@ -307,8 +316,6 @@ export async function handlePlatformRequest({
307
316
  requestOptions,
308
317
  query = '',
309
318
  event,
310
- logger,
311
- handlePlatformResponse = true,
312
319
  }: HandleRequestParams) {
313
320
  if (!baseUrl || baseUrl.length < 3) {
314
321
  // ? Mohli bychom zacit delat poradnou kontrolu validity vstupnich paramteru. Idealne pres ZOD
@@ -323,43 +330,48 @@ export async function handlePlatformRequest({
323
330
  headers: { ...requestOptions.headers },
324
331
  };
325
332
 
326
- const rawPlatformResponse: Response = await fetch(
327
- fullUrl,
328
- fetchRequestOptions,
329
- );
330
-
331
- if (process.env.PUBLISHER_TESTURL) {
332
- await handleTestRequest({
333
- fetchRequestOptions,
334
- fullUrl,
335
- query,
336
- rawPlatformResponse,
337
- });
338
- }
339
-
340
- if (handlePlatformResponse) {
341
- const processedPlatformResponse = await handleResponse(
342
- rawPlatformResponse,
343
- event.event,
344
- );
333
+ let rawPlatformResponse: Response | undefined;
345
334
 
335
+ try {
336
+ rawPlatformResponse = await fetch(fullUrl, fetchRequestOptions);
337
+ } catch (e: unknown) {
346
338
  logEvent(
347
339
  source,
348
340
  event.event,
349
341
  event.region,
350
- rawPlatformResponse.status,
342
+ event.id,
343
+ 0,
351
344
  requestOptions.body ? JSON.stringify(requestOptions.body) : undefined,
352
345
  );
346
+ throw e;
347
+ }
348
+
349
+ if (rawPlatformResponse) {
350
+ if (process.env.PUBLISHER_TESTURL) {
351
+ await handleTestRequest({
352
+ fetchRequestOptions,
353
+ fullUrl,
354
+ query,
355
+ rawPlatformResponse,
356
+ });
357
+ }
353
358
 
354
- return processedPlatformResponse;
355
- } else {
356
359
  logEvent(
357
360
  source,
358
361
  event.event,
359
362
  event.region,
363
+ event.id,
360
364
  rawPlatformResponse.status,
361
365
  requestOptions.body ? JSON.stringify(requestOptions.body) : undefined,
362
366
  );
367
+
368
+ if (!rawPlatformResponse.ok) {
369
+ throw new Error(
370
+ `HTTP Error code: ${rawPlatformResponse.status}, event: ${
371
+ event.event
372
+ }, region: ${event.region}, body: ${await rawPlatformResponse.text()}`,
373
+ );
374
+ }
363
375
  }
364
376
  }
365
377
 
@@ -84,7 +84,8 @@ async function test(serviceBusMessage) {
84
84
  );
85
85
  }
86
86
 
87
- function trigger(data) {
87
+ // eslint-disable-next-line @typescript-eslint/require-await
88
+ async function trigger(data) {
88
89
  const publishers = [];
89
90
  let publisher;
90
91
 
package/tests/CJ.js CHANGED
@@ -118,7 +118,8 @@ async function test(serviceBusMessage) {
118
118
  );
119
119
  }
120
120
 
121
- function trigger(data, log) {
121
+ // eslint-disable-next-line @typescript-eslint/require-await
122
+ async function trigger(data, log) {
122
123
  const publishers = [];
123
124
  if (data.event.code === 'purchase') {
124
125
  let publisher = CJPurchasePublisher;
package/tests/Facebook.js CHANGED
@@ -42,7 +42,7 @@ describe('test Facebook publisher', function () {
42
42
  quantity: 1,
43
43
  },
44
44
  ],
45
- enqueuedAt: 1657003409370,
45
+ enqueuedAt: Date.now(),
46
46
  ads: [],
47
47
  };
48
48
  await test(serviceBusMessage);
@@ -63,15 +63,16 @@ async function test(serviceBusMessage) {
63
63
  );
64
64
  }
65
65
 
66
- function trigger(data) {
66
+ // eslint-disable-next-line @typescript-eslint/require-await
67
+ async function trigger(data) {
67
68
  const publishers = [];
68
69
  let publisher;
69
70
 
70
71
  // Dummy config, replace with your own
71
72
  const config = {
72
- id: 'fffff',
73
+ id: process.env.Publisher_Facebook_PixelId,
73
74
  // eslint-disable-next-line @typescript-eslint/naming-convention
74
- access_token: 'zzzz',
75
+ access_token: process.env.Publisher_Facebook_AccessToken,
75
76
  };
76
77
 
77
78
  switch (data.event?.code) {
@@ -48,6 +48,7 @@ describe('test GoogleAds publisher', function () {
48
48
  adPlatform: 'googleads',
49
49
  adCodes: {
50
50
  gclid: 'mhubcloud',
51
+ gcl_src: 'aw.ds',
51
52
  },
52
53
  visitAt: 1727173492000,
53
54
  },
@@ -71,7 +72,8 @@ async function test(serviceBusMessage) {
71
72
  );
72
73
  }
73
74
 
74
- function trigger(data) {
75
+ // eslint-disable-next-line @typescript-eslint/require-await
76
+ async function trigger(data) {
75
77
  const publishers = [];
76
78
  let publisher;
77
79
 
@@ -97,6 +99,7 @@ function trigger(data) {
97
99
  publishers.push(publisher);
98
100
  break;
99
101
  }
102
+
100
103
  return publishers;
101
104
  }
102
105
 
@@ -63,7 +63,8 @@ async function test(serviceBusMessage) {
63
63
  );
64
64
  }
65
65
 
66
- function trigger(data) {
66
+ // eslint-disable-next-line @typescript-eslint/require-await
67
+ async function trigger(data) {
67
68
  const publishers = [];
68
69
  const config = { trackingId: 'G-289495622' };
69
70
 
package/tests/Piano.js CHANGED
@@ -63,7 +63,8 @@ async function test(serviceBusMessage) {
63
63
  );
64
64
  }
65
65
 
66
- function trigger(data) {
66
+ // eslint-disable-next-line @typescript-eslint/require-await
67
+ async function trigger(data) {
67
68
  const publishers = [];
68
69
  const config = {
69
70
  collectionDomain: 'xxxxx.xiti.com', // Secured collection domain
@@ -54,7 +54,8 @@ async function test(serviceBusMessage) {
54
54
  );
55
55
  }
56
56
 
57
- function trigger(data) {
57
+ // eslint-disable-next-line @typescript-eslint/require-await
58
+ async function trigger(data) {
58
59
  const publishers = [];
59
60
  const config = { trackingId: 'UA-xxxxxxx-yy' };
60
61