@mhubcloud/tms_core 3.6.0 → 3.7.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.
Files changed (2) hide show
  1. package/dist/index.js +236 -25
  2. package/package.json +3 -3
package/dist/index.js CHANGED
@@ -43,7 +43,8 @@ var broker_exports = {};
43
43
  __export(broker_exports, {
44
44
  dispatchEvent: () => dispatchEvent,
45
45
  logToCosmosDb: () => logToCosmosDb,
46
- resolveEvent: () => resolveEvent
46
+ resolveEvent: () => resolveEvent,
47
+ scheduleEvent: () => scheduleEvent
47
48
  });
48
49
  var import_cosmos = require("@azure/cosmos");
49
50
  var import_service_bus = require("@azure/service-bus");
@@ -105,7 +106,19 @@ function logMessage(level, message, properties) {
105
106
  function logEvent(publisher2, event, region, status, payload, id) {
106
107
  let message = "";
107
108
  if (status || status === 0) {
108
- message = status >= 200 && status < 300 ? "Event dispatched to platform" : "Event dispatch failed";
109
+ if (status >= 200 && status < 300) {
110
+ aiClient?.trackMetric({
111
+ name: `Dispatched/${publisher2}-${event}-${region}`,
112
+ value: 1
113
+ });
114
+ message = "Event dispatched to platform";
115
+ } else {
116
+ aiClient?.trackMetric({
117
+ name: `Failed/${publisher2}-${event}-${region}`,
118
+ value: 1
119
+ });
120
+ message = "Event dispatch failed";
121
+ }
109
122
  } else {
110
123
  message = "Event dispatched to platform";
111
124
  }
@@ -275,7 +288,7 @@ async function handlePlatformRequest({
275
288
  event.event,
276
289
  event.region,
277
290
  0,
278
- requestOptions.body ? JSON.stringify(requestOptions.body) : void 0,
291
+ requestOptions.method === "POST" ? JSON.stringify(requestOptions.body) : query,
279
292
  event.id
280
293
  );
281
294
  throw e;
@@ -294,7 +307,7 @@ async function handlePlatformRequest({
294
307
  event.event,
295
308
  event.region,
296
309
  rawPlatformResponse.status,
297
- requestOptions.body ? JSON.stringify(requestOptions.body) : void 0,
310
+ requestOptions.method === "POST" ? JSON.stringify(requestOptions.body) : query,
298
311
  event.id
299
312
  );
300
313
  if (!rawPlatformResponse.ok) {
@@ -377,7 +390,15 @@ function resolveEvent(event, publisherConfig) {
377
390
  }
378
391
  match = matchEvent(event, publisherEvent);
379
392
  if (match) {
380
- publishers.push(publisher2);
393
+ if (typeof publisherEvent === "object" && publisherEvent.schedule) {
394
+ publishers.push({
395
+ queueName: publisher2,
396
+ schedule: publisherEvent.schedule
397
+ });
398
+ } else {
399
+ publishers.push(publisher2);
400
+ }
401
+ break;
381
402
  }
382
403
  }
383
404
  }
@@ -390,16 +411,17 @@ function resolveEvent(event, publisherConfig) {
390
411
  }
391
412
  function matchEvent(event, publisherEvent) {
392
413
  if (typeof publisherEvent === "string") {
393
- const regex = new RegExp(publisherEvent);
414
+ const regex = new RegExp(`^(?:${publisherEvent})$`);
394
415
  const match = regex.test(event.code);
395
416
  return match;
396
417
  } else if (typeof publisherEvent === "object") {
418
+ const eventParams = publisherEvent.event;
397
419
  let paramMatch = true;
398
- for (const key of Object.keys(publisherEvent)) {
399
- const publisherEventParam = publisherEvent[key];
420
+ for (const key of Object.keys(eventParams)) {
421
+ const publisherEventParam = eventParams[key];
400
422
  const eventParam = event[key];
401
423
  if (typeof publisherEventParam === "string" && typeof eventParam === "string") {
402
- const regex = new RegExp(publisherEventParam);
424
+ const regex = new RegExp(`^(?:${publisherEventParam})$`);
403
425
  paramMatch = regex.test(eventParam);
404
426
  if (!paramMatch) {
405
427
  return false;
@@ -424,17 +446,37 @@ async function dispatchEvent(message, queues) {
424
446
  }
425
447
  await Promise.all(
426
448
  queues.map(async (queue) => {
449
+ let queueName;
427
450
  try {
428
451
  let sender;
429
- if (senders[queue]) {
430
- sender = senders[queue];
452
+ let serviceBusMessage;
453
+ if (typeof queue === "string") {
454
+ queueName = queue;
455
+ serviceBusMessage = { body: message };
456
+ } else if (typeof queue === "object") {
457
+ queueName = queue.queueName;
458
+ serviceBusMessage = {
459
+ body: message,
460
+ scheduledEnqueueTimeUtc: parseScheduleOptions(queue.schedule)
461
+ };
431
462
  } else {
432
- senders[queue] = serviceBusClient.createSender(queue);
433
- sender = senders[queue];
463
+ console.log(`Invalid queue name format`);
464
+ }
465
+ if (queueName && serviceBusMessage) {
466
+ if (senders[queueName]) {
467
+ sender = senders[queueName];
468
+ } else {
469
+ senders[queueName] = serviceBusClient.createSender(queueName);
470
+ sender = senders[queueName];
471
+ }
472
+ if (sender) {
473
+ await sender.sendMessages(serviceBusMessage);
474
+ }
434
475
  }
435
- await sender.sendMessages({ body: message });
436
476
  } catch (e) {
437
- console.log(`Sending to queue ${queue} failed`);
477
+ console.log(
478
+ `Sending to queue ${queueName ?? "unknown"} failed. Cause: ${e instanceof Error ? e.message : ""}`
479
+ );
438
480
  }
439
481
  })
440
482
  );
@@ -458,6 +500,71 @@ async function logToCosmosDb(message) {
458
500
  logException(e);
459
501
  }
460
502
  }
503
+ function parseScheduleOptions(options) {
504
+ let dispatchTime;
505
+ const now = /* @__PURE__ */ new Date();
506
+ if ("timestamp" in options) {
507
+ dispatchTime = new Date(options.timestamp);
508
+ } else if ("scheduleHour" in options) {
509
+ dispatchTime = new Date(
510
+ now.getFullYear(),
511
+ now.getMonth(),
512
+ now.getDate() + (options.dayOffset ?? 0),
513
+ options.scheduleHour,
514
+ options.scheduleMinute ?? 0,
515
+ 0,
516
+ 0
517
+ );
518
+ } else if ("scheduleInterval" in options && "startHour" in options) {
519
+ const hour = now.getHours();
520
+ const nextHour = Math.ceil(hour / options.scheduleInterval) * options.scheduleInterval;
521
+ dispatchTime = new Date(
522
+ now.getFullYear(),
523
+ now.getMonth(),
524
+ now.getDate() + (options.dayOffset ?? 0),
525
+ nextHour,
526
+ options.startMinute ?? 0,
527
+ 0,
528
+ 0
529
+ );
530
+ if (nextHour >= 24) {
531
+ dispatchTime.setDate(dispatchTime.getDate() + 1);
532
+ dispatchTime.setHours(options.startHour, options.startMinute);
533
+ }
534
+ } else {
535
+ throw new Error("Invalid scheduling configuration");
536
+ }
537
+ return dispatchTime;
538
+ }
539
+ async function scheduleEvent(message, queue, options) {
540
+ if (!serviceBusClient) {
541
+ if (!serviceBusConnectionString) {
542
+ throw new Error("Service Bus connection string is missing.");
543
+ }
544
+ serviceBusClient = new import_service_bus.ServiceBusClient(serviceBusConnectionString);
545
+ }
546
+ try {
547
+ let sender;
548
+ if (senders[queue]) {
549
+ sender = senders[queue];
550
+ } else {
551
+ senders[queue] = serviceBusClient.createSender(queue);
552
+ sender = senders[queue];
553
+ }
554
+ const dispatchTime = parseScheduleOptions(options);
555
+ const serviceBusMessage = {
556
+ body: message,
557
+ scheduledEnqueueTimeUtc: dispatchTime
558
+ };
559
+ await sender.sendMessages(serviceBusMessage);
560
+ return dispatchTime;
561
+ } catch (e) {
562
+ console.log(
563
+ `Scheduling message to queue ${queue} failed. Cause: ${e instanceof Error ? e.message : ""}`
564
+ );
565
+ return null;
566
+ }
567
+ }
461
568
 
462
569
  // src/defaultPublisher.ts
463
570
  async function defaultPublisher(azureFunctionContext, serviceBusMessage, source, platformPublisher, trigger) {
@@ -839,6 +946,114 @@ var googleAnalytics4 = {
839
946
  }
840
947
  };
841
948
 
949
+ // src/publishers/microsoftAds.ts
950
+ var tokenCache2;
951
+ var tokenPromise2;
952
+ var microsoftAds = {
953
+ source: "MicrosoftAds",
954
+ /**
955
+ *Publishes data to Microsoft Ads
956
+ * @param log Azure Function context.log
957
+ * @param config Publisher configuration
958
+ * @param data Data in publisher-friendly format to be published
959
+ * @param event Event for which the function is triggered
960
+ */
961
+ async publish(log2, config, data, event) {
962
+ if (typeof config.customerAccountId === "string" && typeof config.customerId === "string" && typeof config.developerToken === "string") {
963
+ if (!(data.ConversionName || data.ConversionTime || data.MicrosoftClickId)) {
964
+ logMessage(
965
+ 1,
966
+ `MicrosoftAds: excluded ${event.event} as one or more mandatory parameters were missing`
967
+ );
968
+ return;
969
+ }
970
+ const { token } = await this.getToken(config);
971
+ const body = {
972
+ // eslint-disable-next-line @typescript-eslint/naming-convention
973
+ OfflineConversions: [data]
974
+ };
975
+ const requestOptions = {
976
+ method: "POST",
977
+ body: JSON.stringify(body),
978
+ headers: {
979
+ /* eslint-disable @typescript-eslint/naming-convention */
980
+ Authorization: `Bearer ${token}`,
981
+ CustomerAccountId: config.customerAccountId,
982
+ CustomerId: config.customerId,
983
+ DeveloperToken: config.developerToken,
984
+ "Content-Type": "application/json"
985
+ /* eslint-enable */
986
+ }
987
+ };
988
+ const baseUrl = "https://campaign.api.bingads.microsoft.com/CampaignManagement/v13/OfflineConversions/Apply";
989
+ const response = await fetch(baseUrl, requestOptions);
990
+ const responseJson = await response.json();
991
+ let status = 0;
992
+ let error;
993
+ if (responseJson.PartialErrors || responseJson.Errors) {
994
+ const apiErrors = responseJson.Errors ?? responseJson.PartialErrors ?? [];
995
+ status = 500;
996
+ const message = apiErrors.reduce((acc, curr) => {
997
+ acc += curr.Message + " ";
998
+ return acc;
999
+ }, "Microsoft Ads: Error(s) processing event: ");
1000
+ error = message;
1001
+ } else {
1002
+ status = 200;
1003
+ }
1004
+ logEvent(
1005
+ this.source,
1006
+ event.event,
1007
+ event.region,
1008
+ status,
1009
+ requestOptions.body,
1010
+ event.id
1011
+ );
1012
+ if (error) {
1013
+ throw new Error(error);
1014
+ }
1015
+ } else {
1016
+ throw new Error("Invalid config for platform " + this.source);
1017
+ }
1018
+ },
1019
+ async getToken(config) {
1020
+ if (tokenCache2 && tokenCache2.createdAt + 3500 * 1e3 > Date.now()) {
1021
+ return tokenCache2;
1022
+ }
1023
+ if (!tokenPromise2) {
1024
+ tokenPromise2 = (async () => {
1025
+ const tokenResponse = await fetch(
1026
+ `https://login.microsoftonline.com/${config.tenantId}/oauth2/v2.0/token`,
1027
+ {
1028
+ method: "POST",
1029
+ /* eslint-disable @typescript-eslint/naming-convention */
1030
+ headers: {
1031
+ "Content-Type": "application/x-www-form-urlencoded"
1032
+ },
1033
+ body: new URLSearchParams({
1034
+ client_id: config.client.id,
1035
+ client_secret: config.client.secret,
1036
+ refresh_token: config.refreshToken,
1037
+ grant_type: "refresh_token",
1038
+ scope: "https://ads.microsoft.com/msads.manage"
1039
+ })
1040
+ /* eslint-enable */
1041
+ }
1042
+ );
1043
+ if (!tokenResponse.ok) {
1044
+ throw new Error("Access token fetch failed");
1045
+ }
1046
+ const parsedTokenResponse = await tokenResponse.json();
1047
+ const createdAt = Date.now();
1048
+ tokenCache2 = { token: parsedTokenResponse.access_token, createdAt };
1049
+ tokenPromise2 = null;
1050
+ return tokenCache2;
1051
+ })();
1052
+ }
1053
+ return tokenPromise2;
1054
+ }
1055
+ };
1056
+
842
1057
  // src/publishers/piano.ts
843
1058
  var piano = {
844
1059
  source: "Piano",
@@ -951,20 +1166,16 @@ context.log.warn = function warn(str) {
951
1166
 
952
1167
  // src/index.ts
953
1168
  var publisher = {
954
- // eslint-disable-next-line @typescript-eslint/naming-convention
955
- Facebook: facebook,
956
- // eslint-disable-next-line @typescript-eslint/naming-convention
1169
+ /* eslint-disable @typescript-eslint/naming-convention */
1170
+ AffiliatePort: affiliatePort,
957
1171
  CJ: cj,
958
- // eslint-disable-next-line @typescript-eslint/naming-convention
1172
+ Facebook: facebook,
959
1173
  GoogleAds: googleAds,
960
- // eslint-disable-next-line @typescript-eslint/naming-convention
961
1174
  GoogleAnalytics4: googleAnalytics4,
962
- // eslint-disable-next-line @typescript-eslint/naming-convention
963
- UniversalAnalytics: universalAnalytics,
964
- // eslint-disable-next-line @typescript-eslint/naming-convention
1175
+ MicrosoftAds: microsoftAds,
965
1176
  Piano: piano,
966
- // eslint-disable-next-line @typescript-eslint/naming-convention
967
- AffiliatePort: affiliatePort
1177
+ UniversalAnalytics: universalAnalytics
1178
+ /* eslint-enable */
968
1179
  };
969
1180
  // Annotate the CommonJS export names for ESM import in node:
970
1181
  0 && (module.exports = {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mhubcloud/tms_core",
3
- "version": "3.6.0",
3
+ "version": "3.7.0-beta.1",
4
4
  "description": "Core function for mHub Cloud TMS publishers",
5
5
  "homepage": "https://mhubcloud.com/",
6
6
  "license": "Apache 2.0",
@@ -16,12 +16,12 @@
16
16
  "conversion API"
17
17
  ],
18
18
  "scripts": {
19
- "test": "mocha ./tests/*.js --local-settings=./local.settings.json --timeout 100000",
19
+ "test": "mocha ./tests/broker.js --local-settings=./local.settings.json --timeout 100000",
20
20
  "dev": "ts-node index.ts",
21
21
  "build": "tsup ./src/index.ts --format cjs --target node22"
22
22
  },
23
23
  "dependencies": {
24
- "@azure/cosmos": "^4.4.1",
24
+ "@azure/cosmos": "^4.7.0",
25
25
  "@azure/data-tables": "^13.2.2",
26
26
  "@azure/service-bus": "^7.9.5",
27
27
  "ajv": "^8.12.0",