smoldot 0.3.6 → 0.7.11

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.
Files changed (74) hide show
  1. package/README.md +18 -21
  2. package/dist/cjs/base64.d.ts +22 -0
  3. package/dist/cjs/base64.js +104 -0
  4. package/dist/cjs/client.d.ts +287 -0
  5. package/dist/cjs/client.js +175 -0
  6. package/dist/cjs/index-browser.d.ts +11 -0
  7. package/dist/cjs/index-browser.js +465 -0
  8. package/dist/cjs/index-deno.d.ts +10 -0
  9. package/dist/cjs/index-deno.js +263 -0
  10. package/dist/cjs/index-nodejs.d.ts +10 -0
  11. package/dist/cjs/index-nodejs.js +174 -0
  12. package/dist/cjs/instance/autogen/wasm.d.ts +2 -0
  13. package/dist/cjs/instance/autogen/wasm.js +6 -0
  14. package/dist/cjs/instance/autogen/wasm0.d.ts +1 -0
  15. package/dist/cjs/instance/autogen/wasm0.js +4 -0
  16. package/dist/cjs/instance/autogen/wasm1.d.ts +1 -0
  17. package/dist/cjs/instance/autogen/wasm1.js +4 -0
  18. package/dist/cjs/instance/autogen/wasm2.d.ts +1 -0
  19. package/dist/cjs/instance/autogen/wasm2.js +4 -0
  20. package/dist/cjs/instance/bindings-smoldot-light.d.ts +154 -0
  21. package/dist/cjs/instance/bindings-smoldot-light.js +266 -0
  22. package/dist/cjs/instance/bindings-wasi.d.ts +23 -0
  23. package/dist/cjs/instance/bindings-wasi.js +142 -0
  24. package/dist/cjs/instance/bindings.d.ts +31 -0
  25. package/dist/cjs/instance/bindings.js +5 -0
  26. package/dist/cjs/instance/buffer.d.ts +10 -0
  27. package/dist/cjs/instance/buffer.js +53 -0
  28. package/dist/cjs/instance/instance.d.ts +45 -0
  29. package/dist/cjs/instance/instance.js +301 -0
  30. package/dist/cjs/instance/raw-instance.d.ts +65 -0
  31. package/dist/cjs/instance/raw-instance.js +72 -0
  32. package/dist/cjs/package.json +3 -0
  33. package/dist/mjs/base64.d.ts +22 -0
  34. package/dist/mjs/base64.js +97 -0
  35. package/dist/mjs/client.d.ts +287 -0
  36. package/dist/mjs/client.js +165 -0
  37. package/dist/mjs/index-browser.d.ts +11 -0
  38. package/dist/mjs/index-browser.js +455 -0
  39. package/dist/mjs/index-deno.d.ts +10 -0
  40. package/dist/mjs/index-deno.js +253 -0
  41. package/dist/mjs/index-nodejs.d.ts +10 -0
  42. package/dist/mjs/index-nodejs.js +164 -0
  43. package/dist/mjs/instance/autogen/wasm.d.ts +2 -0
  44. package/dist/mjs/instance/autogen/wasm.js +4 -0
  45. package/dist/mjs/instance/autogen/wasm0.d.ts +1 -0
  46. package/dist/mjs/instance/autogen/wasm0.js +1 -0
  47. package/dist/mjs/instance/autogen/wasm1.d.ts +1 -0
  48. package/dist/mjs/instance/autogen/wasm1.js +1 -0
  49. package/dist/mjs/instance/autogen/wasm2.d.ts +1 -0
  50. package/dist/mjs/instance/autogen/wasm2.js +1 -0
  51. package/dist/mjs/instance/bindings-smoldot-light.d.ts +154 -0
  52. package/dist/mjs/instance/bindings-smoldot-light.js +261 -0
  53. package/dist/mjs/instance/bindings-wasi.d.ts +23 -0
  54. package/{src → dist/mjs/instance}/bindings-wasi.js +47 -58
  55. package/dist/mjs/instance/bindings.d.ts +31 -0
  56. package/dist/mjs/instance/bindings.js +4 -0
  57. package/dist/mjs/instance/buffer.d.ts +10 -0
  58. package/dist/mjs/instance/buffer.js +46 -0
  59. package/dist/mjs/instance/instance.d.ts +45 -0
  60. package/dist/mjs/instance/instance.js +293 -0
  61. package/dist/mjs/instance/raw-instance.d.ts +65 -0
  62. package/dist/mjs/instance/raw-instance.js +67 -0
  63. package/dist/mjs/package.json +3 -0
  64. package/package.json +35 -19
  65. package/src/autogen/wasm.js +0 -1
  66. package/src/bindings-smoldot-js.js +0 -223
  67. package/src/compat-browser.js +0 -25
  68. package/src/compat-nodejs.js +0 -30
  69. package/src/health.js +0 -206
  70. package/src/index.d.ts +0 -68
  71. package/src/index.js +0 -192
  72. package/src/index.test-d.ts +0 -36
  73. package/src/tsconfig.json +0 -13
  74. package/src/worker.js +0 -151
@@ -0,0 +1,465 @@
1
+ "use strict";
2
+ // Smoldot
3
+ // Copyright (C) 2019-2022 Parity Technologies (UK) Ltd.
4
+ // SPDX-License-Identifier: GPL-3.0-or-later WITH Classpath-exception-2.0
5
+ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
6
+ function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
7
+ return new (P || (P = Promise))(function (resolve, reject) {
8
+ function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
9
+ function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
10
+ function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
11
+ step((generator = generator.apply(thisArg, _arguments || [])).next());
12
+ });
13
+ };
14
+ Object.defineProperty(exports, "__esModule", { value: true });
15
+ exports.start = exports.QueueFullError = exports.MalformedJsonRpcError = exports.JsonRpcDisabledError = exports.CrashError = exports.AlreadyDestroyedError = exports.AddChainError = void 0;
16
+ // This program is free software: you can redistribute it and/or modify
17
+ // it under the terms of the GNU General Public License as published by
18
+ // the Free Software Foundation, either version 3 of the License, or
19
+ // (at your option) any later version.
20
+ // This program is distributed in the hope that it will be useful,
21
+ // but WITHOUT ANY WARRANTY; without even the implied warranty of
22
+ // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
23
+ // GNU General Public License for more details.
24
+ // You should have received a copy of the GNU General Public License
25
+ // along with this program. If not, see <http://www.gnu.org/licenses/>.
26
+ /// <reference lib="dom" />
27
+ const client_js_1 = require("./client.js");
28
+ const instance_js_1 = require("./instance/instance.js");
29
+ const base64_js_1 = require("./base64.js");
30
+ const pako_1 = require("pako");
31
+ var client_js_2 = require("./client.js");
32
+ Object.defineProperty(exports, "AddChainError", { enumerable: true, get: function () { return client_js_2.AddChainError; } });
33
+ Object.defineProperty(exports, "AlreadyDestroyedError", { enumerable: true, get: function () { return client_js_2.AlreadyDestroyedError; } });
34
+ Object.defineProperty(exports, "CrashError", { enumerable: true, get: function () { return client_js_2.CrashError; } });
35
+ Object.defineProperty(exports, "JsonRpcDisabledError", { enumerable: true, get: function () { return client_js_2.JsonRpcDisabledError; } });
36
+ Object.defineProperty(exports, "MalformedJsonRpcError", { enumerable: true, get: function () { return client_js_2.MalformedJsonRpcError; } });
37
+ Object.defineProperty(exports, "QueueFullError", { enumerable: true, get: function () { return client_js_2.QueueFullError; } });
38
+ /**
39
+ * Initializes a new client. This is a pre-requisite to connecting to a blockchain.
40
+ *
41
+ * Can never fail.
42
+ *
43
+ * @param options Configuration of the client. Defaults to `{}`.
44
+ */
45
+ function start(options) {
46
+ options = options || {};
47
+ return (0, client_js_1.start)(options, {
48
+ trustedBase64DecodeAndZlibInflate: (input) => {
49
+ return Promise.resolve((0, pako_1.inflate)((0, base64_js_1.classicDecode)(input)));
50
+ },
51
+ registerShouldPeriodicallyYield: (callback) => {
52
+ const wrappedCallback = () => callback(document.visibilityState === 'visible');
53
+ document.addEventListener('visibilitychange', wrappedCallback);
54
+ return [document.visibilityState === 'visible', () => { document.removeEventListener('visibilitychange', wrappedCallback); }];
55
+ },
56
+ performanceNow: () => {
57
+ return performance.now();
58
+ },
59
+ getRandomValues: (buffer) => {
60
+ const crypto = globalThis.crypto;
61
+ if (!crypto)
62
+ throw new Error('randomness not available');
63
+ crypto.getRandomValues(buffer);
64
+ },
65
+ connect: (config) => {
66
+ return connect(config, (options === null || options === void 0 ? void 0 : options.forbidWs) || false, (options === null || options === void 0 ? void 0 : options.forbidNonLocalWs) || false, (options === null || options === void 0 ? void 0 : options.forbidWss) || false, (options === null || options === void 0 ? void 0 : options.forbidWebRtc) || false);
67
+ }
68
+ });
69
+ }
70
+ exports.start = start;
71
+ /**
72
+ * Tries to open a new connection using the given configuration.
73
+ *
74
+ * @see Connection
75
+ * @throws {@link ConnectionError} If the multiaddress couldn't be parsed or contains an invalid protocol.
76
+ */
77
+ function connect(config, forbidWs, forbidNonLocalWs, forbidWss, forbidWebRTC) {
78
+ // Attempt to parse the multiaddress.
79
+ // TODO: remove support for `/wss` in a long time (https://github.com/paritytech/smoldot/issues/1940)
80
+ const wsParsed = config.address.match(/^\/(ip4|ip6|dns4|dns6|dns)\/(.*?)\/tcp\/(.*?)\/(ws|wss|tls\/ws)$/);
81
+ const webRTCParsed = config.address.match(/^\/(ip4|ip6)\/(.*?)\/udp\/(.*?)\/webrtc\/certhash\/(.*?)$/);
82
+ if (wsParsed != null) {
83
+ let connection;
84
+ const proto = (wsParsed[4] == 'ws') ? 'ws' : 'wss';
85
+ if ((proto == 'ws' && forbidWs) ||
86
+ (proto == 'ws' && wsParsed[2] != 'localhost' && wsParsed[2] != '127.0.0.1' && forbidNonLocalWs) ||
87
+ (proto == 'wss' && forbidWss)) {
88
+ throw new instance_js_1.ConnectionError('Connection type not allowed');
89
+ }
90
+ const url = (wsParsed[1] == 'ip6') ?
91
+ (proto + "://[" + wsParsed[2] + "]:" + wsParsed[3]) :
92
+ (proto + "://" + wsParsed[2] + ":" + wsParsed[3]);
93
+ connection = new WebSocket(url);
94
+ connection.binaryType = 'arraybuffer';
95
+ connection.onopen = () => {
96
+ config.onOpen({ type: 'single-stream', handshake: 'multistream-select-noise-yamux' });
97
+ };
98
+ connection.onclose = (event) => {
99
+ const message = "Error code " + event.code + (!!event.reason ? (": " + event.reason) : "");
100
+ config.onConnectionReset(message);
101
+ };
102
+ connection.onmessage = (msg) => {
103
+ config.onMessage(new Uint8Array(msg.data));
104
+ };
105
+ return {
106
+ reset: () => {
107
+ connection.onopen = null;
108
+ connection.onclose = null;
109
+ connection.onmessage = null;
110
+ connection.onerror = null;
111
+ connection.close();
112
+ },
113
+ send: (data) => {
114
+ connection.send(data);
115
+ },
116
+ openOutSubstream: () => { throw new Error('Wrong connection type'); }
117
+ };
118
+ }
119
+ else if (webRTCParsed != null) {
120
+ const targetPort = webRTCParsed[3];
121
+ if (forbidWebRTC || targetPort === '0') {
122
+ throw new instance_js_1.ConnectionError('Connection type not allowed');
123
+ }
124
+ const ipVersion = webRTCParsed[1] == 'ip4' ? '4' : '6';
125
+ const targetIp = webRTCParsed[2];
126
+ const remoteCertMultibase = webRTCParsed[4];
127
+ // The payload of `/certhash` is the hash of the self-generated certificate that the
128
+ // server presents.
129
+ // This function throws an exception if the certhash isn't correct. For this reason, this call
130
+ // is performed as part of the parsing of the multiaddr.
131
+ const remoteCertMultihash = (0, base64_js_1.multibaseBase64Decode)(remoteCertMultibase);
132
+ const remoteCertSha256Hash = multihashToSha256(remoteCertMultihash);
133
+ // TODO: detect localhost for Firefox? https://bugzilla.mozilla.org/show_bug.cgi?id=1659672
134
+ // Note that `pc` can be the connection, but also null or undefined.
135
+ // `undefined` means "certificate generation in progress", while `null` means "opening must
136
+ // be cancelled".
137
+ // While it would be better to use for example a string instead of `null`, using `null` lets
138
+ // us use the `!` operator more easily and leads to more readable code.
139
+ let pc = undefined;
140
+ // Contains the data channels that are open and have been reported to smoldot.
141
+ const dataChannels = new Map();
142
+ // For various reasons explained below, we open a data channel in advance without reporting it
143
+ // to smoldot. This data channel is stored in this variable. Once it is reported to smoldot,
144
+ // it is inserted in `dataChannels`.
145
+ let handshakeDataChannel;
146
+ // Multihash-encoded DTLS certificate of the local node. Unknown as long as it hasn't been
147
+ // generated.
148
+ // TODO: could be merged with `pc` in one variable, and maybe even the other fields as well
149
+ let localTlsCertificateMultihash;
150
+ // Kills all the JavaScript objects (the connection and all its substreams), ensuring that no
151
+ // callback will be called again. Doesn't report anything to smoldot, as this should be done
152
+ // by the caller.
153
+ const killAllJs = () => {
154
+ // The `RTCPeerConnection` is created pretty quickly. It is however still possible for
155
+ // smoldot to cancel the opening, in which case `pc` will still be undefined.
156
+ if (!pc) {
157
+ console.assert(dataChannels.size === 0 && !handshakeDataChannel, "substreams exist while pc is undef");
158
+ pc = null;
159
+ return;
160
+ }
161
+ pc.onconnectionstatechange = null;
162
+ pc.onnegotiationneeded = null;
163
+ pc.ondatachannel = null;
164
+ for (const channel of Array.from(dataChannels.values())) {
165
+ channel.onopen = null;
166
+ channel.onerror = null;
167
+ channel.onclose = null;
168
+ channel.onmessage = null;
169
+ }
170
+ dataChannels.clear();
171
+ if (handshakeDataChannel) {
172
+ handshakeDataChannel.onopen = null;
173
+ handshakeDataChannel.onerror = null;
174
+ handshakeDataChannel.onclose = null;
175
+ handshakeDataChannel.onmessage = null;
176
+ }
177
+ handshakeDataChannel = undefined;
178
+ pc.close(); // Not necessarily necessary, but it doesn't hurt to do so.
179
+ };
180
+ // Function that configures a newly-opened channel and adds it to the map. Used for both
181
+ // inbound and outbound substreams.
182
+ const addChannel = (dataChannel, direction) => {
183
+ const dataChannelId = dataChannel.id;
184
+ dataChannel.binaryType = 'arraybuffer';
185
+ let isOpen = false;
186
+ dataChannel.onopen = () => {
187
+ console.assert(!isOpen, "substream opened twice");
188
+ isOpen = true;
189
+ if (direction === 'first-outbound') {
190
+ console.assert(dataChannels.size === 0, "dataChannels not empty when opening");
191
+ console.assert(handshakeDataChannel === dataChannel, "handshake substream mismatch");
192
+ config.onOpen({
193
+ type: 'multi-stream',
194
+ handshake: 'webrtc',
195
+ // `addChannel` can never be called before the local certificate is generated, so this
196
+ // value is always defined.
197
+ localTlsCertificateMultihash: localTlsCertificateMultihash,
198
+ remoteTlsCertificateMultihash: remoteCertMultihash
199
+ });
200
+ }
201
+ else {
202
+ console.assert(direction !== 'outbound' || !handshakeDataChannel, "handshakeDataChannel still defined");
203
+ config.onStreamOpened(dataChannelId, direction);
204
+ }
205
+ };
206
+ dataChannel.onerror = dataChannel.onclose = (_error) => {
207
+ // A couple of different things could be happening here.
208
+ if (handshakeDataChannel === dataChannel && !isOpen) {
209
+ // The handshake data channel that we have opened ahead of time failed to open. As this
210
+ // happens before we have reported the WebRTC connection as a whole as being open, we
211
+ // need to report that the connection has failed to open.
212
+ killAllJs();
213
+ // Note that the event doesn't give any additional reason for the failure.
214
+ config.onConnectionReset("handshake data channel failed to open");
215
+ }
216
+ else if (handshakeDataChannel === dataChannel) {
217
+ // The handshake data channel has been closed before we reported it to smoldot. This
218
+ // isn't really a problem. We just update the state and continue running. If smoldot
219
+ // requests a substream, another one will be opened. It could be a valid implementation
220
+ // to also just kill the entire connection, however doing so is a bit too intrusive and
221
+ // punches through abstraction layers.
222
+ handshakeDataChannel.onopen = null;
223
+ handshakeDataChannel.onerror = null;
224
+ handshakeDataChannel.onclose = null;
225
+ handshakeDataChannel.onmessage = null;
226
+ handshakeDataChannel = undefined;
227
+ }
228
+ else if (!isOpen) {
229
+ // Substream wasn't opened yet and thus has failed to open. The API has no mechanism to
230
+ // report substream openings failures. We could try opening it again, but given that
231
+ // it's unlikely to succeed, we simply opt to kill the entire connection.
232
+ killAllJs();
233
+ // Note that the event doesn't give any additional reason for the failure.
234
+ config.onConnectionReset("data channel failed to open");
235
+ }
236
+ else {
237
+ // Substream was open and is now closed. Normal situation.
238
+ config.onStreamReset(dataChannelId);
239
+ }
240
+ };
241
+ dataChannel.onmessage = (m) => {
242
+ // The `data` field is an `ArrayBuffer`.
243
+ config.onMessage(new Uint8Array(m.data), dataChannelId);
244
+ };
245
+ if (direction !== 'first-outbound')
246
+ dataChannels.set(dataChannelId, dataChannel);
247
+ else
248
+ handshakeDataChannel = dataChannel;
249
+ };
250
+ // It is possible for the browser to use multiple different certificates.
251
+ // In order for our local certificate to be deterministic, we need to generate it manually and
252
+ // set it explicitly as part of the configuration.
253
+ // According to <https://w3c.github.io/webrtc-pc/#dom-rtcpeerconnection-generatecertificate>,
254
+ // browsers are guaranteed to support `{ name: "ECDSA", namedCurve: "P-256" }`.
255
+ RTCPeerConnection.generateCertificate({ name: "ECDSA", namedCurve: "P-256", hash: "SHA-256" }).then((localCertificate) => __awaiter(this, void 0, void 0, function* () {
256
+ if (pc === null)
257
+ return;
258
+ // Create a new WebRTC connection.
259
+ pc = new RTCPeerConnection({ certificates: [localCertificate] });
260
+ // We need to build the multihash corresponding to the local certificate.
261
+ // While there exists a `RTCPeerConnection.getFingerprints` function, Firefox notably
262
+ // doesn't support it.
263
+ // See <https://developer.mozilla.org/en-US/docs/Web/API/RTCCertificate#browser_compatibility>
264
+ // An alternative to `getFingerprints` is to ask the browser to generate an SDP offer and
265
+ // extract from fingerprint from it. Because we explicitly provide a certificate, we have
266
+ // the guarantee that the list of certificates will always be the same whenever an SDP offer
267
+ // is generated by the browser. However, while this alternative does work on Firefox, it
268
+ // doesn't on Chrome, as the SDP offer is for some reason missing the fingerprints.
269
+ // Therefore, our strategy is to use `getFingerprints` when it is available (i.e. every
270
+ // browser except Firefox), and parse the SDP offer when it is not (i.e. Firefox). In the
271
+ // future, only `getFingerprints` would be used.
272
+ let localTlsCertificateHex;
273
+ if (localCertificate.getFingerprints) {
274
+ for (const { algorithm, value } of localCertificate.getFingerprints()) {
275
+ if (algorithm === 'sha-256') {
276
+ localTlsCertificateHex = value;
277
+ break;
278
+ }
279
+ }
280
+ }
281
+ else {
282
+ const localSdpOffer = yield pc.createOffer();
283
+ // Note that this regex is not strict. The browser isn't a malicious actor, and the
284
+ // objective of this regex is not to detect invalid input.
285
+ const localSdpOfferFingerprintMatch = localSdpOffer.sdp.match(/a(\s*)=(\s*)fingerprint:(\s*)(sha|SHA)-256(\s*)(([a-fA-F0-9]{2}(:)*){32})/);
286
+ if (localSdpOfferFingerprintMatch) {
287
+ localTlsCertificateHex = localSdpOfferFingerprintMatch[6];
288
+ }
289
+ }
290
+ if (localTlsCertificateHex === undefined) {
291
+ // Because we've already returned from the `connect` function at this point, we pretend
292
+ // that the connection has failed to open.
293
+ config.onConnectionReset('Failed to obtain the browser certificate fingerprint');
294
+ return;
295
+ }
296
+ localTlsCertificateMultihash = new Uint8Array(34);
297
+ localTlsCertificateMultihash.set([0x12, 32], 0);
298
+ localTlsCertificateMultihash.set(localTlsCertificateHex.split(':').map((s) => parseInt(s, 16)), 2);
299
+ // `onconnectionstatechange` is used to detect when the connection has closed or has failed
300
+ // to open.
301
+ // Note that smoldot will think that the connection is open even when it is still opening.
302
+ // Therefore we don't care about events concerning the fact that the connection is now fully
303
+ // open.
304
+ pc.onconnectionstatechange = (_event) => {
305
+ if (pc.connectionState == "closed" || pc.connectionState == "disconnected" || pc.connectionState == "failed") {
306
+ killAllJs();
307
+ config.onConnectionReset("WebRTC state transitioned to " + pc.connectionState);
308
+ }
309
+ };
310
+ pc.onnegotiationneeded = (_event) => __awaiter(this, void 0, void 0, function* () {
311
+ var _a;
312
+ // Create a new offer and set it as local description.
313
+ let sdpOffer = (yield pc.createOffer()).sdp;
314
+ // We check that the locally-generated SDP offer has a data channel with the UDP
315
+ // protocol. If that isn't the case, the connection will likely fail.
316
+ if (sdpOffer.match(/^m=application(\s+)(\d+)(\s+)UDP\/DTLS\/SCTP(\s+)webrtc-datachannel$/m) === null) {
317
+ console.error("Local offer doesn't contain UDP data channel. WebRTC connections will likely fail. Please report this issue.");
318
+ }
319
+ // According to the libp2p WebRTC spec, the ufrag and pwd are the same
320
+ // randomly-generated string on both sides, and must be prefixed with
321
+ // `libp2p-webrtc-v1:`. We modify the local description to ensure that.
322
+ // While we could randomly generate a new string, we just grab the one that the
323
+ // browser has generated, in order to make sure that it respects the constraints
324
+ // of the ICE protocol.
325
+ const browserGeneratedPwd = (_a = sdpOffer.match(/^a=ice-pwd:(.+)$/m)) === null || _a === void 0 ? void 0 : _a.at(1);
326
+ if (browserGeneratedPwd === undefined) {
327
+ console.error("Failed to set ufrag to pwd. WebRTC connections will likely fail. Please report this issue.");
328
+ }
329
+ const ufragPwd = "libp2p+webrtc+v1/" + browserGeneratedPwd;
330
+ sdpOffer = sdpOffer.replace(/^a=ice-ufrag.*$/m, 'a=ice-ufrag:' + ufragPwd);
331
+ sdpOffer = sdpOffer.replace(/^a=ice-pwd.*$/m, 'a=ice-pwd:' + ufragPwd);
332
+ yield pc.setLocalDescription({ type: 'offer', sdp: sdpOffer });
333
+ // Transform certificate hash into fingerprint (upper-hex; each byte separated by ":").
334
+ const fingerprint = Array.from(remoteCertSha256Hash).map((n) => ("0" + n.toString(16)).slice(-2).toUpperCase()).join(':');
335
+ // Note that the trailing line feed is important, as otherwise Chrome
336
+ // fails to parse the payload.
337
+ const remoteSdp =
338
+ // Version of the SDP protocol. Always 0. (RFC8866)
339
+ "v=0" + "\n" +
340
+ // Identifies the creator of the SDP document. We are allowed to use dummy values
341
+ // (`-` and `0.0.0.0`) to remain anonymous, which we do. Note that "IN" means
342
+ // "Internet" (and not "input"). (RFC8866)
343
+ "o=- 0 0 IN IP" + ipVersion + " " + targetIp + "\n" +
344
+ // Name for the session. We are allowed to pass a dummy `-`. (RFC8866)
345
+ "s=-" + "\n" +
346
+ // Start and end of the validity of the session. `0 0` means that the session never
347
+ // expires. (RFC8866)
348
+ "t=0 0" + "\n" +
349
+ // A lite implementation is only appropriate for devices that will
350
+ // always be connected to the public Internet and have a public
351
+ // IP address at which it can receive packets from any
352
+ // correspondent. ICE will not function when a lite implementation
353
+ // is placed behind a NAT (RFC8445).
354
+ "a=ice-lite" + "\n" +
355
+ // A `m=` line describes a request to establish a certain protocol.
356
+ // The protocol in this line (i.e. `TCP/DTLS/SCTP` or `UDP/DTLS/SCTP`) must always be
357
+ // the same as the one in the offer. We know that this is true because checked above.
358
+ // The `<fmt>` component must always be `webrtc-datachannel` for WebRTC.
359
+ // The rest of the SDP payload adds attributes to this specific media stream.
360
+ // RFCs: 8839, 8866, 8841
361
+ "m=application " + targetPort + " " + "UDP/DTLS/SCTP webrtc-datachannel" + "\n" +
362
+ // Indicates the IP address of the remote.
363
+ // Note that "IN" means "Internet" (and not "input").
364
+ "c=IN IP" + ipVersion + " " + targetIp + "\n" +
365
+ // Media ID - uniquely identifies this media stream (RFC9143).
366
+ "a=mid:0" + "\n" +
367
+ // Indicates that we are complying with RFC8839 (as oppposed to the legacy RFC5245).
368
+ "a=ice-options:ice2" + "\n" +
369
+ // ICE username and password, which are used for establishing and
370
+ // maintaining the ICE connection. (RFC8839)
371
+ // These values are set according to the libp2p WebRTC specification.
372
+ "a=ice-ufrag:" + ufragPwd + "\n" +
373
+ "a=ice-pwd:" + ufragPwd + "\n" +
374
+ // Fingerprint of the certificate that the server will use during the TLS
375
+ // handshake. (RFC8122)
376
+ // MUST be derived from the certificate used by the answerer (server).
377
+ "a=fingerprint:sha-256 " + fingerprint + "\n" +
378
+ // Indicates that the remote DTLS server will only listen for incoming
379
+ // connections. (RFC5763)
380
+ // The answerer (server) MUST not be located behind a NAT (RFC6135).
381
+ "a=setup:passive" + "\n" +
382
+ // The SCTP port (RFC8841)
383
+ // Note it's different from the "m=" line port value, which
384
+ // indicates the port of the underlying transport-layer protocol
385
+ // (UDP or TCP)
386
+ "a=sctp-port:5000" + "\n" +
387
+ // The maximum SCTP user message size (in bytes) (RFC8841)
388
+ "a=max-message-size:16384" + "\n" + // TODO: should this be part of the spec?
389
+ // A transport address for a candidate that can be used for connectivity
390
+ // checks (RFC8839).
391
+ "a=candidate:1 1 UDP 1 " + targetIp + " " + targetPort + " typ host" + "\n";
392
+ yield pc.setRemoteDescription({ type: "answer", sdp: remoteSdp });
393
+ });
394
+ pc.ondatachannel = ({ channel }) => {
395
+ // TODO: is the substream maybe already open? according to the Internet it seems that no but it's unclear
396
+ addChannel(channel, 'inbound');
397
+ };
398
+ // Creating a `RTCPeerConnection` doesn't actually do anything before `createDataChannel` is
399
+ // called. Smoldot's API, however, requires you to treat entire connections as open or
400
+ // closed. We know, according to the libp2p WebRTC specification, that every connection
401
+ // always starts with a substream where a handshake is performed. After we've reported that
402
+ // the connection is open, smoldot will open a substream in order to perform the handshake.
403
+ // Instead of following this API, we open this substream in advance, and will notify smoldot
404
+ // that the connection is open when the substream is open.
405
+ // Note that the label passed to `createDataChannel` is required to be empty as per the
406
+ // libp2p WebRTC specification.
407
+ addChannel(pc.createDataChannel("", { id: 0, negotiated: true }), 'first-outbound');
408
+ }));
409
+ return {
410
+ reset: (streamId) => {
411
+ // If `streamId` is undefined, then the whole connection must be destroyed.
412
+ if (streamId === undefined) {
413
+ killAllJs();
414
+ }
415
+ else {
416
+ const channel = dataChannels.get(streamId);
417
+ channel.onopen = null;
418
+ channel.onerror = null;
419
+ channel.onclose = null;
420
+ channel.onmessage = null;
421
+ channel.close();
422
+ dataChannels.delete(streamId);
423
+ }
424
+ },
425
+ send: (data, streamId) => {
426
+ dataChannels.get(streamId).send(data);
427
+ },
428
+ openOutSubstream: () => {
429
+ // `openOutSubstream` can only be called after we have called `config.onOpen`, therefore
430
+ // `pc` is guaranteed to be non-null.
431
+ // As explained above, we open a data channel ahead of time. If this data channel is still
432
+ // there, we report it.
433
+ if (handshakeDataChannel) {
434
+ // Do this asynchronously because calling callbacks within callbacks is error-prone.
435
+ (() => __awaiter(this, void 0, void 0, function* () {
436
+ // We need to check again if `handshakeDataChannel` is still defined, as the
437
+ // connection might have been closed.
438
+ if (handshakeDataChannel) {
439
+ config.onStreamOpened(handshakeDataChannel.id, 'outbound');
440
+ dataChannels.set(handshakeDataChannel.id, handshakeDataChannel);
441
+ handshakeDataChannel = undefined;
442
+ }
443
+ }))();
444
+ }
445
+ else {
446
+ // Note that the label passed to `createDataChannel` is required to be empty as per the
447
+ // libp2p WebRTC specification.
448
+ addChannel(pc.createDataChannel(""), 'outbound');
449
+ }
450
+ }
451
+ };
452
+ }
453
+ else {
454
+ throw new instance_js_1.ConnectionError('Unrecognized multiaddr format');
455
+ }
456
+ }
457
+ /// Parses a multihash-multibase-encoded string into a SHA256 hash.
458
+ ///
459
+ /// Throws an exception if the multihash algorithm isn't SHA256.
460
+ const multihashToSha256 = (certMultihash) => {
461
+ if (certMultihash.length != 34 || certMultihash[0] != 0x12 || certMultihash[1] != 32) {
462
+ throw new Error('Certificate multihash is not SHA-256');
463
+ }
464
+ return new Uint8Array(certMultihash.slice(2));
465
+ };
@@ -0,0 +1,10 @@
1
+ import { Client, ClientOptions } from './client.js';
2
+ export { AddChainError, AddChainOptions, AlreadyDestroyedError, Chain, Client, ClientOptions, CrashError, MalformedJsonRpcError, QueueFullError, JsonRpcDisabledError, LogCallback } from './client.js';
3
+ /**
4
+ * Initializes a new client. This is a pre-requisite to connecting to a blockchain.
5
+ *
6
+ * Can never fail.
7
+ *
8
+ * @param options Configuration of the client. Defaults to `{}`.
9
+ */
10
+ export declare function start(options?: ClientOptions): Client;