JavaScript / TypeScript
Universal JavaScript / TypeScript integration for browser environments. This code example provides a lightweight, type-safe way to integrate DarkFeature into your client-side JavaScript applications.
Requirements
- Modern browser with ES6 support
- No Node.js required (for server-side usage, see Node.js documentation)
Installation
NPM/Yarn/PNPM
npm install @darkfeature/sdk-javascript
# or
yarn add @darkfeature/sdk-javascript
# or
pnpm add @darkfeature/sdk-javascript
ES6 Module Import
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
CDN (Browser)
<!-- Option 1: ES6 Modules (Modern browsers) -->
<script type="module">
import { DarkFeatureClient } from 'https://cdn.skypack.dev/@darkfeature/sdk-javascript'
</script>
<!-- Option 2: Global variable via esm.sh (works in all browsers) -->
<script src="https://esm.sh/@darkfeature/sdk-javascript?bundle&global=DarkFeature"></script>
Quick Start
Basic Usage
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: 'your-client-api-key-here', // Store in environment variable
context: {
userId: '123',
email: '[email protected]',
version: '1.0.0',
},
})
// Get a single feature flag with fallback
const isEnabled = await client.getFeature('new-navigation', { fallback: false })
if (isEnabled) {
console.log('New navigation is enabled!')
} else {
console.log('Using old navigation')
}
With Context Override
// Get a feature flag with context override
const featureValue = await client.getFeature('premium-features', {
fallback: 'control',
context: { segment: 'premium' },
})
console.log('Feature value:', featureValue)
API Reference
Constructor
const client = new DarkFeatureClient(config)
Parameters:
config
(object) - Configuration object
Config Options:
apiKey
(string) - Your DarkFeature client API keycontext
(object) - Default user context for all requestsbaseUrl
(string, optional) - Custom API endpointtimeout
(number, optional) - Request timeout in millisecondscache
(object, optional) - Caching configurationdebug
(boolean, optional) - Enable debug logging
Methods
getFeature(featureKey, options)
Get a single feature flag value.
// Basic usage with fallback
const isEnabled = await client.getFeature('new-feature', { fallback: false })
// With context override
const value = await client.getFeature('feature-name', {
fallback: 'default-value',
context: { segment: 'premium' },
})
// With custom fallback
const config = await client.getFeature('api-config', {
fallback: { timeout: 5000, retries: 3 },
})
// With timeout
const result = await client.getFeature('slow-feature', {
fallback: false,
timeout: 1000,
})
getFeatures(features, options)
Get multiple feature flags at once.
// Get multiple features
const features = await client.getFeatures({
features: {
'new-navigation': false,
'premium-features': 'default-value',
'api-config': null,
},
context: {
userId: '456', // Override default context for this request
},
})
console.log(features)
// { 'new-navigation': true, 'premium-features': 'variant-a', 'api-config': { ... } }
setContext(context)
Update the default context for all subsequent requests.
// Update context
client.setContext({
userId: 'new-user-123',
email: '[email protected]',
plan: 'premium',
})
// All subsequent getFeature calls will use this context
const isEnabled = await client.getFeature('premium-features', {
fallback: false,
})
getContext()
Get the current default context.
const context = client.getContext()
console.log('Current context:', context)
clearContext()
Clear the default context.
client.clearContext()
// Subsequent calls will not include user context
isReady()
Check if the client is ready to make requests.
if (client.isReady()) {
console.log('Client is ready!')
} else {
console.log('Client is still initializing...')
}
Browser Integration
Vanilla JavaScript
// app.js
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_CLIENT_API_KEY,
context: {
userId: getCurrentUserId(),
email: getCurrentUserEmail(),
},
})
// Initialize features
async function initializeFeatures() {
const features = await client.getFeatures({
features: {
'new-navigation': false,
'dark-mode': false,
'premium-features': false,
},
})
applyFeatures(features)
}
function applyFeatures(features) {
if (features['new-navigation']) {
document.body.classList.add('new-nav')
}
if (features['dark-mode']) {
document.body.classList.add('dark-theme')
}
if (features['premium-features']) {
showPremiumFeatures()
}
}
// Initialize on page load
document.addEventListener('DOMContentLoaded', initializeFeatures)
React Integration
// useFeatureFlag.js
import { useState, useEffect } from 'react'
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: process.env.REACT_APP_DARKFEATURE_API_KEY,
context: { userId: '123' },
})
export function useFeatureFlag(featureName, fallback = false) {
const [isEnabled, setIsEnabled] = useState(fallback)
const [loading, setLoading] = useState(true)
useEffect(() => {
async function checkFeature() {
try {
const result = await client.getFeature(featureName, { fallback })
setIsEnabled(result)
} catch (error) {
console.error('Feature flag error:', error)
setIsEnabled(fallback)
} finally {
setLoading(false)
}
}
checkFeature()
}, [featureName, fallback])
return { isEnabled, loading }
}
// Component usage
function MyComponent() {
const { isEnabled, loading } = useFeatureFlag('new-feature', false)
if (loading) return <div>Loading...</div>
return isEnabled ? <NewFeature /> : <OldFeature />
}
Vue.js Integration
// featureFlags.js
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: process.env.VUE_APP_DARKFEATURE_API_KEY,
context: { userId: '123' },
})
export const featureFlags = {
async getFeature(featureName, fallback = false) {
try {
return await client.getFeature(featureName, { fallback })
} catch (error) {
console.error('Feature flag error:', error)
return fallback
}
},
async getFeatures(features) {
try {
return await client.getFeatures({ features })
} catch (error) {
console.error('Feature flags error:', error)
return features // Return fallback values
}
},
}
// Vue component
export default {
data() {
return {
isNewFeatureEnabled: false,
}
},
async mounted() {
this.isNewFeatureEnabled = await featureFlags.getFeature(
'new-feature',
false,
)
},
template: `
<div>
<div v-if="isNewFeatureEnabled">New Feature</div>
<div v-else>Old Feature</div>
</div>
`,
}
Advanced Configuration
Environment Variables
// .env
REACT_APP_DARKFEATURE_API_KEY = your - client - api - key
VUE_APP_DARKFEATURE_API_KEY = your - client - api - key
// app.js
const client = new DarkFeatureClient({
apiKey:
process.env.REACT_APP_DARKFEATURE_API_KEY ||
process.env.VUE_APP_DARKFEATURE_API_KEY,
baseUrl: 'https://api.darkfeature.com',
timeout: 5000,
cache: {
enabled: true,
ttl: 300000, // 5 minutes
maxSize: 1000,
},
debug: process.env.NODE_ENV === 'development',
})
Caching Strategy
const client = new DarkFeatureClient({
apiKey: 'your-api-key',
cache: {
enabled: true,
ttl: 300000, // 5 minutes
maxSize: 1000,
},
})
// Features will be cached in browser storage
const isEnabled = await client.getFeature('new-feature', { fallback: false })
Error Handling
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_CLIENT_API_KEY,
})
async function getFeatureWithFallback(featureName, fallback = false) {
try {
return await client.getFeature(featureName, { fallback })
} catch (error) {
console.error(`Error getting feature ${featureName}:`, error)
return fallback
}
}
// Usage
const isEnabled = await getFeatureWithFallback('new-feature', false)
Performance Optimization
Batch Requests
// Get multiple features in one request
const features = await client.getFeatures({
features: {
'feature-a': false,
'feature-b': false,
'feature-c': false,
},
context: { userId: '123' },
})
// Use features
if (features['feature-a']) {
// Feature A logic
}
if (features['feature-b']) {
// Feature B logic
}
Lazy Loading
// Load features only when needed
class FeatureManager {
constructor() {
this.client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_CLIENT_API_KEY,
})
this.cache = new Map()
}
async getFeature(featureName, fallback = false) {
if (this.cache.has(featureName)) {
return this.cache.get(featureName)
}
const result = await this.client.getFeature(featureName, { fallback })
this.cache.set(featureName, result)
return result
}
}
const featureManager = new FeatureManager()
Testing
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
// Mock client for testing
class MockDarkFeatureClient extends DarkFeatureClient {
constructor(mockFlags = {}) {
super({ apiKey: 'test-key' })
this.mockFlags = mockFlags
}
async getFeature(featureName, options = {}) {
return this.mockFlags[featureName] ?? options.fallback
}
async getFeatures(features, options = {}) {
const result = {}
for (const [key, fallback] of Object.entries(features)) {
result[key] = this.mockFlags[key] ?? fallback
}
return result
}
}
// Test usage
const mockClient = new MockDarkFeatureClient({
'new-navigation': true,
'premium-features': false,
})
const isEnabled = await mockClient.getFeature('new-navigation', {
fallback: false,
})
console.log(isEnabled) // true
Best Practices
1. Environment Configuration
// config/features.js
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const createFeatureClient = () => {
return new DarkFeatureClient({
apiKey:
process.env.REACT_APP_DARKFEATURE_API_KEY ||
process.env.VUE_APP_DARKFEATURE_API_KEY,
baseUrl: 'https://api.darkfeature.com',
timeout: 5000,
cache: {
enabled: true,
ttl: 300000,
maxSize: 1000,
},
debug: process.env.NODE_ENV === 'development',
})
}
export default createFeatureClient
2. Error Handling
// utils/features.js
import { DarkFeatureClient } from '@darkfeature/sdk-javascript'
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_CLIENT_API_KEY,
})
export async function getFeatureSafely(featureName, fallback = false) {
try {
return await client.getFeature(featureName, { fallback })
} catch (error) {
console.error(`Feature flag error for ${featureName}:`, error)
return fallback
}
}
export async function getFeaturesSafely(features, context = {}) {
try {
return await client.getFeatures({ features, context })
} catch (error) {
console.error('Feature flags error:', error)
return features // Return fallback values
}
}
3. Context Management
// utils/context.js
export function createUserContext(user) {
return {
userId: user?.id?.toString(),
email: user?.email?.toLowerCase(),
plan: user?.plan || 'free',
userAgent: navigator.userAgent,
language: navigator.language,
}
}
// Usage
const context = createUserContext(currentUser)
client.setContext(context)
Security Considerations
API Key Management
// Use client-side API keys only
const client = new DarkFeatureClient({
apiKey: process.env.DARKFEATURE_CLIENT_API_KEY, // ✅ Client-side key
// apiKey: process.env.DARKFEATURE_SERVER_API_KEY, // ❌ Never use server keys in browser
})
User Context Validation
// Validate and sanitize user context
const sanitizeContext = user => {
return {
userId: user?.id?.toString(),
email: user?.email?.toLowerCase(),
plan: ['free', 'pro', 'enterprise'].includes(user?.plan)
? user.plan
: 'free',
}
}
const context = sanitizeContext(currentUser)
client.setContext(context)
Last updated on