Skip to content

Commit 3b21944

Browse files
authored
Implement Labrinth Canary API flag (#5531)
1 parent 086508b commit 3b21944

7 files changed

Lines changed: 71 additions & 1 deletion

File tree

apps/frontend/src/composables/featureFlags.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const DEFAULT_FEATURE_FLAGS = validateValues({
4343
hidePreviewBanner: false,
4444
i18nDebug: false,
4545
showDiscoverProjectButtons: false,
46+
labrinthApiCanary: false,
4647
} as const)
4748

4849
export type FeatureFlag = keyof typeof DEFAULT_FEATURE_FLAGS

apps/frontend/src/composables/fetch.js

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55

66
let cachedRateLimitKey = undefined
77
let rateLimitKeyPromise = undefined
8+
const LABRINTH_CANARY_COOKIE = 'labrinth-canary=always'
89

910
async function getRateLimitKey(config) {
1011
if (config.rateLimitKey) return config.rateLimitKey
@@ -38,6 +39,15 @@ export const useBaseFetch = async (url, options = {}, skipAuth = false) => {
3839
options.headers['x-ratelimit-key'] = await getRateLimitKey(config)
3940
}
4041

42+
if (useFeatureFlags().value.labrinthApiCanary) {
43+
const existingCookie = options.headers.cookie
44+
if (!existingCookie?.split('; ').includes(LABRINTH_CANARY_COOKIE)) {
45+
options.headers.cookie = existingCookie
46+
? `${existingCookie}; ${LABRINTH_CANARY_COOKIE}`
47+
: LABRINTH_CANARY_COOKIE
48+
}
49+
}
50+
4151
if (!skipAuth) {
4252
const auth = await useAuth()
4353

apps/frontend/src/helpers/api.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@ import {
22
type AbstractFeature,
33
type AuthConfig,
44
AuthFeature,
5+
CanaryCookieFeature,
56
CircuitBreakerFeature,
7+
LABRINTH_CANARY_COOKIE,
68
NodeAuthFeature,
79
nodeAuthState,
810
NuxtCircuitBreakerStorage,
@@ -28,7 +30,11 @@ export function createModrinthClient(
2830
auth: Ref<{ token: string | undefined }>,
2931
config: { apiBaseUrl: string; archonBaseUrl: string; rateLimitKey?: string },
3032
): NuxtModrinthClient {
33+
const flags = useFeatureFlags()
3134
const optionalFeatures = [
35+
new CanaryCookieFeature({
36+
getCookie: () => (flags.value.labrinthApiCanary ? LABRINTH_CANARY_COOKIE : undefined),
37+
}) as AbstractFeature,
3238
import.meta.dev ? (new VerboseLoggingFeature() as AbstractFeature) : undefined,
3339
].filter(Boolean) as AbstractFeature[]
3440

apps/frontend/src/middleware/project.global.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,11 @@ export default defineNuxtRouteMiddleware(async (to) => {
2525

2626
const queryClient = useAppQueryClient()
2727
const authToken = useCookie('auth-token')
28-
const client = useServerModrinthClient({ authToken: authToken.value || undefined })
28+
const flags = useFeatureFlags()
29+
const client = useServerModrinthClient({
30+
authToken: authToken.value || undefined,
31+
canaryCookie: flags.value.labrinthApiCanary,
32+
})
2933
const tags = useGeneratedState()
3034
const projectId = to.params.id as string
3135

apps/frontend/src/server/utils/api-client.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
11
import {
22
type AuthConfig,
33
AuthFeature,
4+
CanaryCookieFeature,
45
type FeatureConfig,
6+
LABRINTH_CANARY_COOKIE,
57
type NuxtClientConfig,
68
NuxtModrinthClient,
79
} from '@modrinth/api-client'
@@ -20,6 +22,7 @@ async function getRateLimitKeyFromSecretsStore(): Promise<string | undefined> {
2022
export interface ServerModrinthClientOptions {
2123
event?: H3Event
2224
authToken?: string
25+
canaryCookie?: boolean
2326
}
2427

2528
export function useServerModrinthClient(options?: ServerModrinthClientOptions): NuxtModrinthClient {
@@ -37,6 +40,10 @@ export function useServerModrinthClient(options?: ServerModrinthClientOptions):
3740
)
3841
}
3942

43+
if (options?.canaryCookie) {
44+
features.push(new CanaryCookieFeature({ getCookie: () => LABRINTH_CANARY_COOKIE }))
45+
}
46+
4047
const clientConfig: NuxtClientConfig = {
4148
labrinthBaseUrl: apiBaseUrl,
4249
rateLimitKey: config.rateLimitKey || getRateLimitKeyFromSecretsStore,
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { AbstractFeature, type FeatureConfig } from '../core/abstract-feature'
2+
3+
export const LABRINTH_CANARY_COOKIE = 'labrinth-canary=always'
4+
5+
export interface CanaryCookieConfig extends FeatureConfig {
6+
getCookie?: () => string | undefined | Promise<string | undefined>
7+
}
8+
9+
export class CanaryCookieFeature extends AbstractFeature {
10+
declare protected config: CanaryCookieConfig
11+
12+
constructor(config?: CanaryCookieConfig) {
13+
super(config)
14+
}
15+
16+
shouldApply(context: Parameters<AbstractFeature['shouldApply']>[0]): boolean {
17+
return super.shouldApply(context) && context.options.api === 'labrinth'
18+
}
19+
20+
async execute<T>(next: () => Promise<T>, context: Parameters<AbstractFeature['execute']>[1]) {
21+
const cookie = this.config.getCookie ? await this.config.getCookie() : LABRINTH_CANARY_COOKIE
22+
if (!cookie) {
23+
return next()
24+
}
25+
26+
const headers = { ...(context.options.headers ?? {}) }
27+
const existingCookie = headers.cookie ?? headers.Cookie
28+
29+
if (!existingCookie?.split('; ').includes(cookie)) {
30+
headers.cookie = existingCookie ? `${existingCookie}; ${cookie}` : cookie
31+
delete headers.Cookie
32+
context.options.headers = headers
33+
}
34+
35+
return next()
36+
}
37+
}

packages/api-client/src/index.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,11 @@ export {
99
} from './core/abstract-websocket'
1010
export { ModrinthApiError, ModrinthServerError } from './core/errors'
1111
export { type AuthConfig, AuthFeature } from './features/auth'
12+
export {
13+
type CanaryCookieConfig,
14+
CanaryCookieFeature,
15+
LABRINTH_CANARY_COOKIE,
16+
} from './features/canary-cookie'
1217
export {
1318
type CircuitBreakerConfig,
1419
CircuitBreakerFeature,

0 commit comments

Comments
 (0)