Refactor typescript message handler

This makes it so that, to register a handler on a given topic, you just
need to call a single function, rather than assembling the entire
flatbuffer request message by yourself.

Change-Id: I540f475d9650ab27efe715a05d7586719045aeed
diff --git a/aos/network/www/proxy.ts b/aos/network/www/proxy.ts
index 5660cef..4fbba85 100644
--- a/aos/network/www/proxy.ts
+++ b/aos/network/www/proxy.ts
@@ -3,6 +3,7 @@
 import {Builder} from 'org_frc971/external/com_github_google_flatbuffers/ts/builder';
 import {ByteBuffer} from 'org_frc971/external/com_github_google_flatbuffers/ts/byte-buffer';
 
+import ChannelFb = configuration.aos.Channel;
 import Configuration = configuration.aos.Configuration;
 import MessageHeader = web_proxy.aos.web_proxy.MessageHeader;
 import WebSocketIce = web_proxy.aos.web_proxy.WebSocketIce;
@@ -10,6 +11,9 @@
 import Payload = web_proxy.aos.web_proxy.Payload;
 import WebSocketSdp = web_proxy.aos.web_proxy.WebSocketSdp;
 import SdpType = web_proxy.aos.web_proxy.SdpType;
+import SubscriberRequest = web_proxy.aos.web_proxy.SubscriberRequest;
+import ChannelRequestFb = web_proxy.aos.web_proxy.ChannelRequest;
+import TransferMethod = web_proxy.aos.web_proxy.TransferMethod;
 
 // There is one handler for each DataChannel, it maintains the state of
 // multi-part messages and delegates to a callback when the message is fully
@@ -53,6 +57,19 @@
   }
 }
 
+class Channel {
+  constructor(public readonly name: string, public readonly type: string) {}
+  key(): string {
+    return this.name + "/" + this.type;
+  }
+}
+
+class ChannelRequest {
+  constructor(
+      public readonly channel: Channel,
+      public readonly transferMethod: TransferMethod) {}
+}
+
 // 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 {
@@ -69,6 +86,8 @@
       new Map<string, (data: Uint8Array, sentTime: number) => void>();
   private readonly handlers = new Set<Handler>();
 
+  private subscribedChannels: ChannelRequest[] = [];
+
   constructor() {
     const server = location.host;
     this.webSocketUrl = `ws://${server}/ws`;
@@ -79,12 +98,62 @@
   }
 
   /**
-   * Add a handler for a specific message type. Until we need to handle
-   * different channel names with the same type differently, this is good
-   * enough.
+   * Add a handler for a specific message type, with reliable delivery of all
+   * messages.
    */
-  addHandler(id: string, handler: (data: Uint8Array, sentTime: number) => void): void {
-    this.handlerFuncs.set(id, handler);
+  addReliableHandler(
+      name: string, type: string,
+      handler: (data: Uint8Array, sentTime: number) => void): void {
+    this.addHandlerImpl(
+        name, type, TransferMethod.EVERYTHING_WITH_HISTORY, handler);
+  }
+
+  /**
+   * Add a handler for a specific message type.
+   */
+  addHandler(
+      name: string, type: string,
+      handler: (data: Uint8Array, sentTime: number) => void): void {
+    this.addHandlerImpl(name, type, TransferMethod.SUBSAMPLE, handler);
+  }
+
+  addHandlerImpl(
+      name: string, type: string, method: TransferMethod,
+      handler: (data: Uint8Array, sentTime: number) => void): void {
+    const channel = new Channel(name, type);
+    const request = new ChannelRequest(channel, method);
+    this.handlerFuncs.set(channel.key(), handler);
+    this.subscribeToChannel(request);
+  }
+
+  subscribeToChannel(channel: ChannelRequest): void {
+    this.subscribedChannels.push(channel);
+    if (this.configInternal === null) {
+      throw new Error(
+          'Must call subscribeToChannel after we\'ve received the config.');
+    }
+    const builder = new Builder(512) as unknown as flatbuffers.Builder;
+    const channels: flatbuffers.Offset[] = [];
+    for (const channel of this.subscribedChannels) {
+      const nameFb = builder.createString(channel.channel.name);
+      const typeFb = builder.createString(channel.channel.type);
+      ChannelFb.startChannel(builder);
+      ChannelFb.addName(builder, nameFb);
+      ChannelFb.addType(builder, typeFb);
+      const channelFb = ChannelFb.endChannel(builder);
+      ChannelRequestFb.startChannelRequest(builder);
+      ChannelRequestFb.addChannel(builder, channelFb);
+      ChannelRequestFb.addMethod(builder, channel.transferMethod);
+      channels.push(ChannelRequestFb.endChannelRequest(builder));
+    }
+
+    const channelsFb =
+        SubscriberRequest.createChannelsToTransferVector(builder, channels);
+    SubscriberRequest.startSubscriberRequest(builder);
+    SubscriberRequest.addChannelsToTransfer(builder, channelsFb);
+    const connect = SubscriberRequest.endSubscriberRequest(builder);
+    builder.finish(connect);
+    this.sendConnectMessage(builder);
   }
 
   connect(): void {
@@ -114,8 +183,7 @@
   onDataChannel(ev: RTCDataChannelEvent): void {
     const channel = ev.channel;
     const name = channel.label;
-    const channelType = name.split('/').pop();
-    const handlerFunc = this.handlerFuncs.get(channelType);
+    const handlerFunc = this.handlerFuncs.get(name);
     this.handlers.add(new Handler(handlerFunc, channel));
   }