diff --git a/Cargo.toml b/Cargo.toml
index f4ad618281f..5f655fcaf75 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -81,6 +81,7 @@ members = [
"examples/wasm2js",
"examples/webaudio",
"examples/webgl",
+ "examples/webrtc_datachannel",
"examples/websockets",
"examples/webxr",
"examples/without-a-bundler",
diff --git a/examples/webrtc_datachannel/Cargo.toml b/examples/webrtc_datachannel/Cargo.toml
new file mode 100644
index 00000000000..603640d19d9
--- /dev/null
+++ b/examples/webrtc_datachannel/Cargo.toml
@@ -0,0 +1,27 @@
+[package]
+name = "webrtc_datachannel"
+version = "0.1.0"
+authors = ["The wasm-bindgen Developers"]
+edition = "2018"
+
+[lib]
+crate-type = ["cdylib"]
+
+[dependencies]
+wasm-bindgen = "0.2.62"
+js-sys = "0.3"
+wasm-bindgen-futures = "0.4.12"
+
+[dependencies.web-sys]
+version = "0.3.22"
+features = [
+ "MessageEvent",
+ "RtcPeerConnection",
+ "RtcSignalingState",
+ "RtcSdpType",
+ "RtcSessionDescriptionInit",
+ "RtcPeerConnectionIceEvent",
+ "RtcIceCandidate",
+ "RtcDataChannel",
+ "RtcDataChannelEvent",
+]
diff --git a/examples/webrtc_datachannel/README.md b/examples/webrtc_datachannel/README.md
new file mode 100644
index 00000000000..3cdbea1ddd3
--- /dev/null
+++ b/examples/webrtc_datachannel/README.md
@@ -0,0 +1,15 @@
+# WebRTC DataChannel Example
+
+[View documentation for this example online][dox] or [View compiled example
+online][compiled]
+
+[compiled]: https://rustwasm.github.io/wasm-bindgen/exbuild/webrtc_datachannel/
+[dox]: https://rustwasm.github.io/wasm-bindgen/examples/webrtc_datachannel.html
+
+You can build the example locally with:
+
+```
+$ npm run serve
+```
+
+and then visiting http://localhost:8080 in a browser should run the example!
diff --git a/examples/webrtc_datachannel/index.html b/examples/webrtc_datachannel/index.html
new file mode 100644
index 00000000000..78e810af774
--- /dev/null
+++ b/examples/webrtc_datachannel/index.html
@@ -0,0 +1,10 @@
+
+
+
+
+ WebRTC DataChannel example
+
+
+ Open DevTools and check the Console.
+
+
diff --git a/examples/webrtc_datachannel/index.js b/examples/webrtc_datachannel/index.js
new file mode 100644
index 00000000000..270c64ae677
--- /dev/null
+++ b/examples/webrtc_datachannel/index.js
@@ -0,0 +1,3 @@
+window.addEventListener('load', async () => {
+ await import('./pkg');
+});
diff --git a/examples/webrtc_datachannel/package.json b/examples/webrtc_datachannel/package.json
new file mode 100644
index 00000000000..5f0670df5c9
--- /dev/null
+++ b/examples/webrtc_datachannel/package.json
@@ -0,0 +1,14 @@
+{
+ "scripts": {
+ "build": "webpack",
+ "serve": "webpack-dev-server"
+ },
+ "devDependencies": {
+ "@wasm-tool/wasm-pack-plugin": "1.0.1",
+ "text-encoding": "^0.7.0",
+ "html-webpack-plugin": "^3.2.0",
+ "webpack": "^4.29.4",
+ "webpack-cli": "^3.1.1",
+ "webpack-dev-server": "^3.1.0"
+ }
+}
diff --git a/examples/webrtc_datachannel/src/lib.rs b/examples/webrtc_datachannel/src/lib.rs
new file mode 100644
index 00000000000..a6a1602c410
--- /dev/null
+++ b/examples/webrtc_datachannel/src/lib.rs
@@ -0,0 +1,166 @@
+use js_sys::Reflect;
+use wasm_bindgen::prelude::*;
+use wasm_bindgen::JsCast;
+use wasm_bindgen_futures::JsFuture;
+use web_sys::{
+ MessageEvent, RtcDataChannelEvent, RtcPeerConnection, RtcPeerConnectionIceEvent, RtcSdpType,
+ RtcSessionDescriptionInit,
+};
+
+macro_rules! console_log {
+ ($($t:tt)*) => (log(&format_args!($($t)*).to_string()))
+}
+macro_rules! console_warn {
+ ($($t:tt)*) => (warn(&format_args!($($t)*).to_string()))
+}
+
+#[wasm_bindgen]
+extern "C" {
+ #[wasm_bindgen(js_namespace = console)]
+ fn log(s: &str);
+ #[wasm_bindgen(js_namespace = console)]
+ fn warn(s: &str);
+}
+
+#[wasm_bindgen(start)]
+pub async fn start() -> Result<(), JsValue> {
+ /*
+ * Set up PeerConnections
+ * pc1 <=> pc2
+ *
+ */
+ let pc1 = RtcPeerConnection::new()?;
+ console_log!("pc1 created: state {:?}", pc1.signaling_state());
+ let pc2 = RtcPeerConnection::new()?;
+ console_log!("pc2 created: state {:?}", pc2.signaling_state());
+
+ /*
+ * Create DataChannel on pc1 to negotiate
+ * Message will be shonw here after connection established
+ *
+ */
+ let dc1 = pc1.create_data_channel("my-data-channel");
+ console_log!("dc1 created: label {:?}", dc1.label());
+
+ let dc1_clone = dc1.clone();
+ let onmessage_callback =
+ Closure::wrap(
+ Box::new(move |ev: MessageEvent| match ev.data().as_string() {
+ Some(message) => {
+ console_warn!("{:?}", message);
+ dc1_clone.send_with_str("Pong from pc1.dc!").unwrap();
+ }
+ None => {}
+ }) as Box,
+ );
+ dc1.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
+ onmessage_callback.forget();
+
+ /*
+ * If negotiaion has done, this closure will be called
+ *
+ */
+ let ondatachannel_callback = Closure::wrap(Box::new(move |ev: RtcDataChannelEvent| {
+ let dc2 = ev.channel();
+ console_log!("pc2.ondatachannel!: {:?}", dc2.label());
+
+ let onmessage_callback =
+ Closure::wrap(
+ Box::new(move |ev: MessageEvent| match ev.data().as_string() {
+ Some(message) => console_warn!("{:?}", message),
+ None => {}
+ }) as Box,
+ );
+ dc2.set_onmessage(Some(onmessage_callback.as_ref().unchecked_ref()));
+ onmessage_callback.forget();
+
+ dc2.send_with_str("Ping from pc2.dc!").unwrap();
+ }) as Box);
+ pc2.set_ondatachannel(Some(ondatachannel_callback.as_ref().unchecked_ref()));
+ ondatachannel_callback.forget();
+
+ /*
+ * Handle ICE candidate each other
+ *
+ */
+ let pc2_clone = pc2.clone();
+ let onicecandidate_callback1 =
+ Closure::wrap(
+ Box::new(move |ev: RtcPeerConnectionIceEvent| match ev.candidate() {
+ Some(candidate) => {
+ console_log!("pc1.onicecandidate: {:#?}", candidate.candidate());
+ let _ =
+ pc2_clone.add_ice_candidate_with_opt_rtc_ice_candidate(Some(&candidate));
+ }
+ None => {}
+ }) as Box,
+ );
+ pc1.set_onicecandidate(Some(onicecandidate_callback1.as_ref().unchecked_ref()));
+ onicecandidate_callback1.forget();
+
+ let pc1_clone = pc1.clone();
+ let onicecandidate_callback2 =
+ Closure::wrap(
+ Box::new(move |ev: RtcPeerConnectionIceEvent| match ev.candidate() {
+ Some(candidate) => {
+ console_log!("pc2.onicecandidate: {:#?}", candidate.candidate());
+ let _ =
+ pc1_clone.add_ice_candidate_with_opt_rtc_ice_candidate(Some(&candidate));
+ }
+ None => {}
+ }) as Box,
+ );
+ pc2.set_onicecandidate(Some(onicecandidate_callback2.as_ref().unchecked_ref()));
+ onicecandidate_callback2.forget();
+
+ /*
+ * Send OFFER from pc1 to pc2
+ *
+ */
+ let offer = JsFuture::from(pc1.create_offer()).await?;
+ let offer_sdp = Reflect::get(&offer, &JsValue::from_str("sdp"))?
+ .as_string()
+ .unwrap();
+ console_log!("pc1: offer {:?}", offer_sdp);
+
+ let mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer);
+ offer_obj.sdp(&offer_sdp);
+ let sld_promise = pc1.set_local_description(&offer_obj);
+ JsFuture::from(sld_promise).await?;
+ console_log!("pc1: state {:?}", pc1.signaling_state());
+
+ /*
+ * Receive OFFER from pc1
+ * Create and send ANSWER from pc2 to pc1
+ *
+ */
+ let mut offer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Offer);
+ offer_obj.sdp(&offer_sdp);
+ let srd_promise = pc2.set_remote_description(&offer_obj);
+ JsFuture::from(srd_promise).await?;
+ console_log!("pc2: state {:?}", pc2.signaling_state());
+
+ let answer = JsFuture::from(pc2.create_answer()).await?;
+ let answer_sdp = Reflect::get(&answer, &JsValue::from_str("sdp"))?
+ .as_string()
+ .unwrap();
+ console_log!("pc2: answer {:?}", answer_sdp);
+
+ let mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer);
+ answer_obj.sdp(&answer_sdp);
+ let sld_promise = pc2.set_local_description(&answer_obj);
+ JsFuture::from(sld_promise).await?;
+ console_log!("pc2: state {:?}", pc2.signaling_state());
+
+ /*
+ * Receive ANSWER from pc2
+ *
+ */
+ let mut answer_obj = RtcSessionDescriptionInit::new(RtcSdpType::Answer);
+ answer_obj.sdp(&answer_sdp);
+ let srd_promise = pc1.set_remote_description(&answer_obj);
+ JsFuture::from(srd_promise).await?;
+ console_log!("pc1: state {:?}", pc1.signaling_state());
+
+ Ok(())
+}
diff --git a/examples/webrtc_datachannel/webpack.config.js b/examples/webrtc_datachannel/webpack.config.js
new file mode 100644
index 00000000000..25afd18002a
--- /dev/null
+++ b/examples/webrtc_datachannel/webpack.config.js
@@ -0,0 +1,27 @@
+const path = require('path');
+const HtmlWebpackPlugin = require('html-webpack-plugin');
+const webpack = require('webpack');
+const WasmPackPlugin = require("@wasm-tool/wasm-pack-plugin");
+
+module.exports = {
+ entry: './index.js',
+ output: {
+ path: path.resolve(__dirname, 'dist'),
+ filename: 'index.js',
+ },
+ plugins: [
+ new HtmlWebpackPlugin({
+ template: 'index.html'
+ }),
+ new WasmPackPlugin({
+ crateDirectory: path.resolve(__dirname, ".")
+ }),
+ // Have this example work in Edge which doesn't ship `TextEncoder` or
+ // `TextDecoder` at this time.
+ new webpack.ProvidePlugin({
+ TextDecoder: ['text-encoding', 'TextDecoder'],
+ TextEncoder: ['text-encoding', 'TextEncoder']
+ })
+ ],
+ mode: 'development'
+};
diff --git a/guide/src/examples/webrtc_datachannel.md b/guide/src/examples/webrtc_datachannel.md
new file mode 100644
index 00000000000..efe3f0cc454
--- /dev/null
+++ b/guide/src/examples/webrtc_datachannel.md
@@ -0,0 +1,25 @@
+# WebRTC DataChannel Example
+
+[View full source code][code] or [view the compiled example online][online]
+
+[online]: https://rustwasm.github.io/wasm-bindgen/exbuild/webrtc_datachannel/
+[code]: https://github.com/rustwasm/wasm-bindgen/tree/master/examples/webrtc_datachannel/
+
+This example creates 2 peer connections and 2 data channels in single browser tab.
+Send ping/pong between `peer1.dc` and `peer2.dc`.
+
+## `Cargo.toml`
+
+The `Cargo.toml` enables features necessary to use WebRTC DataChannel and its negotiation.
+
+```toml
+{{#include ../../../examples/webrtc_datachannel/Cargo.toml}}
+```
+
+## `src/lib.rs`
+
+The Rust code connects WebRTC data channel.
+
+```rust
+{{#include ../../../examples/webrtc_datachannel/src/lib.rs}}
+```