iframe.io 1.2.0 → 1.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +406 -136
- package/dist/index.d.ts +152 -0
- package/dist/index.js +670 -32
- package/package.json +3 -5
- package/src/index.ts +838 -32
package/README.md
CHANGED
|
@@ -4,8 +4,10 @@ Easy and friendly API to connect and interact between content window and its con
|
|
|
4
4
|
|
|
5
5
|
## New Features & Improvements
|
|
6
6
|
|
|
7
|
-
### 🆕 Version 1.
|
|
7
|
+
### 🆕 Version 1.3.0 Updates
|
|
8
8
|
|
|
9
|
+
- **📋 Protocol Versioning**: Message format versioning for future-proof evolution
|
|
10
|
+
- **🔐 Session Key Rotation**: Opt-in session-derived keys for high-security applications
|
|
9
11
|
- **🔒 Enhanced Security**: Origin validation, message sanitization, and payload size limits
|
|
10
12
|
- **🔄 Auto-Reconnection**: Automatic reconnection with exponential backoff strategy
|
|
11
13
|
- **💓 Heartbeat Monitoring**: Connection health monitoring with configurable intervals
|
|
@@ -78,6 +80,277 @@ iframeIO.on('hello', (data) => {
|
|
|
78
80
|
})
|
|
79
81
|
```
|
|
80
82
|
|
|
83
|
+
## Protocol Versioning
|
|
84
|
+
|
|
85
|
+
### What is Protocol Versioning?
|
|
86
|
+
|
|
87
|
+
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.
|
|
88
|
+
|
|
89
|
+
### Current Version
|
|
90
|
+
|
|
91
|
+
The current protocol version is **v1**.
|
|
92
|
+
|
|
93
|
+
### How It Works
|
|
94
|
+
|
|
95
|
+
```javascript
|
|
96
|
+
// All messages automatically include version
|
|
97
|
+
const messageData = {
|
|
98
|
+
v: 1, // Protocol version
|
|
99
|
+
_event: 'userAction',
|
|
100
|
+
payload: { action: 'click' },
|
|
101
|
+
timestamp: 1609459200000
|
|
102
|
+
}
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### Version Checking
|
|
106
|
+
|
|
107
|
+
```javascript
|
|
108
|
+
// Library automatically handles version checking
|
|
109
|
+
iframeIO.on('error', (error) => {
|
|
110
|
+
if (error.type === 'UNSUPPORTED_VERSION') {
|
|
111
|
+
console.log(`Peer using v${error.received}, we support v${error.supported}`)
|
|
112
|
+
// Handle gracefully - maybe show upgrade notice
|
|
113
|
+
}
|
|
114
|
+
})
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Get Version Information
|
|
118
|
+
|
|
119
|
+
```javascript
|
|
120
|
+
const stats = iframeIO.getStats()
|
|
121
|
+
console.log(`Our version: v${stats.protocolVersion}`)
|
|
122
|
+
console.log(`Peer version: v${stats.peerProtocolVersion}`)
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
### Future-Proofing Benefits
|
|
126
|
+
|
|
127
|
+
1. **Seamless Upgrades**: Deploy new versions without breaking old clients
|
|
128
|
+
2. **Feature Detection**: Enable features based on peer capabilities
|
|
129
|
+
3. **Debugging**: Identify which version caused issues
|
|
130
|
+
4. **Graceful Degradation**: Fall back to older protocol when needed
|
|
131
|
+
|
|
132
|
+
## Session Key Rotation (Opt-In)
|
|
133
|
+
|
|
134
|
+
### When to Use Session Keys
|
|
135
|
+
|
|
136
|
+
Enable session key rotation for:
|
|
137
|
+
- ✅ **Long-lived connections** (> 1 hour)
|
|
138
|
+
- ✅ **High-security applications** (banking, healthcare)
|
|
139
|
+
- ✅ **Compliance requirements** (PCI-DSS, HIPAA, SOC 2)
|
|
140
|
+
- ✅ **Multiple concurrent iframe connections**
|
|
141
|
+
- ✅ **Applications handling sensitive data**
|
|
142
|
+
|
|
143
|
+
Don't enable for:
|
|
144
|
+
- ❌ Short-lived connections (< 30 minutes)
|
|
145
|
+
- ❌ Low-risk internal tools
|
|
146
|
+
- ❌ Same-origin communication
|
|
147
|
+
- ❌ Applications with minimal security requirements
|
|
148
|
+
|
|
149
|
+
### Basic Configuration
|
|
150
|
+
|
|
151
|
+
```javascript
|
|
152
|
+
const iframeIO = new IOF({
|
|
153
|
+
type: 'WINDOW',
|
|
154
|
+
cryptoAuth: {
|
|
155
|
+
secret: 'your-master-secret-key',
|
|
156
|
+
requireSigned: true,
|
|
157
|
+
|
|
158
|
+
// Enable session key rotation
|
|
159
|
+
enableSessionKeys: true,
|
|
160
|
+
sessionKeyRotationInterval: 3600000 // 1 hour (default)
|
|
161
|
+
}
|
|
162
|
+
})
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
### How It Works
|
|
166
|
+
|
|
167
|
+
1. **Connection**: Normal ping/pong handshake
|
|
168
|
+
2. **Key Exchange**: Peers exchange random session IDs
|
|
169
|
+
3. **Key Derivation**: Both derive identical session key using HKDF
|
|
170
|
+
4. **Secure Communication**: All signed messages use session key
|
|
171
|
+
5. **Periodic Rotation**: New key derived every rotation interval
|
|
172
|
+
6. **Graceful Transition**: Old key accepted during grace period
|
|
173
|
+
|
|
174
|
+
```
|
|
175
|
+
Parent IFrame
|
|
176
|
+
| |
|
|
177
|
+
|---- ping -------------------->|
|
|
178
|
+
|<--- pong ----------------------|
|
|
179
|
+
| |
|
|
180
|
+
|-- __session_key_init -------->|
|
|
181
|
+
| (sessionId: abc123) |
|
|
182
|
+
| |
|
|
183
|
+
|<- __session_key_ack -----------|
|
|
184
|
+
| (sessionId: def456, keyId) |
|
|
185
|
+
| |
|
|
186
|
+
[Both derive: key = HKDF(master, abc123+def456, keyId)]
|
|
187
|
+
| |
|
|
188
|
+
|-- signed message (keyId) ---->|
|
|
189
|
+
|<- signed message (keyId) ------|
|
|
190
|
+
| |
|
|
191
|
+
[After rotation interval] |
|
|
192
|
+
|-- __session_key_rotate ------>|
|
|
193
|
+
| (newKeyId) |
|
|
194
|
+
| |
|
|
195
|
+
[Both derive new key] |
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
### Configuration Options
|
|
199
|
+
|
|
200
|
+
```javascript
|
|
201
|
+
const iframeIO = new IOF({
|
|
202
|
+
type: 'WINDOW',
|
|
203
|
+
debug: true,
|
|
204
|
+
cryptoAuth: {
|
|
205
|
+
// Master secret (never transmitted)
|
|
206
|
+
secret: 'replace-with-shared-secret',
|
|
207
|
+
|
|
208
|
+
// Require all messages to be signed
|
|
209
|
+
requireSigned: true,
|
|
210
|
+
|
|
211
|
+
// Clock skew tolerance (2 minutes default)
|
|
212
|
+
maxSkewMs: 120000,
|
|
213
|
+
|
|
214
|
+
// Replay protection window (500 nonces default)
|
|
215
|
+
replayWindowSize: 500,
|
|
216
|
+
|
|
217
|
+
// SESSION KEY ROTATION (opt-in)
|
|
218
|
+
enableSessionKeys: true,
|
|
219
|
+
|
|
220
|
+
// Rotate every hour (adjust based on security needs)
|
|
221
|
+
// - High security: 15-30 minutes
|
|
222
|
+
// - Standard: 1 hour
|
|
223
|
+
// - Low risk: 2-4 hours
|
|
224
|
+
sessionKeyRotationInterval: 3600000
|
|
225
|
+
}
|
|
226
|
+
})
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
### Monitoring Session Keys
|
|
230
|
+
|
|
231
|
+
```javascript
|
|
232
|
+
// Listen for session key events
|
|
233
|
+
iframeIO.on('session_key_established', (data) => {
|
|
234
|
+
console.log(`Session key established: ${data.keyId}`)
|
|
235
|
+
})
|
|
236
|
+
|
|
237
|
+
iframeIO.on('session_key_rotating', (data) => {
|
|
238
|
+
console.log(`Rotating to new key: ${data.keyId}`)
|
|
239
|
+
})
|
|
240
|
+
|
|
241
|
+
iframeIO.on('session_key_rotated', (data) => {
|
|
242
|
+
console.log(`Peer rotated to: ${data.keyId}`)
|
|
243
|
+
})
|
|
244
|
+
|
|
245
|
+
// Check current key status
|
|
246
|
+
const stats = iframeIO.getStats()
|
|
247
|
+
console.log(`Session key active: ${stats.sessionKeyActive}`)
|
|
248
|
+
console.log(`Current key ID: ${stats.sessionKeyId}`)
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Security Considerations
|
|
252
|
+
|
|
253
|
+
**✅ What Session Keys Provide:**
|
|
254
|
+
- Forward secrecy: Past sessions remain secure if master key leaks
|
|
255
|
+
- Session isolation: Each connection uses unique keys
|
|
256
|
+
- Automatic rotation: Limits exposure window
|
|
257
|
+
- Replay protection: Per-session nonce tracking
|
|
258
|
+
|
|
259
|
+
**⚠️ Important Security Notes:**
|
|
260
|
+
|
|
261
|
+
1. **Not a Sandbox Boundary**: If attacker can execute JS in either peer, they can read keys
|
|
262
|
+
2. **Master Secret Security**: Protect your master secret like a password
|
|
263
|
+
3. **HTTPS Required**: Always use HTTPS for iframe communication
|
|
264
|
+
4. **Origin Validation**: Session keys don't replace origin checking
|
|
265
|
+
|
|
266
|
+
### Performance Impact
|
|
267
|
+
|
|
268
|
+
Session key rotation has minimal performance impact:
|
|
269
|
+
- **Key derivation**: ~5-10ms (uses WebCrypto HKDF)
|
|
270
|
+
- **Memory**: ~1KB per active key
|
|
271
|
+
- **Network**: 3 small handshake messages on connect
|
|
272
|
+
- **CPU**: Negligible during rotation (background process)
|
|
273
|
+
|
|
274
|
+
### Example: Banking Application
|
|
275
|
+
|
|
276
|
+
```javascript
|
|
277
|
+
// High-security banking app
|
|
278
|
+
const bankingIO = new IOF({
|
|
279
|
+
type: 'WINDOW',
|
|
280
|
+
debug: false,
|
|
281
|
+
cryptoAuth: {
|
|
282
|
+
secret: process.env.IFRAME_MASTER_SECRET,
|
|
283
|
+
requireSigned: true,
|
|
284
|
+
enableSessionKeys: true,
|
|
285
|
+
sessionKeyRotationInterval: 1800000 // 30 minutes
|
|
286
|
+
},
|
|
287
|
+
// Additional security
|
|
288
|
+
allowedIncomingEvents: [
|
|
289
|
+
'transaction_request',
|
|
290
|
+
'balance_query',
|
|
291
|
+
'account_info'
|
|
292
|
+
],
|
|
293
|
+
validateIncoming: (event, payload, origin) => {
|
|
294
|
+
// Validate payload structure
|
|
295
|
+
if (event === 'transaction_request') {
|
|
296
|
+
return payload &&
|
|
297
|
+
typeof payload.amount === 'number' &&
|
|
298
|
+
typeof payload.recipient === 'string'
|
|
299
|
+
}
|
|
300
|
+
return true
|
|
301
|
+
}
|
|
302
|
+
})
|
|
303
|
+
|
|
304
|
+
// Monitor security events
|
|
305
|
+
bankingIO.on('session_key_rotating', () => {
|
|
306
|
+
console.log('Security: Rotating session keys')
|
|
307
|
+
// Log to security audit trail
|
|
308
|
+
})
|
|
309
|
+
|
|
310
|
+
bankingIO.on('error', (error) => {
|
|
311
|
+
if (error.type === 'AUTH_FAILED') {
|
|
312
|
+
console.error('Security: Authentication failed')
|
|
313
|
+
// Trigger security alert
|
|
314
|
+
}
|
|
315
|
+
})
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Example: Healthcare Application (HIPAA Compliance)
|
|
319
|
+
|
|
320
|
+
```javascript
|
|
321
|
+
// HIPAA-compliant healthcare app
|
|
322
|
+
const healthcareIO = new IOF({
|
|
323
|
+
type: 'IFRAME',
|
|
324
|
+
debug: false,
|
|
325
|
+
cryptoAuth: {
|
|
326
|
+
secret: process.env.PHI_MASTER_SECRET,
|
|
327
|
+
requireSigned: true,
|
|
328
|
+
enableSessionKeys: true,
|
|
329
|
+
sessionKeyRotationInterval: 3600000 // 1 hour
|
|
330
|
+
},
|
|
331
|
+
maxMessageSize: 512 * 1024, // 512KB limit for PHI
|
|
332
|
+
heartbeatInterval: 60000 // 1 minute health check
|
|
333
|
+
})
|
|
334
|
+
|
|
335
|
+
// Audit logging
|
|
336
|
+
healthcareIO.on('session_key_established', (data) => {
|
|
337
|
+
auditLog({
|
|
338
|
+
event: 'SESSION_KEY_ESTABLISHED',
|
|
339
|
+
keyId: data.keyId,
|
|
340
|
+
timestamp: new Date().toISOString(),
|
|
341
|
+
user: getCurrentUser()
|
|
342
|
+
})
|
|
343
|
+
})
|
|
344
|
+
|
|
345
|
+
healthcareIO.on('connect', () => {
|
|
346
|
+
// Send encrypted patient data
|
|
347
|
+
healthcareIO.emitSigned('patient_data', {
|
|
348
|
+
patientId: 'encrypted-id',
|
|
349
|
+
records: encryptedRecords
|
|
350
|
+
})
|
|
351
|
+
})
|
|
352
|
+
```
|
|
353
|
+
|
|
81
354
|
## Enhanced Configuration Options
|
|
82
355
|
|
|
83
356
|
```javascript
|
|
@@ -90,8 +363,17 @@ const iframeIO = new IOF({
|
|
|
90
363
|
maxMessagesPerSecond: 100, // Rate limit (100 messages/second)
|
|
91
364
|
autoReconnect: true, // Enable automatic reconnection
|
|
92
365
|
messageQueueSize: 50, // Max queued messages when disconnected
|
|
93
|
-
allowedIncomingEvents: ['hello', 'response'], // Optional incoming event allowlist
|
|
94
|
-
validateIncoming: (event, payload, origin) => true // Optional custom
|
|
366
|
+
allowedIncomingEvents: ['hello', 'response'], // Optional incoming event allowlist
|
|
367
|
+
validateIncoming: (event, payload, origin) => true, // Optional custom validator
|
|
368
|
+
cryptoAuth: {
|
|
369
|
+
secret: 'replace-with-shared-secret',
|
|
370
|
+
requireSigned: false,
|
|
371
|
+
maxSkewMs: 120000, // 2 minutes clock skew
|
|
372
|
+
replayWindowSize: 500,
|
|
373
|
+
// Session key rotation (opt-in)
|
|
374
|
+
enableSessionKeys: false, // Set to true for high-security apps
|
|
375
|
+
sessionKeyRotationInterval: 3600000 // 1 hour
|
|
376
|
+
}
|
|
95
377
|
})
|
|
96
378
|
```
|
|
97
379
|
|
|
@@ -100,9 +382,9 @@ const iframeIO = new IOF({
|
|
|
100
382
|
### Send Messages with Acknowledgments
|
|
101
383
|
|
|
102
384
|
```javascript
|
|
103
|
-
// Send message and wait for
|
|
385
|
+
// Send message and wait for response with timeout
|
|
104
386
|
try {
|
|
105
|
-
const response = await iframeIO.emitAsync('getData', { id: 123 }, 10000)
|
|
387
|
+
const response = await iframeIO.emitAsync('getData', { id: 123 }, 10000)
|
|
106
388
|
console.log('Response:', response)
|
|
107
389
|
} catch (error) {
|
|
108
390
|
console.error('Request failed:', error.message)
|
|
@@ -112,9 +394,9 @@ try {
|
|
|
112
394
|
iframeIO.on('getData', async (data, ack) => {
|
|
113
395
|
try {
|
|
114
396
|
const result = await fetchData(data.id)
|
|
115
|
-
ack(false, result)
|
|
397
|
+
ack(false, result)
|
|
116
398
|
} catch (error) {
|
|
117
|
-
ack(error.message)
|
|
399
|
+
ack(error.message)
|
|
118
400
|
}
|
|
119
401
|
})
|
|
120
402
|
```
|
|
@@ -124,15 +406,11 @@ iframeIO.on('getData', async (data, ack) => {
|
|
|
124
406
|
```javascript
|
|
125
407
|
// Wait for connection with timeout
|
|
126
408
|
try {
|
|
127
|
-
await iframeIO.connectAsync(5000)
|
|
409
|
+
await iframeIO.connectAsync(5000)
|
|
128
410
|
console.log('Connection established!')
|
|
129
411
|
} catch (error) {
|
|
130
412
|
console.error('Connection failed:', error.message)
|
|
131
413
|
}
|
|
132
|
-
|
|
133
|
-
// Wait for single event
|
|
134
|
-
const userData = await iframeIO.onceAsync('userProfile')
|
|
135
|
-
console.log('User data received:', userData)
|
|
136
414
|
```
|
|
137
415
|
|
|
138
416
|
## Enhanced Connection Management
|
|
@@ -167,7 +445,11 @@ console.log(stats)
|
|
|
167
445
|
// queuedMessages: 0,
|
|
168
446
|
// reconnectAttempts: 0,
|
|
169
447
|
// activeListeners: 5,
|
|
170
|
-
// messageRate: 2
|
|
448
|
+
// messageRate: 2,
|
|
449
|
+
// protocolVersion: 1,
|
|
450
|
+
// peerProtocolVersion: 1,
|
|
451
|
+
// sessionKeyActive: true,
|
|
452
|
+
// sessionKeyId: 'key-1609459200000-abc123'
|
|
171
453
|
// }
|
|
172
454
|
```
|
|
173
455
|
|
|
@@ -177,9 +459,8 @@ console.log(stats)
|
|
|
177
459
|
|
|
178
460
|
```javascript
|
|
179
461
|
// Strict origin checking
|
|
180
|
-
iframeIO.listen('https://trusted-domain.com')
|
|
462
|
+
iframeIO.listen('https://trusted-domain.com')
|
|
181
463
|
|
|
182
|
-
// Error handling for invalid origins
|
|
183
464
|
iframeIO.on('error', (error) => {
|
|
184
465
|
if (error.type === 'INVALID_ORIGIN') {
|
|
185
466
|
console.log(`Rejected message from ${error.received}`)
|
|
@@ -190,95 +471,28 @@ iframeIO.on('error', (error) => {
|
|
|
190
471
|
### Message Sanitization
|
|
191
472
|
|
|
192
473
|
```javascript
|
|
193
|
-
// Automatic payload sanitization
|
|
474
|
+
// Automatic payload sanitization
|
|
194
475
|
iframeIO.emit('data', {
|
|
195
476
|
text: 'Hello',
|
|
196
|
-
func: () => {},
|
|
197
|
-
undef: undefined
|
|
198
|
-
})
|
|
199
|
-
```
|
|
200
|
-
|
|
201
|
-
### Rate Limiting
|
|
202
|
-
|
|
203
|
-
```javascript
|
|
204
|
-
const iframeIO = new IOF({
|
|
205
|
-
maxMessagesPerSecond: 10 // Limit to 10 messages per second
|
|
206
|
-
})
|
|
207
|
-
|
|
208
|
-
iframeIO.on('error', (error) => {
|
|
209
|
-
if (error.type === 'RATE_LIMIT_EXCEEDED') {
|
|
210
|
-
console.log(`Rate limited: ${error.current}/${error.limit}`)
|
|
211
|
-
}
|
|
477
|
+
func: () => {}, // Automatically removed
|
|
478
|
+
undef: undefined // Automatically removed
|
|
212
479
|
})
|
|
213
480
|
```
|
|
214
481
|
|
|
215
|
-
###
|
|
216
|
-
|
|
217
|
-
For defense-in-depth, you can restrict which **application-level** events are accepted and/or validate incoming payloads. Reserved internal events (`ping`, `pong`, `__heartbeat`, `__heartbeat_response`) are always allowed.
|
|
482
|
+
### Cryptographic Message Authentication
|
|
218
483
|
|
|
219
484
|
```javascript
|
|
220
485
|
const iframeIO = new IOF({
|
|
221
|
-
type: '
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
// Example: basic shape checks
|
|
226
|
-
if (event === 'getData') return payload && typeof payload.id === 'number'
|
|
227
|
-
return true
|
|
228
|
-
}
|
|
229
|
-
})
|
|
230
|
-
|
|
231
|
-
iframeIO.on('error', (error) => {
|
|
232
|
-
if (error.type === 'DISALLOWED_EVENT' || error.type === 'INVALID_MESSAGE') {
|
|
233
|
-
console.warn('Dropped incoming message:', error)
|
|
234
|
-
}
|
|
235
|
-
})
|
|
236
|
-
```
|
|
237
|
-
|
|
238
|
-
## Comprehensive Error Handling
|
|
239
|
-
|
|
240
|
-
```javascript
|
|
241
|
-
iframeIO.on('error', (error) => {
|
|
242
|
-
switch (error.type) {
|
|
243
|
-
case 'INVALID_ORIGIN':
|
|
244
|
-
console.error(`Invalid origin: expected ${error.expected}, got ${error.received}`)
|
|
245
|
-
break
|
|
246
|
-
case 'ORIGIN_MISMATCH':
|
|
247
|
-
console.error(`Origin mismatch: expected ${error.expected}, got ${error.received}`)
|
|
248
|
-
break
|
|
249
|
-
case 'RATE_LIMIT_EXCEEDED':
|
|
250
|
-
console.warn(`Rate limit exceeded: ${error.current}/${error.limit} messages/second`)
|
|
251
|
-
break
|
|
252
|
-
case 'MESSAGE_HANDLING_ERROR':
|
|
253
|
-
console.error(`Error handling event ${error.event}: ${error.error}`)
|
|
254
|
-
break
|
|
255
|
-
case 'EMIT_ERROR':
|
|
256
|
-
console.error(`Error sending event ${error.event}: ${error.error}`)
|
|
257
|
-
break
|
|
258
|
-
case 'LISTENER_ERROR':
|
|
259
|
-
console.error(`Error in listener for ${error.event}: ${error.error}`)
|
|
260
|
-
break
|
|
261
|
-
case 'NO_CONNECTION':
|
|
262
|
-
console.error(`Attempted to send ${error.event} without connection`)
|
|
263
|
-
break
|
|
264
|
-
default:
|
|
265
|
-
console.error('Unknown error:', error)
|
|
486
|
+
type: 'WINDOW',
|
|
487
|
+
cryptoAuth: {
|
|
488
|
+
secret: 'replace-with-shared-secret',
|
|
489
|
+
requireSigned: true
|
|
266
490
|
}
|
|
267
491
|
})
|
|
268
|
-
```
|
|
269
492
|
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// Messages are automatically queued when disconnected
|
|
274
|
-
iframeIO.emit('important-data', { data: 'This will be queued if disconnected' })
|
|
275
|
-
|
|
276
|
-
// Clear queue manually if needed
|
|
277
|
-
iframeIO.clearQueue()
|
|
278
|
-
|
|
279
|
-
// Check queue status
|
|
280
|
-
const stats = iframeIO.getStats()
|
|
281
|
-
console.log(`${stats.queuedMessages} messages queued`)
|
|
493
|
+
// Send signed messages
|
|
494
|
+
await iframeIO.emitSigned('hello', { msg: 'signed' })
|
|
495
|
+
const reply = await iframeIO.emitAsyncSigned('getData', { id: 123 }, 5000)
|
|
282
496
|
```
|
|
283
497
|
|
|
284
498
|
## API Reference
|
|
@@ -286,24 +500,26 @@ console.log(`${stats.queuedMessages} messages queued`)
|
|
|
286
500
|
### New Methods
|
|
287
501
|
|
|
288
502
|
#### Async Methods
|
|
289
|
-
- **`emitAsync(event, payload?, timeout?)`** - Send message and wait for response
|
|
290
|
-
- **`
|
|
291
|
-
- **`
|
|
503
|
+
- **`emitAsync(event, payload?, timeout?)`** - Send message and wait for response
|
|
504
|
+
- **`emitAsyncSigned(event, payload?, timeout?)`** - Send signed message and wait for response
|
|
505
|
+
- **`connectAsync(timeout?)`** - Wait for connection with timeout
|
|
506
|
+
- **`onceAsync(event)`** - Wait for single event
|
|
292
507
|
|
|
293
508
|
#### Utility Methods
|
|
294
|
-
- **`getStats()`** - Get connection statistics
|
|
509
|
+
- **`getStats()`** - Get connection statistics (includes version and session key info)
|
|
295
510
|
- **`clearQueue()`** - Clear queued messages
|
|
296
511
|
|
|
297
512
|
### Connection Methods
|
|
298
513
|
|
|
299
|
-
- **`initiate(contentWindow, iframeOrigin)`** - Establish connection (WINDOW peer
|
|
300
|
-
- **`listen(hostOrigin?)`** - Listen for connection (IFRAME peer
|
|
514
|
+
- **`initiate(contentWindow, iframeOrigin)`** - Establish connection (WINDOW peer)
|
|
515
|
+
- **`listen(hostOrigin?)`** - Listen for connection (IFRAME peer)
|
|
301
516
|
- **`disconnect(callback?)`** - Disconnect and cleanup
|
|
302
517
|
- **`isConnected()`** - Check connection status
|
|
303
518
|
|
|
304
519
|
### Messaging Methods
|
|
305
520
|
|
|
306
521
|
- **`emit(event, payload?, callback?)`** - Send message
|
|
522
|
+
- **`emitSigned(event, payload?, callback?)`** - Send signed message
|
|
307
523
|
- **`on(event, listener)`** - Add event listener
|
|
308
524
|
- **`once(event, listener)`** - Add one-time event listener
|
|
309
525
|
- **`off(event, listener?)`** - Remove event listener(s)
|
|
@@ -317,6 +533,11 @@ console.log(`${stats.queuedMessages} messages queued`)
|
|
|
317
533
|
- **`reconnecting`** - Reconnection attempt started
|
|
318
534
|
- **`reconnection_failed`** - All reconnection attempts failed
|
|
319
535
|
|
|
536
|
+
#### Session Key Events (when enabled)
|
|
537
|
+
- **`session_key_established`** - Session key derived and active
|
|
538
|
+
- **`session_key_rotating`** - New key being generated
|
|
539
|
+
- **`session_key_rotated`** - Peer rotated to new key
|
|
540
|
+
|
|
320
541
|
#### Error Events
|
|
321
542
|
- **`error`** - Various error conditions with detailed error objects
|
|
322
543
|
|
|
@@ -325,33 +546,24 @@ console.log(`${stats.queuedMessages} messages queued`)
|
|
|
325
546
|
Full TypeScript support with comprehensive type definitions:
|
|
326
547
|
|
|
327
548
|
```typescript
|
|
328
|
-
import IOF, { Options, Listener, AckFunction } from 'iframe.io'
|
|
549
|
+
import IOF, { Options, Listener, AckFunction, SessionKeyInfo } from 'iframe.io'
|
|
329
550
|
|
|
330
551
|
const options: Options = {
|
|
331
552
|
type: 'WINDOW',
|
|
332
553
|
debug: true,
|
|
333
|
-
|
|
334
|
-
|
|
554
|
+
cryptoAuth: {
|
|
555
|
+
secret: 'my-secret',
|
|
556
|
+
enableSessionKeys: true,
|
|
557
|
+
sessionKeyRotationInterval: 1800000
|
|
558
|
+
}
|
|
335
559
|
}
|
|
336
560
|
|
|
337
561
|
const iframeIO = new IOF(options)
|
|
338
562
|
|
|
339
|
-
//
|
|
340
|
-
iframeIO.on('
|
|
341
|
-
console.log(`
|
|
563
|
+
// Session key events are typed
|
|
564
|
+
iframeIO.on('session_key_established', (data: { keyId: string }) => {
|
|
565
|
+
console.log(`Key established: ${data.keyId}`)
|
|
342
566
|
})
|
|
343
|
-
|
|
344
|
-
// Typed async responses
|
|
345
|
-
interface ApiResponse {
|
|
346
|
-
success: boolean
|
|
347
|
-
data: any[]
|
|
348
|
-
}
|
|
349
|
-
|
|
350
|
-
const response = await iframeIO.emitAsync<{ query: string }, ApiResponse>(
|
|
351
|
-
'search',
|
|
352
|
-
{ query: 'hello' },
|
|
353
|
-
5000 // 5 second timeout
|
|
354
|
-
)
|
|
355
567
|
```
|
|
356
568
|
|
|
357
569
|
## Error Types Reference
|
|
@@ -360,13 +572,16 @@ const response = await iframeIO.emitAsync<{ query: string }, ApiResponse>(
|
|
|
360
572
|
|------------|-------------|
|
|
361
573
|
| `INVALID_ORIGIN` | Message from unexpected origin |
|
|
362
574
|
| `ORIGIN_MISMATCH` | Origin changed during session |
|
|
575
|
+
| `UNSUPPORTED_VERSION` | Peer using unsupported protocol version |
|
|
363
576
|
| `MESSAGE_HANDLING_ERROR` | Error processing incoming message |
|
|
364
577
|
| `EMIT_ERROR` | Error sending message |
|
|
365
578
|
| `LISTENER_ERROR` | Error in event listener |
|
|
366
|
-
| `DISALLOWED_EVENT` |
|
|
367
|
-
| `INVALID_MESSAGE` |
|
|
579
|
+
| `DISALLOWED_EVENT` | Event rejected by allowlist |
|
|
580
|
+
| `INVALID_MESSAGE` | Message rejected by validator |
|
|
581
|
+
| `AUTH_FAILED` | Cryptographic authentication failed |
|
|
582
|
+
| `AUTH_ERROR` | Crypto verification errored |
|
|
368
583
|
| `RATE_LIMIT_EXCEEDED` | Too many messages sent |
|
|
369
|
-
| `NO_CONNECTION` | Attempted
|
|
584
|
+
| `NO_CONNECTION` | Attempted send without connection |
|
|
370
585
|
|
|
371
586
|
## Browser Compatibility
|
|
372
587
|
|
|
@@ -375,37 +590,70 @@ const response = await iframeIO.emitAsync<{ query: string }, ApiResponse>(
|
|
|
375
590
|
- Safari 12+
|
|
376
591
|
- Edge 79+
|
|
377
592
|
|
|
593
|
+
WebCrypto API required for session key rotation feature.
|
|
594
|
+
|
|
378
595
|
## Migration Guide
|
|
379
596
|
|
|
380
|
-
### From v1.
|
|
597
|
+
### From v1.2.x to v1.3.0
|
|
381
598
|
|
|
382
599
|
All existing code continues to work without changes. New features are additive:
|
|
383
600
|
|
|
384
601
|
```javascript
|
|
385
602
|
// Old way (still works)
|
|
386
|
-
iframeIO
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
console.log('Result:', result)
|
|
603
|
+
const iframeIO = new IOF({
|
|
604
|
+
type: 'WINDOW',
|
|
605
|
+
cryptoAuth: {
|
|
606
|
+
secret: 'my-secret'
|
|
391
607
|
}
|
|
392
608
|
})
|
|
393
609
|
|
|
394
|
-
// New
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
}
|
|
610
|
+
// New way with session keys (opt-in)
|
|
611
|
+
const iframeIO = new IOF({
|
|
612
|
+
type: 'WINDOW',
|
|
613
|
+
cryptoAuth: {
|
|
614
|
+
secret: 'my-secret',
|
|
615
|
+
enableSessionKeys: true // Enable for high-security apps
|
|
616
|
+
}
|
|
617
|
+
})
|
|
618
|
+
|
|
619
|
+
// Protocol version is automatic - no code changes needed
|
|
401
620
|
```
|
|
402
621
|
|
|
403
622
|
## Performance Considerations
|
|
404
623
|
|
|
405
|
-
- **Message Size**: Keep
|
|
406
|
-
- **Rate Limiting**: Respect
|
|
624
|
+
- **Message Size**: Keep under `maxMessageSize` (default 1MB)
|
|
625
|
+
- **Rate Limiting**: Respect `maxMessagesPerSecond` (default 100/sec)
|
|
407
626
|
- **Queue Size**: Monitor queued messages to avoid memory issues
|
|
408
|
-
- **Heartbeat**: Adjust `heartbeatInterval` based on
|
|
627
|
+
- **Heartbeat**: Adjust `heartbeatInterval` based on needs
|
|
628
|
+
- **Session Keys**: Minimal overhead (~5-10ms derivation, ~1KB memory)
|
|
629
|
+
|
|
630
|
+
## Best Practices
|
|
631
|
+
|
|
632
|
+
### Security
|
|
633
|
+
|
|
634
|
+
1. **Always use HTTPS** for production deployments
|
|
635
|
+
2. **Validate origins** strictly in both peers
|
|
636
|
+
3. **Enable session keys** for applications handling sensitive data
|
|
637
|
+
4. **Use allowedIncomingEvents** to limit attack surface
|
|
638
|
+
5. **Implement custom validation** for critical payloads
|
|
639
|
+
6. **Monitor error events** for security incidents
|
|
640
|
+
7. **Rotate master secrets** periodically (outside of session rotation)
|
|
641
|
+
|
|
642
|
+
### Performance
|
|
643
|
+
|
|
644
|
+
1. **Batch related messages** when possible
|
|
645
|
+
2. **Use appropriate queue sizes** for your use case
|
|
646
|
+
3. **Monitor connection stats** regularly
|
|
647
|
+
4. **Adjust heartbeat intervals** based on reliability needs
|
|
648
|
+
5. **Set reasonable rotation intervals** (1 hour is good default)
|
|
649
|
+
|
|
650
|
+
### Reliability
|
|
651
|
+
|
|
652
|
+
1. **Handle all error types** appropriately
|
|
653
|
+
2. **Implement retry logic** at application level for critical operations
|
|
654
|
+
3. **Use `emitAsync`** for operations requiring acknowledgment
|
|
655
|
+
4. **Monitor reconnection events** and alert on failures
|
|
656
|
+
5. **Test disconnection scenarios** thoroughly
|
|
409
657
|
|
|
410
658
|
## License
|
|
411
659
|
|
|
@@ -420,3 +668,25 @@ Contributions are welcome! Please read our contributing guidelines and submit pu
|
|
|
420
668
|
- Create an issue on GitHub for bug reports
|
|
421
669
|
- Check existing issues for common problems
|
|
422
670
|
- Review the documentation for usage examples
|
|
671
|
+
|
|
672
|
+
## Changelog
|
|
673
|
+
|
|
674
|
+
### v1.3.0
|
|
675
|
+
- Added protocol versioning for future-proof evolution
|
|
676
|
+
- Added optional session key rotation for high-security applications
|
|
677
|
+
- Enhanced connection statistics with version and key information
|
|
678
|
+
- Improved error handling for version mismatches
|
|
679
|
+
- Added comprehensive documentation and examples
|
|
680
|
+
|
|
681
|
+
### v1.2.0
|
|
682
|
+
- Enhanced security features (origin validation, sanitization, rate limiting)
|
|
683
|
+
- Auto-reconnection with exponential backoff
|
|
684
|
+
- Heartbeat monitoring
|
|
685
|
+
- Message queuing
|
|
686
|
+
- Modern async/await APIs
|
|
687
|
+
- Comprehensive error handling
|
|
688
|
+
|
|
689
|
+
### v1.1.0
|
|
690
|
+
- Initial release
|
|
691
|
+
- Basic iframe communication
|
|
692
|
+
- Event-based API
|