diff --git a/apps/frontend/src/composables/featureFlags.ts b/apps/frontend/src/composables/featureFlags.ts index a30b70a5d0..a1acb5878c 100644 --- a/apps/frontend/src/composables/featureFlags.ts +++ b/apps/frontend/src/composables/featureFlags.ts @@ -38,6 +38,7 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({ showProjectPageQuickServerButton: false, newProjectGeneralSettings: false, newProjectEnvironmentSettings: true, + archonSentryCapture: false, hideRussiaCensorshipBanner: false, disablePrettyProjectUrlRedirects: false, hidePreviewBanner: false, diff --git a/apps/frontend/src/helpers/api.ts b/apps/frontend/src/helpers/api.ts index 5b49bcf3c3..1434fe144f 100644 --- a/apps/frontend/src/helpers/api.ts +++ b/apps/frontend/src/helpers/api.ts @@ -13,6 +13,8 @@ import { } from '@modrinth/api-client' import type { Ref } from 'vue' +import { useFeatureFlags } from '~/composables/featureFlags.ts' + async function getRateLimitKeyFromSecretsStore(): Promise { try { const mod = 'cloudflare:workers' @@ -28,6 +30,7 @@ export function createModrinthClient( auth: Ref<{ token: string | undefined }>, config: { apiBaseUrl: string; archonBaseUrl: string; rateLimitKey?: string }, ): NuxtModrinthClient { + const flags = useFeatureFlags() const optionalFeatures = [ import.meta.dev ? (new VerboseLoggingFeature() as AbstractFeature) : undefined, ].filter(Boolean) as AbstractFeature[] @@ -35,6 +38,7 @@ export function createModrinthClient( const clientConfig: NuxtClientConfig = { labrinthBaseUrl: config.apiBaseUrl, archonBaseUrl: config.archonBaseUrl, + archonSentryCapture: () => flags.value.archonSentryCapture, rateLimitKey: config.rateLimitKey || getRateLimitKeyFromSecretsStore, features: [ // for modrinth hosting diff --git a/packages/api-client/src/core/abstract-client.ts b/packages/api-client/src/core/abstract-client.ts index 42429d3ae3..8ab7fee256 100644 --- a/packages/api-client/src/core/abstract-client.ts +++ b/packages/api-client/src/core/abstract-client.ts @@ -126,6 +126,7 @@ export abstract class AbstractModrinthClient extends AbstractUploadClient { ...options.headers, }, } + this.attachArchonSentryCaptureHeader(mergedOptions) const headers = mergedOptions.headers if (headers && 'Content-Type' in headers && headers['Content-Type'] === '') { @@ -309,6 +310,21 @@ export abstract class AbstractModrinthClient extends AbstractUploadClient { return headers } + protected attachArchonSentryCaptureHeader(options: RequestOptions): void { + if (options.api !== 'archon' || !options.headers || !this.shouldCaptureArchonRequests()) { + return + } + + options.headers['modrinth-sentry-capture'] = '1' + } + + private shouldCaptureArchonRequests(): boolean { + const archonSentryCapture = this.config.archonSentryCapture + return typeof archonSentryCapture === 'function' + ? archonSentryCapture() + : archonSentryCapture === true + } + /** * Execute the actual HTTP request * diff --git a/packages/api-client/src/platform/xhr-upload-client.ts b/packages/api-client/src/platform/xhr-upload-client.ts index de7dd9c233..c47b4c342f 100644 --- a/packages/api-client/src/platform/xhr-upload-client.ts +++ b/packages/api-client/src/platform/xhr-upload-client.ts @@ -46,6 +46,7 @@ export abstract class XHRUploadClient extends AbstractModrinthClient { ...options.headers, }, } + this.attachArchonSentryCaptureHeader(mergedOptions) const context = this.buildUploadContext(url, path, mergedOptions) diff --git a/packages/api-client/src/types/client.ts b/packages/api-client/src/types/client.ts index fa9d20a31c..5196f2209a 100644 --- a/packages/api-client/src/types/client.ts +++ b/packages/api-client/src/types/client.ts @@ -55,6 +55,14 @@ export interface ClientConfig { */ headers?: Record + /** + * Whether to attach `modrinth-sentry-capture: 1` to Archon requests. + * Can be a callback so apps can drive this from runtime feature flags. + * + * @default false + */ + archonSentryCapture?: boolean | (() => boolean) + /** * Features to enable for this client * Features are applied in the order they appear in this array