diff --git a/src/runtime/auth/index.ts b/src/runtime/auth/index.ts index 331426ec..68123643 100644 --- a/src/runtime/auth/index.ts +++ b/src/runtime/auth/index.ts @@ -1,2 +1,2 @@ -export { useUser, clearUser } from './user' -export { useAuthHeaders, useApiEndpoint, useLogin, useLogout, useLoginGate } from './auth' +export * from './user' +export * from './auth' diff --git a/src/runtime/config/index.ts b/src/runtime/config/index.ts index 2eced981..f0b16d73 100644 --- a/src/runtime/config/index.ts +++ b/src/runtime/config/index.ts @@ -1,2 +1,2 @@ -export { setupPolly } from './pollySetup' -export { eslintRules, stylisticConfig, eslintConfig } from './eslint' +export * from './pollySetup' +export * from './eslint' diff --git a/src/runtime/config/pollySetup.ts b/src/runtime/config/pollySetup.ts index a7d060b3..0225018d 100644 --- a/src/runtime/config/pollySetup.ts +++ b/src/runtime/config/pollySetup.ts @@ -6,7 +6,22 @@ import FsPersister from '@pollyjs/persister-fs' Polly.register(FetchAdapter) Polly.register(FsPersister) -export function setupPolly (recordingName: string) { +export { Polly } + +export interface PollyConfig { + // Patterns for requests that should pass through without recording + passthroughPatterns?: string[] + // Patterns for requests that should be recorded/replayed + recordPatterns?: string[] + // Whether to allow unmatched requests (default: false - will fail on unmatched) + allowUnmatched?: boolean + // Custom logic for complex passthrough decisions + shouldPassthrough?: (req: any) => boolean + // Custom logic for complex record decisions + shouldRecord?: (req: any) => boolean +} + +export function setupPolly (recordingName: string, config: PollyConfig) { const isCI = process.env.CI === 'true' const polly = new Polly(recordingName, { adapters: ['fetch'], @@ -26,23 +41,53 @@ export function setupPolly (recordingName: string) { }, }) - // Passthrough requests to localhost and 127.0.0.1 - polly.server.any().filter((req) => { - console.warn(`Polly checking filter: ${req.url}`) - return (req.url.startsWith('http://localhost') || req.url.startsWith('http://127.0.0.1')) - }) - .passthrough() - // Add request sanitization to remove sensitive headers from recordings polly.server.any().on('beforePersist', (req, recording) => { - console.log(`Sanitizing request for recording`) + console.log(`Polly: Sanitizing request for recording`) const sensitiveHeaders = ['authorization', 'apikey'] recording.request.headers = recording.request.headers.map((header: any) => { if (sensitiveHeaders.includes(header.name?.toLowerCase())) { - header.value = '' + header.value = '[REDACTED]' } return header }) }) + + // Record filter + polly.server.any().filter((req) => { + return config.shouldRecord?.(req) || matchesAnyPattern(req.url, config.recordPatterns) + }) + + // Passthrough filter + polly.server.any().filter((req) => { + return config.shouldPassthrough?.(req) || matchesAnyPattern(req.url, config.passthroughPatterns) + }).passthrough() + + // Fail on unmatched requests unless explicitly allowed + if (!config.allowUnmatched) { + polly.server.any().filter((req) => { + const passthrough = config.shouldPassthrough?.(req) || matchesAnyPattern(req.url, config.passthroughPatterns) + const recorded = config.shouldRecord?.(req) || matchesAnyPattern(req.url, config.recordPatterns) + return !passthrough && !recorded + }).intercept((req, res) => { + res.status(400).json({ + error: 'Request blocked by Polly', + message: `URL ${req.url} is not configured for passthrough or recording. Add to passthroughPatterns, recordPatterns, or set allowUnmatched: true`, + url: req.url + }) + }) + } return polly } + +// Helper to check if URL matches any pattern +function matchesAnyPattern (url: string, patterns: string[]): boolean { + return (patterns || []).some((pattern) => { + try { + return new RegExp(pattern).test(url) + } catch (e) { + // If pattern is not valid regex, treat as literal string match + return url.includes(pattern) + } + }) +}