diff options
Diffstat (limited to 'vendor/github.com/pion/webrtc/v3/peerconnection_js.go')
-rw-r--r-- | vendor/github.com/pion/webrtc/v3/peerconnection_js.go | 676 |
1 files changed, 676 insertions, 0 deletions
diff --git a/vendor/github.com/pion/webrtc/v3/peerconnection_js.go b/vendor/github.com/pion/webrtc/v3/peerconnection_js.go new file mode 100644 index 0000000..c293194 --- /dev/null +++ b/vendor/github.com/pion/webrtc/v3/peerconnection_js.go @@ -0,0 +1,676 @@ +// +build js,wasm + +// Package webrtc implements the WebRTC 1.0 as defined in W3C WebRTC specification document. +package webrtc + +import ( + "syscall/js" + + "github.com/pion/ice/v2" + "github.com/pion/webrtc/v3/pkg/rtcerr" +) + +// PeerConnection represents a WebRTC connection that establishes a +// peer-to-peer communications with another PeerConnection instance in a +// browser, or to another endpoint implementing the required protocols. +type PeerConnection struct { + // Pointer to the underlying JavaScript RTCPeerConnection object. + underlying js.Value + + // Keep track of handlers/callbacks so we can call Release as required by the + // syscall/js API. Initially nil. + onSignalingStateChangeHandler *js.Func + onDataChannelHandler *js.Func + onNegotiationNeededHandler *js.Func + onConnectionStateChangeHandler *js.Func + onICEConnectionStateChangeHandler *js.Func + onICECandidateHandler *js.Func + onICEGatheringStateChangeHandler *js.Func + + // Used by GatheringCompletePromise + onGatherCompleteHandler func() + + // A reference to the associated API state used by this connection + api *API +} + +// NewPeerConnection creates a peerconnection. +func NewPeerConnection(configuration Configuration) (*PeerConnection, error) { + api := NewAPI() + return api.NewPeerConnection(configuration) +} + +// NewPeerConnection creates a new PeerConnection with the provided configuration against the received API object +func (api *API) NewPeerConnection(configuration Configuration) (_ *PeerConnection, err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + configMap := configurationToValue(configuration) + underlying := js.Global().Get("window").Get("RTCPeerConnection").New(configMap) + return &PeerConnection{ + underlying: underlying, + api: api, + }, nil +} + +func (pc *PeerConnection) JSValue() js.Value { + return pc.underlying +} + +// OnSignalingStateChange sets an event handler which is invoked when the +// peer connection's signaling state changes +func (pc *PeerConnection) OnSignalingStateChange(f func(SignalingState)) { + if pc.onSignalingStateChangeHandler != nil { + oldHandler := pc.onSignalingStateChangeHandler + defer oldHandler.Release() + } + onSignalingStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + state := newSignalingState(args[0].String()) + go f(state) + return js.Undefined() + }) + pc.onSignalingStateChangeHandler = &onSignalingStateChangeHandler + pc.underlying.Set("onsignalingstatechange", onSignalingStateChangeHandler) +} + +// OnDataChannel sets an event handler which is invoked when a data +// channel message arrives from a remote peer. +func (pc *PeerConnection) OnDataChannel(f func(*DataChannel)) { + if pc.onDataChannelHandler != nil { + oldHandler := pc.onDataChannelHandler + defer oldHandler.Release() + } + onDataChannelHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + // pion/webrtc/projects/15 + // This reference to the underlying DataChannel doesn't know + // about any other references to the same DataChannel. This might result in + // memory leaks where we don't clean up handler functions. Could possibly fix + // by keeping a mutex-protected list of all DataChannel references as a + // property of this PeerConnection, but at the cost of additional overhead. + dataChannel := &DataChannel{ + underlying: args[0].Get("channel"), + api: pc.api, + } + go f(dataChannel) + return js.Undefined() + }) + pc.onDataChannelHandler = &onDataChannelHandler + pc.underlying.Set("ondatachannel", onDataChannelHandler) +} + +// OnNegotiationNeeded sets an event handler which is invoked when +// a change has occurred which requires session negotiation +func (pc *PeerConnection) OnNegotiationNeeded(f func()) { + if pc.onNegotiationNeededHandler != nil { + oldHandler := pc.onNegotiationNeededHandler + defer oldHandler.Release() + } + onNegotiationNeededHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go f() + return js.Undefined() + }) + pc.onNegotiationNeededHandler = &onNegotiationNeededHandler + pc.underlying.Set("onnegotiationneeded", onNegotiationNeededHandler) +} + +// OnICEConnectionStateChange sets an event handler which is called +// when an ICE connection state is changed. +func (pc *PeerConnection) OnICEConnectionStateChange(f func(ICEConnectionState)) { + if pc.onICEConnectionStateChangeHandler != nil { + oldHandler := pc.onICEConnectionStateChangeHandler + defer oldHandler.Release() + } + onICEConnectionStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + connectionState := NewICEConnectionState(pc.underlying.Get("iceConnectionState").String()) + go f(connectionState) + return js.Undefined() + }) + pc.onICEConnectionStateChangeHandler = &onICEConnectionStateChangeHandler + pc.underlying.Set("oniceconnectionstatechange", onICEConnectionStateChangeHandler) +} + +// OnConnectionStateChange sets an event handler which is called +// when an PeerConnectionState is changed. +func (pc *PeerConnection) OnConnectionStateChange(f func(PeerConnectionState)) { + if pc.onConnectionStateChangeHandler != nil { + oldHandler := pc.onConnectionStateChangeHandler + defer oldHandler.Release() + } + onConnectionStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + connectionState := newPeerConnectionState(pc.underlying.Get("connectionState").String()) + go f(connectionState) + return js.Undefined() + }) + pc.onConnectionStateChangeHandler = &onConnectionStateChangeHandler + pc.underlying.Set("onconnectionstatechange", onConnectionStateChangeHandler) +} + +func (pc *PeerConnection) checkConfiguration(configuration Configuration) error { + // https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-setconfiguration (step #2) + if pc.ConnectionState() == PeerConnectionStateClosed { + return &rtcerr.InvalidStateError{Err: ErrConnectionClosed} + } + + existingConfig := pc.GetConfiguration() + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #3) + if configuration.PeerIdentity != "" { + if configuration.PeerIdentity != existingConfig.PeerIdentity { + return &rtcerr.InvalidModificationError{Err: ErrModifyingPeerIdentity} + } + } + + // https://github.com/pion/webrtc/issues/513 + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #4) + // if len(configuration.Certificates) > 0 { + // if len(configuration.Certificates) != len(existingConfiguration.Certificates) { + // return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates} + // } + + // for i, certificate := range configuration.Certificates { + // if !pc.configuration.Certificates[i].Equals(certificate) { + // return &rtcerr.InvalidModificationError{Err: ErrModifyingCertificates} + // } + // } + // pc.configuration.Certificates = configuration.Certificates + // } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #5) + if configuration.BundlePolicy != BundlePolicy(Unknown) { + if configuration.BundlePolicy != existingConfig.BundlePolicy { + return &rtcerr.InvalidModificationError{Err: ErrModifyingBundlePolicy} + } + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #6) + if configuration.RTCPMuxPolicy != RTCPMuxPolicy(Unknown) { + if configuration.RTCPMuxPolicy != existingConfig.RTCPMuxPolicy { + return &rtcerr.InvalidModificationError{Err: ErrModifyingRTCPMuxPolicy} + } + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #7) + if configuration.ICECandidatePoolSize != 0 { + if configuration.ICECandidatePoolSize != existingConfig.ICECandidatePoolSize && + pc.LocalDescription() != nil { + return &rtcerr.InvalidModificationError{Err: ErrModifyingICECandidatePoolSize} + } + } + + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11) + if len(configuration.ICEServers) > 0 { + // https://www.w3.org/TR/webrtc/#set-the-configuration (step #11.3) + for _, server := range configuration.ICEServers { + if _, err := server.validate(); err != nil { + return err + } + } + } + return nil +} + +// SetConfiguration updates the configuration of this PeerConnection object. +func (pc *PeerConnection) SetConfiguration(configuration Configuration) (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + if err := pc.checkConfiguration(configuration); err != nil { + return err + } + configMap := configurationToValue(configuration) + pc.underlying.Call("setConfiguration", configMap) + return nil +} + +// GetConfiguration returns a Configuration object representing the current +// configuration of this PeerConnection object. The returned object is a +// copy and direct mutation on it will not take affect until SetConfiguration +// has been called with Configuration passed as its only argument. +// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-getconfiguration +func (pc *PeerConnection) GetConfiguration() Configuration { + return valueToConfiguration(pc.underlying.Call("getConfiguration")) +} + +// CreateOffer starts the PeerConnection and generates the localDescription +func (pc *PeerConnection) CreateOffer(options *OfferOptions) (_ SessionDescription, err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + promise := pc.underlying.Call("createOffer", offerOptionsToValue(options)) + desc, err := awaitPromise(promise) + if err != nil { + return SessionDescription{}, err + } + return *valueToSessionDescription(desc), nil +} + +// CreateAnswer starts the PeerConnection and generates the localDescription +func (pc *PeerConnection) CreateAnswer(options *AnswerOptions) (_ SessionDescription, err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + promise := pc.underlying.Call("createAnswer", answerOptionsToValue(options)) + desc, err := awaitPromise(promise) + if err != nil { + return SessionDescription{}, err + } + return *valueToSessionDescription(desc), nil +} + +// SetLocalDescription sets the SessionDescription of the local peer +func (pc *PeerConnection) SetLocalDescription(desc SessionDescription) (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + promise := pc.underlying.Call("setLocalDescription", sessionDescriptionToValue(&desc)) + _, err = awaitPromise(promise) + return err +} + +// LocalDescription returns PendingLocalDescription if it is not null and +// otherwise it returns CurrentLocalDescription. This property is used to +// determine if setLocalDescription has already been called. +// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-localdescription +func (pc *PeerConnection) LocalDescription() *SessionDescription { + return valueToSessionDescription(pc.underlying.Get("localDescription")) +} + +// SetRemoteDescription sets the SessionDescription of the remote peer +func (pc *PeerConnection) SetRemoteDescription(desc SessionDescription) (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + promise := pc.underlying.Call("setRemoteDescription", sessionDescriptionToValue(&desc)) + _, err = awaitPromise(promise) + return err +} + +// RemoteDescription returns PendingRemoteDescription if it is not null and +// otherwise it returns CurrentRemoteDescription. This property is used to +// determine if setRemoteDescription has already been called. +// https://www.w3.org/TR/webrtc/#dom-rtcpeerconnection-remotedescription +func (pc *PeerConnection) RemoteDescription() *SessionDescription { + return valueToSessionDescription(pc.underlying.Get("remoteDescription")) +} + +// AddICECandidate accepts an ICE candidate string and adds it +// to the existing set of candidates +func (pc *PeerConnection) AddICECandidate(candidate ICECandidateInit) (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + promise := pc.underlying.Call("addIceCandidate", iceCandidateInitToValue(candidate)) + _, err = awaitPromise(promise) + return err +} + +// ICEConnectionState returns the ICE connection state of the +// PeerConnection instance. +func (pc *PeerConnection) ICEConnectionState() ICEConnectionState { + return NewICEConnectionState(pc.underlying.Get("iceConnectionState").String()) +} + +// OnICECandidate sets an event handler which is invoked when a new ICE +// candidate is found. +func (pc *PeerConnection) OnICECandidate(f func(candidate *ICECandidate)) { + if pc.onICECandidateHandler != nil { + oldHandler := pc.onICECandidateHandler + defer oldHandler.Release() + } + onICECandidateHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + candidate := valueToICECandidate(args[0].Get("candidate")) + if candidate == nil && pc.onGatherCompleteHandler != nil { + go pc.onGatherCompleteHandler() + } + + go f(candidate) + return js.Undefined() + }) + pc.onICECandidateHandler = &onICECandidateHandler + pc.underlying.Set("onicecandidate", onICECandidateHandler) +} + +// OnICEGatheringStateChange sets an event handler which is invoked when the +// ICE candidate gathering state has changed. +func (pc *PeerConnection) OnICEGatheringStateChange(f func()) { + if pc.onICEGatheringStateChangeHandler != nil { + oldHandler := pc.onICEGatheringStateChangeHandler + defer oldHandler.Release() + } + onICEGatheringStateChangeHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} { + go f() + return js.Undefined() + }) + pc.onICEGatheringStateChangeHandler = &onICEGatheringStateChangeHandler + pc.underlying.Set("onicegatheringstatechange", onICEGatheringStateChangeHandler) +} + +// CreateDataChannel creates a new DataChannel object with the given label +// and optional DataChannelInit used to configure properties of the +// underlying channel such as data reliability. +func (pc *PeerConnection) CreateDataChannel(label string, options *DataChannelInit) (_ *DataChannel, err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + channel := pc.underlying.Call("createDataChannel", label, dataChannelInitToValue(options)) + return &DataChannel{ + underlying: channel, + api: pc.api, + }, nil +} + +// SetIdentityProvider is used to configure an identity provider to generate identity assertions +func (pc *PeerConnection) SetIdentityProvider(provider string) (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + pc.underlying.Call("setIdentityProvider", provider) + return nil +} + +// Close ends the PeerConnection +func (pc *PeerConnection) Close() (err error) { + defer func() { + if e := recover(); e != nil { + err = recoveryToError(e) + } + }() + + pc.underlying.Call("close") + + // Release any handlers as required by the syscall/js API. + if pc.onSignalingStateChangeHandler != nil { + pc.onSignalingStateChangeHandler.Release() + } + if pc.onDataChannelHandler != nil { + pc.onDataChannelHandler.Release() + } + if pc.onNegotiationNeededHandler != nil { + pc.onNegotiationNeededHandler.Release() + } + if pc.onConnectionStateChangeHandler != nil { + pc.onConnectionStateChangeHandler.Release() + } + if pc.onICEConnectionStateChangeHandler != nil { + pc.onICEConnectionStateChangeHandler.Release() + } + if pc.onICECandidateHandler != nil { + pc.onICECandidateHandler.Release() + } + if pc.onICEGatheringStateChangeHandler != nil { + pc.onICEGatheringStateChangeHandler.Release() + } + + return nil +} + +// CurrentLocalDescription represents the local description that was +// successfully negotiated the last time the PeerConnection transitioned +// into the stable state plus any local candidates that have been generated +// by the ICEAgent since the offer or answer was created. +func (pc *PeerConnection) CurrentLocalDescription() *SessionDescription { + desc := pc.underlying.Get("currentLocalDescription") + return valueToSessionDescription(desc) +} + +// PendingLocalDescription represents a local description that is in the +// process of being negotiated plus any local candidates that have been +// generated by the ICEAgent since the offer or answer was created. If the +// PeerConnection is in the stable state, the value is null. +func (pc *PeerConnection) PendingLocalDescription() *SessionDescription { + desc := pc.underlying.Get("pendingLocalDescription") + return valueToSessionDescription(desc) +} + +// CurrentRemoteDescription represents the last remote description that was +// successfully negotiated the last time the PeerConnection transitioned +// into the stable state plus any remote candidates that have been supplied +// via AddICECandidate() since the offer or answer was created. +func (pc *PeerConnection) CurrentRemoteDescription() *SessionDescription { + desc := pc.underlying.Get("currentRemoteDescription") + return valueToSessionDescription(desc) +} + +// PendingRemoteDescription represents a remote description that is in the +// process of being negotiated, complete with any remote candidates that +// have been supplied via AddICECandidate() since the offer or answer was +// created. If the PeerConnection is in the stable state, the value is +// null. +func (pc *PeerConnection) PendingRemoteDescription() *SessionDescription { + desc := pc.underlying.Get("pendingRemoteDescription") + return valueToSessionDescription(desc) +} + +// SignalingState returns the signaling state of the PeerConnection instance. +func (pc *PeerConnection) SignalingState() SignalingState { + rawState := pc.underlying.Get("signalingState").String() + return newSignalingState(rawState) +} + +// ICEGatheringState attribute the ICE gathering state of the PeerConnection +// instance. +func (pc *PeerConnection) ICEGatheringState() ICEGatheringState { + rawState := pc.underlying.Get("iceGatheringState").String() + return NewICEGatheringState(rawState) +} + +// ConnectionState attribute the connection state of the PeerConnection +// instance. +func (pc *PeerConnection) ConnectionState() PeerConnectionState { + rawState := pc.underlying.Get("connectionState").String() + return newPeerConnectionState(rawState) +} + +func (pc *PeerConnection) setGatherCompleteHandler(handler func()) { + pc.onGatherCompleteHandler = handler + + // If no onIceCandidate handler has been set provide an empty one + // otherwise our onGatherCompleteHandler will not be executed + if pc.onICECandidateHandler == nil { + pc.OnICECandidate(func(i *ICECandidate) {}) + } +} + +// Converts a Configuration to js.Value so it can be passed +// through to the JavaScript WebRTC API. Any zero values are converted to +// js.Undefined(), which will result in the default value being used. +func configurationToValue(configuration Configuration) js.Value { + return js.ValueOf(map[string]interface{}{ + "iceServers": iceServersToValue(configuration.ICEServers), + "iceTransportPolicy": stringEnumToValueOrUndefined(configuration.ICETransportPolicy.String()), + "bundlePolicy": stringEnumToValueOrUndefined(configuration.BundlePolicy.String()), + "rtcpMuxPolicy": stringEnumToValueOrUndefined(configuration.RTCPMuxPolicy.String()), + "peerIdentity": stringToValueOrUndefined(configuration.PeerIdentity), + "iceCandidatePoolSize": uint8ToValueOrUndefined(configuration.ICECandidatePoolSize), + + // Note: Certificates are not currently supported. + // "certificates": configuration.Certificates, + }) +} + +func iceServersToValue(iceServers []ICEServer) js.Value { + if len(iceServers) == 0 { + return js.Undefined() + } + maps := make([]interface{}, len(iceServers)) + for i, server := range iceServers { + maps[i] = iceServerToValue(server) + } + return js.ValueOf(maps) +} + +func iceServerToValue(server ICEServer) js.Value { + return js.ValueOf(map[string]interface{}{ + "urls": stringsToValue(server.URLs), // required + "username": stringToValueOrUndefined(server.Username), + // Note: credential and credentialType are not currently supported. + // "credential": interfaceToValueOrUndefined(server.Credential), + // "credentialType": stringEnumToValueOrUndefined(server.CredentialType.String()), + }) +} + +func valueToConfiguration(configValue js.Value) Configuration { + if jsValueIsNull(configValue) || jsValueIsUndefined(configValue) { + return Configuration{} + } + return Configuration{ + ICEServers: valueToICEServers(configValue.Get("iceServers")), + ICETransportPolicy: NewICETransportPolicy(valueToStringOrZero(configValue.Get("iceTransportPolicy"))), + BundlePolicy: newBundlePolicy(valueToStringOrZero(configValue.Get("bundlePolicy"))), + RTCPMuxPolicy: newRTCPMuxPolicy(valueToStringOrZero(configValue.Get("rtcpMuxPolicy"))), + PeerIdentity: valueToStringOrZero(configValue.Get("peerIdentity")), + ICECandidatePoolSize: valueToUint8OrZero(configValue.Get("iceCandidatePoolSize")), + + // Note: Certificates are not supported. + // Certificates []Certificate + } +} + +func valueToICEServers(iceServersValue js.Value) []ICEServer { + if jsValueIsNull(iceServersValue) || jsValueIsUndefined(iceServersValue) { + return nil + } + iceServers := make([]ICEServer, iceServersValue.Length()) + for i := 0; i < iceServersValue.Length(); i++ { + iceServers[i] = valueToICEServer(iceServersValue.Index(i)) + } + return iceServers +} + +func valueToICEServer(iceServerValue js.Value) ICEServer { + return ICEServer{ + URLs: valueToStrings(iceServerValue.Get("urls")), // required + Username: valueToStringOrZero(iceServerValue.Get("username")), + // Note: Credential and CredentialType are not currently supported. + // Credential: iceServerValue.Get("credential"), + // CredentialType: newICECredentialType(valueToStringOrZero(iceServerValue.Get("credentialType"))), + } +} + +func valueToICECandidate(val js.Value) *ICECandidate { + if jsValueIsNull(val) || jsValueIsUndefined(val) { + return nil + } + if jsValueIsUndefined(val.Get("protocol")) && !jsValueIsUndefined(val.Get("candidate")) { + // Missing some fields, assume it's Firefox and parse SDP candidate. + c, err := ice.UnmarshalCandidate(val.Get("candidate").String()) + if err != nil { + return nil + } + + iceCandidate, err := newICECandidateFromICE(c) + if err != nil { + return nil + } + + return &iceCandidate + } + protocol, _ := NewICEProtocol(val.Get("protocol").String()) + candidateType, _ := NewICECandidateType(val.Get("type").String()) + return &ICECandidate{ + Foundation: val.Get("foundation").String(), + Priority: valueToUint32OrZero(val.Get("priority")), + Address: val.Get("address").String(), + Protocol: protocol, + Port: valueToUint16OrZero(val.Get("port")), + Typ: candidateType, + Component: stringToComponentIDOrZero(val.Get("component").String()), + RelatedAddress: val.Get("relatedAddress").String(), + RelatedPort: valueToUint16OrZero(val.Get("relatedPort")), + } +} + +func stringToComponentIDOrZero(val string) uint16 { + // See: https://developer.mozilla.org/en-US/docs/Web/API/RTCIceComponent + switch val { + case "rtp": + return 1 + case "rtcp": + return 2 + } + return 0 +} + +func sessionDescriptionToValue(desc *SessionDescription) js.Value { + if desc == nil { + return js.Undefined() + } + return js.ValueOf(map[string]interface{}{ + "type": desc.Type.String(), + "sdp": desc.SDP, + }) +} + +func valueToSessionDescription(descValue js.Value) *SessionDescription { + if jsValueIsNull(descValue) || jsValueIsUndefined(descValue) { + return nil + } + return &SessionDescription{ + Type: NewSDPType(descValue.Get("type").String()), + SDP: descValue.Get("sdp").String(), + } +} + +func offerOptionsToValue(offerOptions *OfferOptions) js.Value { + if offerOptions == nil { + return js.Undefined() + } + return js.ValueOf(map[string]interface{}{ + "iceRestart": offerOptions.ICERestart, + "voiceActivityDetection": offerOptions.VoiceActivityDetection, + }) +} + +func answerOptionsToValue(answerOptions *AnswerOptions) js.Value { + if answerOptions == nil { + return js.Undefined() + } + return js.ValueOf(map[string]interface{}{ + "voiceActivityDetection": answerOptions.VoiceActivityDetection, + }) +} + +func iceCandidateInitToValue(candidate ICECandidateInit) js.Value { + return js.ValueOf(map[string]interface{}{ + "candidate": candidate.Candidate, + "sdpMid": stringPointerToValue(candidate.SDPMid), + "sdpMLineIndex": uint16PointerToValue(candidate.SDPMLineIndex), + "usernameFragment": stringPointerToValue(candidate.UsernameFragment), + }) +} + +func dataChannelInitToValue(options *DataChannelInit) js.Value { + if options == nil { + return js.Undefined() + } + + maxPacketLifeTime := uint16PointerToValue(options.MaxPacketLifeTime) + return js.ValueOf(map[string]interface{}{ + "ordered": boolPointerToValue(options.Ordered), + "maxPacketLifeTime": maxPacketLifeTime, + // See https://bugs.chromium.org/p/chromium/issues/detail?id=696681 + // Chrome calls this "maxRetransmitTime" + "maxRetransmitTime": maxPacketLifeTime, + "maxRetransmits": uint16PointerToValue(options.MaxRetransmits), + "protocol": stringPointerToValue(options.Protocol), + "negotiated": boolPointerToValue(options.Negotiated), + "id": uint16PointerToValue(options.ID), + }) +} |