Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
2 changes: 2 additions & 0 deletions .talismanrc
Original file line number Diff line number Diff line change
Expand Up @@ -7,4 +7,6 @@ fileignoreconfig:
checksum: 9d592c580a6890473e007c339d2f91c2d94ad936be1740dcef5ac500fde0cdb4
- filename: lib/stack/asset/index.js
checksum: b3358310e9cb2fb493d70890b7219db71e2202360be764465d505ef71907eefe
- filename: examples/robust-error-handling.js
checksum: e8a32ffbbbdba2a15f3d327273f0a5b4eb33cf84cd346562596ab697125bbbc6
version: ""
87 changes: 87 additions & 0 deletions examples/robust-error-handling.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
// Example: Configuring Robust Error Handling for Transient Network Failures
// This example shows how to use the enhanced retry mechanisms in the Contentstack Management SDK

const contentstack = require('../lib/contentstack')

// Example 1: Basic configuration with enhanced network retry
const clientWithBasicRetry = contentstack.client({
api_key: 'your_api_key',
management_token: 'your_management_token',
// Enhanced network retry configuration
retryOnNetworkFailure: true, // Enable network failure retries
maxNetworkRetries: 3, // Max 3 attempts for network failures
networkRetryDelay: 100, // Start with 100ms delay
networkBackoffStrategy: 'exponential' // Use exponential backoff (100ms, 200ms, 400ms)
})

// Example 2: Advanced configuration with fine-grained control
const clientWithAdvancedRetry = contentstack.client({
api_key: 'your_api_key',
management_token: 'your_management_token',
// Network failure retry settings
retryOnNetworkFailure: true,
retryOnDnsFailure: true, // Retry on DNS resolution failures (EAI_AGAIN)
retryOnSocketFailure: true, // Retry on socket errors (ECONNRESET, ETIMEDOUT, etc.)
retryOnHttpServerError: true, // Retry on HTTP 5xx errors
maxNetworkRetries: 5, // Allow up to 5 network retries
networkRetryDelay: 200, // Start with 200ms delay
networkBackoffStrategy: 'exponential',

// Original retry settings (for non-network errors)
retryOnError: true,
retryLimit: 3,
retryDelay: 500,

// Custom logging
logHandler: (level, message) => {
console.log(`[${level.toUpperCase()}] ${new Date().toISOString()}: ${message}`)
}
})

// Example 3: Conservative configuration for production
const clientForProduction = contentstack.client({
api_key: 'your_api_key',
management_token: 'your_management_token',
// Conservative retry settings for production
retryOnNetworkFailure: true,
maxNetworkRetries: 2, // Only 2 retries to avoid long delays
networkRetryDelay: 300, // Longer initial delay
networkBackoffStrategy: 'fixed', // Fixed delay instead of exponential

// Custom retry condition for additional control
retryCondition: (error) => {
// Custom logic: only retry on specific conditions
return error.response && error.response.status >= 500
}
})

// Example usage with error handling
async function demonstrateRobustErrorHandling () {
try {
const stack = clientWithAdvancedRetry.stack('your_stack_api_key')
const contentTypes = await stack.contentType().query().find()
console.log('Content types retrieved successfully:', contentTypes.items.length)
} catch (error) {
if (error.retryAttempts) {
console.error(`Request failed after ${error.retryAttempts} retry attempts:`, error.message)
console.error('Original error:', error.originalError?.code)
} else {
console.error('Request failed:', error.message)
}
}
}

// The SDK will now automatically handle:
// ✅ DNS resolution failures (EAI_AGAIN)
// ✅ Socket errors (ECONNRESET, ETIMEDOUT, ECONNREFUSED)
// ✅ HTTP timeouts (ECONNABORTED)
// ✅ HTTP 5xx server errors (500-599)
// ✅ Exponential backoff with configurable delays
// ✅ Clear logging and user-friendly error messages

module.exports = {
clientWithBasicRetry,
clientWithAdvancedRetry,
clientForProduction,
demonstrateRobustErrorHandling
}
60 changes: 60 additions & 0 deletions lib/core/Util.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,3 +100,63 @@ export default function getUserAgent (sdk, application, integration, feature) {

return `${headerParts.filter((item) => item !== '').join('; ')};`
}

// URL validation functions to prevent SSRF attacks
const isValidURL = (url) => {
try {
// Allow relative URLs (they are safe as they use the same origin)
if (url.startsWith('/') || url.startsWith('./') || url.startsWith('../')) {
return true
}

// Only validate absolute URLs for SSRF protection
const parsedURL = new URL(url)
return isAllowedHost(parsedURL.hostname)
} catch (error) {
// If URL parsing fails, it might be a relative URL without protocol
// Allow it if it doesn't contain protocol indicators
return !url.includes('://') && !url.includes('\\')
}
}

const isAllowedHost = (hostname) => {
// Define allowed domains for Contentstack API
const allowedDomains = [
'api.contentstack.io',
'eu-api.contentstack.com',
'azure-na-api.contentstack.com',
'azure-eu-api.contentstack.com',
'gcp-na-api.contentstack.com',
'gcp-eu-api.contentstack.com'
]

// Check for localhost/development environments
const localhostPatterns = [
'localhost',
'127.0.0.1',
'0.0.0.0'
]

// Allow localhost for development
if (localhostPatterns.includes(hostname)) {
return true
}

// Check if hostname is in allowed domains or is a subdomain of allowed domains
return allowedDomains.some(domain => {
return hostname === domain || hostname.endsWith('.' + domain)
})
}

export const validateAndSanitizeConfig = (config) => {
if (!config || !config.url) {
throw new Error('Invalid request configuration: missing URL')
}

// Validate the URL to prevent SSRF attacks
if (!isValidURL(config.url)) {
throw new Error(`SSRF Prevention: URL "${config.url}" is not allowed`)
}

return config
}
Loading
Loading