1// Copyright (C) 2019 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 {binaryDecode, binaryEncode} from '../base/string_utils';
16import {Actions} from '../common/actions';
17import {TRACE_SUFFIX} from '../common/constants';
18
19import {
20  ConsumerPortResponse,
21  isReadBuffersResponse,
22  Typed
23} from './consumer_port_types';
24import {globals} from './globals';
25import {Consumer, RpcConsumerPort} from './record_controller_interfaces';
26
27export interface ChromeExtensionError extends Typed {
28  error: string;
29}
30
31export interface ChromeExtensionStatus extends Typed {
32  status: string;
33}
34
35export interface GetCategoriesResponse extends Typed {
36  categories: string[];
37}
38
39export type ChromeExtensionMessage = ChromeExtensionError|ChromeExtensionStatus|
40    ConsumerPortResponse|GetCategoriesResponse;
41
42function isError(obj: Typed): obj is ChromeExtensionError {
43  return obj.type === 'ChromeExtensionError';
44}
45
46function isStatus(obj: Typed): obj is ChromeExtensionStatus {
47  return obj.type === 'ChromeExtensionStatus';
48}
49
50function isGetCategoriesResponse(obj: Typed): obj is GetCategoriesResponse {
51  return obj.type === 'GetCategoriesResponse';
52}
53
54// This class acts as a proxy from the record controller (running in a worker),
55// to the frontend. This is needed because we can't directly talk with the
56// extension from a web-worker, so we use a MessagePort to communicate with the
57// frontend, that will consecutively forward it to the extension.
58
59// Rationale for the binaryEncode / binaryDecode calls below:
60// Messages to/from extensions need to be JSON serializable. ArrayBuffers are
61// not supported. For this reason here we use binaryEncode/Decode.
62// See https://developer.chrome.com/extensions/messaging#simple
63
64export class ChromeExtensionConsumerPort extends RpcConsumerPort {
65  private extensionPort: MessagePort;
66
67  constructor(extensionPort: MessagePort, consumer: Consumer) {
68    super(consumer);
69    this.extensionPort = extensionPort;
70    this.extensionPort.onmessage = this.onExtensionMessage.bind(this);
71  }
72
73  onExtensionMessage(message: {data: ChromeExtensionMessage}) {
74    if (isError(message.data)) {
75      this.sendErrorMessage(message.data.error);
76      return;
77    }
78    if (isStatus(message.data)) {
79      this.sendStatus(message.data.status);
80      return;
81    }
82    if (isGetCategoriesResponse(message.data)) {
83      globals.dispatch(Actions.setChromeCategories(message.data));
84      return;
85    }
86
87    // In this else branch message.data will be a ConsumerPortResponse.
88    if (isReadBuffersResponse(message.data) && message.data.slices) {
89      const slice = message.data.slices[0].data as unknown as string;
90      message.data.slices[0].data = binaryDecode(slice);
91    }
92    this.sendMessage(message.data);
93  }
94
95  handleCommand(method: string, requestData: Uint8Array): void {
96    const reqEncoded = binaryEncode(requestData);
97    this.extensionPort.postMessage({method, requestData: reqEncoded});
98  }
99
100  getRecordedTraceSuffix(): string {
101    return `${TRACE_SUFFIX}.gz`;
102  }
103}
104