1// Copyright (C) 2022 The Android Open Source Project
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7//      http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15import {defer} from '../../../base/deferred';
16import {
17  ByteStream,
18  OnStreamCloseCallback,
19  OnStreamDataCallback,
20} from './recording_interfaces_v2';
21
22// A HostOsByteStream instantiates a websocket connection to the host OS.
23// It exposes an API to write commands to this websocket and read its output.
24export class HostOsByteStream implements ByteStream {
25  // handshakeSignal will be resolved with the stream when the websocket
26  // connection becomes open.
27  private handshakeSignal = defer<HostOsByteStream>();
28  private _isConnected: boolean = false;
29  private websocket: WebSocket;
30  private onStreamDataCallbacks: OnStreamDataCallback[] = [];
31  private onStreamCloseCallbacks: OnStreamCloseCallback[] = [];
32
33  private constructor(websocketUrl: string) {
34    this.websocket = new WebSocket(websocketUrl);
35    this.websocket.onmessage = this.onMessage.bind(this);
36    this.websocket.onopen = this.onOpen.bind(this);
37  }
38
39  addOnStreamDataCallback(onStreamData: OnStreamDataCallback): void {
40    this.onStreamDataCallbacks.push(onStreamData);
41  }
42
43  addOnStreamCloseCallback(onStreamClose: OnStreamCloseCallback): void {
44    this.onStreamCloseCallbacks.push(onStreamClose);
45  }
46
47  close(): void {
48    this.websocket.close();
49    for (const onStreamClose of this.onStreamCloseCallbacks) {
50      onStreamClose();
51    }
52    this.onStreamDataCallbacks = [];
53    this.onStreamCloseCallbacks = [];
54  }
55
56  async closeAndWaitForTeardown(): Promise<void> {
57    this.close();
58  }
59
60  isConnected(): boolean {
61    return this._isConnected;
62  }
63
64  write(msg: string | Uint8Array): void {
65    this.websocket.send(msg);
66  }
67
68  private async onMessage(evt: MessageEvent) {
69    for (const onStreamData of this.onStreamDataCallbacks) {
70      const arrayBufferResponse = await evt.data.arrayBuffer();
71      onStreamData(new Uint8Array(arrayBufferResponse));
72    }
73  }
74
75  private onOpen() {
76    this._isConnected = true;
77    this.handshakeSignal.resolve(this);
78  }
79
80  static create(websocketUrl: string): Promise<HostOsByteStream> {
81    return new HostOsByteStream(websocketUrl).handshakeSignal;
82  }
83}
84