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