diff --git a/packages/back-end/src/jobs/sdkWebhooks.ts b/packages/back-end/src/jobs/sdkWebhooks.ts index b8d309dd38d..e577df8f9a0 100644 --- a/packages/back-end/src/jobs/sdkWebhooks.ts +++ b/packages/back-end/src/jobs/sdkWebhooks.ts @@ -308,85 +308,117 @@ export async function fireSdkWebhook( } } -export async function fireGlobalSdkWebhooks( +export async function getSDKConnectionsByPayloadKeys( context: ReqContext | ApiReqContext, payloadKeys: SDKPayloadKey[] ) { - for (const webhook of WEBHOOKS) { - const { url, signingKey, method, headers, sendPayload } = webhook; - if (!payloadKeys.length) return; + if (!payloadKeys.length) return []; - const connections = await findSDKConnectionsByOrganization(context); + const connections = await findSDKConnectionsByOrganization(context); + if (!connections) return []; - if (!connections) return; - for (let i = 0; i < connections.length; i++) { - const connection = connections[i]; + return connections.filter((c) => { + const environmentDoc = context.org?.settings?.environments?.find( + (e) => e.id === c.environment + ); + const filteredProjects = filterProjectsByEnvironmentWithNull( + c.projects, + environmentDoc, + true + ); + if (!filteredProjects) { + return false; + } - const environmentDoc = context.org?.settings?.environments?.find( - (e) => e.id === connection.environment - ); - const filteredProjects = filterProjectsByEnvironmentWithNull( - connection.projects, - environmentDoc, - true - ); - if (!filteredProjects) { - continue; - } + // Skip if this SDK Connection isn't affected by the changes + if ( + !payloadKeys.some( + (key) => + key.environment === c.environment && + (!filteredProjects.length || filteredProjects.includes(key.project)) + ) + ) { + return false; + } + return true; + }); +} - // Skip if this SDK Connection isn't affected by the changes - if ( - payloadKeys.some( - (key) => - key.environment === connection.environment && - (!filteredProjects.length || filteredProjects.includes(key.project)) - ) - ) { - const defs = await getFeatureDefinitions({ - context, - capabilities: getConnectionSDKCapabilities(connection), - environment: connection.environment, - projects: filteredProjects, - encryptionKey: connection.encryptPayload - ? connection.encryptionKey - : undefined, - - includeVisualExperiments: connection.includeVisualExperiments, - includeDraftExperiments: connection.includeDraftExperiments, - includeExperimentNames: connection.includeExperimentNames, - includeRedirectExperiments: connection.includeRedirectExperiments, - hashSecureAttributes: connection.hashSecureAttributes, - }); +export async function fireGlobalSdkWebhooksByPayloadKeys( + context: ReqContext | ApiReqContext, + payloadKeys: SDKPayloadKey[] +) { + const connections = await getSDKConnectionsByPayloadKeys( + context, + payloadKeys + ); + await fireGlobalSdkWebhooks(context, connections); +} - const id = `global_${md5(url)}`; - const w: WebhookInterface = { - id, - endpoint: url, - signingKey: signingKey || id, - httpMethod: method, - headers: - typeof headers !== "string" ? JSON.stringify(headers) : headers, - sendPayload: !!sendPayload, - organization: context.org?.id, - created: new Date(), - error: "", - lastSuccess: new Date(), - name: "", - sdks: [connection.key], - useSdkMode: true, - featuresOnly: true, - }; - - const payload = JSON.stringify(defs); - runWebhookFetch({ - webhook: w, - key: connection.key, - payload, - global: true, - }).catch((e) => { - logger.error(e, "Failed to fire global webhook"); - }); - } - } +export async function fireGlobalSdkWebhooks( + context: ReqContext | ApiReqContext, + connections: SDKConnectionInterface[] +) { + if (!connections.length) return; + + for (const connection of connections) { + const environmentDoc = context.org?.settings?.environments?.find( + (e) => e.id === connection.environment + ); + const filteredProjects = filterProjectsByEnvironmentWithNull( + connection.projects, + environmentDoc, + true + ); + + const defs = await getFeatureDefinitions({ + context, + capabilities: getConnectionSDKCapabilities(connection), + environment: connection.environment, + projects: filteredProjects, + encryptionKey: connection.encryptPayload + ? connection.encryptionKey + : undefined, + + includeVisualExperiments: connection.includeVisualExperiments, + includeDraftExperiments: connection.includeDraftExperiments, + includeExperimentNames: connection.includeExperimentNames, + includeRedirectExperiments: connection.includeRedirectExperiments, + hashSecureAttributes: connection.hashSecureAttributes, + }); + + const payload = JSON.stringify(defs); + + WEBHOOKS.forEach((webhook) => { + const { url, signingKey, method, headers, sendPayload } = webhook; + + const id = `global_${md5(url)}`; + const w: WebhookInterface = { + id, + endpoint: url, + signingKey: signingKey || id, + httpMethod: method, + headers: + typeof headers !== "string" ? JSON.stringify(headers) : headers, + sendPayload: !!sendPayload, + organization: context.org?.id, + created: new Date(), + error: "", + lastSuccess: new Date(), + name: "", + sdks: [connection.key], + useSdkMode: true, + featuresOnly: true, + }; + + runWebhookFetch({ + webhook: w, + key: connection.key, + payload, + global: true, + }).catch((e) => { + logger.error(e, "Failed to fire global webhook"); + }); + }); } } diff --git a/packages/back-end/src/jobs/updateAllJobs.ts b/packages/back-end/src/jobs/updateAllJobs.ts index 2c2350721d8..9c50561353d 100644 --- a/packages/back-end/src/jobs/updateAllJobs.ts +++ b/packages/back-end/src/jobs/updateAllJobs.ts @@ -9,10 +9,12 @@ import { getSurrogateKeysFromEnvironments, purgeCDNCache, } from "../util/cdn.util"; +import { logger } from "../util/logger"; import { IS_CLOUD } from "../util/secrets"; import { queueProxyUpdate, queueSingleProxyUpdate } from "./proxyUpdate"; import { fireGlobalSdkWebhooks, + fireGlobalSdkWebhooksByPayloadKeys, queueWebhooksBySdkPayloadKeys, queueWebhooksForSdkConnection, } from "./sdkWebhooks"; @@ -25,10 +27,20 @@ export const triggerWebhookJobs = async ( isProxyEnabled: boolean, isFeature = true ) => { - queueWebhooksBySdkPayloadKeys(context, payloadKeys); - fireGlobalSdkWebhooks(context, payloadKeys); - if (isProxyEnabled) queueProxyUpdate(context, payloadKeys); - queueLegacySdkWebhooks(context, payloadKeys, isFeature); + queueWebhooksBySdkPayloadKeys(context, payloadKeys).catch((e) => { + logger.error(e, "Error queueing webhooks"); + }); + fireGlobalSdkWebhooksByPayloadKeys(context, payloadKeys).catch((e) => { + logger.error(e, "Error firing global webhooks"); + }); + if (isProxyEnabled) { + queueProxyUpdate(context, payloadKeys).catch((e) => { + logger.error(e, "Error queueing proxy update"); + }); + } + queueLegacySdkWebhooks(context, payloadKeys, isFeature).catch((e) => { + logger.error(e, "Error queueing legacy webhooks"); + }); const surrogateKeys = getSurrogateKeysFromEnvironments(context.org.id, [ ...environments, ]); @@ -42,7 +54,9 @@ export const triggerSingleSDKWebhookJobs = async ( newProxy: ProxyConnection, isUsingProxy: boolean ) => { - queueWebhooksForSdkConnection(context, connection); + queueWebhooksForSdkConnection(context, connection).catch((e) => { + logger.error(e, "Error queueing webhooks"); + }); if (isUsingProxy) { if (IS_CLOUD) { const newConnection = { @@ -51,9 +65,17 @@ export const triggerSingleSDKWebhookJobs = async ( proxy: newProxy, } as SDKConnectionInterface; - queueSingleProxyUpdate(context.org.id, newConnection, IS_CLOUD); + queueSingleProxyUpdate(context.org.id, newConnection, IS_CLOUD).catch( + (e) => { + logger.error(e, "Error queueing single proxy update"); + } + ); } } + fireGlobalSdkWebhooks(context, [connection]).catch((e) => { + logger.error(e, "Error firing global webhook"); + }); + await purgeCDNCache(connection.organization, [connection.key]); }; diff --git a/packages/back-end/src/models/ExperimentModel.ts b/packages/back-end/src/models/ExperimentModel.ts index cbdfd3b2b60..4f0363023e7 100644 --- a/packages/back-end/src/models/ExperimentModel.ts +++ b/packages/back-end/src/models/ExperimentModel.ts @@ -1364,7 +1364,9 @@ const onExperimentUpdate = async ({ isEqual ); - refreshSDKPayloadCache(context, payloadKeys); + refreshSDKPayloadCache(context, payloadKeys).catch((e) => { + logger.error(e, "Error refreshing SDK payload cache"); + }); } }; @@ -1381,5 +1383,7 @@ const onExperimentDelete = async ( } const payloadKeys = getPayloadKeys(context, experiment, linkedFeatures); - refreshSDKPayloadCache(context, payloadKeys); + refreshSDKPayloadCache(context, payloadKeys).catch((e) => { + logger.error(e, "Error refreshing SDK payload cache"); + }); }; diff --git a/packages/back-end/src/services/features.ts b/packages/back-end/src/services/features.ts index ee1920d12b6..b3372a71afc 100644 --- a/packages/back-end/src/services/features.ts +++ b/packages/back-end/src/services/features.ts @@ -409,7 +409,9 @@ export async function refreshSDKPayloadCache( // Batch the promises in chunks of 4 at a time to avoid overloading Mongo await promiseAllChunks(promises, 4); - triggerWebhookJobs(context, payloadKeys, environments, true); + triggerWebhookJobs(context, payloadKeys, environments, true).catch((e) => { + logger.error(e, "Error triggering webhook jobs"); + }); } export type FeatureDefinitionsResponseArgs = { diff --git a/packages/back-end/src/util/http.util.ts b/packages/back-end/src/util/http.util.ts index d96f13437df..83a428a7d06 100644 --- a/packages/back-end/src/util/http.util.ts +++ b/packages/back-end/src/util/http.util.ts @@ -70,8 +70,6 @@ export const cancellableFetch = async ( stringBody, }; } catch (e) { - logger.error(e, "cancellableFetch -> readResponseBody"); - if (e.name === "AbortError" && response) { logger.warn(e, `Response aborted due to content size: ${received}`);