datachannel_js.go 11 KB

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