@oneuptime/common 12.0.18 → 12.0.20
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/EnterpriseLicenseAPI.ts +94 -19
- package/Server/API/UserMicrosoftTeamsAPI.ts +127 -0
- package/Server/API/UserSlackAPI.ts +125 -0
- package/Server/EnvironmentConfig.ts +29 -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/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 +171 -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/Response.ts +2 -2
- package/Server/Utils/StartServer.ts +2 -2
- package/Server/Utils/Workspace/MicrosoftTeams/MicrosoftTeams.ts +191 -4
- package/Server/Views/Partials/AnalyticsConsent.ejs +533 -0
- package/Tests/App/Dashboard/AdminNotificationRulesPage.test.tsx +22 -14
- package/Tests/App/Dashboard/AdminUserNotificationMethodsPage.test.tsx +22 -11
- package/Tests/App/Dashboard/AlertEpisodeViewFields.test.tsx +465 -0
- package/Tests/App/Dashboard/DashboardChartWidgetZoom.test.tsx +302 -0
- package/Tests/App/Dashboard/DiscoveryScanWizardValidation.test.tsx +408 -0
- package/Tests/App/Dashboard/IncidentEpisodeViewFields.test.tsx +474 -0
- package/Tests/App/Dashboard/MetricSeriesInvestigateMenu.test.tsx +368 -0
- package/Tests/App/Dashboard/OnCallPreventionGuards.test.tsx +37 -1
- package/Tests/App/Dashboard/OnCallRulesTable.test.tsx +18 -10
- package/Tests/Models/DatabaseModels/DomainRoleTierCoverage.test.ts +27 -1
- package/Tests/Models/DatabaseModels/MonitorSecretKeyColumnAccessControl.test.ts +214 -0
- package/Tests/Server/API/EnterpriseLicenseReportUserCount.test.ts +763 -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/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/ProjectServiceChangePlan.test.ts +138 -0
- package/Tests/Server/Services/ProjectServiceCreateSubscriptionStarted.test.ts +516 -0
- 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/ResponseRenderAnalytics.test.ts +53 -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/Server/Views/AnalyticsConsentPartial.test.ts +241 -0
- package/Tests/UI/Components/Charts/ChartTooltipSorted.test.tsx +129 -0
- package/Tests/UI/Components/Charts/TooltipEntries.test.ts +155 -0
- package/Tests/UI/Components/Detail/DetailFieldErrors.test.tsx +347 -0
- package/Tests/UI/Components/Forms/BasicFormStepValidation.test.tsx +392 -0
- package/Tests/UI/Components/Forms/ValidationFormSteps.test.ts +549 -0
- package/Tests/UI/Utils/NotificationMethodUtil.test.ts +137 -9
- package/Tests/UI/Utils/PermissionGate.test.ts +105 -0
- package/Tests/Utils/EnterpriseLicenseSync.test.ts +562 -0
- package/Types/Analytics/RevenueEvent.ts +1 -0
- package/Types/Marketing/MarketingEvent.ts +140 -0
- package/UI/Components/Charts/Area/AreaChart.tsx +8 -1
- package/UI/Components/Charts/Bar/BarChart.tsx +8 -1
- package/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.tsx +19 -4
- package/UI/Components/Charts/ChartLibrary/BarChart/BarChart.tsx +20 -2
- package/UI/Components/Charts/ChartLibrary/LineChart/LineChart.tsx +19 -4
- package/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.ts +105 -0
- package/UI/Components/Charts/Line/LineChart.tsx +8 -1
- package/UI/Components/Detail/Detail.tsx +18 -1
- package/UI/Utils/NotificationMethodUtil.ts +41 -1
- package/UI/Utils/PermissionGate.ts +56 -0
- package/Utils/EnterpriseLicense/EnterpriseLicenseSync.ts +244 -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/EnterpriseLicenseAPI.js +66 -12
- package/build/dist/Server/API/EnterpriseLicenseAPI.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 +25 -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/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 +144 -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/Response.js +2 -2
- package/build/dist/Server/Utils/Response.js.map +1 -1
- package/build/dist/Server/Utils/StartServer.js +2 -2
- package/build/dist/Server/Utils/StartServer.js.map +1 -1
- 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/Analytics/RevenueEvent.js +1 -0
- package/build/dist/Types/Analytics/RevenueEvent.js.map +1 -1
- package/build/dist/Types/Marketing/MarketingEvent.js +82 -0
- package/build/dist/Types/Marketing/MarketingEvent.js.map +1 -0
- package/build/dist/UI/Components/Charts/Area/AreaChart.js +9 -1
- package/build/dist/UI/Components/Charts/Area/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js +9 -1
- package/build/dist/UI/Components/Charts/Bar/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js +22 -13
- package/build/dist/UI/Components/Charts/ChartLibrary/AreaChart/AreaChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js +27 -15
- package/build/dist/UI/Components/Charts/ChartLibrary/BarChart/BarChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js +27 -18
- package/build/dist/UI/Components/Charts/ChartLibrary/LineChart/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js +71 -0
- package/build/dist/UI/Components/Charts/ChartLibrary/Utils/TooltipEntries.js.map +1 -0
- package/build/dist/UI/Components/Charts/Line/LineChart.js +9 -1
- package/build/dist/UI/Components/Charts/Line/LineChart.js.map +1 -1
- package/build/dist/UI/Components/Detail/Detail.js +17 -1
- package/build/dist/UI/Components/Detail/Detail.js.map +1 -1
- 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/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js +174 -0
- package/build/dist/Utils/EnterpriseLicense/EnterpriseLicenseSync.js.map +1 -0
- 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,221 +0,0 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
|
-
import { GoogleAdsApiVersion, GoogleAdsCustomerId, GoogleAdsDeveloperToken, GoogleAdsEnhancedConversionsForLeadsEnabled, GoogleAdsLoginCustomerId, GoogleAdsMeetingBookedConversionActionId, GoogleAdsOAuthClientId, GoogleAdsOAuthClientSecret, GoogleAdsOAuthRefreshToken, GoogleAdsPaidSubscriptionConversionActionId, GoogleAdsSignUpConversionActionId, } from "../../../EnvironmentConfig";
|
|
3
|
-
import ConversionUploadProvider from "../ConversionUploadProvider";
|
|
4
|
-
import { MarketingConversionType } from "../../../../Types/Marketing/MarketingConversion";
|
|
5
|
-
import logger from "../../Logger";
|
|
6
|
-
const REQUEST_TIMEOUT_MS = 30000;
|
|
7
|
-
// Google Ads accepts click conversions up to 90 days after the click.
|
|
8
|
-
const GOOGLE_MAX_CONVERSION_AGE_IN_DAYS = 90;
|
|
9
|
-
/*
|
|
10
|
-
* Google Ads offline click conversions
|
|
11
|
-
* (customers/{id}:uploadClickConversions) with OAuth2 refresh-token auth.
|
|
12
|
-
* Supports gclid, wbraid and gbraid click ids, plus enhanced conversions — a
|
|
13
|
-
* SHA-256 email in userIdentifiers. Uses partialFailure so valid conversions
|
|
14
|
-
* succeed even when others in the batch fail.
|
|
15
|
-
*
|
|
16
|
-
* A hashed email is attached to EVERY upload that has one, which only improves
|
|
17
|
-
* matching. Uploading a conversion that has ONLY a hashed email — enhanced
|
|
18
|
-
* conversions for leads, and the only way a sales-led deal months after the
|
|
19
|
-
* click is ever attributed — additionally requires the account to be set up
|
|
20
|
-
* for it, so it is gated on
|
|
21
|
-
* GOOGLE_ADS_ENHANCED_CONVERSIONS_FOR_LEADS_ENABLED.
|
|
22
|
-
*/
|
|
23
|
-
export default class GoogleAdsProvider extends ConversionUploadProvider {
|
|
24
|
-
constructor() {
|
|
25
|
-
super(...arguments);
|
|
26
|
-
this.key = "google";
|
|
27
|
-
this.displayName = "Google Ads";
|
|
28
|
-
this.cachedAccessToken = null;
|
|
29
|
-
this.cachedAccessTokenExpiresAt = 0;
|
|
30
|
-
}
|
|
31
|
-
isConfigured() {
|
|
32
|
-
return Boolean(GoogleAdsDeveloperToken &&
|
|
33
|
-
GoogleAdsOAuthClientId &&
|
|
34
|
-
GoogleAdsOAuthClientSecret &&
|
|
35
|
-
GoogleAdsOAuthRefreshToken &&
|
|
36
|
-
GoogleAdsCustomerId);
|
|
37
|
-
}
|
|
38
|
-
getProviderSkipReason(conversion) {
|
|
39
|
-
if (!this.getGoogleClickId(conversion) &&
|
|
40
|
-
!this.canUploadAsLead(conversion)) {
|
|
41
|
-
return {
|
|
42
|
-
reason: GoogleAdsEnhancedConversionsForLeadsEnabled
|
|
43
|
-
? "No Google click id (gclid/wbraid/gbraid) and no email to match on"
|
|
44
|
-
: "No Google click id (gclid/wbraid/gbraid)",
|
|
45
|
-
isPermanent: true,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
if (!this.getConversionActionId(conversion)) {
|
|
49
|
-
// Config gap — leave pending so it uploads once the id is configured.
|
|
50
|
-
return {
|
|
51
|
-
reason: "No Google Ads conversion action id configured for this conversion type",
|
|
52
|
-
isPermanent: false,
|
|
53
|
-
};
|
|
54
|
-
}
|
|
55
|
-
if (this.isOlderThanDays(conversion, GOOGLE_MAX_CONVERSION_AGE_IN_DAYS)) {
|
|
56
|
-
return {
|
|
57
|
-
reason: "Conversion older than Google's 90-day click window",
|
|
58
|
-
isPermanent: true,
|
|
59
|
-
};
|
|
60
|
-
}
|
|
61
|
-
return null;
|
|
62
|
-
}
|
|
63
|
-
getGoogleClickId(conversion) {
|
|
64
|
-
const gclid = this.getClickId(conversion, "gclid");
|
|
65
|
-
if (gclid) {
|
|
66
|
-
return { gclid: gclid };
|
|
67
|
-
}
|
|
68
|
-
const wbraid = this.getClickId(conversion, "wbraid");
|
|
69
|
-
if (wbraid) {
|
|
70
|
-
return { wbraid: wbraid };
|
|
71
|
-
}
|
|
72
|
-
const gbraid = this.getClickId(conversion, "gbraid");
|
|
73
|
-
if (gbraid) {
|
|
74
|
-
return { gbraid: gbraid };
|
|
75
|
-
}
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
/*
|
|
79
|
-
* Enhanced conversions for leads: this conversion has no click id, but it
|
|
80
|
-
* has an email and the account is configured to match on one.
|
|
81
|
-
*/
|
|
82
|
-
canUploadAsLead(conversion) {
|
|
83
|
-
return Boolean(GoogleAdsEnhancedConversionsForLeadsEnabled &&
|
|
84
|
-
this.getHashedEmail(conversion));
|
|
85
|
-
}
|
|
86
|
-
/*
|
|
87
|
-
* userIdentifiers per the enhanced-conversions spec. FIRST_PARTY is the
|
|
88
|
-
* correct source: the address was given to OneUptime directly, not bought or
|
|
89
|
-
* inferred.
|
|
90
|
-
*/
|
|
91
|
-
getUserIdentifiers(conversion) {
|
|
92
|
-
const hashedEmail = this.getHashedEmail(conversion);
|
|
93
|
-
if (!hashedEmail) {
|
|
94
|
-
return [];
|
|
95
|
-
}
|
|
96
|
-
return [
|
|
97
|
-
{
|
|
98
|
-
hashedEmail: hashedEmail,
|
|
99
|
-
userIdentifierSource: "FIRST_PARTY",
|
|
100
|
-
},
|
|
101
|
-
];
|
|
102
|
-
}
|
|
103
|
-
getConversionActionId(conversion) {
|
|
104
|
-
const mapping = {
|
|
105
|
-
[MarketingConversionType.SignUp]: GoogleAdsSignUpConversionActionId,
|
|
106
|
-
[MarketingConversionType.MeetingBooked]: GoogleAdsMeetingBookedConversionActionId,
|
|
107
|
-
[MarketingConversionType.PaidSubscription]: GoogleAdsPaidSubscriptionConversionActionId,
|
|
108
|
-
};
|
|
109
|
-
return this.resolveByConversionType(conversion, mapping) || "";
|
|
110
|
-
}
|
|
111
|
-
async getAccessToken() {
|
|
112
|
-
var _a, _b;
|
|
113
|
-
if (this.cachedAccessToken &&
|
|
114
|
-
Date.now() < this.cachedAccessTokenExpiresAt) {
|
|
115
|
-
return this.cachedAccessToken;
|
|
116
|
-
}
|
|
117
|
-
const params = new URLSearchParams();
|
|
118
|
-
params.append("client_id", GoogleAdsOAuthClientId);
|
|
119
|
-
params.append("client_secret", GoogleAdsOAuthClientSecret);
|
|
120
|
-
params.append("refresh_token", GoogleAdsOAuthRefreshToken);
|
|
121
|
-
params.append("grant_type", "refresh_token");
|
|
122
|
-
const response = await axios.post("https://oauth2.googleapis.com/token", params.toString(), {
|
|
123
|
-
headers: {
|
|
124
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
125
|
-
},
|
|
126
|
-
timeout: REQUEST_TIMEOUT_MS,
|
|
127
|
-
});
|
|
128
|
-
const accessToken = (_a = response.data) === null || _a === void 0 ? void 0 : _a.access_token;
|
|
129
|
-
if (!accessToken) {
|
|
130
|
-
throw new Error("Google OAuth token response had no access_token");
|
|
131
|
-
}
|
|
132
|
-
this.cachedAccessToken = accessToken;
|
|
133
|
-
// Refresh one minute before actual expiry.
|
|
134
|
-
this.cachedAccessTokenExpiresAt =
|
|
135
|
-
Date.now() + ((((_b = response.data) === null || _b === void 0 ? void 0 : _b.expires_in) || 3600) - 60) * 1000;
|
|
136
|
-
return accessToken;
|
|
137
|
-
}
|
|
138
|
-
// Format Google Ads expects: "yyyy-mm-dd hh:mm:ss+00:00".
|
|
139
|
-
formatConversionDateTime(date) {
|
|
140
|
-
const iso = date.toISOString(); // 2026-07-18T12:34:56.789Z
|
|
141
|
-
return `${iso.slice(0, 10)} ${iso.slice(11, 19)}+00:00`;
|
|
142
|
-
}
|
|
143
|
-
async upload(conversions) {
|
|
144
|
-
const accessToken = await this.getAccessToken();
|
|
145
|
-
const headers = {
|
|
146
|
-
Authorization: `Bearer ${accessToken}`,
|
|
147
|
-
"developer-token": GoogleAdsDeveloperToken,
|
|
148
|
-
};
|
|
149
|
-
if (GoogleAdsLoginCustomerId) {
|
|
150
|
-
headers["login-customer-id"] = GoogleAdsLoginCustomerId;
|
|
151
|
-
}
|
|
152
|
-
const body = {
|
|
153
|
-
conversions: conversions.map((conversion) => {
|
|
154
|
-
const payload = Object.assign(Object.assign({}, (this.getGoogleClickId(conversion) || {})), { conversionAction: `customers/${GoogleAdsCustomerId}/conversionActions/${this.getConversionActionId(conversion)}`, conversionDateTime: this.formatConversionDateTime(conversion.conversionAt || new Date()) });
|
|
155
|
-
const userIdentifiers = this.getUserIdentifiers(conversion);
|
|
156
|
-
if (userIdentifiers.length > 0) {
|
|
157
|
-
payload["userIdentifiers"] = userIdentifiers;
|
|
158
|
-
}
|
|
159
|
-
const valueInUSD = this.getValueInUSD(conversion);
|
|
160
|
-
if (valueInUSD !== undefined) {
|
|
161
|
-
payload["conversionValue"] = valueInUSD;
|
|
162
|
-
payload["currencyCode"] = "USD";
|
|
163
|
-
}
|
|
164
|
-
return payload;
|
|
165
|
-
}),
|
|
166
|
-
partialFailure: true,
|
|
167
|
-
};
|
|
168
|
-
const url = `https://googleads.googleapis.com/${GoogleAdsApiVersion}/customers/${GoogleAdsCustomerId}:uploadClickConversions`;
|
|
169
|
-
const response = await axios.post(url, body, {
|
|
170
|
-
headers: headers,
|
|
171
|
-
timeout: REQUEST_TIMEOUT_MS,
|
|
172
|
-
});
|
|
173
|
-
return this.parsePartialFailure(response.data);
|
|
174
|
-
}
|
|
175
|
-
/*
|
|
176
|
-
* partialFailureError.details[].errors[] entries carry the index of the
|
|
177
|
-
* failing operation in location.fieldPathElements ({fieldName:
|
|
178
|
-
* "conversions", index: N}).
|
|
179
|
-
*/
|
|
180
|
-
parsePartialFailure(responseData) {
|
|
181
|
-
var _a;
|
|
182
|
-
const permanentFailures = new Map();
|
|
183
|
-
const partialFailureError = responseData === null || responseData === void 0 ? void 0 : responseData["partialFailureError"];
|
|
184
|
-
if (!partialFailureError) {
|
|
185
|
-
return { permanentFailures };
|
|
186
|
-
}
|
|
187
|
-
const details = partialFailureError["details"] || [];
|
|
188
|
-
let alreadyExistsCount = 0;
|
|
189
|
-
for (const detail of details) {
|
|
190
|
-
const errors = detail["errors"] || [];
|
|
191
|
-
for (const error of errors) {
|
|
192
|
-
const message = error["message"] || "Unknown Google Ads error";
|
|
193
|
-
/*
|
|
194
|
-
* A duplicate rejection means the conversion WAS counted by a prior
|
|
195
|
-
* run (e.g. crash between upload and status write) — that is a
|
|
196
|
-
* success, not a failure.
|
|
197
|
-
*/
|
|
198
|
-
if (JSON.stringify(error).includes("ALREADY_EXISTS")) {
|
|
199
|
-
alreadyExistsCount++;
|
|
200
|
-
continue;
|
|
201
|
-
}
|
|
202
|
-
const fieldPathElements = ((_a = error["location"]) === null || _a === void 0 ? void 0 : _a["fieldPathElements"]) || [];
|
|
203
|
-
for (const element of fieldPathElements) {
|
|
204
|
-
if (element["fieldName"] === "conversions" &&
|
|
205
|
-
typeof element["index"] === "number") {
|
|
206
|
-
permanentFailures.set(element["index"], message);
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
/*
|
|
212
|
-
* A partial failure with no parseable indexes should not be silently
|
|
213
|
-
* treated as full success.
|
|
214
|
-
*/
|
|
215
|
-
if (permanentFailures.size === 0 && alreadyExistsCount === 0) {
|
|
216
|
-
logger.error(`Google Ads partial failure with unparseable indexes: ${JSON.stringify(partialFailureError).slice(0, 1000)}`);
|
|
217
|
-
}
|
|
218
|
-
return { permanentFailures };
|
|
219
|
-
}
|
|
220
|
-
}
|
|
221
|
-
//# sourceMappingURL=GoogleAds.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"GoogleAds.js","sourceRoot":"","sources":["../../../../../../Server/Utils/Marketing/Providers/GoogleAds.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,137 +0,0 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
|
-
import { LinkedInApiVersion, LinkedInConversionsAccessToken, LinkedInMeetingBookedConversionId, LinkedInPaidSubscriptionConversionId, LinkedInSignUpConversionId, } from "../../../EnvironmentConfig";
|
|
3
|
-
import ConversionUploadProvider from "../ConversionUploadProvider";
|
|
4
|
-
import { MarketingConversionType } from "../../../../Types/Marketing/MarketingConversion";
|
|
5
|
-
const REQUEST_TIMEOUT_MS = 30000;
|
|
6
|
-
// LinkedIn accepts conversions up to 90 days after the interaction.
|
|
7
|
-
const LINKEDIN_MAX_CONVERSION_AGE_IN_DAYS = 90;
|
|
8
|
-
/*
|
|
9
|
-
* LinkedIn Conversions API (api.linkedin.com/rest/conversionEvents) matching
|
|
10
|
-
* on the li_fat_id first-party click id, a SHA-256 email, or both — LinkedIn
|
|
11
|
-
* accepts several userIds and resolves whichever it can. Conversion rules are
|
|
12
|
-
* referenced by urn:lla:llaPartnerConversion:{id}. Events are posted one at a
|
|
13
|
-
* time (small volumes); auth-level failures abort the batch for retry, other
|
|
14
|
-
* per-event rejections are recorded as permanent.
|
|
15
|
-
*
|
|
16
|
-
* Note: LinkedIn access tokens expire (typically 60 days) — the token in
|
|
17
|
-
* LINKEDIN_CONVERSIONS_ACCESS_TOKEN must be rotated externally.
|
|
18
|
-
*/
|
|
19
|
-
export default class LinkedInProvider extends ConversionUploadProvider {
|
|
20
|
-
constructor() {
|
|
21
|
-
super(...arguments);
|
|
22
|
-
this.key = "linkedin";
|
|
23
|
-
this.displayName = "LinkedIn";
|
|
24
|
-
/*
|
|
25
|
-
* LinkedIn has no batch endpoint, so upload() issues one request per
|
|
26
|
-
* conversion — keep runs short enough to finish inside the job timeout.
|
|
27
|
-
*/
|
|
28
|
-
this.maxBatchSize = 50;
|
|
29
|
-
}
|
|
30
|
-
isConfigured() {
|
|
31
|
-
return Boolean(LinkedInConversionsAccessToken);
|
|
32
|
-
}
|
|
33
|
-
getProviderSkipReason(conversion) {
|
|
34
|
-
if (this.getUserIds(conversion).length === 0) {
|
|
35
|
-
return {
|
|
36
|
-
reason: "No LinkedIn click id (li_fat_id) and no email to match on",
|
|
37
|
-
isPermanent: true,
|
|
38
|
-
};
|
|
39
|
-
}
|
|
40
|
-
if (!this.getConversionRuleId(conversion)) {
|
|
41
|
-
// Config gap — leave pending so it uploads once the id is configured.
|
|
42
|
-
return {
|
|
43
|
-
reason: "No LinkedIn conversion rule id configured for this conversion type",
|
|
44
|
-
isPermanent: false,
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
if (this.isOlderThanDays(conversion, LINKEDIN_MAX_CONVERSION_AGE_IN_DAYS)) {
|
|
48
|
-
return {
|
|
49
|
-
reason: "Conversion older than LinkedIn's 90-day window",
|
|
50
|
-
isPermanent: true,
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
return null;
|
|
54
|
-
}
|
|
55
|
-
getConversionRuleId(conversion) {
|
|
56
|
-
const mapping = {
|
|
57
|
-
[MarketingConversionType.SignUp]: LinkedInSignUpConversionId,
|
|
58
|
-
[MarketingConversionType.MeetingBooked]: LinkedInMeetingBookedConversionId,
|
|
59
|
-
[MarketingConversionType.PaidSubscription]: LinkedInPaidSubscriptionConversionId,
|
|
60
|
-
};
|
|
61
|
-
return this.resolveByConversionType(conversion, mapping) || "";
|
|
62
|
-
}
|
|
63
|
-
/*
|
|
64
|
-
* Every identifier LinkedIn can resolve this person by. Sending both when
|
|
65
|
-
* both exist raises the match rate; sending only the email is how a demo
|
|
66
|
-
* booked on one device and a licence signed on another are still joined to
|
|
67
|
-
* the click that started it.
|
|
68
|
-
*/
|
|
69
|
-
getUserIds(conversion) {
|
|
70
|
-
const userIds = [];
|
|
71
|
-
const clickId = this.getClickId(conversion, "li_fat_id");
|
|
72
|
-
if (clickId) {
|
|
73
|
-
userIds.push({
|
|
74
|
-
idType: "LINKEDIN_FIRST_PARTY_ADS_TRACKING_UUID",
|
|
75
|
-
idValue: clickId,
|
|
76
|
-
});
|
|
77
|
-
}
|
|
78
|
-
const hashedEmail = this.getHashedEmail(conversion);
|
|
79
|
-
if (hashedEmail) {
|
|
80
|
-
userIds.push({
|
|
81
|
-
idType: "SHA256_EMAIL",
|
|
82
|
-
idValue: hashedEmail,
|
|
83
|
-
});
|
|
84
|
-
}
|
|
85
|
-
return userIds;
|
|
86
|
-
}
|
|
87
|
-
async upload(conversions) {
|
|
88
|
-
var _a;
|
|
89
|
-
const permanentFailures = new Map();
|
|
90
|
-
for (let i = 0; i < conversions.length; i++) {
|
|
91
|
-
const conversion = conversions[i];
|
|
92
|
-
const body = {
|
|
93
|
-
conversion: `urn:lla:llaPartnerConversion:${this.getConversionRuleId(conversion)}`,
|
|
94
|
-
conversionHappenedAt: (conversion.conversionAt || new Date()).getTime(),
|
|
95
|
-
// Dedup key: a retried batch must not double-count conversions.
|
|
96
|
-
eventId: conversion.id.toString(),
|
|
97
|
-
user: {
|
|
98
|
-
userIds: this.getUserIds(conversion),
|
|
99
|
-
},
|
|
100
|
-
};
|
|
101
|
-
const valueInUSD = this.getValueInUSD(conversion);
|
|
102
|
-
if (valueInUSD !== undefined) {
|
|
103
|
-
body["conversionValue"] = {
|
|
104
|
-
currencyCode: "USD",
|
|
105
|
-
amount: valueInUSD.toFixed(2),
|
|
106
|
-
};
|
|
107
|
-
}
|
|
108
|
-
try {
|
|
109
|
-
await axios.post("https://api.linkedin.com/rest/conversionEvents", body, {
|
|
110
|
-
headers: {
|
|
111
|
-
Authorization: `Bearer ${LinkedInConversionsAccessToken}`,
|
|
112
|
-
"LinkedIn-Version": LinkedInApiVersion,
|
|
113
|
-
"X-Restli-Protocol-Version": "2.0.0",
|
|
114
|
-
"Content-Type": "application/json",
|
|
115
|
-
},
|
|
116
|
-
timeout: REQUEST_TIMEOUT_MS,
|
|
117
|
-
});
|
|
118
|
-
}
|
|
119
|
-
catch (err) {
|
|
120
|
-
const status = (_a = err.response) === null || _a === void 0 ? void 0 : _a.status;
|
|
121
|
-
/*
|
|
122
|
-
* Only definitive per-event validation rejections are permanent.
|
|
123
|
-
* Everything else — auth/token problems, rate limits, 5xx, and
|
|
124
|
-
* network errors (no response, so no status) — is transport-level:
|
|
125
|
-
* throw so the whole batch stays pending and is retried. eventId
|
|
126
|
-
* makes redelivery idempotent on LinkedIn's side.
|
|
127
|
-
*/
|
|
128
|
-
if (status !== 400 && status !== 422) {
|
|
129
|
-
throw err;
|
|
130
|
-
}
|
|
131
|
-
permanentFailures.set(i, ConversionUploadProvider.getErrorMessage(err));
|
|
132
|
-
}
|
|
133
|
-
}
|
|
134
|
-
return { permanentFailures };
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
//# sourceMappingURL=LinkedIn.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"LinkedIn.js","sourceRoot":"","sources":["../../../../../../Server/Utils/Marketing/Providers/LinkedIn.ts"],"names":[],"mappings":"AAAA,OAAO,KAAqB,MAAM,OAAO,CAAC;AAC1C,OAAO,EACL,kBAAkB,EAClB,8BAA8B,EAC9B,iCAAiC,EACjC,oCAAoC,EACpC,0BAA0B,GAC3B,MAAM,4BAA4B,CAAC;AACpC,OAAO,wBAIN,MAAM,6BAA6B,CAAC;AAErC,OAAO,EAAE,uBAAuB,EAAE,MAAM,iDAAiD,CAAC;AAG1F,MAAM,kBAAkB,GAAW,KAAK,CAAC;AAEzC,oEAAoE;AACpE,MAAM,mCAAmC,GAAW,EAAE,CAAC;AAEvD;;;;;;;;;;GAUG;AACH,MAAM,CAAC,OAAO,OAAO,gBAAiB,SAAQ,wBAAwB;IAAtE;;QAC2B,QAAG,GAAW,UAAU,CAAC;QACzB,gBAAW,GAAW,UAAU,CAAC;QAE1D;;;WAGG;QACsB,iBAAY,GAAW,EAAE,CAAC;IA+IrD,CAAC;IA7IiB,YAAY;QAC1B,OAAO,OAAO,CAAC,8BAA8B,CAAC,CAAC;IACjD,CAAC;IAEkB,qBAAqB,CACtC,UAA+B;QAE/B,IAAI,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC7C,OAAO;gBACL,MAAM,EAAE,2DAA2D;gBACnE,WAAW,EAAE,IAAI;aAClB,CAAC;QACJ,CAAC;QAED,IAAI,CAAC,IAAI,CAAC,mBAAmB,CAAC,UAAU,CAAC,EAAE,CAAC;YAC1C,sEAAsE;YACtE,OAAO;gBACL,MAAM,EACJ,oEAAoE;gBACtE,WAAW,EAAE,KAAK;aACnB,CAAC;QACJ,CAAC;QAED,IAAI,IAAI,CAAC,eAAe,CAAC,UAAU,EAAE,mCAAmC,CAAC,EAAE,CAAC;YAC1E,OAAO;gBACL,MAAM,EAAE,gDAAgD;gBACxD,WAAW,EAAE,IAAI;aAClB,CAAC;QACJ,CAAC;QAED,OAAO,IAAI,CAAC;IACd,CAAC;IAEO,mBAAmB,CAAC,UAA+B;QACzD,MAAM,OAAO,GAAkC;YAC7C,CAAC,uBAAuB,CAAC,MAAM,CAAC,EAAE,0BAA0B;YAC5D,CAAC,uBAAuB,CAAC,aAAa,CAAC,EACrC,iCAAiC;YACnC,CAAC,uBAAuB,CAAC,gBAAgB,CAAC,EACxC,oCAAoC;SACvC,CAAC;QAEF,OAAO,IAAI,CAAC,uBAAuB,CAAC,UAAU,EAAE,OAAO,CAAC,IAAI,EAAE,CAAC;IACjE,CAAC;IAED;;;;;OAKG;IACK,UAAU,CAAC,UAA+B;QAChD,MAAM,OAAO,GAAsB,EAAE,CAAC;QAEtC,MAAM,OAAO,GAAuB,IAAI,CAAC,UAAU,CACjD,UAAU,EACV,WAAW,CACZ,CAAC;QAEF,IAAI,OAAO,EAAE,CAAC;YACZ,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM,EAAE,wCAAwC;gBAChD,OAAO,EAAE,OAAO;aACjB,CAAC,CAAC;QACL,CAAC;QAED,MAAM,WAAW,GAAuB,IAAI,CAAC,cAAc,CAAC,UAAU,CAAC,CAAC;QAExE,IAAI,WAAW,EAAE,CAAC;YAChB,OAAO,CAAC,IAAI,CAAC;gBACX,MAAM,EAAE,cAAc;gBACtB,OAAO,EAAE,WAAW;aACrB,CAAC,CAAC;QACL,CAAC;QAED,OAAO,OAAO,CAAC;IACjB,CAAC;IAEe,KAAK,CAAC,MAAM,CAC1B,WAAuC;;QAEvC,MAAM,iBAAiB,GAAwB,IAAI,GAAG,EAAkB,CAAC;QAEzE,KAAK,IAAI,CAAC,GAAW,CAAC,EAAE,CAAC,GAAG,WAAW,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE,CAAC;YACpD,MAAM,UAAU,GAAwB,WAAW,CAAC,CAAC,CAAE,CAAC;YAExD,MAAM,IAAI,GAAe;gBACvB,UAAU,EAAE,gCAAgC,IAAI,CAAC,mBAAmB,CAClE,UAAU,CACX,EAAE;gBACH,oBAAoB,EAAE,CAAC,UAAU,CAAC,YAAY,IAAI,IAAI,IAAI,EAAE,CAAC,CAAC,OAAO,EAAE;gBACvE,gEAAgE;gBAChE,OAAO,EAAE,UAAU,CAAC,EAAG,CAAC,QAAQ,EAAE;gBAClC,IAAI,EAAE;oBACJ,OAAO,EAAE,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC;iBACrC;aACF,CAAC;YAEF,MAAM,UAAU,GAAuB,IAAI,CAAC,aAAa,CAAC,UAAU,CAAC,CAAC;YAEtE,IAAI,UAAU,KAAK,SAAS,EAAE,CAAC;gBAC7B,IAAI,CAAC,iBAAiB,CAAC,GAAG;oBACxB,YAAY,EAAE,KAAK;oBACnB,MAAM,EAAE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC;iBAC9B,CAAC;YACJ,CAAC;YAED,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,IAAI,CACd,gDAAgD,EAChD,IAAI,EACJ;oBACE,OAAO,EAAE;wBACP,aAAa,EAAE,UAAU,8BAA8B,EAAE;wBACzD,kBAAkB,EAAE,kBAAkB;wBACtC,2BAA2B,EAAE,OAAO;wBACpC,cAAc,EAAE,kBAAkB;qBACnC;oBACD,OAAO,EAAE,kBAAkB;iBAC5B,CACF,CAAC;YACJ,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,MAAM,MAAM,GAAuB,MAAC,GAAkB,CAAC,QAAQ,0CAAE,MAAM,CAAC;gBAExE;;;;;;mBAMG;gBACH,IAAI,MAAM,KAAK,GAAG,IAAI,MAAM,KAAK,GAAG,EAAE,CAAC;oBACrC,MAAM,GAAG,CAAC;gBACZ,CAAC;gBAED,iBAAiB,CAAC,GAAG,CAAC,CAAC,EAAE,wBAAwB,CAAC,eAAe,CAAC,GAAG,CAAC,CAAC,CAAC;YAC1E,CAAC;QACH,CAAC;QAED,OAAO,EAAE,iBAAiB,EAAE,CAAC;IAC/B,CAAC;CACF"}
|
|
@@ -1,133 +0,0 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
|
-
import { MetaConversionsAccessToken, MetaConversionsPixelId, MetaGraphApiVersion, } from "../../../EnvironmentConfig";
|
|
3
|
-
import ConversionUploadProvider from "../ConversionUploadProvider";
|
|
4
|
-
import { MarketingConversionType } from "../../../../Types/Marketing/MarketingConversion";
|
|
5
|
-
const REQUEST_TIMEOUT_MS = 30000;
|
|
6
|
-
// Meta rejects website events older than 7 days at upload time.
|
|
7
|
-
const META_MAX_EVENT_AGE_IN_DAYS = 7;
|
|
8
|
-
/*
|
|
9
|
-
* Meta (Facebook) Conversions API
|
|
10
|
-
* (graph.facebook.com/{version}/{pixelId}/events). Emails are SHA-256 hashed
|
|
11
|
-
* per Meta's customer-information matching spec; the raw email never leaves
|
|
12
|
-
* OneUptime. event_id is the conversion row id, so Meta deduplicates
|
|
13
|
-
* retried uploads (and browser-pixel duplicates) server-side.
|
|
14
|
-
*
|
|
15
|
-
* An event is uploadable with a click id, a hashed email, or both — Meta
|
|
16
|
-
* matches on either. Requiring fbclid, as this provider used to, discarded
|
|
17
|
-
* every conversion whose click id had not survived to the moment of
|
|
18
|
-
* conversion, which is most of a sales-led funnel.
|
|
19
|
-
*/
|
|
20
|
-
export default class MetaProvider extends ConversionUploadProvider {
|
|
21
|
-
constructor() {
|
|
22
|
-
super(...arguments);
|
|
23
|
-
this.key = "meta";
|
|
24
|
-
this.displayName = "Meta";
|
|
25
|
-
}
|
|
26
|
-
isConfigured() {
|
|
27
|
-
return Boolean(MetaConversionsPixelId && MetaConversionsAccessToken);
|
|
28
|
-
}
|
|
29
|
-
getProviderSkipReason(conversion) {
|
|
30
|
-
if (!this.getClickId(conversion, "fbclid") &&
|
|
31
|
-
!this.getHashedEmail(conversion)) {
|
|
32
|
-
return {
|
|
33
|
-
reason: "No Meta click id (fbclid) and no email to match on",
|
|
34
|
-
isPermanent: true,
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
if (!this.getEventName(conversion)) {
|
|
38
|
-
return {
|
|
39
|
-
reason: "No Meta event name mapped for this conversion type",
|
|
40
|
-
isPermanent: true,
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
if (this.isOlderThanDays(conversion, META_MAX_EVENT_AGE_IN_DAYS)) {
|
|
44
|
-
return {
|
|
45
|
-
reason: "Conversion older than Meta's 7-day upload window",
|
|
46
|
-
isPermanent: true,
|
|
47
|
-
};
|
|
48
|
-
}
|
|
49
|
-
return null;
|
|
50
|
-
}
|
|
51
|
-
/*
|
|
52
|
-
* Standard Meta event names. `Schedule` is Meta's own name for a booked
|
|
53
|
-
* meeting, and it carries no revenue — which is what keeps a demo out of the
|
|
54
|
-
* Purchase optimisation pool.
|
|
55
|
-
*/
|
|
56
|
-
getEventName(conversion) {
|
|
57
|
-
const mapping = {
|
|
58
|
-
[MarketingConversionType.SignUp]: "CompleteRegistration",
|
|
59
|
-
[MarketingConversionType.MeetingBooked]: "Schedule",
|
|
60
|
-
[MarketingConversionType.PaidSubscription]: "Purchase",
|
|
61
|
-
};
|
|
62
|
-
return this.resolveByConversionType(conversion, mapping);
|
|
63
|
-
}
|
|
64
|
-
/*
|
|
65
|
-
* fbc format per Meta spec: fb.1.{creationTimeMs}.{fbclid}. We use the
|
|
66
|
-
* conversion time as creation time since the original click timestamp is
|
|
67
|
-
* not stored.
|
|
68
|
-
*/
|
|
69
|
-
buildFbc(fbclid, eventAt) {
|
|
70
|
-
return `fb.1.${eventAt.getTime()}.${fbclid}`;
|
|
71
|
-
}
|
|
72
|
-
async upload(conversions) {
|
|
73
|
-
var _a;
|
|
74
|
-
const data = conversions.map((conversion) => {
|
|
75
|
-
const eventAt = conversion.conversionAt || new Date();
|
|
76
|
-
const fbclid = this.getClickId(conversion, "fbclid");
|
|
77
|
-
const userData = {};
|
|
78
|
-
// Only send fbc when there is a click id to build it from.
|
|
79
|
-
if (fbclid) {
|
|
80
|
-
userData["fbc"] = this.buildFbc(fbclid, eventAt);
|
|
81
|
-
}
|
|
82
|
-
const hashedEmail = this.getHashedEmail(conversion);
|
|
83
|
-
if (hashedEmail) {
|
|
84
|
-
userData["em"] = [hashedEmail];
|
|
85
|
-
}
|
|
86
|
-
const eventName = this.getEventName(conversion) || "Lead";
|
|
87
|
-
const payload = {
|
|
88
|
-
event_name: eventName,
|
|
89
|
-
event_time: Math.floor(eventAt.getTime() / 1000),
|
|
90
|
-
event_id: conversion.id.toString(),
|
|
91
|
-
/*
|
|
92
|
-
* Not "website": website events REQUIRE client_user_agent and
|
|
93
|
-
* event_source_url, which a server-side backfill does not have —
|
|
94
|
-
* Meta rejects the whole batch without them.
|
|
95
|
-
*/
|
|
96
|
-
action_source: "other",
|
|
97
|
-
user_data: userData,
|
|
98
|
-
};
|
|
99
|
-
const valueInUSD = this.getValueInUSD(conversion);
|
|
100
|
-
/*
|
|
101
|
-
* Purchase events REQUIRE custom_data.value and currency — one
|
|
102
|
-
* valueless Purchase (custom-pricing plan) would 400 the entire
|
|
103
|
-
* batch. Send 0 when the value is unknown. Lead-shaped events
|
|
104
|
-
* (Schedule, Lead) never carry a value: getValueInUSD returns
|
|
105
|
-
* undefined for them, so no custom_data is attached at all.
|
|
106
|
-
*/
|
|
107
|
-
if (eventName === "Purchase" || valueInUSD !== undefined) {
|
|
108
|
-
payload["custom_data"] = {
|
|
109
|
-
value: valueInUSD !== null && valueInUSD !== void 0 ? valueInUSD : 0,
|
|
110
|
-
currency: "USD",
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
return payload;
|
|
114
|
-
});
|
|
115
|
-
const url = `https://graph.facebook.com/${MetaGraphApiVersion}/${MetaConversionsPixelId}/events`;
|
|
116
|
-
const response = await axios.post(url, {
|
|
117
|
-
data: data,
|
|
118
|
-
access_token: MetaConversionsAccessToken,
|
|
119
|
-
}, {
|
|
120
|
-
timeout: REQUEST_TIMEOUT_MS,
|
|
121
|
-
});
|
|
122
|
-
const eventsReceived = ((_a = response.data) === null || _a === void 0 ? void 0 : _a["events_received"]) || 0;
|
|
123
|
-
if (eventsReceived !== conversions.length) {
|
|
124
|
-
/*
|
|
125
|
-
* Retrying the whole batch is safe: event_id makes redelivery
|
|
126
|
-
* idempotent on Meta's side.
|
|
127
|
-
*/
|
|
128
|
-
throw new Error(`Meta accepted ${eventsReceived} of ${conversions.length} events`);
|
|
129
|
-
}
|
|
130
|
-
return { permanentFailures: new Map() };
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
//# sourceMappingURL=Meta.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"Meta.js","sourceRoot":"","sources":["../../../../../../Server/Utils/Marketing/Providers/Meta.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
import axios from "axios";
|
|
2
|
-
import { MicrosoftAdsAccountId, MicrosoftAdsCustomerId, MicrosoftAdsDeveloperToken, MicrosoftAdsMeetingBookedConversionName, MicrosoftAdsOAuthClientId, MicrosoftAdsOAuthClientSecret, MicrosoftAdsOAuthRefreshToken, MicrosoftAdsPaidSubscriptionConversionName, MicrosoftAdsSignUpConversionName, } from "../../../EnvironmentConfig";
|
|
3
|
-
import ConversionUploadProvider from "../ConversionUploadProvider";
|
|
4
|
-
import { MarketingConversionType } from "../../../../Types/Marketing/MarketingConversion";
|
|
5
|
-
const REQUEST_TIMEOUT_MS = 30000;
|
|
6
|
-
// Microsoft accepts offline conversions up to 90 days after the click.
|
|
7
|
-
const MICROSOFT_MAX_CONVERSION_AGE_IN_DAYS = 90;
|
|
8
|
-
/*
|
|
9
|
-
* Microsoft Advertising (Bing Ads) offline conversions via the Campaign
|
|
10
|
-
* Management v13 REST surface (OfflineConversions/Apply), matching on msclkid
|
|
11
|
-
* or on a SHA-256 email (Microsoft's enhanced conversions). Offline conversion
|
|
12
|
-
* goals are matched by conversion NAME configured in the Microsoft Advertising
|
|
13
|
-
* UI. OAuth2 refresh-token flow against Microsoft identity platform with the
|
|
14
|
-
* msads.manage scope.
|
|
15
|
-
*/
|
|
16
|
-
export default class MicrosoftAdsProvider extends ConversionUploadProvider {
|
|
17
|
-
constructor() {
|
|
18
|
-
super(...arguments);
|
|
19
|
-
this.key = "microsoft";
|
|
20
|
-
this.displayName = "Microsoft Advertising";
|
|
21
|
-
this.cachedAccessToken = null;
|
|
22
|
-
this.cachedAccessTokenExpiresAt = 0;
|
|
23
|
-
}
|
|
24
|
-
isConfigured() {
|
|
25
|
-
return Boolean(MicrosoftAdsDeveloperToken &&
|
|
26
|
-
MicrosoftAdsOAuthClientId &&
|
|
27
|
-
MicrosoftAdsOAuthRefreshToken &&
|
|
28
|
-
MicrosoftAdsCustomerId &&
|
|
29
|
-
MicrosoftAdsAccountId);
|
|
30
|
-
}
|
|
31
|
-
getProviderSkipReason(conversion) {
|
|
32
|
-
if (!this.getClickId(conversion, "msclkid") &&
|
|
33
|
-
!this.getHashedEmail(conversion)) {
|
|
34
|
-
return {
|
|
35
|
-
reason: "No Microsoft click id (msclkid) and no email to match on",
|
|
36
|
-
isPermanent: true,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
if (!this.getConversionName(conversion)) {
|
|
40
|
-
// Config gap — leave pending so it uploads once the name is configured.
|
|
41
|
-
return {
|
|
42
|
-
reason: "No Microsoft Advertising conversion name configured for this conversion type",
|
|
43
|
-
isPermanent: false,
|
|
44
|
-
};
|
|
45
|
-
}
|
|
46
|
-
if (this.isOlderThanDays(conversion, MICROSOFT_MAX_CONVERSION_AGE_IN_DAYS)) {
|
|
47
|
-
return {
|
|
48
|
-
reason: "Conversion older than Microsoft's 90-day click window",
|
|
49
|
-
isPermanent: true,
|
|
50
|
-
};
|
|
51
|
-
}
|
|
52
|
-
return null;
|
|
53
|
-
}
|
|
54
|
-
getConversionName(conversion) {
|
|
55
|
-
const mapping = {
|
|
56
|
-
[MarketingConversionType.SignUp]: MicrosoftAdsSignUpConversionName,
|
|
57
|
-
[MarketingConversionType.MeetingBooked]: MicrosoftAdsMeetingBookedConversionName,
|
|
58
|
-
[MarketingConversionType.PaidSubscription]: MicrosoftAdsPaidSubscriptionConversionName,
|
|
59
|
-
};
|
|
60
|
-
return this.resolveByConversionType(conversion, mapping) || "";
|
|
61
|
-
}
|
|
62
|
-
async getAccessToken() {
|
|
63
|
-
var _a, _b;
|
|
64
|
-
if (this.cachedAccessToken &&
|
|
65
|
-
Date.now() < this.cachedAccessTokenExpiresAt) {
|
|
66
|
-
return this.cachedAccessToken;
|
|
67
|
-
}
|
|
68
|
-
const params = new URLSearchParams();
|
|
69
|
-
params.append("client_id", MicrosoftAdsOAuthClientId);
|
|
70
|
-
if (MicrosoftAdsOAuthClientSecret) {
|
|
71
|
-
params.append("client_secret", MicrosoftAdsOAuthClientSecret);
|
|
72
|
-
}
|
|
73
|
-
params.append("refresh_token", MicrosoftAdsOAuthRefreshToken);
|
|
74
|
-
params.append("grant_type", "refresh_token");
|
|
75
|
-
params.append("scope", "https://ads.microsoft.com/msads.manage offline_access");
|
|
76
|
-
const response = await axios.post("https://login.microsoftonline.com/common/oauth2/v2.0/token", params.toString(), {
|
|
77
|
-
headers: {
|
|
78
|
-
"content-type": "application/x-www-form-urlencoded",
|
|
79
|
-
},
|
|
80
|
-
timeout: REQUEST_TIMEOUT_MS,
|
|
81
|
-
});
|
|
82
|
-
const accessToken = (_a = response.data) === null || _a === void 0 ? void 0 : _a.access_token;
|
|
83
|
-
if (!accessToken) {
|
|
84
|
-
throw new Error("Microsoft OAuth token response had no access_token");
|
|
85
|
-
}
|
|
86
|
-
this.cachedAccessToken = accessToken;
|
|
87
|
-
this.cachedAccessTokenExpiresAt =
|
|
88
|
-
Date.now() + ((((_b = response.data) === null || _b === void 0 ? void 0 : _b.expires_in) || 3600) - 60) * 1000;
|
|
89
|
-
return accessToken;
|
|
90
|
-
}
|
|
91
|
-
async upload(conversions) {
|
|
92
|
-
var _a;
|
|
93
|
-
const accessToken = await this.getAccessToken();
|
|
94
|
-
const body = {
|
|
95
|
-
OfflineConversions: conversions.map((conversion) => {
|
|
96
|
-
const payload = {
|
|
97
|
-
ConversionName: this.getConversionName(conversion),
|
|
98
|
-
// ISO 8601 UTC, e.g. 2026-07-18T12:34:56Z.
|
|
99
|
-
ConversionTime: (conversion.conversionAt || new Date())
|
|
100
|
-
.toISOString()
|
|
101
|
-
.replace(/\.\d{3}Z$/, "Z"),
|
|
102
|
-
};
|
|
103
|
-
const clickId = this.getClickId(conversion, "msclkid");
|
|
104
|
-
if (clickId) {
|
|
105
|
-
payload["MicrosoftClickId"] = clickId;
|
|
106
|
-
}
|
|
107
|
-
const hashedEmail = this.getHashedEmail(conversion);
|
|
108
|
-
if (hashedEmail) {
|
|
109
|
-
payload["HashedEmailAddress"] = hashedEmail;
|
|
110
|
-
}
|
|
111
|
-
const valueInUSD = this.getValueInUSD(conversion);
|
|
112
|
-
if (valueInUSD !== undefined) {
|
|
113
|
-
payload["ConversionValue"] = valueInUSD;
|
|
114
|
-
payload["ConversionCurrencyCode"] = "USD";
|
|
115
|
-
}
|
|
116
|
-
return payload;
|
|
117
|
-
}),
|
|
118
|
-
};
|
|
119
|
-
const response = await axios.post("https://campaign.api.bingads.microsoft.com/CampaignManagement/v13/OfflineConversions/Apply", body, {
|
|
120
|
-
headers: {
|
|
121
|
-
Authorization: `Bearer ${accessToken}`,
|
|
122
|
-
DeveloperToken: MicrosoftAdsDeveloperToken,
|
|
123
|
-
CustomerId: MicrosoftAdsCustomerId,
|
|
124
|
-
CustomerAccountId: MicrosoftAdsAccountId,
|
|
125
|
-
"Content-Type": "application/json",
|
|
126
|
-
},
|
|
127
|
-
timeout: REQUEST_TIMEOUT_MS,
|
|
128
|
-
});
|
|
129
|
-
/*
|
|
130
|
-
* The Apply response reports per-item failures in PartialErrors, each
|
|
131
|
-
* carrying the Index of the failing item in the submitted array.
|
|
132
|
-
*/
|
|
133
|
-
const permanentFailures = new Map();
|
|
134
|
-
const partialErrors = ((_a = response.data) === null || _a === void 0 ? void 0 : _a["PartialErrors"]) || [];
|
|
135
|
-
for (const partialError of partialErrors) {
|
|
136
|
-
if (typeof (partialError === null || partialError === void 0 ? void 0 : partialError["Index"]) === "number") {
|
|
137
|
-
permanentFailures.set(partialError["Index"], `${partialError["ErrorCode"] || ""} ${partialError["Message"] || "Unknown Microsoft Advertising error"}`.trim());
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
|
-
return { permanentFailures };
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
//# sourceMappingURL=MicrosoftAds.js.map
|