blob: 4ba7e39fde03cadecbfdb85aa21272c149b33aad [file] [log] [blame]
Alex Perry5f474f22020-02-01 12:14:24 -08001import {ConfigHandler} from './config_handler';
Alex Perryd5e13572020-02-22 15:15:08 -08002import {Configuration} 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,
Alex Perry5f474f22020-02-01 12:14:24 -080014 private readonly channel: RTCPeerConnection) {
15 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;
52 private dataChannel: DataChannel|null = null;
53 private webSocketUrl: string;
Alex Perry6249aaf2020-02-29 14:51:49 -080054
55 private configInternal: Configuration|null = null;
56 // A set of functions that accept the config to handle.
57 private readonly configHandlers = new Set<(config: Configuration) => void>();
58
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
Alex Perry6249aaf2020-02-29 14:51:49 -080068 addConfigHandler(handler: (config: Configuration) => void): void {
69 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() {
Alex Perry6249aaf2020-02-29 14:51:49 -080091 return this.config_internal;
92 }
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);
Alex Perry6249aaf2020-02-29 14:51:49 -080098 this.configInternal = 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.
131 onOfferCreated(description: RTCSessionDescription): 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));
Alex Perryb3b50792020-01-18 16:13:45 -0800154 window.dc = this.dataChannel;
155 this.rtcPeerConnection.addEventListener(
156 'icecandidate', (e) => this.onIceCandidate(e));
157 this.rtcPeerConnection.createOffer().then(
158 (offer) => this.onOfferCreated(offer));
159 }
160
161 // When we receive a websocket message, we need to determine what type it is
162 // and handle appropriately. Either by setting the remote description or
163 // adding the remote ice candidate.
164 onWebSocketMessage(e: MessageEvent): void {
Alex Perryb3b50792020-01-18 16:13:45 -0800165 const buffer = new Uint8Array(e.data)
166 const fbBuffer = new flatbuffers.ByteBuffer(buffer);
167 const message =
Alex Perryd5e13572020-02-22 15:15:08 -0800168 WebProxy.WebSocketMessage.getRootAsWebSocketMessage(fbBuffer);
Alex Perryb3b50792020-01-18 16:13:45 -0800169 switch (message.payloadType()) {
Alex Perryd5e13572020-02-22 15:15:08 -0800170 case WebProxy.Payload.WebSocketSdp:
171 const sdpFb = message.payload(new WebProxy.WebSocketSdp());
172 if (sdpFb.type() !== WebProxy.SdpType.ANSWER) {
Alex Perryb3b50792020-01-18 16:13:45 -0800173 console.log('got something other than an answer back');
174 break;
175 }
176 this.rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(
177 {'type': 'answer', 'sdp': sdpFb.payload()}));
178 break;
Alex Perryd5e13572020-02-22 15:15:08 -0800179 case WebProxy.Payload.WebSocketIce:
180 const iceFb = message.payload(new WebProxy.WebSocketIce());
Alex Perryb3b50792020-01-18 16:13:45 -0800181 const candidate = {} as RTCIceCandidateInit;
182 candidate.candidate = iceFb.candidate();
183 candidate.sdpMid = iceFb.sdpMid();
184 candidate.sdpMLineIndex = iceFb.sdpMLineIndex();
185 this.rtcPeerConnection.addIceCandidate(candidate);
186 break;
187 default:
188 console.log('got an unknown message');
189 break;
190 }
191 }
Alex Perry6249aaf2020-02-29 14:51:49 -0800192
193 /**
Alex Perryb49a3fb2020-02-29 15:26:54 -0800194 * Subscribes to messages. Only the most recent connect message is in use. Any
195 * channels not specified in the message are implicitely unsubscribed.
Alex Perry6249aaf2020-02-29 14:51:49 -0800196 * @param a Finished flatbuffer.Builder containing a Connect message to send.
197 */
198 sendConnectMessage(builder: any) {
Alex Perry3dfcb812020-03-04 19:32:17 -0800199 const array = builder.asUint8Array();
Alex Perry6249aaf2020-02-29 14:51:49 -0800200 this.dataChannel.send(array.buffer.slice(array.byteOffset));
201 }
Alex Perryb3b50792020-01-18 16:13:45 -0800202}