github.com/pion/webrtc/v4@v4.0.1/datachannel_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
     8  
     9  import (
    10  	"fmt"
    11  	"syscall/js"
    12  
    13  	"github.com/pion/datachannel"
    14  )
    15  
    16  const dataChannelBufferSize = 16384 // Lowest common denominator among browsers
    17  
    18  // DataChannel represents a WebRTC DataChannel
    19  // The DataChannel interface represents a network channel
    20  // which can be used for bidirectional peer-to-peer transfers of arbitrary data
    21  type DataChannel struct {
    22  	// Pointer to the underlying JavaScript RTCPeerConnection object.
    23  	underlying js.Value
    24  
    25  	// Keep track of handlers/callbacks so we can call Release as required by the
    26  	// syscall/js API. Initially nil.
    27  	onOpenHandler       *js.Func
    28  	onCloseHandler      *js.Func
    29  	onMessageHandler    *js.Func
    30  	onBufferedAmountLow *js.Func
    31  
    32  	// A reference to the associated api object used by this datachannel
    33  	api *API
    34  }
    35  
    36  // OnOpen sets an event handler which is invoked when
    37  // the underlying data transport has been established (or re-established).
    38  func (d *DataChannel) OnOpen(f func()) {
    39  	if d.onOpenHandler != nil {
    40  		oldHandler := d.onOpenHandler
    41  		defer oldHandler.Release()
    42  	}
    43  	onOpenHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    44  		go f()
    45  		return js.Undefined()
    46  	})
    47  	d.onOpenHandler = &onOpenHandler
    48  	d.underlying.Set("onopen", onOpenHandler)
    49  }
    50  
    51  // OnClose sets an event handler which is invoked when
    52  // the underlying data transport has been closed.
    53  func (d *DataChannel) OnClose(f func()) {
    54  	if d.onCloseHandler != nil {
    55  		oldHandler := d.onCloseHandler
    56  		defer oldHandler.Release()
    57  	}
    58  	onCloseHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    59  		go f()
    60  		return js.Undefined()
    61  	})
    62  	d.onCloseHandler = &onCloseHandler
    63  	d.underlying.Set("onclose", onCloseHandler)
    64  }
    65  
    66  // OnMessage sets an event handler which is invoked on a binary message arrival
    67  // from a remote peer. Note that browsers may place limitations on message size.
    68  func (d *DataChannel) OnMessage(f func(msg DataChannelMessage)) {
    69  	if d.onMessageHandler != nil {
    70  		oldHandler := d.onMessageHandler
    71  		defer oldHandler.Release()
    72  	}
    73  	onMessageHandler := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
    74  		// pion/webrtc/projects/15
    75  		data := args[0].Get("data")
    76  		go func() {
    77  			// valueToDataChannelMessage may block when handling 'Blob' data
    78  			// so we need to call it from a new routine. See:
    79  			// https://pkg.go.dev/syscall/js#FuncOf
    80  			msg := valueToDataChannelMessage(data)
    81  			f(msg)
    82  		}()
    83  		return js.Undefined()
    84  	})
    85  	d.onMessageHandler = &onMessageHandler
    86  	d.underlying.Set("onmessage", onMessageHandler)
    87  }
    88  
    89  // Send sends the binary message to the DataChannel peer
    90  func (d *DataChannel) Send(data []byte) (err error) {
    91  	defer func() {
    92  		if e := recover(); e != nil {
    93  			err = recoveryToError(e)
    94  		}
    95  	}()
    96  	array := js.Global().Get("Uint8Array").New(len(data))
    97  	js.CopyBytesToJS(array, data)
    98  	d.underlying.Call("send", array)
    99  	return nil
   100  }
   101  
   102  // SendText sends the text message to the DataChannel peer
   103  func (d *DataChannel) SendText(s string) (err error) {
   104  	defer func() {
   105  		if e := recover(); e != nil {
   106  			err = recoveryToError(e)
   107  		}
   108  	}()
   109  	d.underlying.Call("send", s)
   110  	return nil
   111  }
   112  
   113  // Detach allows you to detach the underlying datachannel. This provides
   114  // an idiomatic API to work with, however it disables the OnMessage callback.
   115  // Before calling Detach you have to enable this behavior by calling
   116  // webrtc.DetachDataChannels(). Combining detached and normal data channels
   117  // is not supported.
   118  // Please refer to the data-channels-detach example and the
   119  // pion/datachannel documentation for the correct way to handle the
   120  // resulting DataChannel object.
   121  func (d *DataChannel) Detach() (datachannel.ReadWriteCloser, error) {
   122  	if !d.api.settingEngine.detach.DataChannels {
   123  		return nil, fmt.Errorf("enable detaching by calling webrtc.DetachDataChannels()")
   124  	}
   125  
   126  	detached := newDetachedDataChannel(d)
   127  	return detached, nil
   128  }
   129  
   130  // Close Closes the DataChannel. It may be called regardless of whether
   131  // the DataChannel object was created by this peer or the remote peer.
   132  func (d *DataChannel) Close() (err error) {
   133  	defer func() {
   134  		if e := recover(); e != nil {
   135  			err = recoveryToError(e)
   136  		}
   137  	}()
   138  
   139  	d.underlying.Call("close")
   140  
   141  	// Release any handlers as required by the syscall/js API.
   142  	if d.onOpenHandler != nil {
   143  		d.onOpenHandler.Release()
   144  	}
   145  	if d.onCloseHandler != nil {
   146  		d.onCloseHandler.Release()
   147  	}
   148  	if d.onMessageHandler != nil {
   149  		d.onMessageHandler.Release()
   150  	}
   151  	if d.onBufferedAmountLow != nil {
   152  		d.onBufferedAmountLow.Release()
   153  	}
   154  
   155  	return nil
   156  }
   157  
   158  // Label represents a label that can be used to distinguish this
   159  // DataChannel object from other DataChannel objects. Scripts are
   160  // allowed to create multiple DataChannel objects with the same label.
   161  func (d *DataChannel) Label() string {
   162  	return d.underlying.Get("label").String()
   163  }
   164  
   165  // Ordered represents if the DataChannel is ordered, and false if
   166  // out-of-order delivery is allowed.
   167  func (d *DataChannel) Ordered() bool {
   168  	ordered := d.underlying.Get("ordered")
   169  	if ordered.IsUndefined() {
   170  		return true // default is true
   171  	}
   172  	return ordered.Bool()
   173  }
   174  
   175  // MaxPacketLifeTime represents the length of the time window (msec) during
   176  // which transmissions and retransmissions may occur in unreliable mode.
   177  func (d *DataChannel) MaxPacketLifeTime() *uint16 {
   178  	if !d.underlying.Get("maxPacketLifeTime").IsUndefined() {
   179  		return valueToUint16Pointer(d.underlying.Get("maxPacketLifeTime"))
   180  	}
   181  
   182  	// See https://bugs.chromium.org/p/chromium/issues/detail?id=696681
   183  	// Chrome calls this "maxRetransmitTime"
   184  	return valueToUint16Pointer(d.underlying.Get("maxRetransmitTime"))
   185  }
   186  
   187  // MaxRetransmits represents the maximum number of retransmissions that are
   188  // attempted in unreliable mode.
   189  func (d *DataChannel) MaxRetransmits() *uint16 {
   190  	return valueToUint16Pointer(d.underlying.Get("maxRetransmits"))
   191  }
   192  
   193  // Protocol represents the name of the sub-protocol used with this
   194  // DataChannel.
   195  func (d *DataChannel) Protocol() string {
   196  	return d.underlying.Get("protocol").String()
   197  }
   198  
   199  // Negotiated represents whether this DataChannel was negotiated by the
   200  // application (true), or not (false).
   201  func (d *DataChannel) Negotiated() bool {
   202  	return d.underlying.Get("negotiated").Bool()
   203  }
   204  
   205  // ID represents the ID for this DataChannel. The value is initially
   206  // null, which is what will be returned if the ID was not provided at
   207  // channel creation time. Otherwise, it will return the ID that was either
   208  // selected by the script or generated. After the ID is set to a non-null
   209  // value, it will not change.
   210  func (d *DataChannel) ID() *uint16 {
   211  	return valueToUint16Pointer(d.underlying.Get("id"))
   212  }
   213  
   214  // ReadyState represents the state of the DataChannel object.
   215  func (d *DataChannel) ReadyState() DataChannelState {
   216  	return newDataChannelState(d.underlying.Get("readyState").String())
   217  }
   218  
   219  // BufferedAmount represents the number of bytes of application data
   220  // (UTF-8 text and binary data) that have been queued using send(). Even
   221  // though the data transmission can occur in parallel, the returned value
   222  // MUST NOT be decreased before the current task yielded back to the event
   223  // loop to prevent race conditions. The value does not include framing
   224  // overhead incurred by the protocol, or buffering done by the operating
   225  // system or network hardware. The value of BufferedAmount slot will only
   226  // increase with each call to the send() method as long as the ReadyState is
   227  // open; however, BufferedAmount does not reset to zero once the channel
   228  // closes.
   229  func (d *DataChannel) BufferedAmount() uint64 {
   230  	return uint64(d.underlying.Get("bufferedAmount").Int())
   231  }
   232  
   233  // BufferedAmountLowThreshold represents the threshold at which the
   234  // bufferedAmount is considered to be low. When the bufferedAmount decreases
   235  // from above this threshold to equal or below it, the bufferedamountlow
   236  // event fires. BufferedAmountLowThreshold is initially zero on each new
   237  // DataChannel, but the application may change its value at any time.
   238  func (d *DataChannel) BufferedAmountLowThreshold() uint64 {
   239  	return uint64(d.underlying.Get("bufferedAmountLowThreshold").Int())
   240  }
   241  
   242  // SetBufferedAmountLowThreshold is used to update the threshold.
   243  // See BufferedAmountLowThreshold().
   244  func (d *DataChannel) SetBufferedAmountLowThreshold(th uint64) {
   245  	d.underlying.Set("bufferedAmountLowThreshold", th)
   246  }
   247  
   248  // OnBufferedAmountLow sets an event handler which is invoked when
   249  // the number of bytes of outgoing data becomes lower than or equal to the
   250  // BufferedAmountLowThreshold.
   251  func (d *DataChannel) OnBufferedAmountLow(f func()) {
   252  	if d.onBufferedAmountLow != nil {
   253  		oldHandler := d.onBufferedAmountLow
   254  		defer oldHandler.Release()
   255  	}
   256  	onBufferedAmountLow := js.FuncOf(func(this js.Value, args []js.Value) interface{} {
   257  		go f()
   258  		return js.Undefined()
   259  	})
   260  	d.onBufferedAmountLow = &onBufferedAmountLow
   261  	d.underlying.Set("onbufferedamountlow", onBufferedAmountLow)
   262  }
   263  
   264  // valueToDataChannelMessage converts the given value to a DataChannelMessage.
   265  // val should be obtained from MessageEvent.data where MessageEvent is received
   266  // via the RTCDataChannel.onmessage callback.
   267  func valueToDataChannelMessage(val js.Value) DataChannelMessage {
   268  	// If val is of type string, the conversion is straightforward.
   269  	if val.Type() == js.TypeString {
   270  		return DataChannelMessage{
   271  			IsString: true,
   272  			Data:     []byte(val.String()),
   273  		}
   274  	}
   275  
   276  	// For other types, we need to first determine val.constructor.name.
   277  	constructorName := val.Get("constructor").Get("name").String()
   278  	var data []byte
   279  	switch constructorName {
   280  	case "Uint8Array":
   281  		// We can easily convert Uint8Array to []byte
   282  		data = uint8ArrayValueToBytes(val)
   283  	case "Blob":
   284  		// Convert the Blob to an ArrayBuffer and then convert the ArrayBuffer
   285  		// to a Uint8Array.
   286  		// See: https://developer.mozilla.org/en-US/docs/Web/API/Blob
   287  
   288  		// The JavaScript API for reading from the Blob is asynchronous. We use a
   289  		// channel to signal when reading is done.
   290  		reader := js.Global().Get("FileReader").New()
   291  		doneChan := make(chan struct{})
   292  		reader.Call("addEventListener", "loadend", js.FuncOf(func(this js.Value, args []js.Value) interface{} {
   293  			go func() {
   294  				// Signal that the FileReader is done reading/loading by sending through
   295  				// the doneChan.
   296  				doneChan <- struct{}{}
   297  			}()
   298  			return js.Undefined()
   299  		}))
   300  
   301  		reader.Call("readAsArrayBuffer", val)
   302  
   303  		// Wait for the FileReader to finish reading/loading.
   304  		<-doneChan
   305  
   306  		// At this point buffer.result is a typed array, which we know how to
   307  		// handle.
   308  		buffer := reader.Get("result")
   309  		uint8Array := js.Global().Get("Uint8Array").New(buffer)
   310  		data = uint8ArrayValueToBytes(uint8Array)
   311  	default:
   312  		// Assume we have an ArrayBufferView type which we can convert to a
   313  		// Uint8Array in JavaScript.
   314  		// See: https://developer.mozilla.org/en-US/docs/Web/API/ArrayBufferView
   315  		uint8Array := js.Global().Get("Uint8Array").New(val)
   316  		data = uint8ArrayValueToBytes(uint8Array)
   317  	}
   318  
   319  	return DataChannelMessage{
   320  		IsString: false,
   321  		Data:     data,
   322  	}
   323  }