webview.io 1.1.0 → 1.2.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 +25 -1
- package/dist/index.d.ts +58 -0
- package/dist/index.js +403 -16
- package/package.json +3 -5
- package/src/index.ts +525 -2
package/README.md
CHANGED
|
@@ -144,7 +144,11 @@ const wio = new WIO({
|
|
|
144
144
|
autoReconnect: true, // Enable automatic reconnection
|
|
145
145
|
messageQueueSize: 50, // Max queued messages when disconnected
|
|
146
146
|
allowedIncomingEvents: ['hello', 'response'], // Optional incoming event allowlist (non-reserved events)
|
|
147
|
-
validateIncoming: (event, payload) => true
|
|
147
|
+
validateIncoming: (event, payload) => true, // Optional custom incoming validator
|
|
148
|
+
cryptoAuth: { // Optional cryptographic message authentication (HMAC-SHA256)
|
|
149
|
+
secret: 'replace-with-shared-secret',
|
|
150
|
+
requireSigned: false
|
|
151
|
+
}
|
|
148
152
|
})
|
|
149
153
|
```
|
|
150
154
|
|
|
@@ -355,6 +359,24 @@ wio.on('error', (error) => {
|
|
|
355
359
|
})
|
|
356
360
|
```
|
|
357
361
|
|
|
362
|
+
### Cryptographic Message Authentication (HMAC)
|
|
363
|
+
|
|
364
|
+
If you need **message integrity/authenticity** beyond the default transport guarantees, enable `cryptoAuth` and use the signed APIs. This adds an HMAC-SHA256 signature + timestamp + nonce (with basic replay protection).
|
|
365
|
+
|
|
366
|
+
**Important security note:** if your WebView loads untrusted content, that content can read the shared secret. This protects against some injection/misrouting scenarios—but not against a fully compromised WebView page.
|
|
367
|
+
|
|
368
|
+
```javascript
|
|
369
|
+
// React Native side
|
|
370
|
+
const wio = new WIO({
|
|
371
|
+
type: 'WEBVIEW',
|
|
372
|
+
cryptoAuth: { secret: 'replace-with-shared-secret', requireSigned: true }
|
|
373
|
+
})
|
|
374
|
+
|
|
375
|
+
// Send signed
|
|
376
|
+
await wio.emitSigned('hello', { msg: 'signed' })
|
|
377
|
+
const reply = await wio.emitAsyncSigned('getData', { id: 123 }, 5000)
|
|
378
|
+
```
|
|
379
|
+
|
|
358
380
|
### Rate Limiting
|
|
359
381
|
|
|
360
382
|
```javascript
|
|
@@ -625,6 +647,8 @@ const response = await wio.emitAsync<{ query: string }, ApiResponse>(
|
|
|
625
647
|
| `LISTENER_ERROR` | Error in event listener |
|
|
626
648
|
| `DISALLOWED_EVENT` | Incoming event rejected by `allowedIncomingEvents` |
|
|
627
649
|
| `INVALID_MESSAGE` | Incoming message rejected by `validateIncoming` |
|
|
650
|
+
| `AUTH_FAILED` | Incoming message failed cryptographic authentication |
|
|
651
|
+
| `AUTH_ERROR` | Cryptographic verification errored (missing crypto, etc.) |
|
|
628
652
|
| `RATE_LIMIT_EXCEEDED` | Too many messages sent |
|
|
629
653
|
| `NO_CONNECTION` | Attempted to send without connection |
|
|
630
654
|
|
package/dist/index.d.ts
CHANGED
|
@@ -3,6 +3,30 @@ import type { WebView } from 'react-native-webview';
|
|
|
3
3
|
export type PeerType = 'WEBVIEW' | 'EMBEDDED';
|
|
4
4
|
export type AckFunction = (error: boolean | string, ...args: any[]) => void;
|
|
5
5
|
export type Listener = (payload?: any, ack?: AckFunction) => void;
|
|
6
|
+
export type CryptoAuthOptions = {
|
|
7
|
+
/**
|
|
8
|
+
* Shared secret used for HMAC-SHA256 signing.
|
|
9
|
+
*
|
|
10
|
+
* IMPORTANT: If an attacker can execute JS in either peer, they can read the secret.
|
|
11
|
+
* This is for authenticity/integrity between cooperating peers, not a sandbox boundary.
|
|
12
|
+
*/
|
|
13
|
+
secret: string;
|
|
14
|
+
/**
|
|
15
|
+
* If true, drop any incoming message that doesn't carry valid auth.
|
|
16
|
+
* Default: false (accept unsigned messages)
|
|
17
|
+
*/
|
|
18
|
+
requireSigned?: boolean;
|
|
19
|
+
/**
|
|
20
|
+
* Maximum allowed clock skew for signed messages (ms).
|
|
21
|
+
* Default: 2 minutes
|
|
22
|
+
*/
|
|
23
|
+
maxSkewMs?: number;
|
|
24
|
+
/**
|
|
25
|
+
* Replay window size (max number of nonces kept in memory).
|
|
26
|
+
* Default: 500
|
|
27
|
+
*/
|
|
28
|
+
replayWindowSize?: number;
|
|
29
|
+
};
|
|
6
30
|
export type Options = {
|
|
7
31
|
type?: PeerType;
|
|
8
32
|
debug?: boolean;
|
|
@@ -24,12 +48,19 @@ export type Options = {
|
|
|
24
48
|
* Return false to drop a message; an 'error' event will be emitted.
|
|
25
49
|
*/
|
|
26
50
|
validateIncoming?: (event: string, payload: any) => boolean;
|
|
51
|
+
/**
|
|
52
|
+
* Optional cryptographic message authentication (HMAC-SHA256).
|
|
53
|
+
* When enabled, use `emitSigned` / `emitAsyncSigned` to send signed messages.
|
|
54
|
+
* For EMBEDDED (WebView content) you must set the secret in injected bridge too (handled by `getInjectedJavaScript()` when configured).
|
|
55
|
+
*/
|
|
56
|
+
cryptoAuth?: CryptoAuthOptions;
|
|
27
57
|
};
|
|
28
58
|
export interface RegisteredEvents {
|
|
29
59
|
[index: string]: Listener[];
|
|
30
60
|
}
|
|
31
61
|
export type Peer = {
|
|
32
62
|
type: PeerType;
|
|
63
|
+
protocolVersion?: number;
|
|
33
64
|
webViewRef?: RefObject<WebView>;
|
|
34
65
|
origin?: string;
|
|
35
66
|
connected?: boolean;
|
|
@@ -37,12 +68,19 @@ export type Peer = {
|
|
|
37
68
|
embeddedReady?: boolean;
|
|
38
69
|
};
|
|
39
70
|
export type MessageData = {
|
|
71
|
+
v?: number;
|
|
40
72
|
_event: string;
|
|
41
73
|
payload: any;
|
|
42
74
|
cid: string | undefined;
|
|
43
75
|
timestamp?: number;
|
|
44
76
|
size?: number;
|
|
45
77
|
token?: string;
|
|
78
|
+
auth?: {
|
|
79
|
+
alg: 'HMAC-SHA256';
|
|
80
|
+
ts: number;
|
|
81
|
+
nonce: string;
|
|
82
|
+
sig: string;
|
|
83
|
+
};
|
|
46
84
|
};
|
|
47
85
|
export type Message = {
|
|
48
86
|
data: MessageData;
|
|
@@ -68,7 +106,21 @@ export default class WIO {
|
|
|
68
106
|
private maxReconnectAttempts;
|
|
69
107
|
private connectionToken?;
|
|
70
108
|
private connectionAttempts;
|
|
109
|
+
private seenNonces;
|
|
71
110
|
constructor(options?: Options);
|
|
111
|
+
private cryptoCfg;
|
|
112
|
+
/**
|
|
113
|
+
* Forget nonces that can no longer be replayed, and only then cap the map.
|
|
114
|
+
*
|
|
115
|
+
* Age is what decides replayability: a captured message is refused once its
|
|
116
|
+
* `ts` falls outside maxSkewMs, so a nonce is only worth keeping that long.
|
|
117
|
+
* Pruning purely by count made the two defaults contradict each other — 500
|
|
118
|
+
* remembered nonces at the default 100 messages a second is five seconds of
|
|
119
|
+
* history guarding a two-minute acceptance window.
|
|
120
|
+
*/
|
|
121
|
+
private pruneNonces;
|
|
122
|
+
private signOutgoing;
|
|
123
|
+
private verifyIncomingAuth;
|
|
72
124
|
debug(...args: any[]): void;
|
|
73
125
|
isConnected(): boolean;
|
|
74
126
|
private startHeartbeat;
|
|
@@ -103,6 +155,12 @@ export default class WIO {
|
|
|
103
155
|
}): void;
|
|
104
156
|
fire(_event: string, payload?: MessageData['payload'], cid?: string): void;
|
|
105
157
|
emit<T = any>(_event: string, payload?: T | AckFunction, fn?: AckFunction): this;
|
|
158
|
+
/**
|
|
159
|
+
* Send a signed message (HMAC-SHA256) when `options.cryptoAuth` is configured.
|
|
160
|
+
* This is async because WebCrypto signing is async.
|
|
161
|
+
*/
|
|
162
|
+
emitSigned<T = any>(_event: string, payload?: T | AckFunction, fn?: AckFunction): Promise<this>;
|
|
163
|
+
emitAsyncSigned<T = any, R = any>(_event: string, payload?: T, timeout?: number): Promise<R>;
|
|
106
164
|
on(_event: string, fn: Listener): this;
|
|
107
165
|
once(_event: string, fn: Listener): this;
|
|
108
166
|
off(_event: string, fn?: Listener): this;
|