Skip to content

Commit 8ba0321

Browse files
committed
feat: use workers api key
1 parent 69868a9 commit 8ba0321

File tree

6 files changed

+141
-63
lines changed

6 files changed

+141
-63
lines changed

apps/frontend/nuxt.config.ts

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -238,16 +238,16 @@ export default defineNuxtConfig({
238238
optimizeTranslationDirective: false,
239239
},
240240
},
241-
nitro: {
242-
rollupConfig: {
243-
// @ts-expect-error because of rolldown-vite - completely fine though
244-
plugins: [serverSidedVue()],
245-
},
246-
preset: 'cloudflare_module',
247-
cloudflare: {
248-
nodeCompat: true,
249-
},
250-
},
241+
nitro: {
242+
rollupConfig: {
243+
// @ts-expect-error because of rolldown-vite - completely fine though
244+
plugins: [serverSidedVue()],
245+
},
246+
preset: 'cloudflare_module',
247+
cloudflare: {
248+
nodeCompat: true,
249+
},
250+
},
251251
devtools: {
252252
enabled: true,
253253
},

apps/frontend/package.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,9 @@
1212
"fix": "eslint . --fix && prettier --write .",
1313
"intl:extract": "formatjs extract \"src/{components,composables,layouts,middleware,modules,pages,plugins,utils}/**/*.{vue,ts,tsx,js,jsx,mts,cts,mjs,cjs}\" \"src/error.vue\" --ignore \"**/*.d.ts\" --ignore node_modules --out-file src/locales/en-US/index.json --format crowdin --preserve-whitespace",
1414
"test": "nuxi build",
15-
"cf-deploy": "pnpm run build && wrangler deploy --env preview",
16-
"cf-dev": "pnpm run build && wrangler dev --env preview",
17-
"cf-typegen": "wrangler types"
15+
"cf-deploy": "pnpm run build && wrangler deploy --env preview",
16+
"cf-dev": "pnpm run build && wrangler dev --env preview",
17+
"cf-typegen": "wrangler types"
1818
},
1919
"devDependencies": {
2020
"@formatjs/cli": "^6.2.12",

apps/frontend/src/composables/fetch.js

Lines changed: 23 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,25 @@
1+
let cachedRateLimitKey = undefined
2+
let rateLimitKeyPromise = undefined
3+
4+
async function getRateLimitKey(config) {
5+
if (config.rateLimitKey) return config.rateLimitKey
6+
if (cachedRateLimitKey !== undefined) return cachedRateLimitKey
7+
8+
if (!rateLimitKeyPromise) {
9+
rateLimitKeyPromise = (async () => {
10+
try {
11+
const { env } = await import('cloudflare:workers')
12+
return await env.RATE_LIMIT_KEY_SECRET?.get()
13+
} catch {
14+
return undefined
15+
}
16+
})()
17+
}
18+
19+
cachedRateLimitKey = await rateLimitKeyPromise
20+
return cachedRateLimitKey
21+
}
22+
123
export const useBaseFetch = async (url, options = {}, skipAuth = false) => {
224
const config = useRuntimeConfig()
325
let base = import.meta.server ? config.apiBaseUrl : config.public.apiBaseUrl
@@ -7,7 +29,7 @@ export const useBaseFetch = async (url, options = {}, skipAuth = false) => {
729
}
830

931
if (import.meta.server) {
10-
options.headers['x-ratelimit-key'] = config.rateLimitKey
32+
options.headers['x-ratelimit-key'] = await getRateLimitKey(config)
1133
}
1234

1335
if (!skipAuth) {

apps/frontend/src/helpers/api.ts

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,17 @@ import {
1111
} from '@modrinth/api-client'
1212
import type { Ref } from 'vue'
1313

14+
async function getRateLimitKeyFromSecretsStore(): Promise<string | undefined> {
15+
try {
16+
// @ts-expect-error only avail in workers env
17+
const { env } = await import('cloudflare:workers')
18+
return await env.RATE_LIMIT_KEY_SECRET?.get()
19+
} catch {
20+
// Not running in Cloudflare Workers environment
21+
return undefined
22+
}
23+
}
24+
1425
export function createModrinthClient(
1526
auth: Ref<{ token: string | undefined }>,
1627
config: { apiBaseUrl: string; archonBaseUrl: string; rateLimitKey?: string },
@@ -22,7 +33,7 @@ export function createModrinthClient(
2233
const clientConfig: NuxtClientConfig = {
2334
labrinthBaseUrl: config.apiBaseUrl,
2435
archonBaseUrl: config.archonBaseUrl,
25-
rateLimitKey: config.rateLimitKey,
36+
rateLimitKey: config.rateLimitKey || getRateLimitKeyFromSecretsStore,
2637
features: [
2738
new AuthFeature({
2839
token: async () => auth.value.token,

apps/frontend/wrangler.jsonc

Lines changed: 49 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,52 @@
11
{
2-
"$schema": "node_modules/wrangler/config-schema.json",
3-
"name": "frontend",
4-
"compatibility_date": "2025-12-10",
5-
"main": "./.output/server/index.mjs",
6-
"assets": {
7-
"binding": "ASSETS",
8-
"directory": "./.output/public/"
9-
},
10-
"compatibility_flags": ["nodejs_compat", "no_nodejs_compat_v2"],
11-
"preview_urls": true,
2+
"$schema": "node_modules/wrangler/config-schema.json",
3+
"name": "frontend",
4+
"compatibility_date": "2025-12-10",
5+
"main": "./.output/server/index.mjs",
6+
"assets": {
7+
"binding": "ASSETS",
8+
"directory": "./.output/public/"
9+
},
10+
"compatibility_flags": ["nodejs_compat", "no_nodejs_compat_v2"],
11+
"preview_urls": true,
1212
"workers_dev": true,
13-
"limits": {
14-
"cpu_ms": 1000 // something would have to be wrong to exceed this
15-
},
16-
"observability": {
17-
"enabled": true,
18-
"head_sampling_rate": 0.001
19-
},
20-
"keep_vars": false,
21-
"vars": {
22-
"ENVIRONMENT": "production",
23-
"BASE_URL": "https://api.modrinth.com/v2/",
24-
"BROWSER_BASE_URL": "https://api.modrinth.com/v2/",
25-
"PYRO_BASE_URL": "https://archon.modrinth.com/",
26-
"STRIPE_PUBLISHABLE_KEY": "pk_live_51JbFxJJygY5LJFfKLVVldb10HlLt24p421OWRsTOWc5sXYFOnFUXWieSc6HD3PHo25ktx8db1WcHr36XGFvZFVUz00V9ixrCs5"
27-
},
28-
"env": {
29-
"staging": {
30-
"observability": {
31-
"enabled": true,
32-
"head_sampling_rate": 0.1
33-
},
34-
"routes": ["staging.modrinth.com/*"],
35-
"vars": {
36-
"ENVIRONMENT": "staging",
37-
"BASE_URL": "https://staging-api.modrinth.com/v2/",
38-
"BROWSER_BASE_URL": "https://staging-api.modrinth.com/v2/",
39-
"PYRO_BASE_URL": "https://staging-archon.modrinth.com/",
40-
"STRIPE_PUBLISHABLE_KEY": "pk_test_51JbFxJJygY5LJFfKV50mnXzz3YLvBVe2Gd1jn7ljWAkaBlRz3VQdxN9mXcPSrFbSqxwAb0svte9yhnsmm7qHfcWn00R611Ce7b"
41-
},
42-
"preview_urls": true
43-
},
44-
}
13+
"limits": {
14+
"cpu_ms": 1000 // something would have to be wrong to exceed this
15+
},
16+
"observability": {
17+
"enabled": true,
18+
"head_sampling_rate": 0.001
19+
},
20+
"keep_vars": false,
21+
"secrets_store_secrets": [
22+
{
23+
"binding": "RATE_LIMIT_KEY_SECRET",
24+
"store_id": "<STORE_ID>", // TODO: Michael plz provide
25+
"secret_name": "rate-limit-ignore-key"
26+
}
27+
],
28+
"vars": {
29+
"ENVIRONMENT": "production",
30+
"BASE_URL": "https://api.modrinth.com/v2/",
31+
"BROWSER_BASE_URL": "https://api.modrinth.com/v2/",
32+
"PYRO_BASE_URL": "https://archon.modrinth.com/",
33+
"STRIPE_PUBLISHABLE_KEY": "pk_live_51JbFxJJygY5LJFfKLVVldb10HlLt24p421OWRsTOWc5sXYFOnFUXWieSc6HD3PHo25ktx8db1WcHr36XGFvZFVUz00V9ixrCs5"
34+
},
35+
"env": {
36+
"staging": {
37+
"observability": {
38+
"enabled": true,
39+
"head_sampling_rate": 0.1
40+
},
41+
"routes": ["staging.modrinth.com/*"],
42+
"vars": {
43+
"ENVIRONMENT": "staging",
44+
"BASE_URL": "https://staging-api.modrinth.com/v2/",
45+
"BROWSER_BASE_URL": "https://staging-api.modrinth.com/v2/",
46+
"PYRO_BASE_URL": "https://staging-archon.modrinth.com/",
47+
"STRIPE_PUBLISHABLE_KEY": "pk_test_51JbFxJJygY5LJFfKV50mnXzz3YLvBVe2Gd1jn7ljWAkaBlRz3VQdxN9mXcPSrFbSqxwAb0svte9yhnsmm7qHfcWn00R611Ce7b"
48+
},
49+
"preview_urls": true
50+
}
51+
}
4552
}

packages/api-client/src/platform/nuxt.ts

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -42,10 +42,11 @@ export class NuxtCircuitBreakerStorage implements CircuitBreakerStorage {
4242
export interface NuxtClientConfig extends ClientConfig {
4343
// TODO: do we want to provide this for tauri+base as well? its not used on app
4444
/**
45-
* Rate limit key for server-side requests
46-
* This is injected as x-ratelimit-key header on server-side
45+
* Rate limit key for server-side requests.
46+
* This is injected as x-ratelimit-key header on server-side.
47+
* Can be a string (for env var) or async function (for CF Secrets Store).
4748
*/
48-
rateLimitKey?: string
49+
rateLimitKey?: string | (() => Promise<string | undefined>)
4950
}
5051

5152
/**
@@ -71,7 +72,9 @@ export interface NuxtClientConfig extends ClientConfig {
7172
* ```
7273
*/
7374
export class NuxtModrinthClient extends AbstractModrinthClient {
74-
declare protected config: NuxtClientConfig
75+
protected declare config: NuxtClientConfig
76+
private rateLimitKeyResolved: string | undefined
77+
private rateLimitKeyPromise: Promise<string | undefined> | undefined
7578

7679
constructor(config: NuxtClientConfig) {
7780
super(config)
@@ -84,6 +87,40 @@ export class NuxtModrinthClient extends AbstractModrinthClient {
8487
})
8588
}
8689

90+
/**
91+
* Resolve the rate limit key, handling both string and async function values.
92+
* Results are cached for subsequent calls.
93+
*/
94+
private async resolveRateLimitKey(): Promise<string | undefined> {
95+
if (this.rateLimitKeyResolved !== undefined) {
96+
return this.rateLimitKeyResolved
97+
}
98+
99+
const key = this.config.rateLimitKey
100+
if (typeof key === 'string') {
101+
this.rateLimitKeyResolved = key
102+
} else if (typeof key === 'function') {
103+
if (!this.rateLimitKeyPromise) {
104+
this.rateLimitKeyPromise = key()
105+
}
106+
this.rateLimitKeyResolved = await this.rateLimitKeyPromise
107+
}
108+
109+
return this.rateLimitKeyResolved
110+
}
111+
112+
/**
113+
* Override request to resolve rate limit key before calling super.
114+
* This allows async fetching of the key from CF Secrets Store.
115+
*/
116+
async request<T>(path: string, options: RequestOptions): Promise<T> {
117+
// @ts-expect-error - import.meta is provided by Nuxt
118+
if (import.meta.server) {
119+
await this.resolveRateLimitKey()
120+
}
121+
return super.request(path, options)
122+
}
123+
87124
protected async executeRequest<T>(url: string, options: RequestOptions): Promise<T> {
88125
try {
89126
// @ts-expect-error - $fetch is provided by Nuxt runtime
@@ -115,9 +152,10 @@ export class NuxtModrinthClient extends AbstractModrinthClient {
115152
...super.buildDefaultHeaders(),
116153
}
117154

155+
// Use the resolved key (populated by resolveRateLimitKey in request())
118156
// @ts-expect-error - import.meta is provided by Nuxt
119-
if (import.meta.server && this.config.rateLimitKey) {
120-
headers['x-ratelimit-key'] = this.config.rateLimitKey
157+
if (import.meta.server && this.rateLimitKeyResolved) {
158+
headers['x-ratelimit-key'] = this.rateLimitKeyResolved
121159
}
122160

123161
return headers

0 commit comments

Comments
 (0)