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 }