1// +build js,wasm
2
3// Package webrtc implements the WebRTC 1.0 as defined in W3C WebRTC specification document.
4package webrtc
5
6import (
7	"syscall/js"
8
9	"github.com/pion/webrtc/v2/pkg/rtcerr"
10)
11
12// PeerConnection represents a WebRTC connection that establishes a
13// peer-to-peer communications with another PeerConnection instance in a
14// browser, or to another endpoint implementing the required protocols.
15type PeerConnection struct {
16	// Pointer to the underlying JavaScript RTCPeerConnection object.
17	underlying js.Value
18
19	// Keep track of handlers/callbacks so we can call Release as required by the
20	// syscall/js API. Initially nil.
21	onSignalingStateChangeHandler     *js.Func
22	onDataChannelHandler              *js.Func
23	onConnectionStateChangeHandler    *js.Func
24	onICEConnectionStateChangeHandler *js.Func
25	onICECandidateHandler             *js.Func
26	onICEGatheringStateChangeHandler  *js.Func
27
28	// A reference to the associated API state used by this connection
29	api *API
30}
31
32// NewPeerConnection creates a peerconnection.
33func NewPeerConnection(configuration Configuration) (*PeerConnection, error) {
34	api := NewAPI()
35	return api.NewPeerConnection(configuration)
36}
37
38// NewPeerConnection creates a new PeerConnection with the provided configuration against the received API object
39func (api *API) NewPeerConnection(configuration Configuration) (_ *PeerConnection, err error) {
40	defer func() {
41		if e := recover(); e != nil {
42			err = recoveryToError(e)
43		}
44	}()
45	configMap := configurationToValue(configuration)
46	underlying := js.Global().Get("window").Get("RTCPeerConnection").New(configMap)
47	return &PeerConnection{
48		underlying: underlying,
49		api:        api,
50	}, nil
51}
52
53// OnSignalingStateChange sets an event handler which is invoked when the
54// peer connection's signaling state changes
55func (pc *PeerConnection) OnSignalingStateChange(f func(SignalingState)) {
56	if pc.onSignalingStateChangeHandler != nil {
57		oldHandler := pc.onSignalingStateChangeHandler
58		defer oldHandler.Release()
59	}
60	onSignalingStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
61		state := newSignalingState(args[0].String())
62		go f(state)
63		return js.Undefined()
64	})
65	pc.onSignalingStateChangeHandler = &onSignalingStateChangeHandler
66	pc.underlying.Set("onsignalingstatechange", onSignalingStateChangeHandler)
67}
68
69// OnDataChannel sets an event handler which is invoked when a data
70// channel message arrives from a remote peer.
71func (pc *PeerConnection) OnDataChannel(f func(*DataChannel)) {
72	if pc.onDataChannelHandler != nil {
73		oldHandler := pc.onDataChannelHandler
74		defer oldHandler.Release()
75	}
76	onDataChannelHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
77		// pion/webrtc/projects/15
78		// This reference to the underlying DataChannel doesn't know
79		// about any other references to the same DataChannel. This might result in
80		// memory leaks where we don't clean up handler functions. Could possibly fix
81		// by keeping a mutex-protected list of all DataChannel references as a
82		// property of this PeerConnection, but at the cost of additional overhead.
83		dataChannel := &DataChannel{
84			underlying: args[0].Get("channel"),
85			api:        pc.api,
86		}
87		go f(dataChannel)
88		return js.Undefined()
89	})
90	pc.onDataChannelHandler = &onDataChannelHandler
91	pc.underlying.Set("ondatachannel", onDataChannelHandler)
92}
93
94// OnICEConnectionStateChange sets an event handler which is called
95// when an ICE connection state is changed.
96func (pc *PeerConnection) OnICEConnectionStateChange(f func(ICEConnectionState)) {
97	if pc.onICEConnectionStateChangeHandler != nil {
98		oldHandler := pc.onICEConnectionStateChangeHandler
99		defer oldHandler.Release()
100	}
101	onICEConnectionStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
102		connectionState := NewICEConnectionState(pc.underlying.Get("iceConnectionState").String())
103		go f(connectionState)
104		return js.Undefined()
105	})
106	pc.onICEConnectionStateChangeHandler = &onICEConnectionStateChangeHandler
107	pc.underlying.Set("oniceconnectionstatechange", onICEConnectionStateChangeHandler)
108}
109
110// OnConnectionStateChange sets an event handler which is called
111// when an PeerConnectionState is changed.
112func (pc *PeerConnection) OnConnectionStateChange(f func(PeerConnectionState)) {
113	if pc.onConnectionStateChangeHandler != nil {
114		oldHandler := pc.onConnectionStateChangeHandler
115		defer oldHandler.Release()
116	}
117	onConnectionStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
118		connectionState := newPeerConnectionState(pc.underlying.Get("connectionState").String())
119		go f(connectionState)
120		return js.Undefined()
121	})
122	pc.onConnectionStateChangeHandler = &onConnectionStateChangeHandler
123	pc.underlying.Set("onconnectionstatechange", onConnectionStateChangeHandler)
124}
125
126func (pc *PeerConnection) checkConfiguration(configuration Configuration) error {
127	// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-setconfiguration (step #2)
128	if pc.ConnectionState() == PeerConnectionStateClosed {
129		return &rtcerr.InvalidStateError{Err: ErrConnectionClosed}
130	}
131
132	existingConfig := pc.GetConfiguration()
133	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #3)
134	if configuration.PeerIdentity != "" {
135		if configuration.PeerIdentity != existingConfig.PeerIdentity {
136			return &rtcerr.InvalidModificationError{Err: ErrModifyingPeerIdentity}
137		}
138	}
139
140	// https://github.com/pion/webrtc/issues/513
141	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #4)
142	// if len(configuration.Certificates) > 0 {
143	// 	if len(configuration.Certificates) != len(existingConfiguration.Certificates) {
144	// 		return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}
145	// 	}
146
147	// 	for i, certificate := range configuration.Certificates {
148	// 		if !pc.configuration.Certificates[i].Equals(certificate) {
149	// 			return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates}
150	// 		}
151	// 	}
152	// 	pc.configuration.Certificates = configuration.Certificates
153	// }
154
155	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #5)
156	if configuration.BundlePolicy != BundlePolicy(Unknown) {
157		if configuration.BundlePolicy != existingConfig.BundlePolicy {
158			return &rtcerr.InvalidModificationError{Err: ErrModifyingBundlePolicy}
159		}
160	}
161
162	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #6)
163	if configuration.RTCPMuxPolicy != RTCPMuxPolicy(Unknown) {
164		if configuration.RTCPMuxPolicy != existingConfig.RTCPMuxPolicy {
165			return &rtcerr.InvalidModificationError{Err: ErrModifyingRTCPMuxPolicy}
166		}
167	}
168
169	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #7)
170	if configuration.ICECandidatePoolSize != 0 {
171		if configuration.ICECandidatePoolSize != existingConfig.ICECandidatePoolSize &&
172			pc.LocalDescription() != nil {
173			return &rtcerr.InvalidModificationError{Err: ErrModifyingICECandidatePoolSize}
174		}
175	}
176
177	// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11)
178	if len(configuration.ICEServers) > 0 {
179		// https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3)
180		for _, server := range configuration.ICEServers {
181			if _, err := server.validate(); err != nil {
182				return err
183			}
184		}
185	}
186	return nil
187}
188
189// SetConfiguration updates the configuration of this PeerConnection object.
190func (pc *PeerConnection) SetConfiguration(configuration Configuration) (err error) {
191	defer func() {
192		if e := recover(); e != nil {
193			err = recoveryToError(e)
194		}
195	}()
196	if err := pc.checkConfiguration(configuration); err != nil {
197		return err
198	}
199	configMap := configurationToValue(configuration)
200	pc.underlying.Call("setConfiguration", configMap)
201	return nil
202}
203
204// GetConfiguration returns a Configuration object representing the current
205// configuration of this PeerConnection object. The returned object is a
206// copy and direct mutation on it will not take affect until SetConfiguration
207// has been called with Configuration passed as its only argument.
208// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getconfiguration
209func (pc *PeerConnection) GetConfiguration() Configuration {
210	return valueToConfiguration(pc.underlying.Call("getConfiguration"))
211}
212
213// CreateOffer starts the PeerConnection and generates the localDescription
214func (pc *PeerConnection) CreateOffer(options *OfferOptions) (_ SessionDescription, err error) {
215	defer func() {
216		if e := recover(); e != nil {
217			err = recoveryToError(e)
218		}
219	}()
220	promise := pc.underlying.Call("createOffer", offerOptionsToValue(options))
221	desc, err := awaitPromise(promise)
222	if err != nil {
223		return SessionDescription{}, err
224	}
225	return *valueToSessionDescription(desc), nil
226}
227
228// CreateAnswer starts the PeerConnection and generates the localDescription
229func (pc *PeerConnection) CreateAnswer(options *AnswerOptions) (_ SessionDescription, err error) {
230	defer func() {
231		if e := recover(); e != nil {
232			err = recoveryToError(e)
233		}
234	}()
235	promise := pc.underlying.Call("createAnswer", answerOptionsToValue(options))
236	desc, err := awaitPromise(promise)
237	if err != nil {
238		return SessionDescription{}, err
239	}
240	return *valueToSessionDescription(desc), nil
241}
242
243// SetLocalDescription sets the SessionDescription of the local peer
244func (pc *PeerConnection) SetLocalDescription(desc SessionDescription) (err error) {
245	defer func() {
246		if e := recover(); e != nil {
247			err = recoveryToError(e)
248		}
249	}()
250	promise := pc.underlying.Call("setLocalDescription", sessionDescriptionToValue(&desc))
251	_, err = awaitPromise(promise)
252	return err
253}
254
255// LocalDescription returns PendingLocalDescription if it is not null and
256// otherwise it returns CurrentLocalDescription. This property is used to
257// determine if setLocalDescription has already been called.
258// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-localdescription
259func (pc *PeerConnection) LocalDescription() *SessionDescription {
260	return valueToSessionDescription(pc.underlying.Get("localDescription"))
261}
262
263// SetRemoteDescription sets the SessionDescription of the remote peer
264func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) (err error) {
265	defer func() {
266		if e := recover(); e != nil {
267			err = recoveryToError(e)
268		}
269	}()
270	promise := pc.underlying.Call("setRemoteDescription", sessionDescriptionToValue(&desc))
271	_, err = awaitPromise(promise)
272	return err
273}
274
275// RemoteDescription returns PendingRemoteDescription if it is not null and
276// otherwise it returns CurrentRemoteDescription. This property is used to
277// determine if setRemoteDescription has already been called.
278// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-remotedescription
279func (pc *PeerConnection) RemoteDescription() *SessionDescription {
280	return valueToSessionDescription(pc.underlying.Get("remoteDescription"))
281}
282
283// AddICECandidate accepts an ICE candidate string and adds it
284// to the existing set of candidates
285func (pc *PeerConnection) AddICECandidate(candidate ICECandidateInit) (err error) {
286	defer func() {
287		if e := recover(); e != nil {
288			err = recoveryToError(e)
289		}
290	}()
291	promise := pc.underlying.Call("addIceCandidate", iceCandidateInitToValue(candidate))
292	_, err = awaitPromise(promise)
293	return err
294}
295
296// ICEConnectionState returns the ICE connection state of the
297// PeerConnection instance.
298func (pc *PeerConnection) ICEConnectionState() ICEConnectionState {
299	return NewICEConnectionState(pc.underlying.Get("iceConnectionState").String())
300}
301
302// OnICECandidate sets an event handler which is invoked when a new ICE
303// candidate is found.
304func (pc *PeerConnection) OnICECandidate(f func(candidate *ICECandidate)) {
305	if pc.onICECandidateHandler != nil {
306		oldHandler := pc.onICECandidateHandler
307		defer oldHandler.Release()
308	}
309	onICECandidateHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
310		candidate := valueToICECandidate(args[0].Get("candidate"))
311		go f(candidate)
312		return js.Undefined()
313	})
314	pc.onICECandidateHandler = &onICECandidateHandler
315	pc.underlying.Set("onicecandidate", onICECandidateHandler)
316}
317
318// OnICEGatheringStateChange sets an event handler which is invoked when the
319// ICE candidate gathering state has changed.
320func (pc *PeerConnection) OnICEGatheringStateChange(f func()) {
321	if pc.onICEGatheringStateChangeHandler != nil {
322		oldHandler := pc.onICEGatheringStateChangeHandler
323		defer oldHandler.Release()
324	}
325	onICEGatheringStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
326		go f()
327		return js.Undefined()
328	})
329	pc.onICEGatheringStateChangeHandler = &onICEGatheringStateChangeHandler
330	pc.underlying.Set("onicegatheringstatechange", onICEGatheringStateChangeHandler)
331}
332
333// CreateDataChannel creates a new DataChannel object with the given label
334// and optional DataChannelInit used to configure properties of the
335// underlying channel such as data reliability.
336func (pc *PeerConnection) CreateDataChannel(label string, options *DataChannelInit) (_ *DataChannel, err error) {
337	defer func() {
338		if e := recover(); e != nil {
339			err = recoveryToError(e)
340		}
341	}()
342	channel := pc.underlying.Call("createDataChannel", label, dataChannelInitToValue(options))
343	return &DataChannel{
344		underlying: channel,
345		api:        pc.api,
346	}, nil
347}
348
349// SetIdentityProvider is used to configure an identity provider to generate identity assertions
350func (pc *PeerConnection) SetIdentityProvider(provider string) (err error) {
351	defer func() {
352		if e := recover(); e != nil {
353			err = recoveryToError(e)
354		}
355	}()
356	pc.underlying.Call("setIdentityProvider", provider)
357	return nil
358}
359
360// Close ends the PeerConnection
361func (pc *PeerConnection) Close() (err error) {
362	defer func() {
363		if e := recover(); e != nil {
364			err = recoveryToError(e)
365		}
366	}()
367
368	pc.underlying.Call("close")
369
370	// Release any handlers as required by the syscall/js API.
371	if pc.onSignalingStateChangeHandler != nil {
372		pc.onSignalingStateChangeHandler.Release()
373	}
374	if pc.onDataChannelHandler != nil {
375		pc.onDataChannelHandler.Release()
376	}
377	if pc.onConnectionStateChangeHandler != nil {
378		pc.onConnectionStateChangeHandler.Release()
379	}
380	if pc.onICEConnectionStateChangeHandler != nil {
381		pc.onICEConnectionStateChangeHandler.Release()
382	}
383	if pc.onICECandidateHandler != nil {
384		pc.onICECandidateHandler.Release()
385	}
386	if pc.onICEGatheringStateChangeHandler != nil {
387		pc.onICEGatheringStateChangeHandler.Release()
388	}
389
390	return nil
391}
392
393// CurrentLocalDescription represents the local description that was
394// successfully negotiated the last time the PeerConnection transitioned
395// into the stable state plus any local candidates that have been generated
396// by the ICEAgent since the offer or answer was created.
397func (pc *PeerConnection) CurrentLocalDescription() *SessionDescription {
398	desc := pc.underlying.Get("currentLocalDescription")
399	return valueToSessionDescription(desc)
400}
401
402// PendingLocalDescription represents a local description that is in the
403// process of being negotiated plus any local candidates that have been
404// generated by the ICEAgent since the offer or answer was created. If the
405// PeerConnection is in the stable state, the value is null.
406func (pc *PeerConnection) PendingLocalDescription() *SessionDescription {
407	desc := pc.underlying.Get("pendingLocalDescription")
408	return valueToSessionDescription(desc)
409}
410
411// CurrentRemoteDescription represents the last remote description that was
412// successfully negotiated the last time the PeerConnection transitioned
413// into the stable state plus any remote candidates that have been supplied
414// via AddICECandidate() since the offer or answer was created.
415func (pc *PeerConnection) CurrentRemoteDescription() *SessionDescription {
416	desc := pc.underlying.Get("currentRemoteDescription")
417	return valueToSessionDescription(desc)
418}
419
420// PendingRemoteDescription represents a remote description that is in the
421// process of being negotiated, complete with any remote candidates that
422// have been supplied via AddICECandidate() since the offer or answer was
423// created. If the PeerConnection is in the stable state, the value is
424// null.
425func (pc *PeerConnection) PendingRemoteDescription() *SessionDescription {
426	desc := pc.underlying.Get("pendingRemoteDescription")
427	return valueToSessionDescription(desc)
428}
429
430// SignalingState returns the signaling state of the PeerConnection instance.
431func (pc *PeerConnection) SignalingState() SignalingState {
432	rawState := pc.underlying.Get("signalingState").String()
433	return newSignalingState(rawState)
434}
435
436// ICEGatheringState attribute the ICE gathering state of the PeerConnection
437// instance.
438func (pc *PeerConnection) ICEGatheringState() ICEGatheringState {
439	rawState := pc.underlying.Get("iceGatheringState").String()
440	return NewICEGatheringState(rawState)
441}
442
443// ConnectionState attribute the connection state of the PeerConnection
444// instance.
445func (pc *PeerConnection) ConnectionState() PeerConnectionState {
446	rawState := pc.underlying.Get("connectionState").String()
447	return newPeerConnectionState(rawState)
448}
449
450// Converts a Configuration to js.Value so it can be passed
451// through to the JavaScript WebRTC API. Any zero values are converted to
452// js.Undefined(), which will result in the default value being used.
453func configurationToValue(configuration Configuration) js.Value {
454	return js.ValueOf(map[string]interface{}{
455		"iceServers":           iceServersToValue(configuration.ICEServers),
456		"iceTransportPolicy":   stringEnumToValueOrUndefined(configuration.ICETransportPolicy.String()),
457		"bundlePolicy":         stringEnumToValueOrUndefined(configuration.BundlePolicy.String()),
458		"rtcpMuxPolicy":        stringEnumToValueOrUndefined(configuration.RTCPMuxPolicy.String()),
459		"peerIdentity":         stringToValueOrUndefined(configuration.PeerIdentity),
460		"iceCandidatePoolSize": uint8ToValueOrUndefined(configuration.ICECandidatePoolSize),
461
462		// Note: Certificates are not currently supported.
463		// "certificates": configuration.Certificates,
464	})
465}
466
467func iceServersToValue(iceServers []ICEServer) js.Value {
468	if len(iceServers) == 0 {
469		return js.Undefined()
470	}
471	maps := make([]interface{}, len(iceServers))
472	for i, server := range iceServers {
473		maps[i] = iceServerToValue(server)
474	}
475	return js.ValueOf(maps)
476}
477
478func iceServerToValue(server ICEServer) js.Value {
479	return js.ValueOf(map[string]interface{}{
480		"urls":     stringsToValue(server.URLs), // required
481		"username": stringToValueOrUndefined(server.Username),
482		// Note: credential and credentialType are not currently supported.
483		// "credential":     interfaceToValueOrUndefined(server.Credential),
484		// "credentialType": stringEnumToValueOrUndefined(server.CredentialType.String()),
485	})
486}
487
488func valueToConfiguration(configValue js.Value) Configuration {
489	if configValue == js.Null() || configValue == js.Undefined() {
490		return Configuration{}
491	}
492	return Configuration{
493		ICEServers:           valueToICEServers(configValue.Get("iceServers")),
494		ICETransportPolicy:   NewICETransportPolicy(valueToStringOrZero(configValue.Get("iceTransportPolicy"))),
495		BundlePolicy:         newBundlePolicy(valueToStringOrZero(configValue.Get("bundlePolicy"))),
496		RTCPMuxPolicy:        newRTCPMuxPolicy(valueToStringOrZero(configValue.Get("rtcpMuxPolicy"))),
497		PeerIdentity:         valueToStringOrZero(configValue.Get("peerIdentity")),
498		ICECandidatePoolSize: valueToUint8OrZero(configValue.Get("iceCandidatePoolSize")),
499
500		// Note: Certificates are not supported.
501		// Certificates []Certificate
502	}
503}
504
505func valueToICEServers(iceServersValue js.Value) []ICEServer {
506	if iceServersValue == js.Null() || iceServersValue == js.Undefined() {
507		return nil
508	}
509	iceServers := make([]ICEServer, iceServersValue.Length())
510	for i := 0; i < iceServersValue.Length(); i++ {
511		iceServers[i] = valueToICEServer(iceServersValue.Index(i))
512	}
513	return iceServers
514}
515
516func valueToICEServer(iceServerValue js.Value) ICEServer {
517	return ICEServer{
518		URLs:     valueToStrings(iceServerValue.Get("urls")), // required
519		Username: valueToStringOrZero(iceServerValue.Get("username")),
520		// Note: Credential and CredentialType are not currently supported.
521		// Credential: iceServerValue.Get("credential"),
522		// CredentialType: newICECredentialType(valueToStringOrZero(iceServerValue.Get("credentialType"))),
523	}
524}
525
526func valueToICECandidate(val js.Value) *ICECandidate {
527	if val == js.Null() || val == js.Undefined() {
528		return nil
529	}
530	protocol, _ := NewICEProtocol(val.Get("protocol").String())
531	candidateType, _ := NewICECandidateType(val.Get("type").String())
532	return &ICECandidate{
533		Foundation:     val.Get("foundation").String(),
534		Priority:       valueToUint32OrZero(val.Get("priority")),
535		Address:        val.Get("address").String(),
536		Protocol:       protocol,
537		Port:           valueToUint16OrZero(val.Get("port")),
538		Typ:            candidateType,
539		Component:      stringToComponentIDOrZero(val.Get("component").String()),
540		RelatedAddress: val.Get("relatedAddress").String(),
541		RelatedPort:    valueToUint16OrZero(val.Get("relatedPort")),
542	}
543}
544
545func stringToComponentIDOrZero(val string) uint16 {
546	// See: https://developer.mozilla.org/en-US/docs/Web/API/RTCIceComponent
547	switch val {
548	case "rtp":
549		return 1
550	case "rtcp":
551		return 2
552	}
553	return 0
554}
555
556func sessionDescriptionToValue(desc *SessionDescription) js.Value {
557	if desc == nil {
558		return js.Undefined()
559	}
560	return js.ValueOf(map[string]interface{}{
561		"type": desc.Type.String(),
562		"sdp":  desc.SDP,
563	})
564}
565
566func valueToSessionDescription(descValue js.Value) *SessionDescription {
567	if descValue == js.Null() || descValue == js.Undefined() {
568		return nil
569	}
570	return &SessionDescription{
571		Type: newSDPType(descValue.Get("type").String()),
572		SDP:  descValue.Get("sdp").String(),
573	}
574}
575
576func offerOptionsToValue(offerOptions *OfferOptions) js.Value {
577	if offerOptions == nil {
578		return js.Undefined()
579	}
580	return js.ValueOf(map[string]interface{}{
581		"iceRestart":             offerOptions.ICERestart,
582		"voiceActivityDetection": offerOptions.VoiceActivityDetection,
583	})
584}
585
586func answerOptionsToValue(answerOptions *AnswerOptions) js.Value {
587	if answerOptions == nil {
588		return js.Undefined()
589	}
590	return js.ValueOf(map[string]interface{}{
591		"voiceActivityDetection": answerOptions.VoiceActivityDetection,
592	})
593}
594
595func iceCandidateInitToValue(candidate ICECandidateInit) js.Value {
596	return js.ValueOf(map[string]interface{}{
597		"candidate":        candidate.Candidate,
598		"sdpMid":           stringPointerToValue(candidate.SDPMid),
599		"sdpMLineIndex":    uint16PointerToValue(candidate.SDPMLineIndex),
600		"usernameFragment": candidate.UsernameFragment,
601	})
602}
603
604func dataChannelInitToValue(options *DataChannelInit) js.Value {
605	if options == nil {
606		return js.Undefined()
607	}
608
609	maxPacketLifeTime := uint16PointerToValue(options.MaxPacketLifeTime)
610	return js.ValueOf(map[string]interface{}{
611		"ordered":           boolPointerToValue(options.Ordered),
612		"maxPacketLifeTime": maxPacketLifeTime,
613		// See https://bugs.chromium.org/p/chromium/issues/detail?id=696681
614		// Chrome calls this "maxRetransmitTime"
615		"maxRetransmitTime": maxPacketLifeTime,
616		"maxRetransmits":    uint16PointerToValue(options.MaxRetransmits),
617		"protocol":          stringPointerToValue(options.Protocol),
618		"negotiated":        boolPointerToValue(options.Negotiated),
619		"id":                uint16PointerToValue(options.ID),
620	})
621}
622