datachannel_js.go 11 KB

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