Easy and friendly API to connect and interact between content window and its containing iframe with enhanced security, reliability, and modern async/await support.
- 📋 Protocol Versioning: Message format versioning for future-proof evolution
- 🔐 Session Key Rotation: Opt-in session-derived keys for high-security applications
- 🔒 Enhanced Security: Origin validation, message sanitization, and payload size limits
- 🔄 Auto-Reconnection: Automatic reconnection with exponential backoff strategy
- 💓 Heartbeat Monitoring: Connection health monitoring with configurable intervals
- 📦 Message Queuing: Queue messages when disconnected and replay on reconnection
- ⚡ Rate Limiting: Configurable message rate limiting to prevent spam
- 🎯 Promise Support: Modern async/await APIs with timeout handling
- 📊 Connection Statistics: Real-time connection and performance metrics
- 🛡️ Comprehensive Error Handling: Detailed error types and handling mechanisms
npm install iframe.ioimport IOF from 'iframe.io'
const iframe = document.getElementById('myIframe')
const iframeIO = new IOF({
type: 'WINDOW',
debug: true
})
// Establish connection
iframeIO.initiate(iframe.contentWindow, 'https://child-domain.com')
// Listen for connection
iframeIO.on('connect', () => {
console.log('Connected to iframe!')
// Send a message
iframeIO.emit('hello', { message: 'Hello from parent!' })
})
// Listen for messages
iframeIO.on('response', (data) => {
console.log('Received:', data)
})import IOF from 'iframe.io'
const iframeIO = new IOF({
type: 'IFRAME',
debug: true
})
// Listen for parent connection
iframeIO.listen('https://parent-domain.com')
// Handle connection
iframeIO.on('connect', () => {
console.log('Connected to parent!')
})
// Listen for messages
iframeIO.on('hello', (data) => {
console.log('Received:', data)
// Send response
iframeIO.emit('response', { received: true })
})Every message sent includes a protocol version number (v field). This allows the library to evolve over time while maintaining backward compatibility. If a peer receives a message with an unsupported version, it can handle it gracefully.
The current protocol version is v1.
// All messages automatically include version
const messageData = {
v: 1, // Protocol version
_event: 'userAction',
payload: { action: 'click' },
timestamp: 1609459200000
}// Library automatically handles version checking
iframeIO.on('error', (error) => {
if (error.type === 'UNSUPPORTED_VERSION') {
console.log(`Peer using v${error.received}, we support v${error.supported}`)
// Handle gracefully - maybe show upgrade notice
}
})const stats = iframeIO.getStats()
console.log(`Our version: v${stats.protocolVersion}`)
console.log(`Peer version: v${stats.peerProtocolVersion}`)- Seamless Upgrades: Deploy new versions without breaking old clients
- Feature Detection: Enable features based on peer capabilities
- Debugging: Identify which version caused issues
- Graceful Degradation: Fall back to older protocol when needed
Enable session key rotation for:
- ✅ Long-lived connections (> 1 hour)
- ✅ High-security applications (banking, healthcare)
- ✅ Compliance requirements (PCI-DSS, HIPAA, SOC 2)
- ✅ Multiple concurrent iframe connections
- ✅ Applications handling sensitive data
Don't enable for:
- ❌ Short-lived connections (< 30 minutes)
- ❌ Low-risk internal tools
- ❌ Same-origin communication
- ❌ Applications with minimal security requirements
const iframeIO = new IOF({
type: 'WINDOW',
cryptoAuth: {
secret: 'your-master-secret-key',
requireSigned: true,
// Enable session key rotation
enableSessionKeys: true,
sessionKeyRotationInterval: 3600000 // 1 hour (default)
}
})- Connection: Normal ping/pong handshake
- Key Exchange: Peers exchange random session IDs
- Key Derivation: Both derive identical session key using HKDF
- Secure Communication: All signed messages use session key
- Periodic Rotation: New key derived every rotation interval
- Graceful Transition: Old key accepted during grace period
Parent IFrame
| |
|---- ping -------------------->|
|<--- pong ----------------------|
| |
|-- __session_key_init -------->|
| (sessionId: abc123) |
| |
|<- __session_key_ack -----------|
| (sessionId: def456, keyId) |
| |
[Both derive: key = HKDF(master, abc123+def456, keyId)]
| |
|-- signed message (keyId) ---->|
|<- signed message (keyId) ------|
| |
[After rotation interval] |
|-- __session_key_rotate ------>|
| (newKeyId) |
| |
[Both derive new key] |
const iframeIO = new IOF({
type: 'WINDOW',
debug: true,
cryptoAuth: {
// Master secret (never transmitted)
secret: 'replace-with-shared-secret',
// Require all messages to be signed
requireSigned: true,
// Clock skew tolerance (2 minutes default)
maxSkewMs: 120000,
// Replay protection window (500 nonces default)
replayWindowSize: 500,
// SESSION KEY ROTATION (opt-in)
enableSessionKeys: true,
// Rotate every hour (adjust based on security needs)
// - High security: 15-30 minutes
// - Standard: 1 hour
// - Low risk: 2-4 hours
sessionKeyRotationInterval: 3600000
}
})// Listen for session key events
iframeIO.on('session_key_established', (data) => {
console.log(`Session key established: ${data.keyId}`)
})
iframeIO.on('session_key_rotating', (data) => {
console.log(`Rotating to new key: ${data.keyId}`)
})
iframeIO.on('session_key_rotated', (data) => {
console.log(`Peer rotated to: ${data.keyId}`)
})
// Check current key status
const stats = iframeIO.getStats()
console.log(`Session key active: ${stats.sessionKeyActive}`)
console.log(`Current key ID: ${stats.sessionKeyId}`)✅ What Session Keys Provide:
- Forward secrecy: Past sessions remain secure if master key leaks
- Session isolation: Each connection uses unique keys
- Automatic rotation: Limits exposure window
- Replay protection: Per-session nonce tracking
- Not a Sandbox Boundary: If attacker can execute JS in either peer, they can read keys
- Master Secret Security: Protect your master secret like a password
- HTTPS Required: Always use HTTPS for iframe communication
- Origin Validation: Session keys don't replace origin checking
Session key rotation has minimal performance impact:
- Key derivation: ~5-10ms (uses WebCrypto HKDF)
- Memory: ~1KB per active key
- Network: 3 small handshake messages on connect
- CPU: Negligible during rotation (background process)
// High-security banking app
const bankingIO = new IOF({
type: 'WINDOW',
debug: false,
cryptoAuth: {
secret: process.env.IFRAME_MASTER_SECRET,
requireSigned: true,
enableSessionKeys: true,
sessionKeyRotationInterval: 1800000 // 30 minutes
},
// Additional security
allowedIncomingEvents: [
'transaction_request',
'balance_query',
'account_info'
],
validateIncoming: (event, payload, origin) => {
// Validate payload structure
if (event === 'transaction_request') {
return payload &&
typeof payload.amount === 'number' &&
typeof payload.recipient === 'string'
}
return true
}
})
// Monitor security events
bankingIO.on('session_key_rotating', () => {
console.log('Security: Rotating session keys')
// Log to security audit trail
})
bankingIO.on('error', (error) => {
if (error.type === 'AUTH_FAILED') {
console.error('Security: Authentication failed')
// Trigger security alert
}
})// HIPAA-compliant healthcare app
const healthcareIO = new IOF({
type: 'IFRAME',
debug: false,
cryptoAuth: {
secret: process.env.PHI_MASTER_SECRET,
requireSigned: true,
enableSessionKeys: true,
sessionKeyRotationInterval: 3600000 // 1 hour
},
maxMessageSize: 512 * 1024, // 512KB limit for PHI
heartbeatInterval: 60000 // 1 minute health check
})
// Audit logging
healthcareIO.on('session_key_established', (data) => {
auditLog({
event: 'SESSION_KEY_ESTABLISHED',
keyId: data.keyId,
timestamp: new Date().toISOString(),
user: getCurrentUser()
})
})
healthcareIO.on('connect', () => {
// Send encrypted patient data
healthcareIO.emitSigned('patient_data', {
patientId: 'encrypted-id',
records: encryptedRecords
})
})const iframeIO = new IOF({
type: 'WINDOW', // 'WINDOW' or 'IFRAME'
debug: false, // Enable debug logging
heartbeatInterval: 30000, // Heartbeat interval in ms (30s)
connectionTimeout: 10000, // Connection timeout in ms (10s)
maxMessageSize: 1024 * 1024, // Max message size in bytes (1MB)
maxMessagesPerSecond: 100, // Rate limit (100 messages/second)
autoReconnect: true, // Enable automatic reconnection
messageQueueSize: 50, // Max queued messages when disconnected
allowedIncomingEvents: ['hello', 'response'], // Optional incoming event allowlist
validateIncoming: (event, payload, origin) => true, // Optional custom validator
cryptoAuth: {
secret: 'replace-with-shared-secret',
requireSigned: false,
maxSkewMs: 120000, // 2 minutes clock skew
replayWindowSize: 500,
// Session key rotation (opt-in)
enableSessionKeys: false, // Set to true for high-security apps
sessionKeyRotationInterval: 3600000 // 1 hour
}
})// Send message and wait for response with timeout
try {
const response = await iframeIO.emitAsync('getData', { id: 123 }, 10000)
console.log('Response:', response)
} catch (error) {
console.error('Request failed:', error.message)
}
// Listen and acknowledge
iframeIO.on('getData', async (data, ack) => {
try {
const result = await fetchData(data.id)
ack(false, result)
} catch (error) {
ack(error.message)
}
})// Wait for connection with timeout
try {
await iframeIO.connectAsync(5000)
console.log('Connection established!')
} catch (error) {
console.error('Connection failed:', error.message)
}// Handle connection events
iframeIO.on('disconnect', (data) => {
console.log('Disconnected:', data.reason)
})
iframeIO.on('reconnecting', (data) => {
console.log(`Reconnection attempt ${data.attempt}, delay: ${data.delay}ms`)
})
iframeIO.on('reconnection_failed', (data) => {
console.error(`Failed to reconnect after ${data.attempts} attempts`)
})const stats = iframeIO.getStats()
console.log(stats)
// {
// connected: true,
// peerType: 'WINDOW',
// origin: 'https://example.com',
// lastHeartbeat: 1609459200000,
// queuedMessages: 0,
// reconnectAttempts: 0,
// activeListeners: 5,
// messageRate: 2,
// protocolVersion: 1,
// peerProtocolVersion: 1,
// sessionKeyActive: true,
// sessionKeyId: 'key-1609459200000-abc123'
// }// Strict origin checking
iframeIO.listen('https://trusted-domain.com')
iframeIO.on('error', (error) => {
if (error.type === 'INVALID_ORIGIN') {
console.log(`Rejected message from ${error.received}`)
}
})// Automatic payload sanitization
iframeIO.emit('data', {
text: 'Hello',
func: () => {}, // Automatically removed
undef: undefined // Automatically removed
})const iframeIO = new IOF({
type: 'WINDOW',
cryptoAuth: {
secret: 'replace-with-shared-secret',
requireSigned: true
}
})
// Send signed messages
await iframeIO.emitSigned('hello', { msg: 'signed' })
const reply = await iframeIO.emitAsyncSigned('getData', { id: 123 }, 5000)emitAsync(event, payload?, timeout?)- Send message and wait for responseemitAsyncSigned(event, payload?, timeout?)- Send signed message and wait for responseconnectAsync(timeout?)- Wait for connection with timeoutonceAsync(event)- Wait for single event
getStats()- Get connection statistics (includes version and session key info)clearQueue()- Clear queued messages
initiate(contentWindow, iframeOrigin)- Establish connection (WINDOW peer)listen(hostOrigin?)- Listen for connection (IFRAME peer)disconnect(callback?)- Disconnect and cleanupisConnected()- Check connection status
emit(event, payload?, callback?)- Send messageemitSigned(event, payload?, callback?)- Send signed messageon(event, listener)- Add event listeneronce(event, listener)- Add one-time event listeneroff(event, listener?)- Remove event listener(s)removeListeners(callback?)- Remove all listeners
connect- Connection establisheddisconnect- Connection lost with reasonreconnecting- Reconnection attempt startedreconnection_failed- All reconnection attempts failed
session_key_established- Session key derived and activesession_key_rotating- New key being generatedsession_key_rotated- Peer rotated to new key
error- Various error conditions with detailed error objects
Full TypeScript support with comprehensive type definitions:
import IOF, { Options, Listener, AckFunction, SessionKeyInfo } from 'iframe.io'
const options: Options = {
type: 'WINDOW',
debug: true,
cryptoAuth: {
secret: 'my-secret',
enableSessionKeys: true,
sessionKeyRotationInterval: 1800000
}
}
const iframeIO = new IOF(options)
// Session key events are typed
iframeIO.on('session_key_established', (data: { keyId: string }) => {
console.log(`Key established: ${data.keyId}`)
})| Error Type | Description |
|---|---|
INVALID_ORIGIN |
Message from unexpected origin |
ORIGIN_MISMATCH |
Origin changed during session |
UNSUPPORTED_VERSION |
Peer using unsupported protocol version |
MESSAGE_HANDLING_ERROR |
Error processing incoming message |
EMIT_ERROR |
Error sending message |
LISTENER_ERROR |
Error in event listener |
DISALLOWED_EVENT |
Event rejected by allowlist |
INVALID_MESSAGE |
Message rejected by validator |
AUTH_FAILED |
Cryptographic authentication failed |
AUTH_ERROR |
Crypto verification errored |
RATE_LIMIT_EXCEEDED |
Too many messages sent |
NO_CONNECTION |
Attempted send without connection |
- Chrome 60+
- Firefox 55+
- Safari 12+
- Edge 79+
WebCrypto API required for session key rotation feature.
All existing code continues to work without changes. New features are additive:
// Old way (still works)
const iframeIO = new IOF({
type: 'WINDOW',
cryptoAuth: {
secret: 'my-secret'
}
})
// New way with session keys (opt-in)
const iframeIO = new IOF({
type: 'WINDOW',
cryptoAuth: {
secret: 'my-secret',
enableSessionKeys: true // Enable for high-security apps
}
})
// Protocol version is automatic - no code changes needed- Message Size: Keep under
maxMessageSize(default 1MB) - Rate Limiting: Respect
maxMessagesPerSecond(default 100/sec) - Queue Size: Monitor queued messages to avoid memory issues
- Heartbeat: Adjust
heartbeatIntervalbased on needs - Session Keys: Minimal overhead (~5-10ms derivation, ~1KB memory)
- Always use HTTPS for production deployments
- Validate origins strictly in both peers
- Enable session keys for applications handling sensitive data
- Use allowedIncomingEvents to limit attack surface
- Implement custom validation for critical payloads
- Monitor error events for security incidents
- Rotate master secrets periodically (outside of session rotation)
- Batch related messages when possible
- Use appropriate queue sizes for your use case
- Monitor connection stats regularly
- Adjust heartbeat intervals based on reliability needs
- Set reasonable rotation intervals (1 hour is good default)
- Handle all error types appropriately
- Implement retry logic at application level for critical operations
- Use
emitAsyncfor operations requiring acknowledgment - Monitor reconnection events and alert on failures
- Test disconnection scenarios thoroughly
MIT License - see LICENSE file for details.
Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository.
- Create an issue on GitHub for bug reports
- Check existing issues for common problems
- Review the documentation for usage examples
- Added protocol versioning for future-proof evolution
- Added optional session key rotation for high-security applications
- Enhanced connection statistics with version and key information
- Improved error handling for version mismatches
- Added comprehensive documentation and examples
- Enhanced security features (origin validation, sanitization, rate limiting)
- Auto-reconnection with exponential backoff
- Heartbeat monitoring
- Message queuing
- Modern async/await APIs
- Comprehensive error handling
- Initial release
- Basic iframe communication
- Event-based API