@mhubcloud/tms_core 3.5.1 → 3.6.0-beta.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,433 +0,0 @@
1
- import * as crypto from 'crypto';
2
- import { Logger } from '@azure/functions';
3
- import * as appInsights from 'applicationinsights';
4
- import {
5
- SeverityLevel,
6
- Telemetry,
7
- } from 'applicationinsights/out/Declarations/Contracts';
8
- import { EventPublisherBaseClass } from '../types/EventPublisher';
9
- import { EventToLog } from '../types/EventToLog';
10
- import { PlatformRequestOptions } from '../types/PlatformPublisher';
11
- import { TypeSafeObject } from '../types/UtilTypes';
12
-
13
- let aiClient: appInsights.TelemetryClient | null = null;
14
-
15
- if (process.env.APPLICATIONINSIGHTS_CONNECTION_STRING) {
16
- appInsights
17
- .setup() // Set environment variable APPLICATIONINSIGHTS_CONNECTION_STRING, so setup() can be called w/o parameters
18
- .setAutoCollectDependencies(false)
19
- .setAutoCollectConsole(true, false)
20
- .setSendLiveMetrics(false)
21
- .setAutoCollectRequests(false)
22
- .setAutoCollectPerformance(false)
23
- .setUseDiskRetryCaching(false)
24
- .start();
25
- aiClient = appInsights.defaultClient;
26
- }
27
-
28
- /**
29
- * Base class for Event Publisher classes
30
- * Facilitates registration (depracated) and configuration
31
- */
32
- export class EventPublisherBase implements EventPublisherBaseClass {
33
- config;
34
- constructor(config: TypeSafeObject) {
35
- this.config = config;
36
- }
37
-
38
- /** @deprecated Please use a 'new Class(config)' notation. The .register() method will be deleted in future major version
39
- * Registers a new instance of @see EventPublisherBase -derived class with array of publishers
40
- * @param {EventPublisher[]} publishers Tha publishers array to register with
41
- * @param {Object} config Configuration to pass to new instance
42
- * Call this static method on @see EventPublisherBase -derived class.
43
- * It will create its instance and pass @param config to its constructor.
44
- * Than it will add resulting instance of @param publishers.
45
- */
46
- public static register(publishers: unknown[], config: TypeSafeObject) {
47
- // ? Otestovat zpetnou kompatibilitu
48
- publishers.push(
49
- new this(
50
- config,
51
- ) /* Trick: Creates instance of the specific derived class `register` has been called on */,
52
- );
53
- }
54
- }
55
-
56
- /**
57
- * Logs message to Azure Function context and to Application Insights (if configured)
58
- */
59
- export function logMessage(
60
- level: SeverityLevel,
61
- message: string,
62
- properties?: Telemetry['properties'],
63
- ): void {
64
- console.log(message);
65
- aiClient?.trackTrace({
66
- message,
67
- severity: level,
68
- properties,
69
- });
70
- }
71
-
72
- export function logEvent(
73
- publisher: string,
74
- event: string,
75
- region: string,
76
- status: number,
77
- payload: string | undefined,
78
- id: string,
79
- ): void {
80
- let message = '';
81
-
82
- if (status || status === 0) {
83
- message =
84
- status >= 200 && status < 300
85
- ? 'Event dispatched to platform'
86
- : 'Event dispatch failed';
87
- } else {
88
- message = 'Event dispatched to platform';
89
- }
90
-
91
- let properties: Telemetry['properties'] = {
92
- publisher,
93
- event,
94
- region,
95
- id,
96
- status,
97
- payload,
98
- };
99
-
100
- properties = removeUndefined(properties);
101
-
102
- logMessage(1, message, properties);
103
- }
104
-
105
- /**
106
- * Logs exception to Azure Function context and to Application Insights (if configured)
107
- */
108
- export function logException(exception: Error) {
109
- // log.warn(exception.message);
110
- aiClient?.trackMetric({
111
- name: `Error/${exception.name}`,
112
- value: 1,
113
- });
114
- aiClient?.trackException({ exception: exception });
115
- }
116
-
117
- /**
118
- * Creates a deep copy of given object removing any properties or array items with undefined value
119
- * @param originalValue Object or array to copy. If value is neither array nor object it's returned as passed.
120
- * @returns Deep copy of @param originalValue (providing array or object has been passed in). Any properties or array item with undefined value are skipped when making the copy, recursively. Returns @param obj untouched if neither array nor object has been passed in.
121
- */
122
- export function removeUndefined(
123
- originalValue: TypeSafeObject | unknown[],
124
- ): TypeSafeObject | unknown[] {
125
- // ? Toto bych rad otestoval. Klidne unit testy.
126
- if (originalValue instanceof Array) {
127
- return removeUndefinedFromArray(originalValue);
128
- } else if (originalValue instanceof Object) {
129
- return removeUndefinedFromObject(originalValue);
130
- } else {
131
- return originalValue;
132
- }
133
- }
134
-
135
- function removeUndefinedFromObject(object: TypeSafeObject) {
136
- const resultObject: TypeSafeObject = {};
137
- for (const property in object) {
138
- if (
139
- Object.prototype.hasOwnProperty.call(object, property) &&
140
- object[property] !== undefined
141
- ) {
142
- resultObject[property] = removeUndefined(
143
- object[property] as TypeSafeObject | unknown[],
144
- );
145
- }
146
- }
147
- return resultObject;
148
- }
149
-
150
- function removeUndefinedFromArray(array: unknown[]) {
151
- const resultArray: unknown[] = [];
152
- for (const item of array) {
153
- if (item !== undefined) {
154
- resultArray.push(removeUndefined(item as TypeSafeObject | unknown[]));
155
- }
156
- }
157
- return resultArray;
158
- }
159
-
160
- /**
161
- * Computes SHA-256 hash of an email string. If email is not a string, undefined is returned
162
- */
163
- export function encodeEmail(email: string) {
164
- return computeSha256HashFromString(email);
165
- }
166
-
167
- /**
168
- * Computes SHA-256 hash of any string. If provided value is not a string, undefined is returned
169
- */
170
- function computeSha256HashFromString(stringToHash: string) {
171
- if (typeof stringToHash !== 'string') {
172
- return undefined;
173
- }
174
- return crypto
175
- .createHash('sha256')
176
- .update(stringToHash.toLowerCase(), 'ascii')
177
- .digest('hex')
178
- .toLowerCase();
179
- }
180
-
181
- interface Ad {
182
- adPlatform: string;
183
- visitAt: number;
184
- adCodes?: Record<string, string>;
185
- }
186
-
187
- interface AdCodeInfo {
188
- code: string;
189
- visitAt: number;
190
- }
191
-
192
- /**
193
- * Extracts ad code from standardly structured event data (in legacy or new format)
194
- * @param data Event data to extract ad code from. As you can see the `ads` field is accepted in 2 formats the first being legacy and the later being new one.
195
- * @param adPlatform value of `data.ads.adPlatform` field to filter ad codes by platform (this is for new style data; ignored for legacy data)
196
- * @param adCode value of `data.ads.adType` field to filter items in `data.ads` by (this is for legacy style data); als used for new style data to identify the main parameter from `data.ads.adCodes` to obtain
197
- * @param format If this custom function is provided it forats the code returned, so the function does not return object but string formated by this function. But nothing prevents it from returning object if the consumer can hanlde it.
198
- */
199
- export function extractAdCode(
200
- data: {
201
- ads: Ad[];
202
- },
203
- adPlatform: string,
204
- adCode: string,
205
- format: ((code: AdCodeInfo, ad: Ad) => string | null | unknown) | null = null,
206
- ): AdCodeInfo | null | string | unknown {
207
- // ? Tady se udělalo hodně změn, protože se dal pryč legacy kód. Pro kontrolu je potřebná lepší znalost dat.
208
- // ? Udelat unit test
209
- const ad = data.ads
210
- .filter((x) => {
211
- return x.adPlatform === adPlatform;
212
- })
213
- .sort((a, b) => b.visitAt - a.visitAt)[0];
214
- if (!ad) {
215
- return null;
216
- }
217
- const code: string | null | undefined = ad.adCodes
218
- ? ad.adCodes[adCode]
219
- : null;
220
- if (code) {
221
- const returnValue: AdCodeInfo = { code: code, visitAt: ad.visitAt };
222
- if (format) {
223
- return format(returnValue, ad);
224
- }
225
- return returnValue;
226
- }
227
- return null;
228
- }
229
-
230
- export function extractPlatformCodes(
231
- data: {
232
- ads: Ad[];
233
- },
234
- adPlatform: string,
235
- ) {
236
- const platformCodes = data.ads
237
- .filter((x) => {
238
- return x.adPlatform === adPlatform;
239
- })
240
- .sort((a, b) => b.visitAt - a.visitAt)[0];
241
- if (!platformCodes) {
242
- return null;
243
- } else {
244
- return {
245
- adCodes: platformCodes.adCodes,
246
- visitAt: platformCodes.visitAt,
247
- };
248
- }
249
- }
250
-
251
- /**
252
- * Transforms an object to be flat, for instance for GA4
253
- * @param event Event that is received
254
- * @param separator Separator that will be used to unnest the structure. The default separator is underscore _
255
- * @returns
256
- */
257
- export function flatten(
258
- event: TypeSafeObject,
259
- separator = '_',
260
- ): TypeSafeObject {
261
- // ? Tady je pro safety potřeba celou funkci vylepšit
262
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
263
- return Object.assign(
264
- {},
265
- // eslint-disable-next-line @typescript-eslint/naming-convention, @typescript-eslint/no-unsafe-argument
266
- ...(function _flatten(
267
- o: TypeSafeObject,
268
- separator: string,
269
- parent?: string,
270
- ): any {
271
- return [].concat(
272
- // eslint-disable-next-line @typescript-eslint/no-unsafe-argument
273
- ...Object.keys(o).map((key) => {
274
- if (typeof o[key] === 'object' && o[key] !== null) {
275
- const newParent = parent ? parent + separator + key : key;
276
- // eslint-disable-next-line @typescript-eslint/no-unsafe-return
277
- return _flatten(o[key] as TypeSafeObject, separator, newParent);
278
- } else {
279
- const newKey = parent ? parent + separator + key : key;
280
- return { [newKey]: o[key] };
281
- }
282
- }),
283
- );
284
- })(event, separator),
285
- );
286
- }
287
-
288
- /**
289
- * Enhances header before sending test request to secondary target (like Pipedream)
290
- * @param headers Request header dictionary. This contains the original headers and will be ehnanced.
291
- * @param origUrl Original URL the request has been sent to
292
- * @param origRequest The entire original request options
293
- * @param origResponse Response recievend from original request
294
- */
295
- export function prepareTestRequestHeaders(
296
- headers: TypeSafeObject,
297
- origUrl: string,
298
- origRequest: TypeSafeObject,
299
- origResponse: Response,
300
- ) {
301
- headers['x-mhub-orig-url'] = origUrl;
302
- headers['x-mhub-orig-method'] = origRequest.method;
303
- headers['x-mhub-orig-response-status-code'] = origResponse.status;
304
- headers['x-mhub-orig-response-status-message'] = origResponse.statusText;
305
- }
306
-
307
- interface HandleRequestParams {
308
- source: string;
309
- baseUrl: string;
310
- requestOptions: PlatformRequestOptions;
311
- query?: string;
312
- event: EventToLog;
313
- logger: Logger;
314
- handlePlatformResponse?: boolean;
315
- }
316
-
317
- /**
318
- *
319
- * @param params
320
- * @returns
321
- */
322
-
323
- export async function handlePlatformRequest({
324
- source,
325
- baseUrl,
326
- requestOptions,
327
- query = '',
328
- event,
329
- }: HandleRequestParams) {
330
- if (!baseUrl || baseUrl.length < 3) {
331
- // ? Mohli bychom zacit delat poradnou kontrolu validity vstupnich paramteru. Idealne pres ZOD
332
- throw new Error('URL for request was not provided or was not valid');
333
- }
334
- const fullUrl = baseUrl + query;
335
-
336
- const fetchRequestOptions: RequestInit = {
337
- method: requestOptions.method ?? 'POST',
338
- body: requestOptions.body ?? undefined,
339
- // eslint-disable-next-line @typescript-eslint/naming-convention
340
- headers: { ...requestOptions.headers },
341
- };
342
-
343
- let rawPlatformResponse: Response | undefined;
344
-
345
- try {
346
- rawPlatformResponse = await fetch(fullUrl, fetchRequestOptions);
347
- } catch (e: unknown) {
348
- logEvent(
349
- source,
350
- event.event,
351
- event.region,
352
- 0,
353
- requestOptions.body ? JSON.stringify(requestOptions.body) : undefined,
354
- event.id,
355
- );
356
- throw e;
357
- }
358
-
359
- if (rawPlatformResponse) {
360
- if (process.env.PUBLISHER_TESTURL) {
361
- await handleTestRequest({
362
- fetchRequestOptions,
363
- fullUrl,
364
- query,
365
- rawPlatformResponse,
366
- });
367
- }
368
-
369
- logEvent(
370
- source,
371
- event.event,
372
- event.region,
373
- rawPlatformResponse.status,
374
- requestOptions.body ? JSON.stringify(requestOptions.body) : undefined,
375
- event.id,
376
- );
377
-
378
- if (!rawPlatformResponse.ok) {
379
- throw new Error(
380
- `HTTP Error code: ${rawPlatformResponse.status}, event: ${
381
- event.event
382
- }, region: ${event.region}, body: ${await rawPlatformResponse.text()}`,
383
- );
384
- }
385
- }
386
- }
387
-
388
- interface HandleTestRequestParams {
389
- fetchRequestOptions: RequestInit;
390
- fullUrl: string;
391
- query: string | undefined;
392
- rawPlatformResponse: Response;
393
- }
394
-
395
- async function handleTestRequest(params: HandleTestRequestParams) {
396
- try {
397
- params.fetchRequestOptions.headers = {
398
- ...params.fetchRequestOptions.headers,
399
- // eslint-disable-next-line @typescript-eslint/naming-convention
400
- 'x-mhub-orig-url': params.fullUrl,
401
- // eslint-disable-next-line @typescript-eslint/naming-convention
402
- 'x-mhub-orig-method': params.fetchRequestOptions.method ?? '',
403
- // eslint-disable-next-line @typescript-eslint/naming-convention
404
- 'x-mhub-orig-response-status-code': `${params.rawPlatformResponse.status}`,
405
- // eslint-disable-next-line @typescript-eslint/naming-convention
406
- 'x-mhub-orig-response-status-message':
407
- params.rawPlatformResponse.statusText,
408
- };
409
- await fetch(
410
- `${process.env.PUBLISHER_TESTURL ?? ''} + ${params.query ?? ''}`,
411
- params.fetchRequestOptions,
412
- );
413
- } catch {
414
- /* empty */
415
- }
416
- }
417
-
418
- export function formatError(error: Error | Error['message']) {
419
- let formattedError: Error;
420
- if (error instanceof Error) {
421
- formattedError = error;
422
- if (error.cause) {
423
- formattedError.message = `${
424
- error.message
425
- } Cause: ${error.cause.toString()}`;
426
- } else {
427
- formattedError.message = error.message;
428
- }
429
- } else {
430
- formattedError = new Error(error);
431
- }
432
- return formattedError;
433
- }
@@ -1,133 +0,0 @@
1
- const localSettings = require('../local.settings.json').Values;
2
- process.env = { ...process.env, ...localSettings };
3
- const publisher = require('../dist/index.js').publisher.AffiliatePort;
4
- const defaultPublisher = require('../dist/index.js').DefaultPublisher;
5
- const utils = require('../dist/index.js').utils;
6
- const testUtils = require('../dist/index.js').testUtils;
7
-
8
- describe('test AffiliatePort publisher', function () {
9
- it('purchase', async function () {
10
- var serviceBusMessage = {
11
- event: {
12
- region: 'smarty.cz',
13
- id: '825333270-1011.1659006863812',
14
- code: 'purchase',
15
- },
16
- user: {
17
- ts: 'customer.HL',
18
- email: 'jan.brzobohaty@crossmasters.com',
19
- id: 'a2992fb8-40ac-4b88-9cf5-7fba02f1f611',
20
- },
21
- session: {
22
- userAgent:
23
- 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Version/15.2 Safari/605.1.15',
24
- ip: '217.195.174.253',
25
- },
26
- permissions: [],
27
- currencyCode: 'CZK',
28
- publisher: {
29
- code: 'www.smarty.cz',
30
- tm: 'GTM-NVNTFG',
31
- tmv: '193',
32
- },
33
- $schema: 'https://schema.measurementhub.net/mhub/2021-12-27/purchase',
34
- id: '3511111111',
35
- revenue: 10,
36
- products: [
37
- {
38
- id: '67137',
39
- price: 10,
40
- quantity: 1,
41
- category: 'Playstation',
42
- masterType: 'Konzole',
43
- },
44
- {
45
- id: '67137',
46
- price: 20,
47
- quantity: 1,
48
- category: 'Monitor',
49
- masterType: 'Video',
50
- },
51
- ],
52
- location: 'https://www.smarty.cz/checkoutSuccess',
53
- referrer:
54
- 'https://www.smarty.cz/Order/AddReservation?productId=57873&shopAddressId=24798',
55
- enqueuedAt: 1659006864038,
56
- ads: [
57
- {
58
- adPlatform: 'affiliateport',
59
- adCodes: {
60
- transaction_id: '816f5670dbf52c3dc27a7e849903c327d705ef643993fbab3',
61
- },
62
- visitAt: 1658392668000,
63
- },
64
- ],
65
- EventProcessedUtcTime: '2022-07-28T11:54:50.2528883Z',
66
- PartitionId: 0,
67
- EventEnqueuedUtcTime: '2022-07-28T11:14:24.1170000Z',
68
- };
69
- await test(serviceBusMessage);
70
- });
71
- });
72
-
73
- /**
74
- * Test function
75
- * @param [object] object obsahující nastavení testovací parametry
76
- */
77
- async function test(serviceBusMessage) {
78
- await defaultPublisher(
79
- testUtils.context,
80
- serviceBusMessage,
81
- 'AffiliatePort',
82
- publisher,
83
- trigger,
84
- );
85
- }
86
-
87
- // eslint-disable-next-line @typescript-eslint/require-await
88
- async function trigger(data) {
89
- const publishers = [];
90
- let publisher;
91
-
92
- // Dummy config, replace with your own
93
- const config = {
94
- offerId: 123,
95
- };
96
-
97
- switch (data.event?.code) {
98
- case 'purchase':
99
- publisher = AffiliatePortPurchasePublisher;
100
- publisher.register(publishers, config);
101
- break;
102
- }
103
- return publishers;
104
- }
105
-
106
- /**
107
- * Implements Purchase event publisher for Facebook
108
- * @implements {EventPublisher<{id: string, access_token: string}>}
109
- */
110
- class AffiliatePortPurchasePublisher extends utils.EventPublisherBase {
111
- /**
112
- * Maps `purchase` event data to AffiliatePort payload
113
- * @param {Object} data `purchase` event data
114
- * @returns {Object} Data mapped from incoming Service Bus message to platform payload
115
- */
116
- map(data) {
117
- return {
118
- orderId: data.id,
119
- revenue: data.revenue, // should be without VAT and shipping, payment fees
120
- products: data.products.map((pr) => ({
121
- quantity: pr.quantity, // e.g. 1
122
- price: pr.price, // e.g. 109
123
- category: pr.category.replace(/ /g, '-'), // e.g. Playstation
124
- masterType: pr.masterType, // e.g. Konzole
125
- })),
126
- transactionID: utils.extractAdCode(
127
- data,
128
- 'affiliateport',
129
- 'transaction_id',
130
- )?.code, // AffiliatePort identificator taken automatically from URL
131
- };
132
- }
133
- }