@peerkit/transport-libp2p-react-native 0.1.0-alpha.13
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 +155 -0
- package/dist/datachannel-early-message-polyfill.d.ts +23 -0
- package/dist/datachannel-early-message-polyfill.d.ts.map +1 -0
- package/dist/datachannel-early-message-polyfill.js +131 -0
- package/dist/datachannel-early-message-polyfill.js.map +1 -0
- package/dist/event-target-polyfill.d.ts +40 -0
- package/dist/event-target-polyfill.d.ts.map +1 -0
- package/dist/event-target-polyfill.js +192 -0
- package/dist/event-target-polyfill.js.map +1 -0
- package/dist/factories.d.ts +68 -0
- package/dist/factories.d.ts.map +1 -0
- package/dist/factories.js +73 -0
- package/dist/factories.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/polyfills.d.ts +41 -0
- package/dist/polyfills.d.ts.map +1 -0
- package/dist/polyfills.js +67 -0
- package/dist/polyfills.js.map +1 -0
- package/dist/quick-crypto-noise.d.ts +35 -0
- package/dist/quick-crypto-noise.d.ts.map +1 -0
- package/dist/quick-crypto-noise.js +105 -0
- package/dist/quick-crypto-noise.js.map +1 -0
- package/dist/text-codec-polyfill.d.ts +25 -0
- package/dist/text-codec-polyfill.d.ts.map +1 -0
- package/dist/text-codec-polyfill.js +79 -0
- package/dist/text-codec-polyfill.js.map +1 -0
- package/dist/webrtc-certificate-polyfill.d.ts +37 -0
- package/dist/webrtc-certificate-polyfill.d.ts.map +1 -0
- package/dist/webrtc-certificate-polyfill.js +53 -0
- package/dist/webrtc-certificate-polyfill.js.map +1 -0
- package/dist/websocket-buffered-amount-polyfill.d.ts +21 -0
- package/dist/websocket-buffered-amount-polyfill.d.ts.map +1 -0
- package/dist/websocket-buffered-amount-polyfill.js +41 -0
- package/dist/websocket-buffered-amount-polyfill.js.map +1 -0
- package/package.json +78 -0
- package/src/datachannel-early-message-polyfill.ts +176 -0
- package/src/event-target-polyfill.ts +268 -0
- package/src/factories.ts +116 -0
- package/src/index.ts +10 -0
- package/src/polyfills.ts +72 -0
- package/src/quick-crypto-noise.ts +130 -0
- package/src/rn-modules.d.ts +100 -0
- package/src/text-codec-polyfill.ts +118 -0
- package/src/webrtc-certificate-polyfill.ts +60 -0
- package/src/websocket-buffered-amount-polyfill.ts +45 -0
package/src/polyfills.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime polyfills required by libp2p when running on React Native (Hermes).
|
|
3
|
+
*
|
|
4
|
+
* Side-effect import from the app entry, **before any other import**:
|
|
5
|
+
*
|
|
6
|
+
* ```ts
|
|
7
|
+
* import "@peerkit/transport-libp2p-react-native/polyfills";
|
|
8
|
+
* ```
|
|
9
|
+
*
|
|
10
|
+
* Order matters:
|
|
11
|
+
* 1. `react-native-get-random-values` — patches `globalThis.crypto.getRandomValues`.
|
|
12
|
+
* Must run first so anything that touches RNG during module init (peer-id,
|
|
13
|
+
* noise key generation) observes the JSI implementation.
|
|
14
|
+
* 2. `react-native-quick-crypto` `install()` — installs the JSI-backed
|
|
15
|
+
* `globalThis.crypto.subtle` (Ed25519, X25519, AES-GCM, ChaCha20-Poly1305,
|
|
16
|
+
* HMAC, SHA-256) used by `@chainsafe/libp2p-noise`.
|
|
17
|
+
* 3. `react-native-webrtc` `registerGlobals()` — defines the global
|
|
18
|
+
* `RTCPeerConnection`, `RTCSessionDescription`, etc. used by
|
|
19
|
+
* `@libp2p/webrtc`, followed by `installWebRtcCertificatePolyfill()` which
|
|
20
|
+
* adds the static `RTCPeerConnection.generateCertificate` method the
|
|
21
|
+
* webrtc-direct dialer needs but `react-native-webrtc` does not provide
|
|
22
|
+
* (see `webrtc-certificate-polyfill`), and
|
|
23
|
+
* `installDataChannelEarlyMessagePolyfill()` which recovers the first
|
|
24
|
+
* inbound datachannel message `react-native-webrtc` drops during connection
|
|
25
|
+
* setup, otherwise mobile-to-mobile stream handshakes hang (see
|
|
26
|
+
* `datachannel-early-message-polyfill`).
|
|
27
|
+
*
|
|
28
|
+
* It also installs the WHATWG `Event` / `EventTarget` / `CustomEvent` globals
|
|
29
|
+
* that Hermes lacks but libp2p extends at module-evaluation time (see
|
|
30
|
+
* `event-target-polyfill`), plus the `TextEncoder` / `TextDecoder` globals that
|
|
31
|
+
* `uint8arrays` constructs at module-evaluation time (see `text-codec-polyfill`).
|
|
32
|
+
* Those steps are order-independent relative to the three above.
|
|
33
|
+
*
|
|
34
|
+
* Consumers must also configure Metro to map bare Node imports (`crypto`,
|
|
35
|
+
* `stream`, `buffer`, `events`) to their React Native equivalents and enable
|
|
36
|
+
* `@babel/plugin-transform-private-methods` (loose) so libp2p's ES2022
|
|
37
|
+
* private fields parse under Hermes. See the package README for the full
|
|
38
|
+
* Metro / Babel configuration.
|
|
39
|
+
*/
|
|
40
|
+
|
|
41
|
+
import "react-native-get-random-values";
|
|
42
|
+
import { Buffer } from "buffer";
|
|
43
|
+
import process from "process";
|
|
44
|
+
import { install as installQuickCrypto } from "react-native-quick-crypto";
|
|
45
|
+
import { registerGlobals as registerWebRtcGlobals } from "react-native-webrtc";
|
|
46
|
+
import { installDataChannelEarlyMessagePolyfill } from "./datachannel-early-message-polyfill.js";
|
|
47
|
+
import { installEventTargetPolyfill } from "./event-target-polyfill.js";
|
|
48
|
+
import { installTextCodecPolyfill } from "./text-codec-polyfill.js";
|
|
49
|
+
import { installWebRtcCertificatePolyfill } from "./webrtc-certificate-polyfill.js";
|
|
50
|
+
import { installWebSocketBufferedAmountPolyfill } from "./websocket-buffered-amount-polyfill.js";
|
|
51
|
+
|
|
52
|
+
// Buffer + process must live on globalThis: some libp2p dependencies reach for these as globals
|
|
53
|
+
// rather than as ES module imports. Metro's `extraNodeModules` only rewrites
|
|
54
|
+
// bare imports — code that touches `globalThis.Buffer` or
|
|
55
|
+
// `globalThis.process.nextTick` directly relies on this assignment.
|
|
56
|
+
const globalScope = globalThis as {
|
|
57
|
+
Buffer?: typeof Buffer;
|
|
58
|
+
process?: typeof process;
|
|
59
|
+
};
|
|
60
|
+
globalScope.Buffer ??= Buffer;
|
|
61
|
+
globalScope.process ??= process;
|
|
62
|
+
|
|
63
|
+
installEventTargetPolyfill();
|
|
64
|
+
installTextCodecPolyfill();
|
|
65
|
+
installWebSocketBufferedAmountPolyfill();
|
|
66
|
+
installQuickCrypto();
|
|
67
|
+
registerWebRtcGlobals();
|
|
68
|
+
// Must run after `registerWebRtcGlobals()`: both patch the global
|
|
69
|
+
// `RTCPeerConnection` that step installs (the early-message polyfill also probes
|
|
70
|
+
// it for the `RTCDataChannel` prototype).
|
|
71
|
+
installWebRtcCertificatePolyfill();
|
|
72
|
+
installDataChannelEarlyMessagePolyfill();
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `ICryptoInterface` implementation backed by `react-native-quick-crypto`,
|
|
3
|
+
* intended to be passed to `noise({ crypto: quickCryptoNoise })` on React
|
|
4
|
+
* Native. JSI-backed primitives are noticeably faster than the pure-JS Noise
|
|
5
|
+
* defaults on Hermes, which matters for handshake latency and bulk transfer.
|
|
6
|
+
*
|
|
7
|
+
* Only the primitives that have a usable synchronous quick-crypto mapping are
|
|
8
|
+
* overridden:
|
|
9
|
+
*
|
|
10
|
+
* - `hashSHA256` — `createHash('sha256')`
|
|
11
|
+
* - `getHKDF` — RFC 5869 HKDF built on `createHmac('sha256', ...)`
|
|
12
|
+
* - `chaCha20Poly1305Encrypt` / `chaCha20Poly1305Decrypt` —
|
|
13
|
+
* `createCipheriv` / `createDecipheriv` with the `chacha20-poly1305`
|
|
14
|
+
* AEAD, 16-byte authentication tag appended to / split from the
|
|
15
|
+
* ciphertext per the Noise specification.
|
|
16
|
+
*
|
|
17
|
+
* X25519 key generation and shared-secret derivation are delegated to
|
|
18
|
+
* `pureJsCrypto` because the quick-crypto X25519 API is asynchronous
|
|
19
|
+
* (subtle-style) whereas `ICryptoInterface` requires synchronous methods.
|
|
20
|
+
* The pure-JS X25519 implementation is fast enough that JSI offers little
|
|
21
|
+
* practical gain.
|
|
22
|
+
*
|
|
23
|
+
* This module pulls in `react-native-quick-crypto` at import time and must
|
|
24
|
+
* therefore only be imported in a React Native bundle. It is exposed via the
|
|
25
|
+
* dedicated `./quick-crypto-noise` subexport so the package's main entry
|
|
26
|
+
* remains free of React Native-only side effects.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { pureJsCrypto, type ICryptoInterface } from "@chainsafe/libp2p-noise";
|
|
30
|
+
import QuickCrypto from "react-native-quick-crypto";
|
|
31
|
+
import type { Uint8ArrayList } from "uint8arraylist";
|
|
32
|
+
|
|
33
|
+
const TAG_LENGTH = 16;
|
|
34
|
+
|
|
35
|
+
function toBytes(data: Uint8Array | Uint8ArrayList): Uint8Array {
|
|
36
|
+
// `Uint8ArrayList.subarray()` returns a contiguous Uint8Array view, which
|
|
37
|
+
// is what quick-crypto's Node-compatible API expects.
|
|
38
|
+
return data instanceof Uint8Array ? data : data.subarray();
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function hashSHA256(data: Uint8Array | Uint8ArrayList): Uint8Array {
|
|
42
|
+
return QuickCrypto.createHash("sha256").update(toBytes(data)).digest();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function hmacSHA256(key: Uint8Array, data: Uint8Array): Uint8Array {
|
|
46
|
+
return QuickCrypto.createHmac("sha256", key).update(data).digest();
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function getHKDF(
|
|
50
|
+
ck: Uint8Array,
|
|
51
|
+
ikm: Uint8Array,
|
|
52
|
+
): [Uint8Array, Uint8Array, Uint8Array] {
|
|
53
|
+
// RFC 5869 HKDF with SHA-256, three 32-byte outputs as required by the
|
|
54
|
+
// Noise specification. Extract step: PRK = HMAC(ck, ikm). Expand step:
|
|
55
|
+
// outN = HMAC(PRK, outN-1 || counter), starting from an empty previous
|
|
56
|
+
// block.
|
|
57
|
+
const prk = hmacSHA256(ck, ikm);
|
|
58
|
+
const out1 = hmacSHA256(prk, new Uint8Array([0x01]));
|
|
59
|
+
|
|
60
|
+
const out2Input = new Uint8Array(out1.length + 1);
|
|
61
|
+
out2Input.set(out1, 0);
|
|
62
|
+
out2Input[out1.length] = 0x02;
|
|
63
|
+
const out2 = hmacSHA256(prk, out2Input);
|
|
64
|
+
|
|
65
|
+
const out3Input = new Uint8Array(out2.length + 1);
|
|
66
|
+
out3Input.set(out2, 0);
|
|
67
|
+
out3Input[out2.length] = 0x03;
|
|
68
|
+
const out3 = hmacSHA256(prk, out3Input);
|
|
69
|
+
|
|
70
|
+
return [out1, out2, out3];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function chaCha20Poly1305Encrypt(
|
|
74
|
+
plaintext: Uint8Array | Uint8ArrayList,
|
|
75
|
+
nonce: Uint8Array,
|
|
76
|
+
ad: Uint8Array,
|
|
77
|
+
k: Uint8Array,
|
|
78
|
+
): Uint8Array {
|
|
79
|
+
const cipher = QuickCrypto.createCipheriv("chacha20-poly1305", k, nonce, {
|
|
80
|
+
authTagLength: TAG_LENGTH,
|
|
81
|
+
});
|
|
82
|
+
cipher.setAAD(ad);
|
|
83
|
+
const head = cipher.update(toBytes(plaintext));
|
|
84
|
+
const tail = cipher.final();
|
|
85
|
+
const tag = cipher.getAuthTag();
|
|
86
|
+
// Noise wire format: ciphertext || tag (16 bytes).
|
|
87
|
+
const out = new Uint8Array(head.length + tail.length + tag.length);
|
|
88
|
+
out.set(head, 0);
|
|
89
|
+
out.set(tail, head.length);
|
|
90
|
+
out.set(tag, head.length + tail.length);
|
|
91
|
+
return out;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function chaCha20Poly1305Decrypt(
|
|
95
|
+
ciphertext: Uint8Array | Uint8ArrayList,
|
|
96
|
+
nonce: Uint8Array,
|
|
97
|
+
ad: Uint8Array,
|
|
98
|
+
k: Uint8Array,
|
|
99
|
+
): Uint8Array {
|
|
100
|
+
const bytes = toBytes(ciphertext);
|
|
101
|
+
if (bytes.length < TAG_LENGTH) {
|
|
102
|
+
throw new Error("ChaCha20-Poly1305 ciphertext shorter than tag length");
|
|
103
|
+
}
|
|
104
|
+
const ct = bytes.subarray(0, bytes.length - TAG_LENGTH);
|
|
105
|
+
const tag = bytes.subarray(bytes.length - TAG_LENGTH);
|
|
106
|
+
const decipher = QuickCrypto.createDecipheriv("chacha20-poly1305", k, nonce, {
|
|
107
|
+
authTagLength: TAG_LENGTH,
|
|
108
|
+
});
|
|
109
|
+
decipher.setAAD(ad);
|
|
110
|
+
decipher.setAuthTag(tag);
|
|
111
|
+
const head = decipher.update(ct);
|
|
112
|
+
const tail = decipher.final();
|
|
113
|
+
const out = new Uint8Array(head.length + tail.length);
|
|
114
|
+
out.set(head, 0);
|
|
115
|
+
out.set(tail, head.length);
|
|
116
|
+
return out;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
/**
|
|
120
|
+
* Quick-crypto-backed Noise crypto. Inherits synchronous X25519 operations
|
|
121
|
+
* from `pureJsCrypto`; overrides SHA-256, HKDF, and ChaCha20-Poly1305 with
|
|
122
|
+
* JSI-backed implementations.
|
|
123
|
+
*/
|
|
124
|
+
export const quickCryptoNoise: ICryptoInterface = {
|
|
125
|
+
...pureJsCrypto,
|
|
126
|
+
hashSHA256,
|
|
127
|
+
getHKDF,
|
|
128
|
+
chaCha20Poly1305Encrypt,
|
|
129
|
+
chaCha20Poly1305Decrypt,
|
|
130
|
+
};
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
// Ambient module declarations for React Native-only packages that ship without
|
|
2
|
+
// TypeScript types or are not resolvable in the workspace's host toolchain.
|
|
3
|
+
// Real implementations are pulled in by Metro at app build time; here we only
|
|
4
|
+
// need enough surface to let `tsc` type-check `polyfills.ts`.
|
|
5
|
+
|
|
6
|
+
declare module "react-native-get-random-values";
|
|
7
|
+
|
|
8
|
+
declare module "react-native-quick-crypto" {
|
|
9
|
+
export function install(): void;
|
|
10
|
+
|
|
11
|
+
type BinaryLike = Uint8Array;
|
|
12
|
+
|
|
13
|
+
export interface Hash {
|
|
14
|
+
update(data: BinaryLike): Hash;
|
|
15
|
+
digest(): Uint8Array;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export interface Hmac {
|
|
19
|
+
update(data: BinaryLike): Hmac;
|
|
20
|
+
digest(): Uint8Array;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface CipherOptions {
|
|
24
|
+
authTagLength?: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface Cipher {
|
|
28
|
+
setAAD(buffer: BinaryLike): Cipher;
|
|
29
|
+
update(data: BinaryLike): Uint8Array;
|
|
30
|
+
final(): Uint8Array;
|
|
31
|
+
getAuthTag(): Uint8Array;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface Decipher {
|
|
35
|
+
setAAD(buffer: BinaryLike): Decipher;
|
|
36
|
+
setAuthTag(tag: BinaryLike): Decipher;
|
|
37
|
+
update(data: BinaryLike): Uint8Array;
|
|
38
|
+
final(): Uint8Array;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function createHash(algorithm: "sha256"): Hash;
|
|
42
|
+
export function createHmac(algorithm: "sha256", key: BinaryLike): Hmac;
|
|
43
|
+
export function createCipheriv(
|
|
44
|
+
algorithm: "chacha20-poly1305",
|
|
45
|
+
key: BinaryLike,
|
|
46
|
+
iv: BinaryLike,
|
|
47
|
+
options?: CipherOptions,
|
|
48
|
+
): Cipher;
|
|
49
|
+
export function createDecipheriv(
|
|
50
|
+
algorithm: "chacha20-poly1305",
|
|
51
|
+
key: BinaryLike,
|
|
52
|
+
iv: BinaryLike,
|
|
53
|
+
options?: CipherOptions,
|
|
54
|
+
): Decipher;
|
|
55
|
+
|
|
56
|
+
const QuickCrypto: {
|
|
57
|
+
install: typeof install;
|
|
58
|
+
createHash: typeof createHash;
|
|
59
|
+
createHmac: typeof createHmac;
|
|
60
|
+
createCipheriv: typeof createCipheriv;
|
|
61
|
+
createDecipheriv: typeof createDecipheriv;
|
|
62
|
+
};
|
|
63
|
+
export default QuickCrypto;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
declare module "react-native-webrtc" {
|
|
67
|
+
export function registerGlobals(): void;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
declare module "react-native" {
|
|
71
|
+
export const NativeModules: Record<string, unknown>;
|
|
72
|
+
|
|
73
|
+
export interface NativeEventSubscription {
|
|
74
|
+
remove(): void;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export class NativeEventEmitter {
|
|
78
|
+
constructor(nativeModule?: unknown);
|
|
79
|
+
addListener(
|
|
80
|
+
eventType: string,
|
|
81
|
+
listener: (event: unknown) => void,
|
|
82
|
+
): NativeEventSubscription;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
declare module "buffer" {
|
|
87
|
+
export const Buffer: {
|
|
88
|
+
from(...args: readonly unknown[]): unknown;
|
|
89
|
+
alloc(...args: readonly unknown[]): unknown;
|
|
90
|
+
isBuffer(value: unknown): boolean;
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
declare module "process" {
|
|
95
|
+
const process: {
|
|
96
|
+
nextTick(callback: () => void): void;
|
|
97
|
+
env: Record<string, string | undefined>;
|
|
98
|
+
};
|
|
99
|
+
export default process;
|
|
100
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A `TextEncoder` / `TextDecoder` polyfill for the React Native Hermes engine.
|
|
3
|
+
*
|
|
4
|
+
* Hermes ships neither global, yet libp2p reaches for them at
|
|
5
|
+
* module-evaluation time: `uint8arrays` (a transitive dependency of nearly
|
|
6
|
+
* every libp2p package) constructs `new TextEncoder()` / `new TextDecoder()`
|
|
7
|
+
* to convert between UTF-8 strings and byte arrays for peer ids, multiaddrs,
|
|
8
|
+
* and protocol names. Without these globals the bundle red-screens with
|
|
9
|
+
* `Property 'TextDecoder' doesn't exist` before any application code runs.
|
|
10
|
+
*
|
|
11
|
+
* Only UTF-8 is implemented — the single encoding libp2p uses through these
|
|
12
|
+
* APIs. The conversion is delegated to `Buffer` (already a required shim, see
|
|
13
|
+
* `polyfills`), which provides a spec-correct UTF-8 implementation including
|
|
14
|
+
* surrogate-pair handling and replacement of malformed sequences. Owning a
|
|
15
|
+
* thin wrapper here, rather than pulling in a third-party text-encoding
|
|
16
|
+
* package, keeps the polyfill surface small and dependency-free.
|
|
17
|
+
*/
|
|
18
|
+
|
|
19
|
+
import { Buffer } from "buffer";
|
|
20
|
+
|
|
21
|
+
const UTF8_ENCODING = "utf-8";
|
|
22
|
+
|
|
23
|
+
// The WHATWG labels that all denote UTF-8. A `TextDecoder` constructed with any
|
|
24
|
+
// other label must throw, matching browser behavior and surfacing accidental
|
|
25
|
+
// non-UTF-8 usage rather than silently misdecoding.
|
|
26
|
+
const UTF8_LABELS: ReadonlySet<string> = new Set([
|
|
27
|
+
"utf-8",
|
|
28
|
+
"utf8",
|
|
29
|
+
"unicode-1-1-utf-8",
|
|
30
|
+
]);
|
|
31
|
+
|
|
32
|
+
interface TextDecoderOptionsLike {
|
|
33
|
+
readonly fatal?: boolean;
|
|
34
|
+
readonly ignoreBOM?: boolean;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// A `Buffer` is a `Uint8Array` whose `toString` accepts an encoding argument.
|
|
38
|
+
// The package's ambient `buffer` declaration (see `rn-modules.d.ts`) types
|
|
39
|
+
// `from` loosely as returning `unknown`; this narrows it to just the surface
|
|
40
|
+
// the codec needs without widening that shared shim.
|
|
41
|
+
interface Utf8Buffer extends Uint8Array {
|
|
42
|
+
toString(encoding?: string): string;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
interface BufferFactory {
|
|
46
|
+
from(value: string, encoding: string): Utf8Buffer;
|
|
47
|
+
from(
|
|
48
|
+
value: ArrayBufferLike,
|
|
49
|
+
byteOffset?: number,
|
|
50
|
+
length?: number,
|
|
51
|
+
): Utf8Buffer;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
const bufferFactory = Buffer as unknown as BufferFactory;
|
|
55
|
+
|
|
56
|
+
/** WHATWG `TextEncoder`, UTF-8 only. */
|
|
57
|
+
class PolyfillTextEncoder {
|
|
58
|
+
get encoding(): string {
|
|
59
|
+
return UTF8_ENCODING;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
encode(input: string = ""): Uint8Array {
|
|
63
|
+
return new Uint8Array(bufferFactory.from(input, "utf8"));
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* WHATWG `TextDecoder`, UTF-8 only.
|
|
69
|
+
*
|
|
70
|
+
* `fatal` and `ignoreBOM` are accepted and reflected as readonly properties for
|
|
71
|
+
* API-shape compatibility, but not honored: `Buffer.toString('utf8')` always
|
|
72
|
+
* replaces malformed sequences (`fatal: false`) and never strips a BOM. libp2p
|
|
73
|
+
* constructs decoders with default options, so this is sufficient — a caller
|
|
74
|
+
* relying on `fatal`/`ignoreBOM` semantics is out of this polyfill's scope.
|
|
75
|
+
*/
|
|
76
|
+
class PolyfillTextDecoder {
|
|
77
|
+
readonly encoding = UTF8_ENCODING;
|
|
78
|
+
readonly fatal: boolean;
|
|
79
|
+
readonly ignoreBOM: boolean;
|
|
80
|
+
|
|
81
|
+
constructor(
|
|
82
|
+
label: string = UTF8_ENCODING,
|
|
83
|
+
options: TextDecoderOptionsLike = {},
|
|
84
|
+
) {
|
|
85
|
+
if (!UTF8_LABELS.has(label.toLowerCase())) {
|
|
86
|
+
throw new RangeError(
|
|
87
|
+
`Unsupported encoding: ${label}. Only UTF-8 is supported.`,
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
this.fatal = options.fatal ?? false;
|
|
91
|
+
this.ignoreBOM = options.ignoreBOM ?? false;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
decode(input?: ArrayBuffer | ArrayBufferView): string {
|
|
95
|
+
if (input == null) {
|
|
96
|
+
return "";
|
|
97
|
+
}
|
|
98
|
+
const bytes = ArrayBuffer.isView(input)
|
|
99
|
+
? bufferFactory.from(input.buffer, input.byteOffset, input.byteLength)
|
|
100
|
+
: bufferFactory.from(input);
|
|
101
|
+
return bytes.toString("utf8");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Install `TextEncoder` and `TextDecoder` on the global scope if the runtime
|
|
107
|
+
* does not already provide them. Idempotent and safe to call more than once;
|
|
108
|
+
* on a runtime that ships its own implementations (e.g. Node in tests) this is
|
|
109
|
+
* a no-op.
|
|
110
|
+
*/
|
|
111
|
+
export function installTextCodecPolyfill(): void {
|
|
112
|
+
const codecScope = globalThis as {
|
|
113
|
+
TextEncoder?: unknown;
|
|
114
|
+
TextDecoder?: unknown;
|
|
115
|
+
};
|
|
116
|
+
codecScope.TextEncoder ??= PolyfillTextEncoder;
|
|
117
|
+
codecScope.TextDecoder ??= PolyfillTextDecoder;
|
|
118
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Give React Native's `RTCPeerConnection` the static `generateCertificate`
|
|
3
|
+
* method that `@libp2p/webrtc`'s webrtc-direct dialer requires.
|
|
4
|
+
*
|
|
5
|
+
* Metro selects the browser build of `@libp2p/webrtc` on React Native. Its
|
|
6
|
+
* dialer creates the peer connection like this:
|
|
7
|
+
*
|
|
8
|
+
* ```js
|
|
9
|
+
* const certificate = await RTCPeerConnection.generateCertificate({
|
|
10
|
+
* name: "ECDSA",
|
|
11
|
+
* namedCurve: "P-256",
|
|
12
|
+
* });
|
|
13
|
+
* new RTCPeerConnection({ certificates: [certificate] });
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* `react-native-webrtc` (v124) implements neither the static
|
|
17
|
+
* `generateCertificate` method nor the `certificates` constructor option — it
|
|
18
|
+
* always generates its own DTLS certificate natively — so the dialer throws
|
|
19
|
+
* `RTCPeerConnection.generateCertificate is not a function`.
|
|
20
|
+
*
|
|
21
|
+
* Supplying a specific certificate is unnecessary for webrtc-direct. The dialer
|
|
22
|
+
* reads its own fingerprint back out of the generated local SDP
|
|
23
|
+
* (`getFingerprintFromSdp(peerConnection.localDescription.sdp)`) and binds it
|
|
24
|
+
* into the Noise prologue, so the certificate the native layer produces is the
|
|
25
|
+
* one that actually gets authenticated. We only need the call to resolve: the
|
|
26
|
+
* returned value is forwarded as the `certificates` option, which
|
|
27
|
+
* `react-native-webrtc` does not read, so a plain placeholder object that the
|
|
28
|
+
* native bridge can serialize is sufficient.
|
|
29
|
+
*/
|
|
30
|
+
|
|
31
|
+
interface CertificateCapablePeerConnection {
|
|
32
|
+
generateCertificate?: (algorithm?: unknown) => Promise<unknown>;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
const CERTIFICATE_TTL_MS = 365 * 24 * 60 * 60 * 1000;
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Install the static `generateCertificate` method on the global
|
|
39
|
+
* `RTCPeerConnection`. Idempotent, and a no-op when the global is absent
|
|
40
|
+
* (`registerGlobals()` has not run, or under tests) or already provides the
|
|
41
|
+
* method (a browser, or a future `react-native-webrtc` release).
|
|
42
|
+
*/
|
|
43
|
+
export function installWebRtcCertificatePolyfill(): void {
|
|
44
|
+
const scope = globalThis as {
|
|
45
|
+
RTCPeerConnection?: CertificateCapablePeerConnection;
|
|
46
|
+
};
|
|
47
|
+
const peerConnection = scope.RTCPeerConnection;
|
|
48
|
+
if (peerConnection == null) {
|
|
49
|
+
return;
|
|
50
|
+
}
|
|
51
|
+
if (typeof peerConnection.generateCertificate === "function") {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
peerConnection.generateCertificate = (): Promise<unknown> => {
|
|
55
|
+
// react-native-webrtc generates its own DTLS certificate natively; libp2p
|
|
56
|
+
// reads the resulting fingerprint from the SDP rather than from this
|
|
57
|
+
// object, so a serializable placeholder is enough to satisfy the dialer.
|
|
58
|
+
return Promise.resolve({ expires: Date.now() + CERTIFICATE_TTL_MS });
|
|
59
|
+
};
|
|
60
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Make React Native's `WebSocket` report a numeric `bufferedAmount`.
|
|
3
|
+
*
|
|
4
|
+
* React Native declares `bufferedAmount` on its `WebSocket` but never assigns
|
|
5
|
+
* it, so it reads as `undefined`. `@libp2p/websockets` drives write backpressure
|
|
6
|
+
* off that value, and the `undefined` comparisons leave every write parked
|
|
7
|
+
* waiting for a `drain` that never fires, hanging the connection.
|
|
8
|
+
*
|
|
9
|
+
* React Native hands every frame straight to the native socket with no
|
|
10
|
+
* JS-visible send buffer to measure, so `0` ("fully flushed") is the correct,
|
|
11
|
+
* backpressure-free answer. A prototype accessor returns `0` for reads and
|
|
12
|
+
* swallows React Native's `undefined` field initialization with a no-op setter,
|
|
13
|
+
* so no shadowing own property is left on the instance.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
interface PrototypeHost {
|
|
17
|
+
readonly prototype?: object;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Install the `bufferedAmount` accessor on the global `WebSocket` prototype.
|
|
22
|
+
* Idempotent, and a no-op when the runtime already exposes a real getter (e.g.
|
|
23
|
+
* a browser or Node polyfill) or provides no `WebSocket` at all (e.g. tests).
|
|
24
|
+
*/
|
|
25
|
+
export function installWebSocketBufferedAmountPolyfill(): void {
|
|
26
|
+
const scope = globalThis as { WebSocket?: PrototypeHost };
|
|
27
|
+
const prototype = scope.WebSocket?.prototype;
|
|
28
|
+
if (prototype == null) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
const existing = Object.getOwnPropertyDescriptor(prototype, "bufferedAmount");
|
|
32
|
+
if (existing?.get != null) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
Object.defineProperty(prototype, "bufferedAmount", {
|
|
36
|
+
configurable: true,
|
|
37
|
+
get(): number {
|
|
38
|
+
return 0;
|
|
39
|
+
},
|
|
40
|
+
set(): void {
|
|
41
|
+
// Swallow React Native's `undefined` field initialization so it cannot
|
|
42
|
+
// create an own property that shadows this accessor.
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
}
|