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 }