github.com/pion/webrtc/v4@v4.0.1/peerconnection_js.go (about)

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