@oneuptime/common 12.0.17 → 12.0.19

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 (220) hide show
  1. package/Models/DatabaseModels/EnterpriseLicense.ts +23 -0
  2. package/Models/DatabaseModels/Index.ts +4 -2
  3. package/Models/DatabaseModels/Monitor.ts +39 -9
  4. package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
  5. package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
  6. package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
  7. package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
  8. package/Models/DatabaseModels/UserSlack.ts +341 -0
  9. package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
  10. package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
  11. package/Server/API/UserSlackAPI.ts +125 -0
  12. package/Server/EnvironmentConfig.ts +12 -107
  13. package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
  14. package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
  15. package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
  16. package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
  17. package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
  18. package/Server/Infrastructure/Queue.ts +7 -0
  19. package/Server/Services/BillingService.ts +8 -0
  20. package/Server/Services/EnterpriseLicenseService.ts +60 -0
  21. package/Server/Services/Index.ts +6 -2
  22. package/Server/Services/LlmLogService.ts +25 -9
  23. package/Server/Services/OnCallReadinessService.ts +86 -2
  24. package/Server/Services/ProjectService.ts +67 -0
  25. package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
  26. package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
  27. package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
  28. package/Server/Services/UserNotificationRuleService.ts +589 -11
  29. package/Server/Services/UserNotificationSettingService.ts +138 -0
  30. package/Server/Services/UserService.ts +28 -0
  31. package/Server/Services/UserSlackService.ts +218 -0
  32. package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
  33. package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
  34. package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
  35. package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
  36. package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
  37. package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
  38. package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
  39. package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
  40. package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
  41. package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
  42. package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
  43. package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
  44. package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +17 -0
  45. package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
  46. package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
  47. package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
  48. package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
  49. package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
  50. package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
  51. package/Tests/Server/Services/BillingService.test.ts +20 -0
  52. package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
  53. package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
  54. package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
  55. package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
  56. package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
  57. package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
  58. package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
  59. package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
  60. package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
  61. package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
  62. package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
  63. package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
  64. package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
  65. package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
  66. package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
  67. package/Tests/Server/Services/UserSlackService.test.ts +407 -0
  68. package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
  69. package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
  70. package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
  71. package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
  72. package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
  73. package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
  74. package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
  75. package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
  76. package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
  77. package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
  78. package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
  79. package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
  80. package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
  81. package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
  82. package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
  83. package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
  84. package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
  85. package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
  86. package/Tests/Types/Kubernetes/KubernetesObjectParsers.test.ts +794 -0
  87. package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +53 -0
  88. package/Tests/Types/Measurement/MeasurementAggregationType.test.ts +100 -0
  89. package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +114 -0
  90. package/Tests/Types/Workspace/WorkspaceType.test.ts +53 -0
  91. package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
  92. package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
  93. package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +257 -0
  94. package/Types/Billing/PayAsYouGoPricing.ts +1 -1
  95. package/Types/Marketing/MarketingEvent.ts +97 -0
  96. package/UI/Utils/NotificationMethodUtil.ts +41 -1
  97. package/UI/Utils/PermissionGate.ts +56 -0
  98. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
  99. package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
  100. package/build/dist/Models/DatabaseModels/Index.js +4 -2
  101. package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
  102. package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
  103. package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
  104. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
  105. package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
  106. package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
  107. package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
  108. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
  109. package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
  110. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
  111. package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
  112. package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
  113. package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
  114. package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
  115. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
  116. package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
  117. package/build/dist/Server/API/UserSlackAPI.js +81 -0
  118. package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
  119. package/build/dist/Server/EnvironmentConfig.js +10 -70
  120. package/build/dist/Server/EnvironmentConfig.js.map +1 -1
  121. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
  122. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
  123. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
  124. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
  125. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
  126. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
  127. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
  128. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
  129. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
  130. package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
  131. package/build/dist/Server/Infrastructure/Queue.js +7 -0
  132. package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
  133. package/build/dist/Server/Services/BillingService.js +6 -0
  134. package/build/dist/Server/Services/BillingService.js.map +1 -1
  135. package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
  136. package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
  137. package/build/dist/Server/Services/Index.js +6 -2
  138. package/build/dist/Server/Services/Index.js.map +1 -1
  139. package/build/dist/Server/Services/LlmLogService.js +25 -9
  140. package/build/dist/Server/Services/LlmLogService.js.map +1 -1
  141. package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
  142. package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
  143. package/build/dist/Server/Services/ProjectService.js +56 -0
  144. package/build/dist/Server/Services/ProjectService.js.map +1 -1
  145. package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
  146. package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
  147. package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
  148. package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
  149. package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
  150. package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
  151. package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
  152. package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
  153. package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
  154. package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
  155. package/build/dist/Server/Services/UserService.js +26 -1
  156. package/build/dist/Server/Services/UserService.js.map +1 -1
  157. package/build/dist/Server/Services/UserSlackService.js +195 -0
  158. package/build/dist/Server/Services/UserSlackService.js.map +1 -0
  159. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
  160. package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
  161. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
  162. package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
  163. package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
  164. package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
  165. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
  166. package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
  167. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
  168. package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
  169. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
  170. package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
  171. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
  172. package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
  173. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
  174. package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
  175. package/build/dist/Types/Billing/PayAsYouGoPricing.js +1 -1
  176. package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -1
  177. package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
  178. package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
  179. package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
  180. package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
  181. package/build/dist/UI/Utils/PermissionGate.js +32 -0
  182. package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
  183. package/package.json +1 -1
  184. package/Models/DatabaseModels/MarketingConversion.ts +0 -410
  185. package/Server/Services/MarketingConversionService.ts +0 -10
  186. package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
  187. package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
  188. package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
  189. package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
  190. package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
  191. package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
  192. package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
  193. package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
  194. package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
  195. package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
  196. package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
  197. package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
  198. package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
  199. package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
  200. package/Types/Marketing/MarketingConversion.ts +0 -53
  201. package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
  202. package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
  203. package/build/dist/Server/Services/MarketingConversionService.js +0 -9
  204. package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
  205. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
  206. package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
  207. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
  208. package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
  209. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
  210. package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
  211. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
  212. package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
  213. package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
  214. package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
  215. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
  216. package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
  217. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
  218. package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
  219. package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
  220. package/build/dist/Types/Marketing/MarketingConversion.js.map +0 -1
@@ -1,185 +0,0 @@
1
- import axios, { AxiosError } from "axios";
2
- import {
3
- LinkedInApiVersion,
4
- LinkedInConversionsAccessToken,
5
- LinkedInMeetingBookedConversionId,
6
- LinkedInPaidSubscriptionConversionId,
7
- LinkedInSignUpConversionId,
8
- } from "../../../EnvironmentConfig";
9
- import ConversionUploadProvider, {
10
- ConversionSkip,
11
- ConversionTypeMapping,
12
- ConversionUploadBatchResult,
13
- } from "../ConversionUploadProvider";
14
- import { JSONObject } from "../../../../Types/JSON";
15
- import { MarketingConversionType } from "../../../../Types/Marketing/MarketingConversion";
16
- import MarketingConversion from "../../../../Models/DatabaseModels/MarketingConversion";
17
-
18
- const REQUEST_TIMEOUT_MS: number = 30000;
19
-
20
- // LinkedIn accepts conversions up to 90 days after the interaction.
21
- const LINKEDIN_MAX_CONVERSION_AGE_IN_DAYS: number = 90;
22
-
23
- /*
24
- * LinkedIn Conversions API (api.linkedin.com/rest/conversionEvents) matching
25
- * on the li_fat_id first-party click id, a SHA-256 email, or both — LinkedIn
26
- * accepts several userIds and resolves whichever it can. Conversion rules are
27
- * referenced by urn:lla:llaPartnerConversion:{id}. Events are posted one at a
28
- * time (small volumes); auth-level failures abort the batch for retry, other
29
- * per-event rejections are recorded as permanent.
30
- *
31
- * Note: LinkedIn access tokens expire (typically 60 days) — the token in
32
- * LINKEDIN_CONVERSIONS_ACCESS_TOKEN must be rotated externally.
33
- */
34
- export default class LinkedInProvider extends ConversionUploadProvider {
35
- public override readonly key: string = "linkedin";
36
- public override readonly displayName: string = "LinkedIn";
37
-
38
- /*
39
- * LinkedIn has no batch endpoint, so upload() issues one request per
40
- * conversion — keep runs short enough to finish inside the job timeout.
41
- */
42
- public override readonly maxBatchSize: number = 50;
43
-
44
- public override isConfigured(): boolean {
45
- return Boolean(LinkedInConversionsAccessToken);
46
- }
47
-
48
- protected override getProviderSkipReason(
49
- conversion: MarketingConversion,
50
- ): ConversionSkip | null {
51
- if (this.getUserIds(conversion).length === 0) {
52
- return {
53
- reason: "No LinkedIn click id (li_fat_id) and no email to match on",
54
- isPermanent: true,
55
- };
56
- }
57
-
58
- if (!this.getConversionRuleId(conversion)) {
59
- // Config gap — leave pending so it uploads once the id is configured.
60
- return {
61
- reason:
62
- "No LinkedIn conversion rule id configured for this conversion type",
63
- isPermanent: false,
64
- };
65
- }
66
-
67
- if (this.isOlderThanDays(conversion, LINKEDIN_MAX_CONVERSION_AGE_IN_DAYS)) {
68
- return {
69
- reason: "Conversion older than LinkedIn's 90-day window",
70
- isPermanent: true,
71
- };
72
- }
73
-
74
- return null;
75
- }
76
-
77
- private getConversionRuleId(conversion: MarketingConversion): string {
78
- const mapping: ConversionTypeMapping<string> = {
79
- [MarketingConversionType.SignUp]: LinkedInSignUpConversionId,
80
- [MarketingConversionType.MeetingBooked]:
81
- LinkedInMeetingBookedConversionId,
82
- [MarketingConversionType.PaidSubscription]:
83
- LinkedInPaidSubscriptionConversionId,
84
- };
85
-
86
- return this.resolveByConversionType(conversion, mapping) || "";
87
- }
88
-
89
- /*
90
- * Every identifier LinkedIn can resolve this person by. Sending both when
91
- * both exist raises the match rate; sending only the email is how a demo
92
- * booked on one device and a licence signed on another are still joined to
93
- * the click that started it.
94
- */
95
- private getUserIds(conversion: MarketingConversion): Array<JSONObject> {
96
- const userIds: Array<JSONObject> = [];
97
-
98
- const clickId: string | undefined = this.getClickId(
99
- conversion,
100
- "li_fat_id",
101
- );
102
-
103
- if (clickId) {
104
- userIds.push({
105
- idType: "LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID",
106
- idValue: clickId,
107
- });
108
- }
109
-
110
- const hashedEmail: string | undefined = this.getHashedEmail(conversion);
111
-
112
- if (hashedEmail) {
113
- userIds.push({
114
- idType: "SHA256_EMAIL",
115
- idValue: hashedEmail,
116
- });
117
- }
118
-
119
- return userIds;
120
- }
121
-
122
- public override async upload(
123
- conversions: Array<MarketingConversion>,
124
- ): Promise<ConversionUploadBatchResult> {
125
- const permanentFailures: Map<number, string> = new Map<number, string>();
126
-
127
- for (let i: number = 0; i < conversions.length; i++) {
128
- const conversion: MarketingConversion = conversions[i]!;
129
-
130
- const body: JSONObject = {
131
- conversion: `urn:lla:llaPartnerConversion:${this.getConversionRuleId(
132
- conversion,
133
- )}`,
134
- conversionHappenedAt: (conversion.conversionAt || new Date()).getTime(),
135
- // Dedup key: a retried batch must not double-count conversions.
136
- eventId: conversion.id!.toString(),
137
- user: {
138
- userIds: this.getUserIds(conversion),
139
- },
140
- };
141
-
142
- const valueInUSD: number | undefined = this.getValueInUSD(conversion);
143
-
144
- if (valueInUSD !== undefined) {
145
- body["conversionValue"] = {
146
- currencyCode: "USD",
147
- amount: valueInUSD.toFixed(2),
148
- };
149
- }
150
-
151
- try {
152
- await axios.post(
153
- "https://api.linkedin.com/rest/conversionEvents",
154
- body,
155
- {
156
- headers: {
157
- Authorization: `Bearer ${LinkedInConversionsAccessToken}`,
158
- "LinkedIn-Version": LinkedInApiVersion,
159
- "X-Restli-Protocol-Version": "2.0.0",
160
- "Content-Type": "application/json",
161
- },
162
- timeout: REQUEST_TIMEOUT_MS,
163
- },
164
- );
165
- } catch (err) {
166
- const status: number | undefined = (err as AxiosError).response?.status;
167
-
168
- /*
169
- * Only definitive per-event validation rejections are permanent.
170
- * Everything else — auth/token problems, rate limits, 5xx, and
171
- * network errors (no response, so no status) — is transport-level:
172
- * throw so the whole batch stays pending and is retried. eventId
173
- * makes redelivery idempotent on LinkedIn's side.
174
- */
175
- if (status !== 400 && status !== 422) {
176
- throw err;
177
- }
178
-
179
- permanentFailures.set(i, ConversionUploadProvider.getErrorMessage(err));
180
- }
181
- }
182
-
183
- return { permanentFailures };
184
- }
185
- }
@@ -1,182 +0,0 @@
1
- import axios, { AxiosResponse } from "axios";
2
- import {
3
- MetaConversionsAccessToken,
4
- MetaConversionsPixelId,
5
- MetaGraphApiVersion,
6
- } from "../../../EnvironmentConfig";
7
- import ConversionUploadProvider, {
8
- ConversionSkip,
9
- ConversionTypeMapping,
10
- ConversionUploadBatchResult,
11
- } from "../ConversionUploadProvider";
12
- import { JSONObject } from "../../../../Types/JSON";
13
- import { MarketingConversionType } from "../../../../Types/Marketing/MarketingConversion";
14
- import MarketingConversion from "../../../../Models/DatabaseModels/MarketingConversion";
15
-
16
- const REQUEST_TIMEOUT_MS: number = 30000;
17
-
18
- // Meta rejects website events older than 7 days at upload time.
19
- const META_MAX_EVENT_AGE_IN_DAYS: number = 7;
20
-
21
- /*
22
- * Meta (Facebook) Conversions API
23
- * (graph.facebook.com/{version}/{pixelId}/events). Emails are SHA-256 hashed
24
- * per Meta's customer-information matching spec; the raw email never leaves
25
- * OneUptime. event_id is the conversion row id, so Meta deduplicates
26
- * retried uploads (and browser-pixel duplicates) server-side.
27
- *
28
- * An event is uploadable with a click id, a hashed email, or both — Meta
29
- * matches on either. Requiring fbclid, as this provider used to, discarded
30
- * every conversion whose click id had not survived to the moment of
31
- * conversion, which is most of a sales-led funnel.
32
- */
33
- export default class MetaProvider extends ConversionUploadProvider {
34
- public override readonly key: string = "meta";
35
- public override readonly displayName: string = "Meta";
36
-
37
- public override isConfigured(): boolean {
38
- return Boolean(MetaConversionsPixelId && MetaConversionsAccessToken);
39
- }
40
-
41
- protected override getProviderSkipReason(
42
- conversion: MarketingConversion,
43
- ): ConversionSkip | null {
44
- if (
45
- !this.getClickId(conversion, "fbclid") &&
46
- !this.getHashedEmail(conversion)
47
- ) {
48
- return {
49
- reason: "No Meta click id (fbclid) and no email to match on",
50
- isPermanent: true,
51
- };
52
- }
53
-
54
- if (!this.getEventName(conversion)) {
55
- return {
56
- reason: "No Meta event name mapped for this conversion type",
57
- isPermanent: true,
58
- };
59
- }
60
-
61
- if (this.isOlderThanDays(conversion, META_MAX_EVENT_AGE_IN_DAYS)) {
62
- return {
63
- reason: "Conversion older than Meta's 7-day upload window",
64
- isPermanent: true,
65
- };
66
- }
67
-
68
- return null;
69
- }
70
-
71
- /*
72
- * Standard Meta event names. `Schedule` is Meta's own name for a booked
73
- * meeting, and it carries no revenue — which is what keeps a demo out of the
74
- * Purchase optimisation pool.
75
- */
76
- private getEventName(conversion: MarketingConversion): string | undefined {
77
- const mapping: ConversionTypeMapping<string> = {
78
- [MarketingConversionType.SignUp]: "CompleteRegistration",
79
- [MarketingConversionType.MeetingBooked]: "Schedule",
80
- [MarketingConversionType.PaidSubscription]: "Purchase",
81
- };
82
-
83
- return this.resolveByConversionType(conversion, mapping);
84
- }
85
-
86
- /*
87
- * fbc format per Meta spec: fb.1.{creationTimeMs}.{fbclid}. We use the
88
- * conversion time as creation time since the original click timestamp is
89
- * not stored.
90
- */
91
- private buildFbc(fbclid: string, eventAt: Date): string {
92
- return `fb.1.${eventAt.getTime()}.${fbclid}`;
93
- }
94
-
95
- public override async upload(
96
- conversions: Array<MarketingConversion>,
97
- ): Promise<ConversionUploadBatchResult> {
98
- const data: Array<JSONObject> = conversions.map(
99
- (conversion: MarketingConversion) => {
100
- const eventAt: Date = conversion.conversionAt || new Date();
101
- const fbclid: string | undefined = this.getClickId(
102
- conversion,
103
- "fbclid",
104
- );
105
-
106
- const userData: JSONObject = {};
107
-
108
- // Only send fbc when there is a click id to build it from.
109
- if (fbclid) {
110
- userData["fbc"] = this.buildFbc(fbclid, eventAt);
111
- }
112
-
113
- const hashedEmail: string | undefined = this.getHashedEmail(conversion);
114
-
115
- if (hashedEmail) {
116
- userData["em"] = [hashedEmail];
117
- }
118
-
119
- const eventName: string = this.getEventName(conversion) || "Lead";
120
-
121
- const payload: JSONObject = {
122
- event_name: eventName,
123
- event_time: Math.floor(eventAt.getTime() / 1000),
124
- event_id: conversion.id!.toString(),
125
- /*
126
- * Not "website": website events REQUIRE client_user_agent and
127
- * event_source_url, which a server-side backfill does not have —
128
- * Meta rejects the whole batch without them.
129
- */
130
- action_source: "other",
131
- user_data: userData,
132
- };
133
-
134
- const valueInUSD: number | undefined = this.getValueInUSD(conversion);
135
-
136
- /*
137
- * Purchase events REQUIRE custom_data.value and currency — one
138
- * valueless Purchase (custom-pricing plan) would 400 the entire
139
- * batch. Send 0 when the value is unknown. Lead-shaped events
140
- * (Schedule, Lead) never carry a value: getValueInUSD returns
141
- * undefined for them, so no custom_data is attached at all.
142
- */
143
- if (eventName === "Purchase" || valueInUSD !== undefined) {
144
- payload["custom_data"] = {
145
- value: valueInUSD ?? 0,
146
- currency: "USD",
147
- };
148
- }
149
-
150
- return payload;
151
- },
152
- );
153
-
154
- const url: string = `https://graph.facebook.com/${MetaGraphApiVersion}/${MetaConversionsPixelId}/events`;
155
-
156
- const response: AxiosResponse<JSONObject> = await axios.post(
157
- url,
158
- {
159
- data: data,
160
- access_token: MetaConversionsAccessToken,
161
- },
162
- {
163
- timeout: REQUEST_TIMEOUT_MS,
164
- },
165
- );
166
-
167
- const eventsReceived: number =
168
- (response.data?.["events_received"] as number) || 0;
169
-
170
- if (eventsReceived !== conversions.length) {
171
- /*
172
- * Retrying the whole batch is safe: event_id makes redelivery
173
- * idempotent on Meta's side.
174
- */
175
- throw new Error(
176
- `Meta accepted ${eventsReceived} of ${conversions.length} events`,
177
- );
178
- }
179
-
180
- return { permanentFailures: new Map<number, string>() };
181
- }
182
- }
@@ -1,223 +0,0 @@
1
- import axios, { AxiosResponse } from "axios";
2
- import {
3
- MicrosoftAdsAccountId,
4
- MicrosoftAdsCustomerId,
5
- MicrosoftAdsDeveloperToken,
6
- MicrosoftAdsMeetingBookedConversionName,
7
- MicrosoftAdsOAuthClientId,
8
- MicrosoftAdsOAuthClientSecret,
9
- MicrosoftAdsOAuthRefreshToken,
10
- MicrosoftAdsPaidSubscriptionConversionName,
11
- MicrosoftAdsSignUpConversionName,
12
- } from "../../../EnvironmentConfig";
13
- import ConversionUploadProvider, {
14
- ConversionSkip,
15
- ConversionTypeMapping,
16
- ConversionUploadBatchResult,
17
- } from "../ConversionUploadProvider";
18
- import { JSONObject } from "../../../../Types/JSON";
19
- import { MarketingConversionType } from "../../../../Types/Marketing/MarketingConversion";
20
- import MarketingConversion from "../../../../Models/DatabaseModels/MarketingConversion";
21
-
22
- const REQUEST_TIMEOUT_MS: number = 30000;
23
-
24
- // Microsoft accepts offline conversions up to 90 days after the click.
25
- const MICROSOFT_MAX_CONVERSION_AGE_IN_DAYS: number = 90;
26
-
27
- /*
28
- * Microsoft Advertising (Bing Ads) offline conversions via the Campaign
29
- * Management v13 REST surface (OfflineConversions/Apply), matching on msclkid
30
- * or on a SHA-256 email (Microsoft's enhanced conversions). Offline conversion
31
- * goals are matched by conversion NAME configured in the Microsoft Advertising
32
- * UI. OAuth2 refresh-token flow against Microsoft identity platform with the
33
- * msads.manage scope.
34
- */
35
- export default class MicrosoftAdsProvider extends ConversionUploadProvider {
36
- public override readonly key: string = "microsoft";
37
- public override readonly displayName: string = "Microsoft Advertising";
38
-
39
- private cachedAccessToken: string | null = null;
40
- private cachedAccessTokenExpiresAt: number = 0;
41
-
42
- public override isConfigured(): boolean {
43
- return Boolean(
44
- MicrosoftAdsDeveloperToken &&
45
- MicrosoftAdsOAuthClientId &&
46
- MicrosoftAdsOAuthRefreshToken &&
47
- MicrosoftAdsCustomerId &&
48
- MicrosoftAdsAccountId,
49
- );
50
- }
51
-
52
- protected override getProviderSkipReason(
53
- conversion: MarketingConversion,
54
- ): ConversionSkip | null {
55
- if (
56
- !this.getClickId(conversion, "msclkid") &&
57
- !this.getHashedEmail(conversion)
58
- ) {
59
- return {
60
- reason: "No Microsoft click id (msclkid) and no email to match on",
61
- isPermanent: true,
62
- };
63
- }
64
-
65
- if (!this.getConversionName(conversion)) {
66
- // Config gap — leave pending so it uploads once the name is configured.
67
- return {
68
- reason:
69
- "No Microsoft Advertising conversion name configured for this conversion type",
70
- isPermanent: false,
71
- };
72
- }
73
-
74
- if (
75
- this.isOlderThanDays(conversion, MICROSOFT_MAX_CONVERSION_AGE_IN_DAYS)
76
- ) {
77
- return {
78
- reason: "Conversion older than Microsoft's 90-day click window",
79
- isPermanent: true,
80
- };
81
- }
82
-
83
- return null;
84
- }
85
-
86
- private getConversionName(conversion: MarketingConversion): string {
87
- const mapping: ConversionTypeMapping<string> = {
88
- [MarketingConversionType.SignUp]: MicrosoftAdsSignUpConversionName,
89
- [MarketingConversionType.MeetingBooked]:
90
- MicrosoftAdsMeetingBookedConversionName,
91
- [MarketingConversionType.PaidSubscription]:
92
- MicrosoftAdsPaidSubscriptionConversionName,
93
- };
94
-
95
- return this.resolveByConversionType(conversion, mapping) || "";
96
- }
97
-
98
- private async getAccessToken(): Promise<string> {
99
- if (
100
- this.cachedAccessToken &&
101
- Date.now() < this.cachedAccessTokenExpiresAt
102
- ) {
103
- return this.cachedAccessToken;
104
- }
105
-
106
- const params: URLSearchParams = new URLSearchParams();
107
- params.append("client_id", MicrosoftAdsOAuthClientId);
108
- if (MicrosoftAdsOAuthClientSecret) {
109
- params.append("client_secret", MicrosoftAdsOAuthClientSecret);
110
- }
111
- params.append("refresh_token", MicrosoftAdsOAuthRefreshToken);
112
- params.append("grant_type", "refresh_token");
113
- params.append(
114
- "scope",
115
- "https://ads.microsoft.com/msads.manage offline_access",
116
- );
117
-
118
- const response: AxiosResponse<{
119
- access_token?: string;
120
- expires_in?: number;
121
- }> = await axios.post(
122
- "https://login.microsoftonline.com/common/oauth2/v2.0/token",
123
- params.toString(),
124
- {
125
- headers: {
126
- "content-type": "application/x-www-form-urlencoded",
127
- },
128
- timeout: REQUEST_TIMEOUT_MS,
129
- },
130
- );
131
-
132
- const accessToken: string | undefined = response.data?.access_token;
133
-
134
- if (!accessToken) {
135
- throw new Error("Microsoft OAuth token response had no access_token");
136
- }
137
-
138
- this.cachedAccessToken = accessToken;
139
- this.cachedAccessTokenExpiresAt =
140
- Date.now() + ((response.data?.expires_in || 3600) - 60) * 1000;
141
-
142
- return accessToken;
143
- }
144
-
145
- public override async upload(
146
- conversions: Array<MarketingConversion>,
147
- ): Promise<ConversionUploadBatchResult> {
148
- const accessToken: string = await this.getAccessToken();
149
-
150
- const body: JSONObject = {
151
- OfflineConversions: conversions.map((conversion: MarketingConversion) => {
152
- const payload: JSONObject = {
153
- ConversionName: this.getConversionName(conversion),
154
- // ISO 8601 UTC, e.g. 2026-07-18T12:34:56Z.
155
- ConversionTime: (conversion.conversionAt || new Date())
156
- .toISOString()
157
- .replace(/\.\d{3}Z$/, "Z"),
158
- };
159
-
160
- const clickId: string | undefined = this.getClickId(
161
- conversion,
162
- "msclkid",
163
- );
164
-
165
- if (clickId) {
166
- payload["MicrosoftClickId"] = clickId;
167
- }
168
-
169
- const hashedEmail: string | undefined = this.getHashedEmail(conversion);
170
-
171
- if (hashedEmail) {
172
- payload["HashedEmailAddress"] = hashedEmail;
173
- }
174
-
175
- const valueInUSD: number | undefined = this.getValueInUSD(conversion);
176
-
177
- if (valueInUSD !== undefined) {
178
- payload["ConversionValue"] = valueInUSD;
179
- payload["ConversionCurrencyCode"] = "USD";
180
- }
181
-
182
- return payload;
183
- }),
184
- };
185
-
186
- const response: AxiosResponse<JSONObject> = await axios.post(
187
- "https://campaign.api.bingads.microsoft.com/CampaignManagement/v13/OfflineConversions/Apply",
188
- body,
189
- {
190
- headers: {
191
- Authorization: `Bearer ${accessToken}`,
192
- DeveloperToken: MicrosoftAdsDeveloperToken,
193
- CustomerId: MicrosoftAdsCustomerId,
194
- CustomerAccountId: MicrosoftAdsAccountId,
195
- "Content-Type": "application/json",
196
- },
197
- timeout: REQUEST_TIMEOUT_MS,
198
- },
199
- );
200
-
201
- /*
202
- * The Apply response reports per-item failures in PartialErrors, each
203
- * carrying the Index of the failing item in the submitted array.
204
- */
205
- const permanentFailures: Map<number, string> = new Map<number, string>();
206
-
207
- const partialErrors: Array<JSONObject> =
208
- (response.data?.["PartialErrors"] as Array<JSONObject>) || [];
209
-
210
- for (const partialError of partialErrors) {
211
- if (typeof partialError?.["Index"] === "number") {
212
- permanentFailures.set(
213
- partialError["Index"] as number,
214
- `${partialError["ErrorCode"] || ""} ${
215
- partialError["Message"] || "Unknown Microsoft Advertising error"
216
- }`.trim(),
217
- );
218
- }
219
- }
220
-
221
- return { permanentFailures };
222
- }
223
- }