Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/runtime/auth/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { useUser, clearUser } from './user'
export { useAuthHeaders, useApiEndpoint, useLogin, useLogout, useLoginGate } from './auth'
export * from './user'
export * from './auth'
4 changes: 2 additions & 2 deletions src/runtime/config/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
export { setupPolly } from './pollySetup'
export { eslintRules, stylisticConfig, eslintConfig } from './eslint'
export * from './pollySetup'
export * from './eslint'
65 changes: 55 additions & 10 deletions src/runtime/config/pollySetup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'],
Expand All @@ -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)
}
})
}