blob: 704fc855855fade4bc01dfff11438f677f333cce [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 Perryd5e13572020-02-22 15:15:08 -080052 private configHandler: ConfigHandler|null = null;
53 private config: Configuration|null = null;
Alex Perry5f474f22020-02-01 12:14:24 -080054 private readonly handlerFuncs = new Map<string, (data: Uint8Array) => void>();
55 private readonly handlers = new Set<Handler>();
Alex Perryb3b50792020-01-18 16:13:45 -080056
57 constructor() {
58 const server = location.host;
59 this.webSocketUrl = `ws://${server}/ws`;
60 }
61
Alex Perry5f474f22020-02-01 12:14:24 -080062 addHandler(id: string, handler: (data: Uint8Array) => void): void {
63 this.handlerFuncs.set(id, handler);
64 }
65
Alex Perryb3b50792020-01-18 16:13:45 -080066 connect(): void {
67 this.webSocketConnection = new WebSocket(this.webSocketUrl);
68 this.webSocketConnection.binaryType = 'arraybuffer';
69 this.webSocketConnection.addEventListener(
70 'open', () => this.onWebSocketOpen());
71 this.webSocketConnection.addEventListener(
72 'message', (e) => this.onWebSocketMessage(e));
73 }
74
Alex Perry5f474f22020-02-01 12:14:24 -080075 // Handle messages on the DataChannel. Handles the Configuration message as
76 // all other messages are sent on specific DataChannels.
Alex Perryb3b50792020-01-18 16:13:45 -080077 onDataChannelMessage(e: MessageEvent): void {
Alex Perry5f474f22020-02-01 12:14:24 -080078 const fbBuffer = new flatbuffers.ByteBuffer(new Uint8Array(e.data));
79 // TODO(alex): handle config updates if/when required
80 if (!this.configHandler) {
Austin Schuhf6e71392020-02-26 23:10:15 -080081 const config = Configuration.getRootAsConfiguration(fbBuffer);
Alex Perry5f474f22020-02-01 12:14:24 -080082 this.config = config;
83 this.configHandler = new ConfigHandler(config, this.dataChannel);
84 this.configHandler.printConfig();
85 return;
86 }
87 }
88
89 onDataChannel(ev: RTCDataChannelEvent): void {
90 const channel = ev.channel;
91 const name = channel.label;
92 const channelType = name.split('/').pop();
93 const handlerFunc = this.handlerFuncs.get(channelType);
94 this.handlers.add(new Handler(handlerFunc, channel));
Alex Perryb3b50792020-01-18 16:13:45 -080095 }
96
97 onIceCandidate(e: RTCPeerConnectionIceEvent): void {
Alex Perryb3b50792020-01-18 16:13:45 -080098 if (!e.candidate) {
99 return;
100 }
101 const candidate = e.candidate;
102 const builder = new flatbuffers.Builder(512);
103 const candidateString = builder.createString(candidate.candidate);
104 const sdpMidString = builder.createString(candidate.sdpMid);
105
Alex Perryd5e13572020-02-22 15:15:08 -0800106 const iceFb = WebProxy.WebSocketIce.createWebSocketIce(
Alex Perryb3b50792020-01-18 16:13:45 -0800107 builder, candidateString, sdpMidString, candidate.sdpMLineIndex);
Alex Perryd5e13572020-02-22 15:15:08 -0800108 const messageFb = WebProxy.WebSocketMessage.createWebSocketMessage(
109 builder, WebProxy.Payload.WebSocketIce, iceFb);
Alex Perryb3b50792020-01-18 16:13:45 -0800110 builder.finish(messageFb);
111 const array = builder.asUint8Array();
112 this.webSocketConnection.send(array.buffer.slice(array.byteOffset));
113 }
114
115 // Called for new SDPs. Make sure to set it locally and remotely.
116 onOfferCreated(description: RTCSessionDescription): void {
Alex Perryb3b50792020-01-18 16:13:45 -0800117 this.rtcPeerConnection.setLocalDescription(description);
118 const builder = new flatbuffers.Builder(512);
119 const offerString = builder.createString(description.sdp);
120
Alex Perryd5e13572020-02-22 15:15:08 -0800121 const webSocketSdp = WebProxy.WebSocketSdp.createWebSocketSdp(
122 builder, WebProxy.SdpType.OFFER, offerString);
123 const message = WebProxy.WebSocketMessage.createWebSocketMessage(
124 builder, WebProxy.Payload.WebSocketSdp, webSocketSdp);
Alex Perryb3b50792020-01-18 16:13:45 -0800125 builder.finish(message);
126 const array = builder.asUint8Array();
127 this.webSocketConnection.send(array.buffer.slice(array.byteOffset));
128 }
129
130 // We now have a websocket, so start setting up the peer connection. We only
131 // want a DataChannel, so create it and then create an offer to send.
132 onWebSocketOpen(): void {
133 this.rtcPeerConnection = new RTCPeerConnection({});
Alex Perry5f474f22020-02-01 12:14:24 -0800134 this.rtcPeerConnection.addEventListener(
Alex Perry22824d72020-02-29 17:11:43 -0800135 'datachannel', (e) => this.onDataChannel(e));
Alex Perry5f474f22020-02-01 12:14:24 -0800136 this.dataChannel = this.rtcPeerConnection.createDataChannel('signalling');
Alex Perryb3b50792020-01-18 16:13:45 -0800137 this.dataChannel.addEventListener(
138 'message', (e) => this.onDataChannelMessage(e));
139 window.dc = this.dataChannel;
140 this.rtcPeerConnection.addEventListener(
141 'icecandidate', (e) => this.onIceCandidate(e));
142 this.rtcPeerConnection.createOffer().then(
143 (offer) => this.onOfferCreated(offer));
144 }
145
146 // When we receive a websocket message, we need to determine what type it is
147 // and handle appropriately. Either by setting the remote description or
148 // adding the remote ice candidate.
149 onWebSocketMessage(e: MessageEvent): void {
Alex Perryb3b50792020-01-18 16:13:45 -0800150 const buffer = new Uint8Array(e.data)
151 const fbBuffer = new flatbuffers.ByteBuffer(buffer);
152 const message =
Alex Perryd5e13572020-02-22 15:15:08 -0800153 WebProxy.WebSocketMessage.getRootAsWebSocketMessage(fbBuffer);
Alex Perryb3b50792020-01-18 16:13:45 -0800154 switch (message.payloadType()) {
Alex Perryd5e13572020-02-22 15:15:08 -0800155 case WebProxy.Payload.WebSocketSdp:
156 const sdpFb = message.payload(new WebProxy.WebSocketSdp());
157 if (sdpFb.type() !== WebProxy.SdpType.ANSWER) {
Alex Perryb3b50792020-01-18 16:13:45 -0800158 console.log('got something other than an answer back');
159 break;
160 }
161 this.rtcPeerConnection.setRemoteDescription(new RTCSessionDescription(
162 {'type': 'answer', 'sdp': sdpFb.payload()}));
163 break;
Alex Perryd5e13572020-02-22 15:15:08 -0800164 case WebProxy.Payload.WebSocketIce:
165 const iceFb = message.payload(new WebProxy.WebSocketIce());
Alex Perryb3b50792020-01-18 16:13:45 -0800166 const candidate = {} as RTCIceCandidateInit;
167 candidate.candidate = iceFb.candidate();
168 candidate.sdpMid = iceFb.sdpMid();
169 candidate.sdpMLineIndex = iceFb.sdpMLineIndex();
170 this.rtcPeerConnection.addIceCandidate(candidate);
171 break;
172 default:
173 console.log('got an unknown message');
174 break;
175 }
176 }
177}