UNPKG

iframe.io

Version:

Easy and friendly API to connect and interact between content window and its containing iframe

459 lines (340 loc) 10.2 kB
# iframe.io Easy and friendly API to connect and interact between content window and its containing iframe with enhanced features like heartbeat monitoring, automatic reconnection, message queuing, and rate limiting. ## Features - 🔄 **Bi-directional Communication** - Seamless messaging between parent window and iframe - 💓 **Heartbeat Monitoring** - Automatic connection health checking - 🔌 **Auto Reconnection** - Automatic reconnection with exponential backoff - 📦 **Message Queuing** - Queue messages when connection is lost - 🛡️ **Security** - Origin validation and message sanitization - ⚡ **Rate Limiting** - Prevent message flooding - 🎯 **Event System** - Robust event-driven architecture - ✨ **Promise Support** - Async/await compatible methods - 📊 **Connection Stats** - Real-time connection statistics ## Installation ```bash npm install iframe.io ``` ## Quick Start ### Parent Window (WINDOW type) ```javascript import IOF from 'iframe.io' // Create iframe and get reference const iframe = document.createElement('iframe') iframe.src = 'https://example.com/iframe-content' document.body.appendChild(iframe) // Initialize connection const iof = new IOF({ type: 'WINDOW', debug: true }) iframe.onload = () => { // Initiate connection with iframe iof.initiate(iframe.contentWindow, 'https://example.com') // Listen for connection iof.on('connect', () => { console.log('Connected to iframe!') // Send message iof.emit('hello', { message: 'Hello from parent!' }) }) // Listen for messages iof.on('response', (data) => { console.log('Received:', data) }) } ``` ### Iframe Content (IFRAME type) ```javascript import IOF from 'iframe.io' // Create connection listener const iof = new IOF({ type: 'IFRAME', debug: true }) // Listen for parent connection iof.listen('https://parent-domain.com') // Handle connection iof.on('connect', () => { console.log('Connected to parent!') }) // Listen for messages iof.on('hello', (data, ack) => { console.log('Received from parent:', data) // Send response iof.emit('response', { message: 'Hello from iframe!' }) // Acknowledge receipt (optional) if (ack) ack(false, 'Message received') }) ``` ## API Reference ### Constructor ```javascript new IOF(options?) ``` #### Options | Option | Type | Default | Description | |--------|------|---------|-------------| | `type` | `'WINDOW' \| 'IFRAME'` | `'IFRAME'` | Connection type | | `debug` | `boolean` | `false` | Enable debug logging | | `heartbeatInterval` | `number` | `30000` | Heartbeat interval in ms | | `connectionTimeout` | `number` | `10000` | Connection timeout in ms | | `maxMessageSize` | `number` | `1048576` | Max message size (1MB) | | `maxMessagesPerSecond` | `number` | `100` | Rate limit for messages | | `autoReconnect` | `boolean` | `true` | Enable auto reconnection | | `messageQueueSize` | `number` | `50` | Max queued messages | ### Connection Methods #### `initiate(contentWindow, iframeOrigin)` Establish connection with an iframe (WINDOW type only). ```javascript iof.initiate(iframe.contentWindow, 'https://iframe-origin.com') ``` **Parameters:** - `contentWindow` - The iframe's contentWindow - `iframeOrigin` - The iframe's origin URL #### `listen(hostOrigin?)` Listen for connection from parent window (IFRAME type). ```javascript iof.listen('https://parent-origin.com') // Optional origin restriction ``` **Parameters:** - `hostOrigin` (optional) - Restrict connections to specific origin #### `disconnect(callback?)` Disconnect and clean up all resources. ```javascript iof.disconnect(() => { console.log('Disconnected!') }) ``` ### Messaging Methods #### `emit(event, payload?, callback?)` Send a message to the peer. ```javascript // Simple message iof.emit('myEvent', { data: 'hello' }) // With acknowledgment iof.emit('myEvent', { data: 'hello' }, (error, response) => { if (error) { console.error('Error:', error) } else { console.log('Response:', response) } }) // Callback as second parameter iof.emit('myEvent', (error, response) => { console.log('Response:', response) }) ``` #### `on(event, listener)` Add event listener. ```javascript iof.on('myEvent', (data, ack) => { console.log('Received:', data) // Send acknowledgment (optional) if (ack) ack(false, 'Success response') }) ``` #### `once(event, listener)` Add one-time event listener. ```javascript iof.once('myEvent', (data) => { console.log('This will only fire once:', data) }) ``` #### `off(event, listener?)` Remove event listener(s). ```javascript // Remove specific listener iof.off('myEvent', myListener) // Remove all listeners for event iof.off('myEvent') ``` #### `removeListeners(callback?)` Remove all event listeners. ```javascript iof.removeListeners(() => { console.log('All listeners removed') }) ``` ### Async Methods #### `emitAsync(event, payload?)` Send message and return Promise. ```javascript try { const response = await iof.emitAsync('getData', { id: 123 }) console.log('Response:', response) } catch (error) { console.error('Error:', error) } ``` #### `onceAsync(event)` Wait for single event occurrence. ```javascript const data = await iof.onceAsync('dataReady') console.log('Data received:', data) ``` #### `connectAsync(timeout?)` Wait for connection to be established. ```javascript try { await iof.connectAsync(5000) // 5 second timeout console.log('Connected!') } catch (error) { console.error('Connection timeout') } ``` ### Utility Methods #### `isConnected()` Check if connection is active. ```javascript if (iof.isConnected()) { console.log('Connection is active') } ``` #### `getStats()` Get connection statistics. ```javascript const stats = iof.getStats() console.log('Stats:', stats) /* { connected: true, peerType: 'WINDOW', origin: 'https://example.com', lastHeartbeat: 1640995200000, queuedMessages: 0, reconnectAttempts: 0, activeListeners: 5, messageRate: 10 } */ ``` #### `clearQueue()` Clear queued messages. ```javascript iof.clearQueue() ``` ## Events ### Built-in Events | Event | Description | Data | |-------|-------------|------| | `connect` | Connection established | - | | `disconnect` | Connection lost | `{ reason: string }` | | `reconnecting` | Reconnection attempt | `{ attempt: number, delay: number }` | | `reconnection_failed` | All reconnection attempts failed | `{ attempts: number }` | | `error` | Error occurred | `{ type: string, ...details }` | ### Error Types - `RATE_LIMIT_EXCEEDED` - Message rate limit exceeded - `MESSAGE_HANDLING_ERROR` - Error processing received message - `EMIT_ERROR` - Error sending message - `LISTENER_ERROR` - Error in event listener - `INVALID_ORIGIN` - Message from unauthorized origin - `ORIGIN_MISMATCH` - Origin doesn't match established connection - `NO_CONNECTION` - Attempted to send without connection ## Advanced Usage ### Message Acknowledgments ```javascript // Sender iof.emit('processData', { data: [...] }, (error, result) => { if (error) { console.error('Processing failed:', error) } else { console.log('Processing result:', result) } }) // Receiver iof.on('processData', async (data, ack) => { try { const result = await processData(data.data) ack(false, result) // Success: ack(error, ...args) } catch (error) { ack(error.message) // Error: ack(errorMessage) } }) ``` ### Connection Monitoring ```javascript const iof = new IOF({ heartbeatInterval: 10000, // 10 seconds connectionTimeout: 5000, // 5 seconds autoReconnect: true }) iof.on('disconnect', ({ reason }) => { console.log('Connection lost:', reason) }) iof.on('reconnecting', ({ attempt, delay }) => { console.log(`Reconnecting (${attempt}/5) in ${delay}ms`) }) iof.on('reconnection_failed', ({ attempts }) => { console.log(`Failed to reconnect after ${attempts} attempts`) }) ``` ### Message Queuing ```javascript const iof = new IOF({ messageQueueSize: 100, autoReconnect: true }) // Messages sent while disconnected are automatically queued iof.emit('queuedMessage', { data: 'This will be queued if disconnected' }) // Check queue status const stats = iof.getStats() console.log(`Queued messages: ${stats.queuedMessages}`) ``` ### Rate Limiting ```javascript const iof = new IOF({ maxMessagesPerSecond: 50, maxMessageSize: 512 * 1024 // 512KB }) iof.on('error', ({ type, limit, current }) => { if (type === 'RATE_LIMIT_EXCEEDED') { console.log(`Rate limit exceeded: ${current}/${limit} messages per second`) } }) ``` ### Security Best Practices ```javascript // Always specify allowed origins iof.listen('https://trusted-parent.com') // Handle security errors iof.on('error', ({ type, expected, received }) => { if (type === 'INVALID_ORIGIN') { console.warn(`Rejected message from ${received}, expected ${expected}`) } }) // Validate message content iof.on('userData', (data, ack) => { if (!isValidUserData(data)) { return ack('Invalid data format') } // Process valid data... }) ``` ## Browser Support - Modern browsers with postMessage API support - Chrome 2+ - Firefox 3+ - Safari 4+ - IE 8+ - Edge (all versions) ## TypeScript Support The library is written in TypeScript and includes full type definitions: ```typescript import IOF, { Options, PeerType, AckFunction, Listener } from 'iframe.io' const options: Options = { type: 'WINDOW', debug: true, heartbeatInterval: 15000 } const iof = new IOF(options) // Type-safe event handling iof.on('myEvent', (data: { message: string }, ack?: AckFunction) => { console.log(data.message) ack?.(false, 'success') }) // Type-safe async emissions const response = await iof.emitAsync<{ query: string }, { result: any }>('search', { query: 'typescript' }) ``` ## License MIT License - see LICENSE file for details. ## Contributing Contributions are welcome! Please read our contributing guidelines and submit pull requests to our repository. ## Support - Create an issue on GitHub for bug reports - Check existing issues for common problems - Review the documentation for usage examples