@libp2p/webrtc 1.0.5 → 1.1.1
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/dist/index.min.js +19 -19
- package/dist/src/index.d.ts +5 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +8 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/muxer.d.ts +6 -10
- package/dist/src/muxer.d.ts.map +1 -1
- package/dist/src/muxer.js +44 -13
- package/dist/src/muxer.js.map +1 -1
- package/dist/src/peer_transport/handler.d.ts +14 -0
- package/dist/src/peer_transport/handler.d.ts.map +1 -0
- package/dist/src/peer_transport/handler.js +107 -0
- package/dist/src/peer_transport/handler.js.map +1 -0
- package/dist/src/peer_transport/listener.d.ts +19 -0
- package/dist/src/peer_transport/listener.d.ts.map +1 -0
- package/dist/src/peer_transport/listener.js +32 -0
- package/dist/src/peer_transport/listener.js.map +1 -0
- package/dist/src/peer_transport/pb/index.d.ts +20 -0
- package/dist/src/peer_transport/pb/index.d.ts.map +1 -0
- package/dist/src/peer_transport/pb/index.js +73 -0
- package/dist/src/peer_transport/pb/index.js.map +1 -0
- package/dist/src/peer_transport/transport.d.ts +37 -0
- package/dist/src/peer_transport/transport.d.ts.map +1 -0
- package/dist/src/peer_transport/transport.js +128 -0
- package/dist/src/peer_transport/transport.js.map +1 -0
- package/dist/src/peer_transport/util.d.ts +10 -0
- package/dist/src/peer_transport/util.d.ts.map +1 -0
- package/dist/src/peer_transport/util.js +54 -0
- package/dist/src/peer_transport/util.js.map +1 -0
- package/dist/src/stream.d.ts +1 -0
- package/dist/src/stream.d.ts.map +1 -1
- package/dist/src/stream.js +6 -2
- package/dist/src/stream.js.map +1 -1
- package/dist/src/transport.d.ts +3 -3
- package/dist/src/transport.d.ts.map +1 -1
- package/dist/src/transport.js +6 -5
- package/dist/src/transport.js.map +1 -1
- package/package.json +16 -5
- package/src/index.ts +11 -3
- package/src/muxer.ts +45 -15
- package/src/peer_transport/handler.ts +139 -0
- package/src/peer_transport/listener.ts +44 -0
- package/src/peer_transport/pb/index.proto +16 -0
- package/src/peer_transport/pb/index.ts +92 -0
- package/src/peer_transport/transport.ts +167 -0
- package/src/peer_transport/util.ts +62 -0
- package/src/stream.ts +7 -2
- package/src/transport.ts +9 -8
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import type { IncomingStreamData } from '@libp2p/interface-registrar'
|
|
2
|
+
import { pbStream } from 'it-pb-stream'
|
|
3
|
+
import pDefer, { type DeferredPromise } from 'p-defer'
|
|
4
|
+
import { TimeoutController } from 'timeout-abort-controller'
|
|
5
|
+
import { readCandidatesUntilConnected, resolveOnConnected } from './util.js'
|
|
6
|
+
import * as pb from './pb/index.js'
|
|
7
|
+
import { abortableDuplex } from 'abortable-iterator'
|
|
8
|
+
import { logger } from '@libp2p/logger'
|
|
9
|
+
import type { Stream } from '@libp2p/interface-connection'
|
|
10
|
+
import type { StreamMuxerFactory } from '@libp2p/interface-stream-muxer'
|
|
11
|
+
import { DataChannelMuxerFactory } from '../muxer.js'
|
|
12
|
+
|
|
13
|
+
const DEFAULT_TIMEOUT = 30 * 1000
|
|
14
|
+
|
|
15
|
+
const log = logger('libp2p:webrtc:peer')
|
|
16
|
+
|
|
17
|
+
export type IncomingStreamOpts = { rtcConfiguration?: RTCConfiguration } & IncomingStreamData
|
|
18
|
+
|
|
19
|
+
export async function handleIncomingStream ({ rtcConfiguration, stream: rawStream }: IncomingStreamOpts): Promise<[RTCPeerConnection, StreamMuxerFactory]> {
|
|
20
|
+
const timeoutController = new TimeoutController(DEFAULT_TIMEOUT)
|
|
21
|
+
const signal = timeoutController.signal
|
|
22
|
+
const stream = pbStream(abortableDuplex(rawStream, timeoutController.signal)).pb(pb.Message)
|
|
23
|
+
const pc = new RTCPeerConnection(rtcConfiguration)
|
|
24
|
+
const muxerFactory = new DataChannelMuxerFactory(pc)
|
|
25
|
+
|
|
26
|
+
const connectedPromise: DeferredPromise<void> = pDefer()
|
|
27
|
+
const answerSentPromise: DeferredPromise<void> = pDefer()
|
|
28
|
+
|
|
29
|
+
signal.onabort = () => { connectedPromise.reject() }
|
|
30
|
+
// candidate callbacks
|
|
31
|
+
pc.onicecandidate = ({ candidate }) => {
|
|
32
|
+
answerSentPromise.promise.then(
|
|
33
|
+
() => {
|
|
34
|
+
stream.write({
|
|
35
|
+
type: pb.Message.Type.ICE_CANDIDATE,
|
|
36
|
+
data: (candidate != null) ? JSON.stringify(candidate.toJSON()) : ''
|
|
37
|
+
})
|
|
38
|
+
},
|
|
39
|
+
(err) => {
|
|
40
|
+
log.error('cannot set candidate since sending answer failed', err)
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
resolveOnConnected(pc, connectedPromise)
|
|
46
|
+
|
|
47
|
+
// read an SDP offer
|
|
48
|
+
const pbOffer = await stream.read()
|
|
49
|
+
if (pbOffer.type !== pb.Message.Type.SDP_OFFER) {
|
|
50
|
+
throw new Error(`expected message type SDP_OFFER, received: ${pbOffer.type ?? 'undefined'} `)
|
|
51
|
+
}
|
|
52
|
+
const offer = new RTCSessionDescription({
|
|
53
|
+
type: 'offer',
|
|
54
|
+
sdp: pbOffer.data
|
|
55
|
+
})
|
|
56
|
+
|
|
57
|
+
await pc.setRemoteDescription(offer).catch(err => {
|
|
58
|
+
log.error('could not execute setRemoteDescription', err)
|
|
59
|
+
throw new Error('Failed to set remoteDescription')
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
// create and write an SDP answer
|
|
63
|
+
const answer = await pc.createAnswer().catch(err => {
|
|
64
|
+
log.error('could not execute createAnswer', err)
|
|
65
|
+
answerSentPromise.reject(err)
|
|
66
|
+
throw new Error('Failed to create answer')
|
|
67
|
+
})
|
|
68
|
+
// write the answer to the remote
|
|
69
|
+
stream.write({ type: pb.Message.Type.SDP_ANSWER, data: answer.sdp })
|
|
70
|
+
|
|
71
|
+
await pc.setLocalDescription(answer).catch(err => {
|
|
72
|
+
log.error('could not execute setLocalDescription', err)
|
|
73
|
+
answerSentPromise.reject(err)
|
|
74
|
+
throw new Error('Failed to set localDescription')
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
answerSentPromise.resolve()
|
|
78
|
+
|
|
79
|
+
// wait until candidates are connected
|
|
80
|
+
await readCandidatesUntilConnected(connectedPromise, pc, stream)
|
|
81
|
+
return [pc, muxerFactory]
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface ConnectOptions {
|
|
85
|
+
stream: Stream
|
|
86
|
+
signal: AbortSignal
|
|
87
|
+
rtcConfiguration?: RTCConfiguration
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function initiateConnection ({ rtcConfiguration, signal, stream: rawStream }: ConnectOptions): Promise<[RTCPeerConnection, StreamMuxerFactory]> {
|
|
91
|
+
const stream = pbStream(abortableDuplex(rawStream, signal)).pb(pb.Message)
|
|
92
|
+
|
|
93
|
+
// setup peer connection
|
|
94
|
+
const pc = new RTCPeerConnection(rtcConfiguration)
|
|
95
|
+
const muxerFactory = new DataChannelMuxerFactory(pc)
|
|
96
|
+
|
|
97
|
+
const connectedPromise: DeferredPromise<void> = pDefer()
|
|
98
|
+
resolveOnConnected(pc, connectedPromise)
|
|
99
|
+
|
|
100
|
+
// reject the connectedPromise if the signal aborts
|
|
101
|
+
signal.onabort = connectedPromise.reject
|
|
102
|
+
// we create the channel so that the peerconnection has a component for which
|
|
103
|
+
// to collect candidates. The label is not relevant to connection initiation
|
|
104
|
+
// but can be useful for debugging
|
|
105
|
+
const channel = pc.createDataChannel('init')
|
|
106
|
+
// setup callback to write ICE candidates to the remote
|
|
107
|
+
// peer
|
|
108
|
+
pc.onicecandidate = ({ candidate }) => {
|
|
109
|
+
stream.write({
|
|
110
|
+
type: pb.Message.Type.ICE_CANDIDATE,
|
|
111
|
+
data: (candidate != null) ? JSON.stringify(candidate.toJSON()) : ''
|
|
112
|
+
})
|
|
113
|
+
}
|
|
114
|
+
// create an offer
|
|
115
|
+
const offerSdp = await pc.createOffer()
|
|
116
|
+
// write the offer to the stream
|
|
117
|
+
stream.write({ type: pb.Message.Type.SDP_OFFER, data: offerSdp.sdp })
|
|
118
|
+
// set offer as local description
|
|
119
|
+
await pc.setLocalDescription(offerSdp).catch(err => {
|
|
120
|
+
log.error('could not execute setLocalDescription', err)
|
|
121
|
+
throw new Error('Failed to set localDescription')
|
|
122
|
+
})
|
|
123
|
+
|
|
124
|
+
// read answer
|
|
125
|
+
const answerMessage = await stream.read()
|
|
126
|
+
if (answerMessage.type !== pb.Message.Type.SDP_ANSWER) {
|
|
127
|
+
throw new Error('remote should send an SDP answer')
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
const answerSdp = new RTCSessionDescription({ type: 'answer', sdp: answerMessage.data })
|
|
131
|
+
await pc.setRemoteDescription(answerSdp).catch(err => {
|
|
132
|
+
log.error('could not execute setRemoteDescription', err)
|
|
133
|
+
throw new Error('Failed to set remoteDescription')
|
|
134
|
+
})
|
|
135
|
+
|
|
136
|
+
await readCandidatesUntilConnected(connectedPromise, pc, stream)
|
|
137
|
+
channel.close()
|
|
138
|
+
return [pc, muxerFactory]
|
|
139
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { PeerId } from '@libp2p/interface-peer-id'
|
|
2
|
+
import type { ListenerEvents, TransportManager, Upgrader, Listener } from '@libp2p/interface-transport'
|
|
3
|
+
import { EventEmitter } from '@libp2p/interfaces/events'
|
|
4
|
+
import { multiaddr, Multiaddr } from '@multiformats/multiaddr'
|
|
5
|
+
import { inappropriateMultiaddr } from '../error.js'
|
|
6
|
+
import { TRANSPORT } from './transport.js'
|
|
7
|
+
|
|
8
|
+
export interface ListenerOptions {
|
|
9
|
+
peerId: PeerId
|
|
10
|
+
upgrader: Upgrader
|
|
11
|
+
transportManager: TransportManager
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class WebRTCPeerListener extends EventEmitter<ListenerEvents> implements Listener {
|
|
15
|
+
constructor (
|
|
16
|
+
private readonly opts: ListenerOptions
|
|
17
|
+
) {
|
|
18
|
+
super()
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
private getBaseAddress (ma: Multiaddr): Multiaddr {
|
|
22
|
+
const addrs = ma.toString().split(TRANSPORT)
|
|
23
|
+
if (addrs.length < 2) {
|
|
24
|
+
throw inappropriateMultiaddr('base address not found')
|
|
25
|
+
}
|
|
26
|
+
return multiaddr(addrs[0])
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
private listeningAddrs: Multiaddr[] = []
|
|
30
|
+
async listen (ma: Multiaddr): Promise<void> {
|
|
31
|
+
const baseAddr = this.getBaseAddress(ma)
|
|
32
|
+
const tpt = this.opts.transportManager.transportForMultiaddr(baseAddr)
|
|
33
|
+
const listener = tpt?.createListener({ ...this.opts })
|
|
34
|
+
await listener?.listen(baseAddr)
|
|
35
|
+
const listeningAddr = ma.encapsulate(`/p2p/${this.opts.peerId.toString()}`)
|
|
36
|
+
this.listeningAddrs.push(listeningAddr)
|
|
37
|
+
listener?.addEventListener('close', () => {
|
|
38
|
+
this.listeningAddrs = this.listeningAddrs.filter(a => a !== listeningAddr)
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
getAddrs (): Multiaddr[] { return this.listeningAddrs }
|
|
43
|
+
async close (): Promise<void> { }
|
|
44
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
syntax = "proto3";
|
|
2
|
+
|
|
3
|
+
message Message {
|
|
4
|
+
// Specifies type in `data` field.
|
|
5
|
+
enum Type {
|
|
6
|
+
// String of `RTCSessionDescription.sdp`
|
|
7
|
+
SDP_OFFER = 0;
|
|
8
|
+
// String of `RTCSessionDescription.sdp`
|
|
9
|
+
SDP_ANSWER = 1;
|
|
10
|
+
// String of `RTCIceCandidate.toJSON()`
|
|
11
|
+
ICE_CANDIDATE = 2;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
optional Type type = 1;
|
|
15
|
+
optional string data = 2;
|
|
16
|
+
}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
/* eslint-disable import/export */
|
|
2
|
+
/* eslint-disable complexity */
|
|
3
|
+
/* eslint-disable @typescript-eslint/no-namespace */
|
|
4
|
+
/* eslint-disable @typescript-eslint/no-unnecessary-boolean-literal-compare */
|
|
5
|
+
/* eslint-disable @typescript-eslint/no-empty-interface */
|
|
6
|
+
|
|
7
|
+
import { enumeration, encodeMessage, decodeMessage, message } from 'protons-runtime'
|
|
8
|
+
import type { Uint8ArrayList } from 'uint8arraylist'
|
|
9
|
+
import type { Codec } from 'protons-runtime'
|
|
10
|
+
|
|
11
|
+
export interface Message {
|
|
12
|
+
type?: Message.Type
|
|
13
|
+
data?: string
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export namespace Message {
|
|
17
|
+
export enum Type {
|
|
18
|
+
SDP_OFFER = 'SDP_OFFER',
|
|
19
|
+
SDP_ANSWER = 'SDP_ANSWER',
|
|
20
|
+
ICE_CANDIDATE = 'ICE_CANDIDATE'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
enum __TypeValues {
|
|
24
|
+
SDP_OFFER = 0,
|
|
25
|
+
SDP_ANSWER = 1,
|
|
26
|
+
ICE_CANDIDATE = 2
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export namespace Type {
|
|
30
|
+
export const codec = (): Codec<Type> => {
|
|
31
|
+
return enumeration<Type>(__TypeValues)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
let _codec: Codec<Message>
|
|
36
|
+
|
|
37
|
+
export const codec = (): Codec<Message> => {
|
|
38
|
+
if (_codec == null) {
|
|
39
|
+
_codec = message<Message>((obj, w, opts = {}) => {
|
|
40
|
+
if (opts.lengthDelimited !== false) {
|
|
41
|
+
w.fork()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
if (obj.type != null) {
|
|
45
|
+
w.uint32(8)
|
|
46
|
+
Message.Type.codec().encode(obj.type, w)
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (obj.data != null) {
|
|
50
|
+
w.uint32(18)
|
|
51
|
+
w.string(obj.data)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (opts.lengthDelimited !== false) {
|
|
55
|
+
w.ldelim()
|
|
56
|
+
}
|
|
57
|
+
}, (reader, length) => {
|
|
58
|
+
const obj: any = {}
|
|
59
|
+
|
|
60
|
+
const end = length == null ? reader.len : reader.pos + length
|
|
61
|
+
|
|
62
|
+
while (reader.pos < end) {
|
|
63
|
+
const tag = reader.uint32()
|
|
64
|
+
|
|
65
|
+
switch (tag >>> 3) {
|
|
66
|
+
case 1:
|
|
67
|
+
obj.type = Message.Type.codec().decode(reader)
|
|
68
|
+
break
|
|
69
|
+
case 2:
|
|
70
|
+
obj.data = reader.string()
|
|
71
|
+
break
|
|
72
|
+
default:
|
|
73
|
+
reader.skipType(tag & 7)
|
|
74
|
+
break
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return obj
|
|
79
|
+
})
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
return _codec
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export const encode = (obj: Message): Uint8Array => {
|
|
86
|
+
return encodeMessage(obj, Message.codec())
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export const decode = (buf: Uint8Array | Uint8ArrayList): Message => {
|
|
90
|
+
return decodeMessage(buf, Message.codec())
|
|
91
|
+
}
|
|
92
|
+
}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { Connection } from '@libp2p/interface-connection'
|
|
2
|
+
import { CreateListenerOptions, DialOptions, Listener, symbol, Transport } from '@libp2p/interface-transport'
|
|
3
|
+
import type { TransportManager, Upgrader } from '@libp2p/interface-transport'
|
|
4
|
+
import { multiaddr, Multiaddr, protocols } from '@multiformats/multiaddr'
|
|
5
|
+
import type { IncomingStreamData, Registrar } from '@libp2p/interface-registrar'
|
|
6
|
+
import type { PeerId } from '@libp2p/interface-peer-id'
|
|
7
|
+
import { WebRTCMultiaddrConnection } from '../maconn.js'
|
|
8
|
+
import type { Startable } from '@libp2p/interfaces/startable'
|
|
9
|
+
import { WebRTCPeerListener } from './listener.js'
|
|
10
|
+
import type { PeerStore } from '@libp2p/interface-peer-store'
|
|
11
|
+
import { logger } from '@libp2p/logger'
|
|
12
|
+
import { initiateConnection, handleIncomingStream } from './handler.js'
|
|
13
|
+
import { CodeError } from '@libp2p/interfaces/errors'
|
|
14
|
+
import { codes } from '../error.js'
|
|
15
|
+
|
|
16
|
+
const log = logger('libp2p:webrtc:peer')
|
|
17
|
+
|
|
18
|
+
export const TRANSPORT = '/webrtc'
|
|
19
|
+
export const SIGNALING_PROTO_ID = '/webrtc-signaling/0.0.1'
|
|
20
|
+
export const CODE = protocols('webrtc').code
|
|
21
|
+
|
|
22
|
+
export interface WebRTCTransportInit {
|
|
23
|
+
rtcConfiguration?: RTCConfiguration
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface WebRTCTransportComponents {
|
|
27
|
+
peerId: PeerId
|
|
28
|
+
registrar: Registrar
|
|
29
|
+
upgrader: Upgrader
|
|
30
|
+
transportManager: TransportManager
|
|
31
|
+
peerStore: PeerStore
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export class WebRTCTransport implements Transport, Startable {
|
|
35
|
+
private _started = false
|
|
36
|
+
|
|
37
|
+
constructor (
|
|
38
|
+
private readonly components: WebRTCTransportComponents,
|
|
39
|
+
private readonly init: WebRTCTransportInit
|
|
40
|
+
) {
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
isStarted (): boolean {
|
|
44
|
+
return this._started
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async start (): Promise<void> {
|
|
48
|
+
await this.components.registrar.handle(SIGNALING_PROTO_ID, (data) => {
|
|
49
|
+
this._onProtocol(data).catch(err => { log.error('failed to handle incoming connect from %p', data.connection.remotePeer, err) })
|
|
50
|
+
})
|
|
51
|
+
this._started = true
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
async stop (): Promise<void> {
|
|
55
|
+
await this.components.registrar.unhandle(SIGNALING_PROTO_ID)
|
|
56
|
+
this._started = false
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
createListener (options: CreateListenerOptions): Listener {
|
|
60
|
+
return new WebRTCPeerListener(this.components)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
get [Symbol.toStringTag] (): string {
|
|
64
|
+
return '@libp2p/webrtc'
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get [symbol] (): true {
|
|
68
|
+
return true
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
filter (multiaddrs: Multiaddr[]): Multiaddr[] {
|
|
72
|
+
return multiaddrs.filter((ma) => {
|
|
73
|
+
const codes = ma.protoCodes()
|
|
74
|
+
return codes.includes(CODE)
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/*
|
|
79
|
+
* dial connects to a remote via the circuit relay or any other protocol
|
|
80
|
+
* and proceeds to upgrade to a webrtc connection.
|
|
81
|
+
* multiaddr of the form: <multiaddr>/webrtc/p2p/<destination-peer>
|
|
82
|
+
* For a circuit relay, this will be of the form
|
|
83
|
+
* <relay address>/p2p/<relay-peer>/p2p-circuit/webrtc/p2p/<destination-peer>
|
|
84
|
+
*/
|
|
85
|
+
async dial (ma: Multiaddr, options: DialOptions): Promise<Connection> {
|
|
86
|
+
log.trace('dialing address: ', ma)
|
|
87
|
+
const addrs = ma.toString().split(`${TRANSPORT}/`)
|
|
88
|
+
if (addrs.length !== 2) {
|
|
89
|
+
throw new CodeError('invalid multiaddr', codes.ERR_INVALID_MULTIADDR)
|
|
90
|
+
}
|
|
91
|
+
// look for remote peerId
|
|
92
|
+
let remoteAddr = multiaddr(addrs[0])
|
|
93
|
+
const destination = multiaddr('/' + addrs[1])
|
|
94
|
+
|
|
95
|
+
const destinationIdString = destination.getPeerId()
|
|
96
|
+
if (destinationIdString == null) {
|
|
97
|
+
throw new CodeError('bad destination', codes.ERR_INVALID_MULTIADDR)
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
if (options.signal == null) {
|
|
101
|
+
const controller = new AbortController()
|
|
102
|
+
options.signal = controller.signal
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
const lastProtoInRemote = remoteAddr.protos().pop()
|
|
106
|
+
if (lastProtoInRemote === undefined) {
|
|
107
|
+
throw new CodeError('invalid multiaddr', codes.ERR_INVALID_MULTIADDR)
|
|
108
|
+
}
|
|
109
|
+
if (lastProtoInRemote.name !== 'p2p') {
|
|
110
|
+
remoteAddr = remoteAddr.encapsulate(`/p2p/${destinationIdString}`)
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
const connection = await this.components.transportManager.dial(remoteAddr)
|
|
114
|
+
|
|
115
|
+
const rawStream = await connection.newStream([SIGNALING_PROTO_ID], options)
|
|
116
|
+
|
|
117
|
+
try {
|
|
118
|
+
const [pc, muxerFactory] = await initiateConnection({
|
|
119
|
+
stream: rawStream,
|
|
120
|
+
rtcConfiguration: this.init.rtcConfiguration,
|
|
121
|
+
signal: options.signal
|
|
122
|
+
})
|
|
123
|
+
const result = await options.upgrader.upgradeOutbound(
|
|
124
|
+
new WebRTCMultiaddrConnection({
|
|
125
|
+
peerConnection: pc,
|
|
126
|
+
timeline: { open: (new Date()).getTime() },
|
|
127
|
+
remoteAddr: connection.remoteAddr
|
|
128
|
+
}),
|
|
129
|
+
{
|
|
130
|
+
skipProtection: true,
|
|
131
|
+
skipEncryption: true,
|
|
132
|
+
muxerFactory
|
|
133
|
+
}
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
// close the stream if SDP has been exchanged successfully
|
|
137
|
+
rawStream.close()
|
|
138
|
+
return result
|
|
139
|
+
} catch (err) {
|
|
140
|
+
// reset the stream in case of any error
|
|
141
|
+
rawStream.reset()
|
|
142
|
+
throw err
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async _onProtocol ({ connection, stream }: IncomingStreamData): Promise<void> {
|
|
147
|
+
try {
|
|
148
|
+
const [pc, muxerFactory] = await handleIncomingStream({
|
|
149
|
+
rtcConfiguration: this.init.rtcConfiguration,
|
|
150
|
+
connection,
|
|
151
|
+
stream
|
|
152
|
+
})
|
|
153
|
+
await this.components.upgrader.upgradeInbound(new WebRTCMultiaddrConnection({
|
|
154
|
+
peerConnection: pc,
|
|
155
|
+
timeline: { open: (new Date()).getTime() },
|
|
156
|
+
remoteAddr: connection.remoteAddr
|
|
157
|
+
}), {
|
|
158
|
+
skipEncryption: true,
|
|
159
|
+
skipProtection: true,
|
|
160
|
+
muxerFactory
|
|
161
|
+
})
|
|
162
|
+
} catch (err) {
|
|
163
|
+
stream.reset()
|
|
164
|
+
throw err
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { logger } from '@libp2p/logger'
|
|
2
|
+
import type { DeferredPromise } from 'p-defer'
|
|
3
|
+
import * as pb from './pb/index.js'
|
|
4
|
+
import { detect } from 'detect-browser'
|
|
5
|
+
|
|
6
|
+
const browser = detect()
|
|
7
|
+
const isFirefox = ((browser != null) && browser.name === 'firefox')
|
|
8
|
+
|
|
9
|
+
interface MessageStream {
|
|
10
|
+
read: () => Promise<pb.Message>
|
|
11
|
+
write: (d: pb.Message) => void | Promise<void>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const log = logger('libp2p:webrtc:peer:util')
|
|
15
|
+
|
|
16
|
+
export const readCandidatesUntilConnected = async (connectedPromise: DeferredPromise<void>, pc: RTCPeerConnection, stream: MessageStream): Promise<void> => {
|
|
17
|
+
while (true) {
|
|
18
|
+
const readResult = await Promise.race([connectedPromise.promise, stream.read()])
|
|
19
|
+
// check if readResult is a message
|
|
20
|
+
if (readResult instanceof Object) {
|
|
21
|
+
const message = readResult
|
|
22
|
+
if (message.type !== pb.Message.Type.ICE_CANDIDATE) {
|
|
23
|
+
throw new Error('expected only ice candidates')
|
|
24
|
+
}
|
|
25
|
+
// end of candidates has been signalled
|
|
26
|
+
if (message.data == null || message.data === '') {
|
|
27
|
+
log.trace('end-of-candidates received')
|
|
28
|
+
break
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
log.trace('received new ICE candidate: %s', message.data)
|
|
32
|
+
try {
|
|
33
|
+
await pc.addIceCandidate(new RTCIceCandidate(JSON.parse(message.data)))
|
|
34
|
+
} catch (err) {
|
|
35
|
+
log.error('bad candidate received: ', err)
|
|
36
|
+
throw new Error('bad candidate received')
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
// connected promise resolved
|
|
40
|
+
break
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
await connectedPromise.promise
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function resolveOnConnected (pc: RTCPeerConnection, promise: DeferredPromise<void>): void {
|
|
47
|
+
pc[isFirefox ? 'oniceconnectionstatechange' : 'onconnectionstatechange'] = (_) => {
|
|
48
|
+
log.trace('receiver peerConnectionState state: ', pc.connectionState)
|
|
49
|
+
switch (isFirefox ? pc.iceConnectionState : pc.connectionState) {
|
|
50
|
+
case 'connected':
|
|
51
|
+
promise.resolve()
|
|
52
|
+
break
|
|
53
|
+
case 'failed':
|
|
54
|
+
case 'disconnected':
|
|
55
|
+
case 'closed':
|
|
56
|
+
promise.reject()
|
|
57
|
+
break
|
|
58
|
+
default:
|
|
59
|
+
break
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
package/src/stream.ts
CHANGED
|
@@ -421,8 +421,6 @@ export class WebRTCStream implements Stream {
|
|
|
421
421
|
* @see this.closeWrite
|
|
422
422
|
*/
|
|
423
423
|
reset (): void {
|
|
424
|
-
// TODO Why are you resetting the stat here?
|
|
425
|
-
this.stat = defaultStat(this.stat.direction)
|
|
426
424
|
const [currentState, nextState] = this.streamState.transition({ direction: 'outbound', flag: pb.Message_Flag.RESET })
|
|
427
425
|
if (currentState === nextState) {
|
|
428
426
|
// No change, no op
|
|
@@ -454,4 +452,11 @@ export class WebRTCStream implements Stream {
|
|
|
454
452
|
}
|
|
455
453
|
}
|
|
456
454
|
}
|
|
455
|
+
|
|
456
|
+
eq (stream: Stream): boolean {
|
|
457
|
+
if (stream instanceof WebRTCStream) {
|
|
458
|
+
return stream.channel.id === this.channel.id
|
|
459
|
+
}
|
|
460
|
+
return false
|
|
461
|
+
}
|
|
457
462
|
}
|
package/src/transport.ts
CHANGED
|
@@ -15,6 +15,7 @@ import type { WebRTCDialOptions } from './options.js'
|
|
|
15
15
|
import * as sdp from './sdp.js'
|
|
16
16
|
import { WebRTCStream } from './stream.js'
|
|
17
17
|
import { genUfrag } from './util.js'
|
|
18
|
+
import { protocols } from '@multiformats/multiaddr'
|
|
18
19
|
|
|
19
20
|
const log = logger('libp2p:webrtc:transport')
|
|
20
21
|
|
|
@@ -28,30 +29,30 @@ const HANDSHAKE_TIMEOUT_MS = 10_000
|
|
|
28
29
|
*
|
|
29
30
|
* {@link https://github.com/multiformats/multiaddr/blob/master/protocols.csv}
|
|
30
31
|
*/
|
|
31
|
-
export const WEBRTC_CODE: number =
|
|
32
|
+
export const WEBRTC_CODE: number = protocols('webrtc-direct').code
|
|
32
33
|
|
|
33
34
|
/**
|
|
34
35
|
* Created by converting the hexadecimal protocol code to an integer.
|
|
35
36
|
*
|
|
36
37
|
* {@link https://github.com/multiformats/multiaddr/blob/master/protocols.csv}
|
|
37
38
|
*/
|
|
38
|
-
export const CERTHASH_CODE: number =
|
|
39
|
+
export const CERTHASH_CODE: number = protocols('certhash').code
|
|
39
40
|
|
|
40
41
|
/**
|
|
41
42
|
* The peer for this transport
|
|
42
43
|
*/
|
|
43
44
|
// @TODO(ddimaria): seems like an unnessary abstraction, consider removing
|
|
44
|
-
export interface
|
|
45
|
+
export interface WebRTCDirectTransportComponents {
|
|
45
46
|
peerId: PeerId
|
|
46
47
|
}
|
|
47
48
|
|
|
48
|
-
export class
|
|
49
|
+
export class WebRTCDirectTransport implements Transport {
|
|
49
50
|
/**
|
|
50
51
|
* The peer for this transport
|
|
51
52
|
*/
|
|
52
|
-
private readonly components:
|
|
53
|
+
private readonly components: WebRTCDirectTransportComponents
|
|
53
54
|
|
|
54
|
-
constructor (components:
|
|
55
|
+
constructor (components: WebRTCDirectTransportComponents) {
|
|
55
56
|
this.components = components
|
|
56
57
|
}
|
|
57
58
|
|
|
@@ -82,7 +83,7 @@ export class WebRTCTransport implements Transport {
|
|
|
82
83
|
* Implement toString() for WebRTCTransport
|
|
83
84
|
*/
|
|
84
85
|
get [Symbol.toStringTag] (): string {
|
|
85
|
-
return '@libp2p/webrtc'
|
|
86
|
+
return '@libp2p/webrtc-direct'
|
|
86
87
|
}
|
|
87
88
|
|
|
88
89
|
/**
|
|
@@ -238,5 +239,5 @@ export class WebRTCTransport implements Transport {
|
|
|
238
239
|
*/
|
|
239
240
|
function validMa (ma: Multiaddr): boolean {
|
|
240
241
|
const codes = ma.protoCodes()
|
|
241
|
-
return codes.includes(WEBRTC_CODE) && codes.includes(CERTHASH_CODE) && ma.getPeerId() != null
|
|
242
|
+
return codes.includes(WEBRTC_CODE) && codes.includes(CERTHASH_CODE) && ma.getPeerId() != null && !codes.includes(protocols('p2p-circuit').code)
|
|
242
243
|
}
|