| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323 |
- // SPDX-FileCopyrightText: 2023 The Pion community <https://pion.ly>
- // SPDX-License-Identifier: MIT
- //go:build js && wasm
- // +build js,wasm
- package webrtc
- import (
- "fmt"
- "syscall/js"
- "github.com/pion/datachannel"
- )
- const dataChannelBufferSize = 16384 // Lowest common denominator among browsers
- // DataChannel represents a WebRTC DataChannel
- // The DataChannel interface represents a network channel
- // which can be used for bidirectional peer-to-peer transfers of arbitrary data
- type DataChannel 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.
- onOpenHandler *js.Func
- onCloseHandler *js.Func
- onMessageHandler *js.Func
- onBufferedAmountLow *js.Func
- // A reference to the associated api object used by this datachannel
- api *API
- }
- // OnOpen sets an event handler which is invoked when
- // the underlying data transport has been established (or re-established).
- func (d *DataChannel) OnOpen(f func()) {
- if d.onOpenHandler != nil {
- oldHandler := d.onOpenHandler
- defer oldHandler.Release()
- }
- onOpenHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
- go f()
- return js.Undefined()
- })
- d.onOpenHandler = &onOpenHandler
- d.underlying.Set("onopen", onOpenHandler)
- }
- // OnClose sets an event handler which is invoked when
- // the underlying data transport has been closed.
- func (d *DataChannel) OnClose(f func()) {
- if d.onCloseHandler != nil {
- oldHandler := d.onCloseHandler
- defer oldHandler.Release()
- }
- onCloseHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
- go f()
- return js.Undefined()
- })
- d.onCloseHandler = &onCloseHandler
- d.underlying.Set("onclose", onCloseHandler)
- }
- // OnMessage sets an event handler which is invoked on a binary message arrival
- // from a remote peer. Note that browsers may place limitations on message size.
- func (d *DataChannel) OnMessage(f func(msg DataChannelMessage)) {
- if d.onMessageHandler != nil {
- oldHandler := d.onMessageHandler
- defer oldHandler.Release()
- }
- onMessageHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
- // pion/webrtc/projects/15
- data := args[0].Get("data")
- go func() {
- // valueToDataChannelMessage may block when handling 'Blob' data
- // so we need to call it from a new routine. See:
- // https://pkg.go.dev/syscall/js#FuncOf
- msg := valueToDataChannelMessage(data)
- f(msg)
- }()
- return js.Undefined()
- })
- d.onMessageHandler = &onMessageHandler
- d.underlying.Set("onmessage", onMessageHandler)
- }
- // Send sends the binary message to the DataChannel peer
- func (d *DataChannel) Send(data []byte) (err error) {
- defer func() {
- if e := recover(); e != nil {
- err = recoveryToError(e)
- }
- }()
- array := js.Global().Get("Uint8Array").New(len(data))
- js.CopyBytesToJS(array, data)
- d.underlying.Call("send", array)
- return nil
- }
- // SendText sends the text message to the DataChannel peer
- func (d *DataChannel) SendText(s string) (err error) {
- defer func() {
- if e := recover(); e != nil {
- err = recoveryToError(e)
- }
- }()
- d.underlying.Call("send", s)
- return nil
- }
- // Detach allows you to detach the underlying datachannel. This provides
- // an idiomatic API to work with, however it disables the OnMessage callback.
- // Before calling Detach you have to enable this behavior by calling
- // webrtc.DetachDataChannels(). Combining detached and normal data channels
- // is not supported.
- // Please reffer to the data-channels-detach example and the
- // pion/datachannel documentation for the correct way to handle the
- // resulting DataChannel object.
- func (d *DataChannel) Detach() (datachannel.ReadWriteCloser, error) {
- if !d.api.settingEngine.detach.DataChannels {
- return nil, fmt.Errorf("enable detaching by calling webrtc.DetachDataChannels()")
- }
- detached := newDetachedDataChannel(d)
- return detached, nil
- }
- // Close Closes the DataChannel. It may be called regardless of whether
- // the DataChannel object was created by this peer or the remote peer.
- func (d *DataChannel) Close() (err error) {
- defer func() {
- if e := recover(); e != nil {
- err = recoveryToError(e)
- }
- }()
- d.underlying.Call("close")
- // Release any handlers as required by the syscall/js API.
- if d.onOpenHandler != nil {
- d.onOpenHandler.Release()
- }
- if d.onCloseHandler != nil {
- d.onCloseHandler.Release()
- }
- if d.onMessageHandler != nil {
- d.onMessageHandler.Release()
- }
- if d.onBufferedAmountLow != nil {
- d.onBufferedAmountLow.Release()
- }
- return nil
- }
- // Label represents a label that can be used to distinguish this
- // DataChannel object from other DataChannel objects. Scripts are
- // allowed to create multiple DataChannel objects with the same label.
- func (d *DataChannel) Label() string {
- return d.underlying.Get("label").String()
- }
- // Ordered represents if the DataChannel is ordered, and false if
- // out-of-order delivery is allowed.
- func (d *DataChannel) Ordered() bool {
- ordered := d.underlying.Get("ordered")
- if ordered.IsUndefined() {
- return true // default is true
- }
- return ordered.Bool()
- }
- // MaxPacketLifeTime represents the length of the time window (msec) during
- // which transmissions and retransmissions may occur in unreliable mode.
- func (d *DataChannel) MaxPacketLifeTime() *uint16 {
- if !d.underlying.Get("maxPacketLifeTime").IsUndefined() {
- return valueToUint16Pointer(d.underlying.Get("maxPacketLifeTime"))
- }
- // See https://bugs.chromium.org/p/chromium/issues/detail?id=696681
- // Chrome calls this "maxRetransmitTime"
- return valueToUint16Pointer(d.underlying.Get("maxRetransmitTime"))
- }
- // MaxRetransmits represents the maximum number of retransmissions that are
- // attempted in unreliable mode.
- func (d *DataChannel) MaxRetransmits() *uint16 {
- return valueToUint16Pointer(d.underlying.Get("maxRetransmits"))
- }
- // Protocol represents the name of the sub-protocol used with this
- // DataChannel.
- func (d *DataChannel) Protocol() string {
- return d.underlying.Get("protocol").String()
- }
- // Negotiated represents whether this DataChannel was negotiated by the
- // application (true), or not (false).
- func (d *DataChannel) Negotiated() bool {
- return d.underlying.Get("negotiated").Bool()
- }
- // ID represents the ID for this DataChannel. The value is initially
- // null, which is what will be returned if the ID was not provided at
- // channel creation time. Otherwise, it will return the ID that was either
- // selected by the script or generated. After the ID is set to a non-null
- // value, it will not change.
- func (d *DataChannel) ID() *uint16 {
- return valueToUint16Pointer(d.underlying.Get("id"))
- }
- // ReadyState represents the state of the DataChannel object.
- func (d *DataChannel) ReadyState() DataChannelState {
- return newDataChannelState(d.underlying.Get("readyState").String())
- }
- // BufferedAmount represents the number of bytes of application data
- // (UTF-8 text and binary data) that have been queued using send(). Even
- // though the data transmission can occur in parallel, the returned value
- // MUST NOT be decreased before the current task yielded back to the event
- // loop to prevent race conditions. The value does not include framing
- // overhead incurred by the protocol, or buffering done by the operating
- // system or network hardware. The value of BufferedAmount slot will only
- // increase with each call to the send() method as long as the ReadyState is
- // open; however, BufferedAmount does not reset to zero once the channel
- // closes.
- func (d *DataChannel) BufferedAmount() uint64 {
- return uint64(d.underlying.Get("bufferedAmount").Int())
- }
- // BufferedAmountLowThreshold represents the threshold at which the
- // bufferedAmount is considered to be low. When the bufferedAmount decreases
- // from above this threshold to equal or below it, the bufferedamountlow
- // event fires. BufferedAmountLowThreshold is initially zero on each new
- // DataChannel, but the application may change its value at any time.
- func (d *DataChannel) BufferedAmountLowThreshold() uint64 {
- return uint64(d.underlying.Get("bufferedAmountLowThreshold").Int())
- }
- // SetBufferedAmountLowThreshold is used to update the threshold.
- // See BufferedAmountLowThreshold().
- func (d *DataChannel) SetBufferedAmountLowThreshold(th uint64) {
- d.underlying.Set("bufferedAmountLowThreshold", th)
- }
- // OnBufferedAmountLow sets an event handler which is invoked when
- // the number of bytes of outgoing data becomes lower than the
- // BufferedAmountLowThreshold.
- func (d *DataChannel) OnBufferedAmountLow(f func()) {
- if d.onBufferedAmountLow != nil {
- oldHandler := d.onBufferedAmountLow
- defer oldHandler.Release()
- }
- onBufferedAmountLow := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
- go f()
- return js.Undefined()
- })
- d.onBufferedAmountLow = &onBufferedAmountLow
- d.underlying.Set("onbufferedamountlow", onBufferedAmountLow)
- }
- // valueToDataChannelMessage converts the given value to a DataChannelMessage.
- // val should be obtained from MessageEvent.data where MessageEvent is received
- // via the RTCDataChannel.onmessage callback.
- func valueToDataChannelMessage(val js.Value) DataChannelMessage {
- // If val is of type string, the conversion is straightforward.
- if val.Type() == js.TypeString {
- return DataChannelMessage{
- IsString: true,
- Data: []byte(val.String()),
- }
- }
- // For other types, we need to first determine val.constructor.name.
- constructorName := val.Get("constructor").Get("name").String()
- var data []byte
- switch constructorName {
- case "Uint8Array":
- // We can easily convert Uint8Array to []byte
- data = uint8ArrayValueToBytes(val)
- case "Blob":
- // Convert the Blob to an ArrayBuffer and then convert the ArrayBuffer
- // to a Uint8Array.
- // See: https://developer.mozilla.org/en-US/docs/Web/API/Blob
- // The JavaScript API for reading from the Blob is asynchronous. We use a
- // channel to signal when reading is done.
- reader := js.Global().Get("FileReader").New()
- doneChan := make(chan struct{})
- reader.Call("addEventListener", "loadend", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
- go func() {
- // Signal that the FileReader is done reading/loading by sending through
- // the doneChan.
- doneChan <- struct{}{}
- }()
- return js.Undefined()
- }))
- reader.Call("readAsArrayBuffer", val)
- // Wait for the FileReader to finish reading/loading.
- <-doneChan
- // At this point buffer.result is a typed array, which we know how to
- // handle.
- buffer := reader.Get("result")
- uint8Array := js.Global().Get("Uint8Array").New(buffer)
- data = uint8ArrayValueToBytes(uint8Array)
- default:
- // Assume we have an ArrayBufferView type which we can convert to a
- // Uint8Array in JavaScript.
- // See: https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView
- uint8Array := js.Global().Get("Uint8Array").New(val)
- data = uint8ArrayValueToBytes(uint8Array)
- }
- return DataChannelMessage{
- IsString: false,
- Data: data,
- }
- }
|