Initial web proxy commit
Change-Id: I28481433e5609d9c819a1a2bce69fa9d096691a2
diff --git a/aos/network/www/BUILD b/aos/network/www/BUILD
new file mode 100644
index 0000000..5faae12
--- /dev/null
+++ b/aos/network/www/BUILD
@@ -0,0 +1,41 @@
+load("@build_bazel_rules_typescript//:defs.bzl", "ts_library")
+load("@build_bazel_rules_nodejs//:defs.bzl", "rollup_bundle")
+
+filegroup(
+ name = "files",
+ srcs = glob([
+ "**/*.html",
+ ]),
+ visibility=["//visibility:public"],
+)
+
+ts_library(
+ name = "proxy",
+ srcs = glob([
+ "*.ts",
+ ]),
+ deps = [
+ "//aos/network:web_proxy_ts_fbs",
+ ],
+)
+
+rollup_bundle(
+ name = "proxy_bundle",
+ entry_point = "aos/network/www/main",
+ deps = [
+ "proxy",
+ ],
+ visibility=["//visibility:public"],
+)
+
+genrule(
+ name = "flatbuffers",
+ srcs = [
+ "@com_github_google_flatbuffers//:flatjs",
+ ],
+ outs = [
+ "flatbuffers.js",
+ ],
+ cmd = "cp $(location @com_github_google_flatbuffers//:flatjs) $@",
+ visibility=["//visibility:public"],
+)
diff --git a/aos/network/www/index.html b/aos/network/www/index.html
new file mode 100644
index 0000000..bc90d40
--- /dev/null
+++ b/aos/network/www/index.html
@@ -0,0 +1,6 @@
+<html>
+ <body>
+ <script src="flatbuffers.js"></script>
+ <script src="proxy_bundle.min.js"></script>
+ </body>
+</html>
diff --git a/aos/network/www/main.ts b/aos/network/www/main.ts
new file mode 100644
index 0000000..5a3165e
--- /dev/null
+++ b/aos/network/www/main.ts
@@ -0,0 +1,5 @@
+import {Connection} from './proxy';
+
+const conn = new Connection();
+
+conn.connect();
diff --git a/aos/network/www/proxy.ts b/aos/network/www/proxy.ts
new file mode 100644
index 0000000..1ef6320
--- /dev/null
+++ b/aos/network/www/proxy.ts
@@ -0,0 +1,114 @@
+import {aos.web_proxy} from '../web_proxy_generated';
+
+// Analogous to the Connection class in //aos/network/web_proxy.h. Because most
+// of the apis are native in JS, it is much simpler.
+export class Connection {
+ private webSocketConnection: WebSocket|null = null;
+ private rtcPeerConnection: RTCPeerConnection|null = null;
+ private dataChannel: DataChannel|null = null;
+ private webSocketUrl: string;
+
+ constructor() {
+ const server = location.host;
+ this.webSocketUrl = `ws://${server}/ws`;
+ }
+
+ connect(): void {
+ this.webSocketConnection = new WebSocket(this.webSocketUrl);
+ this.webSocketConnection.binaryType = 'arraybuffer';
+ this.webSocketConnection.addEventListener(
+ 'open', () => this.onWebSocketOpen());
+ this.webSocketConnection.addEventListener(
+ 'message', (e) => this.onWebSocketMessage(e));
+ }
+
+ // Handle messages on the DataChannel. Will delegate to various handlers for
+ // different message types.
+ onDataChannelMessage(e: MessageEvent): void {
+ console.log(e);
+ }
+
+ onIceCandidate(e: RTCPeerConnectionIceEvent): void {
+ console.log('Created ice candidate', e);
+ if (!e.candidate) {
+ return;
+ }
+ const candidate = e.candidate;
+ const builder = new flatbuffers.Builder(512);
+ const candidateString = builder.createString(candidate.candidate);
+ const sdpMidString = builder.createString(candidate.sdpMid);
+
+ const iceFb = aos.web_proxy.WebSocketIce.createWebSocketIce(
+ builder, candidateString, sdpMidString, candidate.sdpMLineIndex);
+ const messageFb = aos.web_proxy.WebSocketMessage.createWebSocketMessage(
+ builder, aos.web_proxy.Payload.WebSocketIce, iceFb);
+ builder.finish(messageFb);
+ const array = builder.asUint8Array();
+ this.webSocketConnection.send(array.buffer.slice(array.byteOffset));
+ }
+
+ // Called for new SDPs. Make sure to set it locally and remotely.
+ onOfferCreated(description: RTCSessionDescription): void {
+ console.log('Created offer', description);
+ this.rtcPeerConnection.setLocalDescription(description);
+ const builder = new flatbuffers.Builder(512);
+ const offerString = builder.createString(description.sdp);
+
+ const webSocketSdp = aos.web_proxy.WebSocketSdp.createWebSocketSdp(
+ builder, aos.web_proxy.SdpType.OFFER, offerString);
+ const message = aos.web_proxy.WebSocketMessage.createWebSocketMessage(
+ builder, aos.web_proxy.Payload.WebSocketSdp, webSocketSdp);
+ builder.finish(message);
+ const array = builder.asUint8Array();
+ this.webSocketConnection.send(array.buffer.slice(array.byteOffset));
+ }
+
+ // We now have a websocket, so start setting up the peer connection. We only
+ // want a DataChannel, so create it and then create an offer to send.
+ onWebSocketOpen(): void {
+ this.rtcPeerConnection = new RTCPeerConnection({});
+ this.dataChannel = this.rtcPeerConnection.createDataChannel('dc');
+ this.dataChannel.addEventListener(
+ 'message', (e) => this.onDataChannelMessage(e));
+ window.dc = this.dataChannel;
+ this.rtcPeerConnection.addEventListener(
+ 'icecandidate', (e) => this.onIceCandidate(e));
+ this.rtcPeerConnection.createOffer().then(
+ (offer) => this.onOfferCreated(offer));
+ }
+
+ // When we receive a websocket message, we need to determine what type it is
+ // and handle appropriately. Either by setting the remote description or
+ // adding the remote ice candidate.
+ onWebSocketMessage(e: MessageEvent): void {
+ console.log('ws: ', e);
+ const buffer = new Uint8Array(e.data)
+ const fbBuffer = new flatbuffers.ByteBuffer(buffer);
+ const message =
+ aos.web_proxy.WebSocketMessage.getRootAsWebSocketMessage(fbBuffer);
+ switch (message.payloadType()) {
+ case aos.web_proxy.Payload.WebSocketSdp:
+ console.log('got an sdp message');
+ const sdpFb = message.payload(new aos.web_proxy.WebSocketSdp());
+ if (sdpFb.type() !== aos.web_proxy.SdpType.ANSWER) {
+ console.log('got something other than an answer back');
+ break;
+ }
+ this.rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(
+ {'type': 'answer', 'sdp': sdpFb.payload()}));
+ break;
+ case aos.web_proxy.Payload.WebSocketIce:
+ console.log('got an ice message');
+ const iceFb = message.payload(new aos.web_proxy.WebSocketIce());
+ const candidate = {} as RTCIceCandidateInit;
+ candidate.candidate = iceFb.candidate();
+ candidate.sdpMid = iceFb.sdpMid();
+ candidate.sdpMLineIndex = iceFb.sdpMLineIndex();
+ this.rtcPeerConnection.addIceCandidate(candidate);
+ break;
+ default:
+ console.log('got an unknown message');
+ break;
+ }
+ }
+}