blob: ee3ad6930568cd25b32bdfccf6fe2b7018718acf [file] [log] [blame]
Alex Perry5f474f22020-02-01 12:14:24 -08001import {ConfigHandler} from './config_handler';
Austin Schuh7c75e582020-11-14 16:41:18 -08002import {aos} from 'aos/configuration_generated';
Austin Schuhf6e71392020-02-26 23:10:15 -08003import * as WebProxy from 'aos/network/web_proxy_generated';
Alex Perry5f474f22020-02-01 12:14:24 -08004
5// There is one handler for each DataChannel, it maintains the state of
6// multi-part messages and delegates to a callback when the message is fully
7// assembled.
8export class Handler {
9 private dataBuffer: Uint8Array|null = null;
10 private receivedMessageLength: number = 0;
11 constructor(
James Kuszmaul48413bf2020-09-01 19:19:05 -070012 private readonly handlerFunc:
13 (data: Uint8Array, sentTime: number) => void,
Philipp Schrader47445a02020-11-14 17:31:04 -080014 private readonly channel: RTCDataChannel) {
Alex Perry5f474f22020-02-01 12:14:24 -080015 channel.addEventListener('message', (e) => this.handleMessage(e));
16 }
17
18 handleMessage(e: MessageEvent): void {
19 const fbBuffer = new flatbuffers.ByteBuffer(new Uint8Array(e.data));
20 const messageHeader =
Alex Perryd5e13572020-02-22 15:15:08 -080021 WebProxy.MessageHeader.getRootAsMessageHeader(fbBuffer);
James Kuszmaul48413bf2020-09-01 19:19:05 -070022 const time = messageHeader.monotonicSentTime().toFloat64() * 1e-9;
Alex Perry5f474f22020-02-01 12:14:24 -080023 // Short circuit if only one packet
Alex Perry22824d72020-02-29 17:11:43 -080024 if (messageHeader.packetCount() === 1) {
James Kuszmaul48413bf2020-09-01 19:19:05 -070025 this.handlerFunc(messageHeader.dataArray(), time);
Alex Perry5f474f22020-02-01 12:14:24 -080026 return;
27 }
28
29 if (messageHeader.packetIndex() === 0) {
30 this.dataBuffer = new Uint8Array(messageHeader.length());
Alex Perry22824d72020-02-29 17:11:43 -080031 this.receivedMessageLength = 0;
32 }
33 if (!messageHeader.dataLength()) {
34 return;
Alex Perry5f474f22020-02-01 12:14:24 -080035 }
36 this.dataBuffer.set(
37 messageHeader.dataArray(),
38 this.receivedMessageLength);
39 this.receivedMessageLength += messageHeader.dataLength();
40
41 if (messageHeader.packetIndex() === messageHeader.packetCount() - 1) {
James Kuszmaul48413bf2020-09-01 19:19:05 -070042 this.handlerFunc(this.dataBuffer, time);
Alex Perry5f474f22020-02-01 12:14:24 -080043 }
44 }
45}
Alex Perryb3b50792020-01-18 16:13:45 -080046
47// Analogous to the Connection class in //aos/network/web_proxy.h. Because most
48// of the apis are native in JS, it is much simpler.
49export class Connection {
50 private webSocketConnection: WebSocket|null = null;
51 private rtcPeerConnection: RTCPeerConnection|null = null;
Philipp Schrader47445a02020-11-14 17:31:04 -080052 private dataChannel: RTCDataChannel|null = null;
Alex Perryb3b50792020-01-18 16:13:45 -080053 private webSocketUrl: string;
Alex Perry6249aaf2020-02-29 14:51:49 -080054
Austin Schuh7c75e582020-11-14 16:41:18 -080055 private configInternal: aos.Configuration|null = null;
Alex Perry6249aaf2020-02-29 14:51:49 -080056 // A set of functions that accept the config to handle.
Austin Schuh7c75e582020-11-14 16:41:18 -080057 private readonly configHandlers = new Set<(config: aos.Configuration) => void>();
Alex Perry6249aaf2020-02-29 14:51:49 -080058
James Kuszmaul48413bf2020-09-01 19:19:05 -070059 private readonly handlerFuncs =
60 new Map<string, (data: Uint8Array, sentTime: number) => void>();
Alex Perry5f474f22020-02-01 12:14:24 -080061 private readonly handlers = new Set<Handler>();
Alex Perryb3b50792020-01-18 16:13:45 -080062
63 constructor() {
64 const server = location.host;
65 this.webSocketUrl = `ws://${server}/ws`;
66 }
67
Austin Schuh7c75e582020-11-14 16:41:18 -080068 addConfigHandler(handler: (config: aos.Configuration) => void): void {
Alex Perry6249aaf2020-02-29 14:51:49 -080069 this.configHandlers.add(handler);
70 }
71
Alex Perryb49a3fb2020-02-29 15:26:54 -080072 /**
73 * Add a handler for a specific message type. Until we need to handle
74 * different channel names with the same type differently, this is good
75 * enough.
76 */
Alex Perry5f474f22020-02-01 12:14:24 -080077 addHandler(id: string, handler: (data: Uint8Array) => void): void {
78 this.handlerFuncs.set(id, handler);
79 }
80
Alex Perryb3b50792020-01-18 16:13:45 -080081 connect(): void {
82 this.webSocketConnection = new WebSocket(this.webSocketUrl);
83 this.webSocketConnection.binaryType = 'arraybuffer';
84 this.webSocketConnection.addEventListener(
85 'open', () => this.onWebSocketOpen());
86 this.webSocketConnection.addEventListener(
87 'message', (e) => this.onWebSocketMessage(e));
88 }
89
Alex Perry3dfcb812020-03-04 19:32:17 -080090 getConfig() {
Philipp Schrader47445a02020-11-14 17:31:04 -080091 return this.configInternal;
Alex Perry6249aaf2020-02-29 14:51:49 -080092 }
93
Alex Perry5f474f22020-02-01 12:14:24 -080094 // Handle messages on the DataChannel. Handles the Configuration message as
95 // all other messages are sent on specific DataChannels.
James Kuszmaul1ec74432020-07-30 20:26:45 -070096 onConfigMessage(data: Uint8Array): void {
97 const fbBuffer = new flatbuffers.ByteBuffer(data);
Austin Schuh7c75e582020-11-14 16:41:18 -080098 this.configInternal = aos.Configuration.getRootAsConfiguration(fbBuffer);
Alex Perry3dfcb812020-03-04 19:32:17 -080099 for (const handler of Array.from(this.configHandlers)) {
Alex Perry6249aaf2020-02-29 14:51:49 -0800100 handler(this.configInternal);
Alex Perry5f474f22020-02-01 12:14:24 -0800101 }
102 }
103
104 onDataChannel(ev: RTCDataChannelEvent): void {
105 const channel = ev.channel;
106 const name = channel.label;
107 const channelType = name.split('/').pop();
108 const handlerFunc = this.handlerFuncs.get(channelType);
109 this.handlers.add(new Handler(handlerFunc, channel));
Alex Perryb3b50792020-01-18 16:13:45 -0800110 }
111
112 onIceCandidate(e: RTCPeerConnectionIceEvent): void {
Alex Perryb3b50792020-01-18 16:13:45 -0800113 if (!e.candidate) {
114 return;
115 }
116 const candidate = e.candidate;
117 const builder = new flatbuffers.Builder(512);
118 const candidateString = builder.createString(candidate.candidate);
119 const sdpMidString = builder.createString(candidate.sdpMid);
120
Alex Perryd5e13572020-02-22 15:15:08 -0800121 const iceFb = WebProxy.WebSocketIce.createWebSocketIce(
Alex Perryb3b50792020-01-18 16:13:45 -0800122 builder, candidateString, sdpMidString, candidate.sdpMLineIndex);
Alex Perryd5e13572020-02-22 15:15:08 -0800123 const messageFb = WebProxy.WebSocketMessage.createWebSocketMessage(
124 builder, WebProxy.Payload.WebSocketIce, iceFb);
Alex Perryb3b50792020-01-18 16:13:45 -0800125 builder.finish(messageFb);
126 const array = builder.asUint8Array();
127 this.webSocketConnection.send(array.buffer.slice(array.byteOffset));
128 }
129
130 // Called for new SDPs. Make sure to set it locally and remotely.
Philipp Schrader47445a02020-11-14 17:31:04 -0800131 onOfferCreated(description: RTCSessionDescriptionInit): void {
Alex Perryb3b50792020-01-18 16:13:45 -0800132 this.rtcPeerConnection.setLocalDescription(description);
133 const builder = new flatbuffers.Builder(512);
134 const offerString = builder.createString(description.sdp);
135
Alex Perryd5e13572020-02-22 15:15:08 -0800136 const webSocketSdp = WebProxy.WebSocketSdp.createWebSocketSdp(
137 builder, WebProxy.SdpType.OFFER, offerString);
138 const message = WebProxy.WebSocketMessage.createWebSocketMessage(
139 builder, WebProxy.Payload.WebSocketSdp, webSocketSdp);
Alex Perryb3b50792020-01-18 16:13:45 -0800140 builder.finish(message);
141 const array = builder.asUint8Array();
142 this.webSocketConnection.send(array.buffer.slice(array.byteOffset));
143 }
144
145 // We now have a websocket, so start setting up the peer connection. We only
146 // want a DataChannel, so create it and then create an offer to send.
147 onWebSocketOpen(): void {
148 this.rtcPeerConnection = new RTCPeerConnection({});
Alex Perry5f474f22020-02-01 12:14:24 -0800149 this.rtcPeerConnection.addEventListener(
Alex Perry22824d72020-02-29 17:11:43 -0800150 'datachannel', (e) => this.onDataChannel(e));
Alex Perry5f474f22020-02-01 12:14:24 -0800151 this.dataChannel = this.rtcPeerConnection.createDataChannel('signalling');
James Kuszmaul1ec74432020-07-30 20:26:45 -0700152 this.handlers.add(
153 new Handler((data) => this.onConfigMessage(data), this.dataChannel));
Philipp Schrader47445a02020-11-14 17:31:04 -0800154 // TODO(james): Is this used? Can we delete it?
155 // window.dc = this.dataChannel;
Alex Perryb3b50792020-01-18 16:13:45 -0800156 this.rtcPeerConnection.addEventListener(
157 'icecandidate', (e) => this.onIceCandidate(e));
158 this.rtcPeerConnection.createOffer().then(
159 (offer) => this.onOfferCreated(offer));
160 }
161
162 // When we receive a websocket message, we need to determine what type it is
163 // and handle appropriately. Either by setting the remote description or
164 // adding the remote ice candidate.
165 onWebSocketMessage(e: MessageEvent): void {
Alex Perryb3b50792020-01-18 16:13:45 -0800166 const buffer = new Uint8Array(e.data)
167 const fbBuffer = new flatbuffers.ByteBuffer(buffer);
168 const message =
Alex Perryd5e13572020-02-22 15:15:08 -0800169 WebProxy.WebSocketMessage.getRootAsWebSocketMessage(fbBuffer);
Alex Perryb3b50792020-01-18 16:13:45 -0800170 switch (message.payloadType()) {
Alex Perryd5e13572020-02-22 15:15:08 -0800171 case WebProxy.Payload.WebSocketSdp:
172 const sdpFb = message.payload(new WebProxy.WebSocketSdp());
173 if (sdpFb.type() !== WebProxy.SdpType.ANSWER) {
Alex Perryb3b50792020-01-18 16:13:45 -0800174 console.log('got something other than an answer back');
175 break;
176 }
177 this.rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(
178 {'type': 'answer', 'sdp': sdpFb.payload()}));
179 break;
Alex Perryd5e13572020-02-22 15:15:08 -0800180 case WebProxy.Payload.WebSocketIce:
181 const iceFb = message.payload(new WebProxy.WebSocketIce());
Alex Perryb3b50792020-01-18 16:13:45 -0800182 const candidate = {} as RTCIceCandidateInit;
183 candidate.candidate = iceFb.candidate();
184 candidate.sdpMid = iceFb.sdpMid();
185 candidate.sdpMLineIndex = iceFb.sdpMLineIndex();
186 this.rtcPeerConnection.addIceCandidate(candidate);
187 break;
188 default:
189 console.log('got an unknown message');
190 break;
191 }
192 }
Alex Perry6249aaf2020-02-29 14:51:49 -0800193
194 /**
Alex Perryb49a3fb2020-02-29 15:26:54 -0800195 * Subscribes to messages. Only the most recent connect message is in use. Any
196 * channels not specified in the message are implicitely unsubscribed.
Alex Perry6249aaf2020-02-29 14:51:49 -0800197 * @param a Finished flatbuffer.Builder containing a Connect message to send.
198 */
199 sendConnectMessage(builder: any) {
Alex Perry3dfcb812020-03-04 19:32:17 -0800200 const array = builder.asUint8Array();
Alex Perry6249aaf2020-02-29 14:51:49 -0800201 this.dataChannel.send(array.buffer.slice(array.byteOffset));
202 }
Alex Perryb3b50792020-01-18 16:13:45 -0800203}