diff --git a/.gitignore b/.gitignore index d15b512..f464ed8 100644 --- a/.gitignore +++ b/.gitignore @@ -36,7 +36,7 @@ bower_components Thumbs.db # Ignore built ts files -dist/**/* +dist/ # ignore yarn.lock yarn.lock diff --git a/package.json b/package.json index 13b704c..5db5740 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,10 @@ "othergen": "./node_modules/.bin/proto-loader-gen-types --longs=String --enums=String --defaults --oneofs --grpcLib=@grpc/grpc-js --outDir=proto_ts/ src/*.proto", "build": "aegir build", "test": "aegir test --target browser", + "test:interop": "run-p --race start-ext-server wait-then-test", + "start-ext-server": "rm -vf dist/test/server-multiaddr.js ; cd ../go-libp2p/ && go run examples/webrtc/main.go ../js-libp2p-webrtc/dist/test/ ", + "wait-for-server": "wait-on --delay 1000 --timeout 10000 dist/test/server-multiaddr.js", + "wait-then-test": "run-s wait-for-server test", "lint": "aegir lint", "lint:fix": "aegir lint --fix", "clean": "aegir clean", @@ -35,14 +39,18 @@ }, "devDependencies": { "@libp2p/interface-mocks": "^4.0.1", + "@libp2p/peer-id-factory": "^1.0.18", + "@multiformats/multiaddr": "^10.4.1", "@types/uuid": "^8.3.4", "@typescript-eslint/parser": "^5.32.0", "aegir": "^37.4.6", "it-all": "^1.0.6", "it-first": "^1.0.7", + "npm-run-all": "^4.1.5", "prettier": "^2.7.1", "typescript": "^4.7.4", - "uint8arrays": "^3.1.0" + "uint8arrays": "^3.1.0", + "wait-on": "^6.0.1" }, "dependencies": { "@chainsafe/libp2p-noise": "^8.0.0", diff --git a/proto_ts/message.ts b/proto_ts/message.ts index 0e29e55..5d0560f 100644 --- a/proto_ts/message.ts +++ b/proto_ts/message.ts @@ -1,4 +1,4 @@ -// @generated by protobuf-ts 2.8.0 +// @generated by protobuf-ts 2.8.1 // @generated from protobuf file "message.proto" (package "webrtc.pb", syntax proto2) // tslint:disable import type { BinaryWriteOptions } from "@protobuf-ts/runtime"; diff --git a/src/connection.ts b/src/connection.ts index ec381bd..7d5f672 100644 --- a/src/connection.ts +++ b/src/connection.ts @@ -11,7 +11,7 @@ import { WebRTCStream } from './stream'; import { select as msselect, handle as mshandle } from '@libp2p/multistream-select'; import { Duplex } from 'it-stream-types'; import { Uint8ArrayList } from 'uint8arraylist'; -import { dataChannelError, operationAborted, overStreamLimit } from './error'; +import { connectionClosedError, dataChannelError, operationAborted, overStreamLimit } from './error'; const log = logger('libp2p:webrtc:connection'); @@ -40,6 +40,7 @@ export class WebRTCConnection implements ic.Connection { remotePeer: PeerId; tags: string[] = []; components: Components; + closed: boolean = false; private _streams: Map = new Map(); private peerConnection: RTCPeerConnection; @@ -60,14 +61,22 @@ export class WebRTCConnection implements ic.Connection { }, }; this.handleIncomingStreams(); + this.peerConnection.onconnectionstatechange = (_) => { + switch(this.peerConnection.connectionState) { + case 'closed': // fallthrough + case 'failed': // fallthrough + case 'disconnected': // fallthrough + log.trace(`peerconnection moved to state: ${this.peerConnection.connectionState}`) + closed = true; + this.streams.forEach((stream) => stream.abort(connectionClosedError(this.peerConnection.connectionState, 'closing stream'))) + } + } } private handleIncomingStreams() { let metrics = this.components.getMetrics(); this.peerConnection.ondatachannel = async ({ channel }) => { - const logPrefix = `[stream:${channel.label}][inbound]`; - log.trace(`incoming stream - ${channel.label}`); - let [openPromise, abortPromise] = [defer(), defer()]; + const [openPromise, abortPromise] = [defer(), defer()]; let controller = new TimeoutController(OPEN_STREAM_TIMEOUT); controller.signal.onabort = () => abortPromise.resolve(); channel.onopen = () => openPromise.resolve(); @@ -77,7 +86,7 @@ export class WebRTCConnection implements ic.Connection { throw operationAborted('prior to a new stream incoming.', controller.signal.reason); } - let rawStream = new WebRTCStream({ + const rawStream = new WebRTCStream({ channel, stat: { direction: 'inbound', @@ -86,26 +95,30 @@ export class WebRTCConnection implements ic.Connection { }, }, }); - let registrar = this.components.getRegistrar(); - let protocols = registrar.getProtocols(); + const registrar = this.components.getRegistrar(); + const protocols = registrar.getProtocols(); - log.trace(`${logPrefix} supported protocols - ${protocols}`); + log.trace(`supported protocols - ${protocols}`); - let { stream, protocol } = await mshandle(rawStream, protocols, { signal: controller.signal }); - if (metrics) { - metrics.trackStream({ stream, protocol, remotePeer: this.remotePeer }); - } + try { + const { stream, protocol } = await mshandle(rawStream, protocols, { signal: controller.signal }); + if (metrics) { + metrics.trackStream({ stream, protocol, remotePeer: this.remotePeer }); + } - log.trace(`${logPrefix} handled protocol - ${protocol}`); + log.trace(`handled protocol - ${protocol}`); - rawStream.stat.protocol = protocol; - let result = this.wrapMsStream(rawStream, stream); + rawStream.stat.protocol = protocol; + const result = this.wrapMsStream(rawStream, stream); - this.addStream(result); + this.addStream(result); - // handle stream - let { handler } = registrar.getHandler(protocol); - handler({ connection: this, stream: result }); + // handle stream + const { handler } = registrar.getHandler(protocol); + handler({ connection: this, stream: result }); + } catch (err) { + log.error('stream error: ', rawStream.id, rawStream.stat.direction); + } }; } @@ -132,9 +145,9 @@ export class WebRTCConnection implements ic.Connection { } private findStreamLimit(protocol: string, direction: ic.Direction): number { - let registrar = this.components.getRegistrar(); + const registrar = this.components.getRegistrar(); try { - let handler = registrar.getHandler(protocol); + const handler = registrar.getHandler(protocol); return direction === 'inbound' ? handler.options.maxInboundStreams || DEFAULT_MAX_INBOUND_STREAMS : handler.options.maxOutboundStreams || DEFAULT_MAX_OUTBOUND_STREAMS; } catch (err) {} return direction === 'inbound' ? DEFAULT_MAX_INBOUND_STREAMS : DEFAULT_MAX_OUTBOUND_STREAMS; @@ -145,12 +158,15 @@ export class WebRTCConnection implements ic.Connection { } async newStream(protocols: string | string[], options: AbortOptions = {}): Promise { - let label = genUuid().slice(0, 8); - let openPromise = defer(); - let abortedPromise = defer(); - let controller: TimeoutController | undefined; - let metrics = this.components.getMetrics(); + if (this.closed) { + throw connectionClosedError(this.peerConnection.connectionState, 'cannot open new stream') + } + const label = genUuid().slice(0, 8); + const [openPromise, abortedPromise] = [defer(), defer()]; + const metrics = this.components.getMetrics(); + let openError: Error | undefined; + let controller: TimeoutController | undefined; log.trace(`opening new stream with protocols: ${protocols}`); @@ -168,7 +184,7 @@ export class WebRTCConnection implements ic.Connection { }; log.trace(`[stream: ${label}] peerconnection state: ${this.peerConnection.connectionState}`); - let channel = this.peerConnection.createDataChannel(label); + const channel = this.peerConnection.createDataChannel(label); channel.onopen = (_evt) => { log.trace(`[stream: ${label}] data channel opened`); openPromise.resolve(); @@ -188,7 +204,7 @@ export class WebRTCConnection implements ic.Connection { throw openError; } - let rawStream = new WebRTCStream({ + const rawStream = new WebRTCStream({ channel, stat: { direction: 'outbound', @@ -198,11 +214,11 @@ export class WebRTCConnection implements ic.Connection { }, }); - let { stream, protocol } = await msselect(rawStream, protocols, { signal: options.signal }); + const { stream, protocol } = await msselect(rawStream, protocols, { signal: options.signal }); log.trace(`[stream ${label}] select protocol - ${protocol}`); // check if stream is within limit after protocol has been negotiated rawStream.stat.protocol = protocol; - let result = this.wrapMsStream(rawStream, stream); + const result = this.wrapMsStream(rawStream, stream); // check if stream can be accomodated if (metrics) { metrics.trackStream({ stream, protocol, remotePeer: this.remotePeer }); @@ -213,10 +229,10 @@ export class WebRTCConnection implements ic.Connection { } addStream(stream: ic.Stream): void { - let protocol = stream.stat.protocol!; - let direction = stream.stat.direction; + const protocol = stream.stat.protocol!; + const direction = stream.stat.direction; if (this.countStream(protocol, direction) === this.findStreamLimit(protocol, direction)) { - let err = overStreamLimit(direction, protocol); + const err = overStreamLimit(direction, protocol); log(err.message); stream.abort(err); throw err; diff --git a/src/error.ts b/src/error.ts index 88d085f..1d7f8d1 100644 --- a/src/error.ts +++ b/src/error.ts @@ -17,6 +17,18 @@ export enum codes { ERR_NOT_IMPLEMENTED = 'ERR_NOT_IMPLEMENTED', ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_INBOUND_PROTOCOL_STREAMS', ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS = 'ERR_TOO_MANY_OUTBOUND_PROTOCOL_STREAMS', + ERR_CONNECTION_CLOSED = 'ERR_CONNECTION_CLOSED', +} + +export class ConnectionClosedError extends WebRTCTransportError { + constructor(state: RTCPeerConnectionState, msg: string) { + super(`peerconnection moved to state: ${state}:` + msg); + this.name = 'WebRTC/ConnectionClosed'; + } +} + +export function connectionClosedError(state: RTCPeerConnectionState, msg: string) { + return createError(new ConnectionClosedError(state, msg), codes.ERR_CONNECTION_CLOSED) } export class InvalidArgumentError extends WebRTCTransportError { @@ -83,7 +95,7 @@ export class DataChannelError extends WebRTCTransportError { } export function dataChannelError(streamLabel: string, msg: string) { - return createError(new OperationAbortedError(streamLabel, msg), codes.ERR_DATA_CHANNEL); + return createError(new DataChannelError(streamLabel, msg), codes.ERR_DATA_CHANNEL); } export class StreamingLimitationError extends WebRTCTransportError { diff --git a/src/message.proto b/src/message.proto index b717f02..7a40990 100644 --- a/src/message.proto +++ b/src/message.proto @@ -17,4 +17,4 @@ message Message { optional Flag flag = 1; optional bytes message = 2; -} \ No newline at end of file +} diff --git a/src/options.ts b/src/options.ts index 0af8b64..7613aea 100644 --- a/src/options.ts +++ b/src/options.ts @@ -6,5 +6,4 @@ export interface WebRTCListenerOptions extends CreateListenerOptions { // channelOptions?: WebRTCReceiverInit } -export interface WebRTCDialOptions extends DialOptions { -} +export interface WebRTCDialOptions extends DialOptions {} diff --git a/src/sdp.ts b/src/sdp.ts index 04d4c22..5a7204c 100644 --- a/src/sdp.ts +++ b/src/sdp.ts @@ -6,7 +6,7 @@ import { bases } from 'multiformats/basics'; const log = logger('libp2p:webrtc:sdp'); -const mbdecoder = (function () { +export const mbdecoder = (function () { const decoders = Object.values(bases).map((b) => b.decoder); let acc = decoders[0].or(decoders[1]); decoders.slice(2).forEach((d) => (acc = acc.or(d))); @@ -16,7 +16,7 @@ const mbdecoder = (function () { const CERTHASH_CODE: number = 466; function ipv(ma: Multiaddr): string { - for (let proto of ma.protoNames()) { + for (const proto of ma.protoNames()) { if (proto.startsWith('ip')) { return proto.toUpperCase(); } @@ -32,8 +32,8 @@ function port(ma: Multiaddr): number { } export function certhash(ma: Multiaddr): string { - let tups = ma.stringTuples(); - let certhash_value = tups.filter((tup) => tup[0] == CERTHASH_CODE).map((tup) => tup[1])[0]; + const tups = ma.stringTuples(); + const certhash_value = tups.filter((tup) => tup[0] == CERTHASH_CODE).map((tup) => tup[1])[0]; if (certhash_value) { return certhash_value; } else { @@ -41,11 +41,11 @@ export function certhash(ma: Multiaddr): string { } } -function certhashToFingerprint(ma: Multiaddr): string { - let certhash_value = certhash(ma); +export function certhashToFingerprint(ma: Multiaddr): string[] { + const certhash_value = certhash(ma); // certhash_value is a multibase encoded multihash encoded string - let mbdecoded = mbdecoder.decode(certhash_value); - let mhdecoded = multihashes.decode(mbdecoded); + const mbdecoded = mbdecoder.decode(certhash_value); + const mhdecoded = multihashes.decode(mbdecoded); let prefix = ''; switch (mhdecoded.name) { case 'md5': @@ -61,17 +61,17 @@ function certhashToFingerprint(ma: Multiaddr): string { throw unsupportedHashAlgorithm(mhdecoded.name); } - let fp = mhdecoded.digest.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), ''); - fp = fp.match(/.{1,2}/g)!.join(':'); + const fp = mhdecoded.digest.reduce((str, byte) => str + byte.toString(16).padStart(2, '0'), ''); + const fpSdp = fp.match(/.{1,2}/g)!.join(':'); - return `${prefix} ${fp}`; + return [`${prefix.toUpperCase()} ${fpSdp.toUpperCase()}`, fp]; } function ma2sdp(ma: Multiaddr, ufrag: string): string { const IP = ip(ma); const IPVERSION = ipv(ma); const PORT = port(ma); - const CERTFP = certhashToFingerprint(ma); + const [CERTFP, _] = certhashToFingerprint(ma); return `v=0 o=- 0 0 IN ${IPVERSION} ${IP} s=- @@ -80,14 +80,13 @@ t=0 0 a=ice-lite m=application ${PORT} UDP/DTLS/SCTP webrtc-datachannel a=mid:0 -a=setup:active -a=ice-options:ice2 +a=setup:passive a=ice-ufrag:${ufrag} a=ice-pwd:${ufrag} a=fingerprint:${CERTFP} a=sctp-port:5000 a=max-message-size:100000 -a=candidate:1 1 UDP 1 ${IP} ${PORT} typ host`; +a=candidate:1467250027 1 UDP 1467250027 ${IP} ${PORT} typ host\r\n`; } export function fromMultiAddr(ma: Multiaddr, ufrag: string): RTCSessionDescriptionInit { diff --git a/src/transport.ts b/src/transport.ts index c410064..c2b8873 100644 --- a/src/transport.ts +++ b/src/transport.ts @@ -3,20 +3,20 @@ import * as p from '@libp2p/peer-id'; import { WebRTCConnection } from './connection'; import { WebRTCDialOptions } from './options'; import { WebRTCStream } from './stream'; -import { Noise, stablelib } from '@chainsafe/libp2p-noise'; +import { Noise } from '@chainsafe/libp2p-noise'; import { Components, Initializable } from '@libp2p/components'; import { Connection } from '@libp2p/interface-connection'; -import type { PeerId } from '@libp2p/interface-peer-id' +import type { PeerId } from '@libp2p/interface-peer-id'; import { CreateListenerOptions, Listener, symbol, Transport } from '@libp2p/interface-transport'; import { logger } from '@libp2p/logger'; import { Multiaddr } from '@multiformats/multiaddr'; import { v4 as genUuid } from 'uuid'; import defer, { DeferredPromise } from 'p-defer'; -import { base64 } from 'multiformats/bases/base64'; import { fromString as uint8arrayFromString } from 'uint8arrays/from-string'; import { concat } from 'uint8arrays/concat'; import * as multihashes from 'multihashes'; -import { inappropriateMultiaddr, unimplemented, invalidArgument, unsupportedHashAlgorithm } from './error'; +import { dataChannelError, inappropriateMultiaddr, unimplemented, invalidArgument, unsupportedHashAlgorithm } from './error'; +import { compare as uint8arrayCompare } from 'uint8arrays/compare'; const log = logger('libp2p:webrtc:transport'); const HANDSHAKE_TIMEOUT_MS = 10000; @@ -53,68 +53,66 @@ export class WebRTCTransport implements Transport, Initializable { } async _connect(ma: Multiaddr, options: WebRTCDialOptions): Promise { - let rps = ma.getPeerId(); + const rps = ma.getPeerId(); if (!rps) { throw inappropriateMultiaddr("we need to have the remote's PeerId"); } - let peerConnection = new RTCPeerConnection(); + // ECDSA is preferred over RSA here. From our testing we find that P-256 elliptic + // curve is supported by Pion, webrtc-rs, as well as Chromium (P-228 and P-384 + // was not supported in Chromium). We fix the hash algorith to SHA-256 for + // reasons documented here: https://github.com/libp2p/specs/pull/412#discussion_r968327480 + const certificate = await RTCPeerConnection.generateCertificate({ + name: 'ECDSA', + namedCurve: 'P-256', + hash: 'SHA-256', + } as any); + const peerConnection = new RTCPeerConnection({ certificates: [certificate] }); // create data channel - let handshakeDataChannel = peerConnection.createDataChannel('data', { negotiated: true, id: 1 }); - // + const dataChannelOpenPromise = defer(); + const handshakeDataChannel = peerConnection.createDataChannel('data', { negotiated: true, id: 1 }); + const handhsakeTimeout = setTimeout(() => { + log.error('Data channel never opened. State was: %s', handshakeDataChannel.readyState.toString()); + dataChannelOpenPromise.reject(dataChannelError('data', `data channel was never opened: state: ${handshakeDataChannel.readyState}`)); + }, HANDSHAKE_TIMEOUT_MS); + + handshakeDataChannel.onopen = (_) => { + clearTimeout(handhsakeTimeout) + dataChannelOpenPromise.resolve(); + } + handshakeDataChannel.onerror = (ev: Event) => { + clearTimeout(handhsakeTimeout) + log.error('Error opening a data channel for handshaking: %s', ev.toString()); + dataChannelOpenPromise.reject(dataChannelError('data', `error opening datachannel: ${ev.toString()}`)); + }; // create offer sdp let offerSdp = await peerConnection.createOffer(); - // - // // generate random string for ufrag - let ufrag = genUuid(); - - // + const ufrag = genUuid().replaceAll('-', ''); // munge sdp with ufrag = pwd offerSdp = sdp.munge(offerSdp, ufrag); - // - // // set local description - peerConnection.setLocalDescription(offerSdp); - // - // + await peerConnection.setLocalDescription(offerSdp); // construct answer sdp from multiaddr - let answerSdp = sdp.fromMultiAddr(ma, ufrag); - - // - // + const answerSdp = sdp.fromMultiAddr(ma, ufrag); // set remote description - peerConnection.setRemoteDescription(answerSdp); - - // - // - // + await peerConnection.setRemoteDescription(answerSdp); // wait for peerconnection.onopen to fire, or for the datachannel to open - let dataChannelOpenPromise = defer(); + await dataChannelOpenPromise.promise; - handshakeDataChannel.onopen = (_) => dataChannelOpenPromise.resolve(); - handshakeDataChannel.onerror = (ev: Event) => { - log.error('Error opening a data channel for handshaking: %s', ev.toString()); - dataChannelOpenPromise.reject(); - }; - setTimeout(() => { - log.error('Data channel never opened. State was: %s', handshakeDataChannel.readyState.toString()); - dataChannelOpenPromise.reject(); - }, HANDSHAKE_TIMEOUT_MS); - - await this.componentsPromise.promise; - - let myPeerId = await this.getPeerId(); - let theirPeerId = p.peerIdFromString(rps); + const myPeerId = await this.getPeerId(); + const theirPeerId = p.peerIdFromString(rps); // do noise handshake //set the Noise Prologue to libp2p-webrtc-noise: before starting the actual Noise handshake. // is the concatenation of the of the two TLS fingerprints of A and B in their multihash byte representation, sorted in ascending order. - let fingerprintsPrologue = this.generateNoisePrologue(peerConnection, ma); - let noise = new Noise(myPeerId.privateKey, undefined, stablelib, fingerprintsPrologue); - let wrappedChannel = new WebRTCStream({ channel: handshakeDataChannel, stat: { direction: 'outbound', timeline: { open: 1 } } }); - let wrappedDuplex = { + const fingerprintsPrologue = this.generateNoisePrologue(peerConnection, ma); + // Since we use the default crypto interface and do not use a static key or early data, + // we pass in undefined for these parameters. + const noise = new Noise(undefined, undefined, undefined, fingerprintsPrologue); + const wrappedChannel = new WebRTCStream({ channel: handshakeDataChannel, stat: { direction: 'outbound', timeline: { open: 1 } } }); + const wrappedDuplex = { ...wrappedChannel, source: { [Symbol.asyncIterator]: async function* () { @@ -125,9 +123,10 @@ export class WebRTCTransport implements Transport, Initializable { }, }; - await noise.secureOutbound(myPeerId, wrappedDuplex, theirPeerId); + // Creating the connection before completion of the noise + // handshake ensures that the stream opening callback is set up - return new WebRTCConnection({ + const connection = new WebRTCConnection({ components: this.components!, id: ma.toString(), remoteAddr: ma, @@ -136,25 +135,23 @@ export class WebRTCTransport implements Transport, Initializable { pc: peerConnection, remotePeer: theirPeerId, }); + + await noise.secureOutbound(myPeerId, wrappedDuplex, theirPeerId); + return connection; } private generateNoisePrologue(pc: RTCPeerConnection, ma: Multiaddr): Uint8Array { - let remoteCerthash = sdp.certhash(ma); - if (!remoteCerthash) { - throw inappropriateMultiaddr('no remote tls fingerprint in multiaddr'); - } - let remote = base64.decode(remoteCerthash); if (pc.getConfiguration().certificates?.length === 0) { throw invalidArgument('no local certificate'); } - let localCert = pc.getConfiguration().certificates![0]; - if (localCert.getFingerprints().length === 0) { + const localCert = pc.getConfiguration().certificates?.at(0)!; + if (!localCert || localCert.getFingerprints().length === 0) { throw invalidArgument('no fingerprint on local certificate'); } - let localFingerprint = localCert.getFingerprints()[0]; - let localFpString = localFingerprint.value!.replaceAll(':', ''); - let localFpArray = uint8arrayFromString(localFpString, 'hex'); + const localFingerprint = localCert.getFingerprints()[0]; + const localFpString = localFingerprint.value!.replaceAll(':', ''); + const localFpArray = uint8arrayFromString(localFpString, 'hex'); let local: Uint8Array; switch (localFingerprint.algorithm!) { case 'md5': @@ -170,11 +167,11 @@ export class WebRTCTransport implements Transport, Initializable { throw unsupportedHashAlgorithm(localFingerprint.algorithm || 'none'); } - let prefix = uint8arrayFromString('libp2p-webrtc-noise:'); - let fps = [local, remote].sort(); + const remote: Uint8Array = sdp.mbdecoder.decode(sdp.certhash(ma)); + const prefix = uint8arrayFromString('libp2p-webrtc-noise:'); + const fps = [remote, local].sort(uint8arrayCompare); - let result = concat([prefix, ...fps]); - return result; + return concat([prefix, ...fps]); } public async getPeerId(): Promise { @@ -187,9 +184,6 @@ const WEBRTC_CODE: number = 280; const CERTHASH_CODE: number = 466; function validMa(ma: Multiaddr): boolean { - let codes = ma.protoCodes(); - return codes.includes(WEBRTC_CODE) - && codes.includes(CERTHASH_CODE) - && ma.getPeerId() != null; + const codes = ma.protoCodes(); + return codes.includes(WEBRTC_CODE) && codes.includes(CERTHASH_CODE) && ma.getPeerId() != null; } - diff --git a/test/connection.browser.spec.ts b/test/connection.browser.spec.ts index 37a8496..7664db2 100644 --- a/test/connection.browser.spec.ts +++ b/test/connection.browser.spec.ts @@ -5,7 +5,7 @@ import { expect } from 'aegir/chai'; import { pipe } from 'it-pipe'; import first from 'it-first'; import {fromString} from 'uint8arrays/from-string'; -import { v4 } from 'uuid'; +import {v4} from 'uuid'; const echoProtocol = '/echo/1.0.0'; @@ -42,6 +42,7 @@ describe('connection browser tests', () => { } expect(responsed).to.be.true(); }); + }); export {}; diff --git a/test/server-multiaddr.js b/test/server-multiaddr.js new file mode 100644 index 0000000..978d6d2 --- /dev/null +++ b/test/server-multiaddr.js @@ -0,0 +1 @@ +export var SERVER_MULTIADDR = ''; diff --git a/test/transport.browser.spec.ts b/test/transport.browser.spec.ts index cee3a84..149d239 100644 --- a/test/transport.browser.spec.ts +++ b/test/transport.browser.spec.ts @@ -5,6 +5,11 @@ import { mockUpgrader } from '@libp2p/interface-mocks'; import { CreateListenerOptions, symbol } from '@libp2p/interface-transport'; import { Multiaddr } from '@multiformats/multiaddr'; import { expect } from 'chai'; +import { createEd25519PeerId } from '@libp2p/peer-id-factory' +import { mockRegistrar } from '@libp2p/interface-mocks' +import { pipe } from 'it-pipe'; +import first from 'it-first'; +import { fromString as uint8arrayFromString } from 'uint8arrays/from-string'; function ignoredDialOption(): CreateListenerOptions { let u = mockUpgrader({}); @@ -89,3 +94,28 @@ describe('basic transport tests', () => { }); }); +import { SERVER_MULTIADDR } from './server-multiaddr'; + +describe('Transport interoperability tests', () => { + it('can connect to a server', async () => { + if (SERVER_MULTIADDR) { + console.log('Will test connecting to', SERVER_MULTIADDR); + } else { + console.log('Will not test connecting to an external server, as we do not appear to have one.'); + return; + } + let t = new underTest.WebRTCTransport(); + let components = new Components({ + peerId: await createEd25519PeerId(), + registrar: mockRegistrar(), + }); + t.init(components); + let ma = new Multiaddr(SERVER_MULTIADDR); + let conn = await t.dial(ma, ignoredDialOption()); + let stream = await conn.newStream(['/echo/1.0.0']); + let data = 'dataToBeEchoedBackToMe\n'; + let response = await pipe([uint8arrayFromString(data)], stream, async (source) => await first(source)); + expect(response?.subarray()).to.equalBytes(uint8arrayFromString(data)); + }); +}); + diff --git a/test/util.ts b/test/util.ts index ef8b3ea..d11f994 100644 --- a/test/util.ts +++ b/test/util.ts @@ -39,7 +39,7 @@ export async function createConnectedRTCPeerConnectionPair(): Promise((res) => { - // dc.onopen = () => res(); - // }); - log('created peer connections'); return [client, server]; } diff --git a/tsconfig.json b/tsconfig.json index 5b10170..f000470 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,8 +1,12 @@ { "extends": "aegir/src/config/tsconfig.aegir.json", "compilerOptions": { - "outDir": "dist", - "importsNotUsedAsValues": "preserve" + "allowJs": true, + "emitDeclarationOnly": false, + "importsNotUsedAsValues": "preserve", + "module": "ES2020", + "moduleResolution": "node", + "outDir": "dist" }, "include": [ "src",