@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.
- package/Models/DatabaseModels/EnterpriseLicense.ts +23 -0
- package/Models/DatabaseModels/Index.ts +4 -2
- package/Models/DatabaseModels/Monitor.ts +39 -9
- package/Models/DatabaseModels/UserMicrosoftTeams.ts +344 -0
- package/Models/DatabaseModels/UserNotificationRule.ts +144 -8
- package/Models/DatabaseModels/UserNotificationSetting.ts +34 -0
- package/Models/DatabaseModels/UserOnCallLogTimeline.ts +95 -0
- package/Models/DatabaseModels/UserSlack.ts +341 -0
- package/Models/DatabaseModels/WorkspaceProjectAuthToken.ts +9 -0
- package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
- package/Server/API/UserSlackAPI.ts +125 -0
- package/Server/EnvironmentConfig.ts +12 -107
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.ts +33 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.ts +51 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.ts +117 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.ts +197 -0
- package/Server/Infrastructure/Postgres/SchemaMigrations/Index.ts +8 -0
- package/Server/Infrastructure/Queue.ts +7 -0
- package/Server/Services/BillingService.ts +8 -0
- package/Server/Services/EnterpriseLicenseService.ts +60 -0
- package/Server/Services/Index.ts +6 -2
- package/Server/Services/LlmLogService.ts +25 -9
- package/Server/Services/OnCallReadinessService.ts +86 -2
- package/Server/Services/ProjectService.ts +67 -0
- package/Server/Services/UserMicrosoftTeamsService.ts +208 -0
- package/Server/Services/UserNotificationMethodAdminService.ts +167 -5
- package/Server/Services/UserNotificationRuleAdminService.ts +40 -6
- package/Server/Services/UserNotificationRuleService.ts +589 -11
- package/Server/Services/UserNotificationSettingService.ts +138 -0
- package/Server/Services/UserService.ts +28 -0
- package/Server/Services/UserSlackService.ts +218 -0
- package/Server/Services/WorkspaceProjectAuthTokenService.ts +67 -1
- package/Server/Services/WorkspaceUserAuthTokenService.ts +73 -0
- package/Server/Services/WorkspaceUserNotificationService.ts +190 -0
- package/Server/Utils/Marketing/MarketingEventUtil.ts +145 -0
- package/Server/Utils/Marketing/MarketingEventWebhook.ts +114 -0
- package/Server/Utils/Monitor/MonitorLogUtil.ts +14 -1
- package/Server/Utils/Monitor/MonitorPayloadRedaction.ts +321 -0
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
- package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
- package/Tests/App/Dashboard/PayAsYouGoNotices.test.tsx +17 -0
- package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
- package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
- package/Tests/Server/API/OnCallReadinessAPI.test.ts +145 -26
- package/Tests/Server/API/UserNotificationMethodAdminAPI.test.ts +2 -2
- package/Tests/Server/Services/AIServiceDailyBudget.test.ts +11 -1
- package/Tests/Server/Services/AdminRuleEditGuards.test.ts +22 -8
- package/Tests/Server/Services/BillingService.test.ts +20 -0
- package/Tests/Server/Services/DeliverNotificationForRuleExtraction.test.ts +18 -2
- package/Tests/Server/Services/EpisodeRuleSeverityRepair.test.ts +5 -1
- package/Tests/Server/Services/LlmLogServiceTokenAggregateParams.test.ts +640 -0
- package/Tests/Server/Services/NotificationChannelEventCoverage.test.ts +246 -22
- package/Tests/Server/Services/NotificationDeletionImpact.test.ts +136 -8
- package/Tests/Server/Services/OnCallNotificationFallback.test.ts +191 -8
- package/Tests/Server/Services/OnCallReadinessService.test.ts +343 -18
- package/Tests/Server/Services/SeverityRuleBackfill.test.ts +5 -1
- package/Tests/Server/Services/UserMicrosoftTeamsService.test.ts +343 -0
- package/Tests/Server/Services/UserNotificationMethodAdminService.test.ts +209 -8
- package/Tests/Server/Services/UserNotificationRuleAdminGuards.test.ts +22 -6
- package/Tests/Server/Services/UserNotificationRuleDefaultCreation.test.ts +12 -6
- package/Tests/Server/Services/UserNotificationRuleExecuteItem.test.ts +37 -3
- package/Tests/Server/Services/UserNotificationRuleWorkspaceDelivery.test.ts +669 -0
- package/Tests/Server/Services/UserNotificationSettingWorkspaceChannels.test.ts +410 -0
- package/Tests/Server/Services/UserSlackService.test.ts +407 -0
- package/Tests/Server/Services/WorkspaceProjectAuthTokenDisconnectCascade.test.ts +224 -0
- package/Tests/Server/Services/WorkspaceUserAuthTokenNotificationMethodCascade.test.ts +189 -0
- package/Tests/Server/Services/WorkspaceUserNotificationService.test.ts +496 -0
- package/Tests/Server/Types/Database/Permissions/AdminNotificationRuleAccess.test.ts +30 -17
- package/Tests/Server/Types/Database/Permissions/CreateOwnershipScoping.test.ts +8 -3
- package/Tests/Server/Types/Database/Permissions/OwnerOnlyColumns.test.ts +72 -3
- package/Tests/Server/Types/Database/Permissions/UserNotificationRuleScoping.test.ts +22 -18
- package/Tests/Server/Types/Database/Permissions/WorkspaceMethodStampedColumnCreate.test.ts +178 -0
- package/Tests/Server/Utils/Marketing/MarketingEventUtil.test.ts +259 -0
- package/Tests/Server/Utils/Marketing/MarketingEventWebhook.test.ts +257 -0
- package/Tests/Server/Utils/Monitor/MonitorLogUtilRedaction.test.ts +300 -0
- package/Tests/Server/Utils/Monitor/MonitorPayloadRedaction.test.ts +381 -0
- package/Tests/Server/Utils/Runbook/RunbookExecutePermission.test.ts +228 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayErasureTombstone.test.ts +189 -0
- package/Tests/Server/Utils/SessionReplay/SessionReplayUsage.test.ts +205 -0
- package/Tests/Server/Utils/Telemetry/AppMetrics.test.ts +275 -0
- package/Tests/Server/Utils/Telemetry/TelemetryContext.test.ts +279 -0
- package/Tests/Server/Utils/Workspace/MicrosoftTeamsDirectMessage.test.ts +454 -0
- package/Tests/Types/Kubernetes/KubernetesObjectParsers.test.ts +794 -0
- package/Tests/Types/Kubernetes/KubernetesRightSizing.test.ts +53 -0
- package/Tests/Types/Measurement/MeasurementAggregationType.test.ts +100 -0
- package/Tests/Types/Monitor/MonitorStepConfigHelpers.test.ts +114 -0
- package/Tests/Types/Workspace/WorkspaceType.test.ts +53 -0
- package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
- package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
- package/Tests/Utils/Dashboard/Components/DashboardComponentDefaults.test.ts +257 -0
- package/Types/Billing/PayAsYouGoPricing.ts +1 -1
- package/Types/Marketing/MarketingEvent.ts +97 -0
- package/UI/Utils/NotificationMethodUtil.ts +41 -1
- package/UI/Utils/PermissionGate.ts +56 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js +24 -0
- package/build/dist/Models/DatabaseModels/EnterpriseLicense.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Index.js +4 -2
- package/build/dist/Models/DatabaseModels/Index.js.map +1 -1
- package/build/dist/Models/DatabaseModels/Monitor.js +39 -9
- package/build/dist/Models/DatabaseModels/Monitor.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js +363 -0
- package/build/dist/Models/DatabaseModels/UserMicrosoftTeams.js.map +1 -0
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js +145 -8
- package/build/dist/Models/DatabaseModels/UserNotificationRule.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserNotificationSetting.js +38 -0
- package/build/dist/Models/DatabaseModels/UserNotificationSetting.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js +96 -0
- package/build/dist/Models/DatabaseModels/UserOnCallLogTimeline.js.map +1 -1
- package/build/dist/Models/DatabaseModels/UserSlack.js +361 -0
- package/build/dist/Models/DatabaseModels/UserSlack.js.map +1 -0
- package/build/dist/Models/DatabaseModels/WorkspaceProjectAuthToken.js.map +1 -1
- package/build/dist/Server/API/UserMicrosoftTeamsAPI.js +82 -0
- package/build/dist/Server/API/UserMicrosoftTeamsAPI.js.map +1 -0
- package/build/dist/Server/API/UserSlackAPI.js +81 -0
- package/build/dist/Server/API/UserSlackAPI.js.map +1 -0
- package/build/dist/Server/EnvironmentConfig.js +10 -70
- package/build/dist/Server/EnvironmentConfig.js.map +1 -1
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js +26 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788700000000-RemoveMarketingConversionUploadState.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js +37 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788800000000-DropMarketingConversionAddEnterpriseLicenseEmail.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js +106 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1788900000000-RedactStoredMonitorIngestSecrets.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js +78 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/1789000000000-AddUserSlackAndMicrosoftTeams.js.map +1 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js +8 -0
- package/build/dist/Server/Infrastructure/Postgres/SchemaMigrations/Index.js.map +1 -1
- package/build/dist/Server/Infrastructure/Queue.js +7 -0
- package/build/dist/Server/Infrastructure/Queue.js.map +1 -1
- package/build/dist/Server/Services/BillingService.js +6 -0
- package/build/dist/Server/Services/BillingService.js.map +1 -1
- package/build/dist/Server/Services/EnterpriseLicenseService.js +65 -0
- package/build/dist/Server/Services/EnterpriseLicenseService.js.map +1 -1
- package/build/dist/Server/Services/Index.js +6 -2
- package/build/dist/Server/Services/Index.js.map +1 -1
- package/build/dist/Server/Services/LlmLogService.js +25 -9
- package/build/dist/Server/Services/LlmLogService.js.map +1 -1
- package/build/dist/Server/Services/OnCallReadinessService.js +74 -2
- package/build/dist/Server/Services/OnCallReadinessService.js.map +1 -1
- package/build/dist/Server/Services/ProjectService.js +56 -0
- package/build/dist/Server/Services/ProjectService.js.map +1 -1
- package/build/dist/Server/Services/UserMicrosoftTeamsService.js +183 -0
- package/build/dist/Server/Services/UserMicrosoftTeamsService.js.map +1 -0
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js +138 -2
- package/build/dist/Server/Services/UserNotificationMethodAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js +32 -4
- package/build/dist/Server/Services/UserNotificationRuleAdminService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationRuleService.js +499 -67
- package/build/dist/Server/Services/UserNotificationRuleService.js.map +1 -1
- package/build/dist/Server/Services/UserNotificationSettingService.js +118 -0
- package/build/dist/Server/Services/UserNotificationSettingService.js.map +1 -1
- package/build/dist/Server/Services/UserService.js +26 -1
- package/build/dist/Server/Services/UserService.js.map +1 -1
- package/build/dist/Server/Services/UserSlackService.js +195 -0
- package/build/dist/Server/Services/UserSlackService.js.map +1 -0
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js +64 -1
- package/build/dist/Server/Services/WorkspaceProjectAuthTokenService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js +70 -0
- package/build/dist/Server/Services/WorkspaceUserAuthTokenService.js.map +1 -1
- package/build/dist/Server/Services/WorkspaceUserNotificationService.js +129 -0
- package/build/dist/Server/Services/WorkspaceUserNotificationService.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js +94 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventUtil.js.map +1 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js +93 -0
- package/build/dist/Server/Utils/Marketing/MarketingEventWebhook.js.map +1 -0
- package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js +12 -1
- package/build/dist/Server/Utils/Monitor/MonitorLogUtil.js.map +1 -1
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js +233 -0
- package/build/dist/Server/Utils/Monitor/MonitorPayloadRedaction.js.map +1 -0
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js +141 -4
- package/build/dist/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.js.map +1 -1
- package/build/dist/Types/Billing/PayAsYouGoPricing.js +1 -1
- package/build/dist/Types/Billing/PayAsYouGoPricing.js.map +1 -1
- package/build/dist/Types/Marketing/MarketingEvent.js +43 -0
- package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
- package/build/dist/UI/Utils/NotificationMethodUtil.js +33 -1
- package/build/dist/UI/Utils/NotificationMethodUtil.js.map +1 -1
- package/build/dist/UI/Utils/PermissionGate.js +32 -0
- package/build/dist/UI/Utils/PermissionGate.js.map +1 -1
- package/package.json +1 -1
- package/Models/DatabaseModels/MarketingConversion.ts +0 -410
- package/Server/Services/MarketingConversionService.ts +0 -10
- package/Server/Utils/Marketing/ConversionUploadProvider.ts +0 -215
- package/Server/Utils/Marketing/ConversionUploadProviders.ts +0 -22
- package/Server/Utils/Marketing/Providers/GoogleAds.ts +0 -333
- package/Server/Utils/Marketing/Providers/LinkedIn.ts +0 -185
- package/Server/Utils/Marketing/Providers/Meta.ts +0 -182
- package/Server/Utils/Marketing/Providers/MicrosoftAds.ts +0 -223
- package/Server/Utils/Marketing/Providers/Reddit.ts +0 -208
- package/Tests/Server/Utils/Marketing/AdUploadableConversionTypes.test.ts +0 -315
- package/Tests/Server/Utils/Marketing/ConversionUploadProvider.test.ts +0 -300
- package/Tests/Server/Utils/Marketing/GoogleAds.test.ts +0 -592
- package/Tests/Server/Utils/Marketing/LinkedIn.test.ts +0 -282
- package/Tests/Server/Utils/Marketing/Meta.test.ts +0 -304
- package/Tests/Server/Utils/Marketing/MicrosoftAds.test.ts +0 -263
- package/Tests/Server/Utils/Marketing/Reddit.test.ts +0 -259
- package/Types/Marketing/MarketingConversion.ts +0 -53
- package/build/dist/Models/DatabaseModels/MarketingConversion.js +0 -445
- package/build/dist/Models/DatabaseModels/MarketingConversion.js.map +0 -1
- package/build/dist/Server/Services/MarketingConversionService.js +0 -9
- package/build/dist/Server/Services/MarketingConversionService.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js +0 -93
- package/build/dist/Server/Utils/Marketing/ConversionUploadProvider.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js +0 -20
- package/build/dist/Server/Utils/Marketing/ConversionUploadProviders.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js +0 -221
- package/build/dist/Server/Utils/Marketing/Providers/GoogleAds.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js +0 -137
- package/build/dist/Server/Utils/Marketing/Providers/LinkedIn.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js +0 -133
- package/build/dist/Server/Utils/Marketing/Providers/Meta.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js +0 -143
- package/build/dist/Server/Utils/Marketing/Providers/MicrosoftAds.js.map +0 -1
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js +0 -145
- package/build/dist/Server/Utils/Marketing/Providers/Reddit.js.map +0 -1
- package/build/dist/Types/Marketing/MarketingConversion.js +0 -52
- 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
|
-
}
|