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/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,50 @@
|
|
|
1
1
|
export type PeerType = 'WINDOW' | 'IFRAME';
|
|
2
2
|
export type AckFunction = (error: boolean | string, ...args: any[]) => void;
|
|
3
3
|
export type Listener = (payload?: any, ack?: AckFunction) => void;
|
|
4
|
+
export type CryptoAuthOptions = {
|
|
5
|
+
/**
|
|
6
|
+
* Shared secret used for HMAC-SHA256 signing.
|
|
7
|
+
*
|
|
8
|
+
* IMPORTANT: If an attacker can execute JS in either peer, they can read the secret.
|
|
9
|
+
* This is for authenticity/integrity between cooperating peers, not a sandbox boundary.
|
|
10
|
+
*/
|
|
11
|
+
secret: string;
|
|
12
|
+
/**
|
|
13
|
+
* If true, drop any incoming message that doesn't carry valid auth.
|
|
14
|
+
* Default: false (accept unsigned messages)
|
|
15
|
+
*/
|
|
16
|
+
requireSigned?: boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Maximum allowed clock skew for signed messages (ms).
|
|
19
|
+
* Default: 2 minutes
|
|
20
|
+
*/
|
|
21
|
+
maxSkewMs?: number;
|
|
22
|
+
/**
|
|
23
|
+
* Replay window size (max number of nonces kept in memory).
|
|
24
|
+
* Default: 500
|
|
25
|
+
*/
|
|
26
|
+
replayWindowSize?: number;
|
|
27
|
+
/**
|
|
28
|
+
* Enable session-derived keys for enhanced security.
|
|
29
|
+
* When enabled, a unique session key is derived from the master secret
|
|
30
|
+
* and exchanged session IDs during connection handshake.
|
|
31
|
+
* Recommended for long-lived connections and high-security applications.
|
|
32
|
+
* Default: false
|
|
33
|
+
*/
|
|
34
|
+
enableSessionKeys?: boolean;
|
|
35
|
+
/**
|
|
36
|
+
* How often to rotate session keys (in milliseconds).
|
|
37
|
+
* Only applies when enableSessionKeys is true.
|
|
38
|
+
* Default: 3600000 (1 hour)
|
|
39
|
+
*/
|
|
40
|
+
sessionKeyRotationInterval?: number;
|
|
41
|
+
};
|
|
42
|
+
export type SessionKeyInfo = {
|
|
43
|
+
keyId: string;
|
|
44
|
+
key: string;
|
|
45
|
+
createdAt: number;
|
|
46
|
+
expiresAt: number;
|
|
47
|
+
};
|
|
4
48
|
export type Options = {
|
|
5
49
|
type?: PeerType;
|
|
6
50
|
debug?: boolean;
|
|
@@ -20,6 +64,11 @@ export type Options = {
|
|
|
20
64
|
* Return false to drop a message; an 'error' event will be emitted.
|
|
21
65
|
*/
|
|
22
66
|
validateIncoming?: (event: string, payload: any, origin: string) => boolean;
|
|
67
|
+
/**
|
|
68
|
+
* Optional cryptographic message authentication (HMAC-SHA256).
|
|
69
|
+
* When enabled, use `emitSigned` / `emitAsyncSigned` to send signed messages.
|
|
70
|
+
*/
|
|
71
|
+
cryptoAuth?: CryptoAuthOptions;
|
|
23
72
|
};
|
|
24
73
|
export interface RegisteredEvents {
|
|
25
74
|
[index: string]: Listener[];
|
|
@@ -30,13 +79,24 @@ export type Peer = {
|
|
|
30
79
|
origin?: string;
|
|
31
80
|
connected?: boolean;
|
|
32
81
|
lastHeartbeat?: number;
|
|
82
|
+
protocolVersion?: number;
|
|
83
|
+
sessionId?: string;
|
|
33
84
|
};
|
|
34
85
|
export type MessageData = {
|
|
86
|
+
v: number;
|
|
35
87
|
_event: string;
|
|
36
88
|
payload: any;
|
|
37
89
|
cid: string | undefined;
|
|
38
90
|
timestamp?: number;
|
|
39
91
|
size?: number;
|
|
92
|
+
auth?: {
|
|
93
|
+
alg: 'HMAC-SHA256';
|
|
94
|
+
ts: number;
|
|
95
|
+
nonce: string;
|
|
96
|
+
sig: string;
|
|
97
|
+
keyId?: string;
|
|
98
|
+
};
|
|
99
|
+
sessionId?: string;
|
|
40
100
|
};
|
|
41
101
|
export type Message = {
|
|
42
102
|
origin: string;
|
|
@@ -56,11 +116,93 @@ export default class IOF {
|
|
|
56
116
|
private messageListener?;
|
|
57
117
|
private heartbeatTimer?;
|
|
58
118
|
private reconnectTimer?;
|
|
119
|
+
private sessionKeyRotationTimer?;
|
|
59
120
|
private messageQueue;
|
|
60
121
|
private messageRateTracker;
|
|
61
122
|
private reconnectAttempts;
|
|
62
123
|
private maxReconnectAttempts;
|
|
124
|
+
private seenNonces;
|
|
125
|
+
private currentSessionKey?;
|
|
126
|
+
private pendingSessionKey?;
|
|
127
|
+
private previousSessionKey?;
|
|
128
|
+
private mySessionId?;
|
|
63
129
|
constructor(options?: Options);
|
|
130
|
+
private cryptoCfg;
|
|
131
|
+
/**
|
|
132
|
+
* Forget nonces that can no longer be replayed, and only then cap the map.
|
|
133
|
+
*
|
|
134
|
+
* Age is what actually decides replayability: a captured message is refused
|
|
135
|
+
* once its `ts` falls outside maxSkewMs, so a nonce is only worth keeping
|
|
136
|
+
* that long. Pruning purely by count — as this did — made the two defaults
|
|
137
|
+
* contradict each other: 500 remembered nonces at the default 100 messages a
|
|
138
|
+
* second is five seconds of history guarding a two-minute acceptance window,
|
|
139
|
+
* so anything captured could simply be replayed after five seconds.
|
|
140
|
+
*
|
|
141
|
+
* The count cap stays as a memory bound. Reaching it means the rate limiter
|
|
142
|
+
* is admitting more traffic than the window can remember, so it is reported
|
|
143
|
+
* rather than applied in silence.
|
|
144
|
+
*/
|
|
145
|
+
private pruneNonces;
|
|
146
|
+
/**
|
|
147
|
+
* Get the appropriate secret for signing messages
|
|
148
|
+
* Uses session key if available, otherwise falls back to master secret
|
|
149
|
+
*/
|
|
150
|
+
private getSigningSecret;
|
|
151
|
+
/**
|
|
152
|
+
* Get the appropriate secret for verifying incoming messages
|
|
153
|
+
* Tries current key, then pending, then previous, then master
|
|
154
|
+
*/
|
|
155
|
+
private getVerificationSecrets;
|
|
156
|
+
/**
|
|
157
|
+
* Application-level admission check for one incoming message.
|
|
158
|
+
*
|
|
159
|
+
* Reserved events bypass it deliberately: the handshake and the heartbeats
|
|
160
|
+
* must survive an allow-list that does not name them, or configuring one
|
|
161
|
+
* would silently sever the connection.
|
|
162
|
+
*
|
|
163
|
+
* This lives in a method because it used to be written out at each place a
|
|
164
|
+
* message can arrive — the authenticated and unauthenticated branches of
|
|
165
|
+
* `initiate()` and of `listen()` — and `listen()`'s unauthenticated branch
|
|
166
|
+
* never got a copy. An embedded bridge configured with an allow-list and no
|
|
167
|
+
* cryptoAuth, which is exactly how de.eui runs it, therefore accepted every
|
|
168
|
+
* event name a host cared to send.
|
|
169
|
+
*/
|
|
170
|
+
private acceptIncoming;
|
|
171
|
+
/**
|
|
172
|
+
* Initialize session key exchange
|
|
173
|
+
* Called after connection is established if enableSessionKeys is true
|
|
174
|
+
*/
|
|
175
|
+
private initiateSessionKeyExchange;
|
|
176
|
+
/**
|
|
177
|
+
* Handle incoming session key initialization
|
|
178
|
+
*/
|
|
179
|
+
private handleSessionKeyInit;
|
|
180
|
+
/**
|
|
181
|
+
* Handle session key acknowledgment
|
|
182
|
+
*/
|
|
183
|
+
private handleSessionKeyAck;
|
|
184
|
+
/**
|
|
185
|
+
* Derive and store a session key
|
|
186
|
+
*/
|
|
187
|
+
private deriveAndStoreSessionKey;
|
|
188
|
+
/**
|
|
189
|
+
* Start session key rotation timer
|
|
190
|
+
*/
|
|
191
|
+
private startSessionKeyRotation;
|
|
192
|
+
/**
|
|
193
|
+
* Rotate session key
|
|
194
|
+
*/
|
|
195
|
+
private rotateSessionKey;
|
|
196
|
+
/**
|
|
197
|
+
* Handle incoming session key rotation
|
|
198
|
+
*/
|
|
199
|
+
private handleSessionKeyRotate;
|
|
200
|
+
/**
|
|
201
|
+
* Stop session key rotation timer
|
|
202
|
+
*/
|
|
203
|
+
private stopSessionKeyRotation;
|
|
204
|
+
private signOutgoing;
|
|
205
|
+
private verifyIncomingAuth;
|
|
64
206
|
debug(...args: any[]): void;
|
|
65
207
|
isConnected(): boolean;
|
|
66
208
|
private startHeartbeat;
|
|
@@ -81,6 +223,12 @@ export default class IOF {
|
|
|
81
223
|
listen(hostOrigin?: string): this;
|
|
82
224
|
fire(_event: string, payload?: MessageData['payload'], cid?: string): void;
|
|
83
225
|
emit<T = any>(_event: string, payload?: T | AckFunction, fn?: AckFunction): this;
|
|
226
|
+
/**
|
|
227
|
+
* Send a signed message (HMAC-SHA256) when `options.cryptoAuth` is configured.
|
|
228
|
+
* This is async because WebCrypto signing is async.
|
|
229
|
+
*/
|
|
230
|
+
emitSigned<T = any>(_event: string, payload?: T | AckFunction, fn?: AckFunction): Promise<this>;
|
|
231
|
+
emitAsyncSigned<T = any, R = any>(_event: string, payload?: T, timeout?: number): Promise<R>;
|
|
84
232
|
on(_event: string, fn: Listener): this;
|
|
85
233
|
once(_event: string, fn: Listener): this;
|
|
86
234
|
off(_event: string, fn?: Listener): this;
|
|
@@ -99,6 +247,10 @@ export default class IOF {
|
|
|
99
247
|
reconnectAttempts: number;
|
|
100
248
|
activeListeners: number;
|
|
101
249
|
messageRate: number;
|
|
250
|
+
protocolVersion: number;
|
|
251
|
+
peerProtocolVersion: number | undefined;
|
|
252
|
+
sessionKeyActive: boolean;
|
|
253
|
+
sessionKeyId: string | undefined;
|
|
102
254
|
};
|
|
103
255
|
clearQueue(): this;
|
|
104
256
|
}
|