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;
+    }
+  }
+}