blob: 7bcf5751ef9f538ddc92001201f21a21b68f5d81 [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(
12 private readonly handlerFunc: (data: Uint8Array) => void,
13 private readonly channel: RTCPeerConnection) {
14 channel.addEventListener('message', (e) => this.handleMessage(e));
15 }
16
17 handleMessage(e: MessageEvent): void {
18 const fbBuffer = new flatbuffers.ByteBuffer(new Uint8Array(e.data));
19 const messageHeader =
Alex Perryd5e13572020-02-22 15:15:08 -080020 WebProxy.MessageHeader.getRootAsMessageHeader(fbBuffer);
Alex Perry5f474f22020-02-01 12:14:24 -080021 // Short circuit if only one packet
Alex Perry22824d72020-02-29 17:11:43 -080022 if (messageHeader.packetCount() === 1) {
Alex Perry5f474f22020-02-01 12:14:24 -080023 this.handlerFunc(messageHeader.dataArray());
24 return;
25 }
26
27 if (messageHeader.packetIndex() === 0) {
28 this.dataBuffer = new Uint8Array(messageHeader.length());
Alex Perry22824d72020-02-29 17:11:43 -080029 this.receivedMessageLength = 0;
30 }
31 if (!messageHeader.dataLength()) {
32 return;
Alex Perry5f474f22020-02-01 12:14:24 -080033 }
34 this.dataBuffer.set(
35 messageHeader.dataArray(),
36 this.receivedMessageLength);
37 this.receivedMessageLength += messageHeader.dataLength();
38
39 if (messageHeader.packetIndex() === messageHeader.packetCount() - 1) {
40 this.handlerFunc(this.dataBuffer);
41 }
42 }
43}
Alex Perryb3b50792020-01-18 16:13:45 -080044
45// Analogous to the Connection class in //aos/network/web_proxy.h. Because most
46// of the apis are native in JS, it is much simpler.
47export class Connection {
48 private webSocketConnection: WebSocket|null = null;
49 private rtcPeerConnection: RTCPeerConnection|null = null;
50 private dataChannel: DataChannel|null = null;
51 private webSocketUrl: string;
Alex Perry6249aaf2020-02-29 14:51:49 -080052
53 private configInternal: Configuration|null = null;
54 // A set of functions that accept the config to handle.
55 private readonly configHandlers = new Set<(config: Configuration) => void>();
56
Alex Perry5f474f22020-02-01 12:14:24 -080057 private readonly handlerFuncs = new Map<string, (data: Uint8Array) => void>();
58 private readonly handlers = new Set<Handler>();
Alex Perryb3b50792020-01-18 16:13:45 -080059
60 constructor() {
61 const server = location.host;
62 this.webSocketUrl = `ws://${server}/ws`;
63 }
64
Alex Perry6249aaf2020-02-29 14:51:49 -080065 addConfigHandler(handler: (config: Configuration) => void): void {
66 this.configHandlers.add(handler);
67 }
68
Alex Perryb49a3fb2020-02-29 15:26:54 -080069 /**
70 * Add a handler for a specific message type. Until we need to handle
71 * different channel names with the same type differently, this is good
72 * enough.
73 */
Alex Perry5f474f22020-02-01 12:14:24 -080074 addHandler(id: string, handler: (data: Uint8Array) => void): void {
75 this.handlerFuncs.set(id, handler);
76 }
77
Alex Perryb3b50792020-01-18 16:13:45 -080078 connect(): void {
79 this.webSocketConnection = new WebSocket(this.webSocketUrl);
80 this.webSocketConnection.binaryType = 'arraybuffer';
81 this.webSocketConnection.addEventListener(
82 'open', () => this.onWebSocketOpen());
83 this.webSocketConnection.addEventListener(
84 'message', (e) => this.onWebSocketMessage(e));
85 }
86
Alex Perry3dfcb812020-03-04 19:32:17 -080087 getConfig() {
Alex Perry6249aaf2020-02-29 14:51:49 -080088 return this.config_internal;
89 }
90
Alex Perry5f474f22020-02-01 12:14:24 -080091 // Handle messages on the DataChannel. Handles the Configuration message as
92 // all other messages are sent on specific DataChannels.
Alex Perryb3b50792020-01-18 16:13:45 -080093 onDataChannelMessage(e: MessageEvent): void {
Alex Perry5f474f22020-02-01 12:14:24 -080094 const fbBuffer = new flatbuffers.ByteBuffer(new Uint8Array(e.data));
Alex Perry6249aaf2020-02-29 14:51:49 -080095 this.configInternal = Configuration.getRootAsConfiguration(fbBuffer);
Alex Perry3dfcb812020-03-04 19:32:17 -080096 for (const handler of Array.from(this.configHandlers)) {
Alex Perry6249aaf2020-02-29 14:51:49 -080097 handler(this.configInternal);
Alex Perry5f474f22020-02-01 12:14:24 -080098 }
99 }
100
101 onDataChannel(ev: RTCDataChannelEvent): void {
102 const channel = ev.channel;
103 const name = channel.label;
104 const channelType = name.split('/').pop();
105 const handlerFunc = this.handlerFuncs.get(channelType);
106 this.handlers.add(new Handler(handlerFunc, channel));
Alex Perryb3b50792020-01-18 16:13:45 -0800107 }
108
109 onIceCandidate(e: RTCPeerConnectionIceEvent): void {
Alex Perryb3b50792020-01-18 16:13:45 -0800110 if (!e.candidate) {
111 return;
112 }
113 const candidate = e.candidate;
114 const builder = new flatbuffers.Builder(512);
115 const candidateString = builder.createString(candidate.candidate);
116 const sdpMidString = builder.createString(candidate.sdpMid);
117
Alex Perryd5e13572020-02-22 15:15:08 -0800118 const iceFb = WebProxy.WebSocketIce.createWebSocketIce(
Alex Perryb3b50792020-01-18 16:13:45 -0800119 builder, candidateString, sdpMidString, candidate.sdpMLineIndex);
Alex Perryd5e13572020-02-22 15:15:08 -0800120 const messageFb = WebProxy.WebSocketMessage.createWebSocketMessage(
121 builder, WebProxy.Payload.WebSocketIce, iceFb);
Alex Perryb3b50792020-01-18 16:13:45 -0800122 builder.finish(messageFb);
123 const array = builder.asUint8Array();
124 this.webSocketConnection.send(array.buffer.slice(array.byteOffset));
125 }
126
127 // Called for new SDPs. Make sure to set it locally and remotely.
128 onOfferCreated(description: RTCSessionDescription): void {
Alex Perryb3b50792020-01-18 16:13:45 -0800129 this.rtcPeerConnection.setLocalDescription(description);
130 const builder = new flatbuffers.Builder(512);
131 const offerString = builder.createString(description.sdp);
132
Alex Perryd5e13572020-02-22 15:15:08 -0800133 const webSocketSdp = WebProxy.WebSocketSdp.createWebSocketSdp(
134 builder, WebProxy.SdpType.OFFER, offerString);
135 const message = WebProxy.WebSocketMessage.createWebSocketMessage(
136 builder, WebProxy.Payload.WebSocketSdp, webSocketSdp);
Alex Perryb3b50792020-01-18 16:13:45 -0800137 builder.finish(message);
138 const array = builder.asUint8Array();
139 this.webSocketConnection.send(array.buffer.slice(array.byteOffset));
140 }
141
142 // We now have a websocket, so start setting up the peer connection. We only
143 // want a DataChannel, so create it and then create an offer to send.
144 onWebSocketOpen(): void {
145 this.rtcPeerConnection = new RTCPeerConnection({});
Alex Perry5f474f22020-02-01 12:14:24 -0800146 this.rtcPeerConnection.addEventListener(
Alex Perry22824d72020-02-29 17:11:43 -0800147 'datachannel', (e) => this.onDataChannel(e));
Alex Perry5f474f22020-02-01 12:14:24 -0800148 this.dataChannel = this.rtcPeerConnection.createDataChannel('signalling');
Alex Perryb3b50792020-01-18 16:13:45 -0800149 this.dataChannel.addEventListener(
150 'message', (e) => this.onDataChannelMessage(e));
151 window.dc = this.dataChannel;
152 this.rtcPeerConnection.addEventListener(
153 'icecandidate', (e) => this.onIceCandidate(e));
154 this.rtcPeerConnection.createOffer().then(
155 (offer) => this.onOfferCreated(offer));
156 }
157
158 // When we receive a websocket message, we need to determine what type it is
159 // and handle appropriately. Either by setting the remote description or
160 // adding the remote ice candidate.
161 onWebSocketMessage(e: MessageEvent): void {
Alex Perryb3b50792020-01-18 16:13:45 -0800162 const buffer = new Uint8Array(e.data)
163 const fbBuffer = new flatbuffers.ByteBuffer(buffer);
164 const message =
Alex Perryd5e13572020-02-22 15:15:08 -0800165 WebProxy.WebSocketMessage.getRootAsWebSocketMessage(fbBuffer);
Alex Perryb3b50792020-01-18 16:13:45 -0800166 switch (message.payloadType()) {
Alex Perryd5e13572020-02-22 15:15:08 -0800167 case WebProxy.Payload.WebSocketSdp:
168 const sdpFb = message.payload(new WebProxy.WebSocketSdp());
169 if (sdpFb.type() !== WebProxy.SdpType.ANSWER) {
Alex Perryb3b50792020-01-18 16:13:45 -0800170 console.log('got something other than an answer back');
171 break;
172 }
173 this.rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(
174 {'type': 'answer', 'sdp': sdpFb.payload()}));
175 break;
Alex Perryd5e13572020-02-22 15:15:08 -0800176 case WebProxy.Payload.WebSocketIce:
177 const iceFb = message.payload(new WebProxy.WebSocketIce());
Alex Perryb3b50792020-01-18 16:13:45 -0800178 const candidate = {} as RTCIceCandidateInit;
179 candidate.candidate = iceFb.candidate();
180 candidate.sdpMid = iceFb.sdpMid();
181 candidate.sdpMLineIndex = iceFb.sdpMLineIndex();
182 this.rtcPeerConnection.addIceCandidate(candidate);
183 break;
184 default:
185 console.log('got an unknown message');
186 break;
187 }
188 }
Alex Perry6249aaf2020-02-29 14:51:49 -0800189
190 /**
Alex Perryb49a3fb2020-02-29 15:26:54 -0800191 * Subscribes to messages. Only the most recent connect message is in use. Any
192 * channels not specified in the message are implicitely unsubscribed.
Alex Perry6249aaf2020-02-29 14:51:49 -0800193 * @param a Finished flatbuffer.Builder containing a Connect message to send.
194 */
195 sendConnectMessage(builder: any) {
Alex Perry3dfcb812020-03-04 19:32:17 -0800196 const array = builder.asUint8Array();
Alex Perry6249aaf2020-02-29 14:51:49 -0800197 this.dataChannel.send(array.buffer.slice(array.byteOffset));
198 }
Alex Perryb3b50792020-01-18 16:13:45 -0800199}