• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 {getErrorMessage} from '../../base/errors';
16import {
17  showAllowUSBDebugging,
18  showConnectionLostError,
19  showExtensionNotInstalled,
20  showFailedToPushBinary,
21  showIssueParsingTheTracedResponse,
22  showNoDeviceSelected,
23  showWebsocketConnectionIssue,
24  showWebUSBErrorV2,
25} from '../../frontend/error_dialog';
26
27import {OnMessageCallback} from './recording_interfaces_v2';
28import {
29  ALLOW_USB_DEBUGGING,
30  BINARY_PUSH_FAILURE,
31  BINARY_PUSH_UNKNOWN_RESPONSE,
32  EXTENSION_NOT_INSTALLED,
33  NO_DEVICE_SELECTED,
34  PARSING_UNABLE_TO_DECODE_METHOD,
35  PARSING_UNKNWON_REQUEST_ID,
36  PARSING_UNRECOGNIZED_MESSAGE,
37  PARSING_UNRECOGNIZED_PORT,
38  WEBSOCKET_UNABLE_TO_CONNECT,
39} from './recording_utils';
40
41// The pattern for handling recording error can have the following nesting in
42// case of errors:
43// A. wrapRecordingError -> wraps a promise
44// B. onFailure -> has user defined logic and calls showRecordingModal
45// C. showRecordingModal -> shows UX for a given error; this is not called
46//    directly by wrapRecordingError, because we want the caller (such as the
47//    UI) to dictate the UX
48
49// This method takes a promise and a callback to be execute in case the promise
50// fails. It then awaits the promise and executes the callback in case of
51// failure. In the recording code it is used to wrap:
52// 1. Acessing the WebUSB API.
53// 2. Methods returning promises which can be rejected. For instance:
54// a) When the user clicks 'Add a new device' but then doesn't select a valid
55//    device.
56// b) When the user starts a tracing session, but cancels it before they
57//    authorize the session on the device.
58export async function wrapRecordingError<T>(
59  promise: Promise<T>,
60  onFailure: OnMessageCallback,
61): Promise<T | undefined> {
62  try {
63    return await promise;
64  } catch (e) {
65    // Sometimes the message is wrapped in an Error object, sometimes not, so
66    // we make sure we transform it into a string.
67    const errorMessage = getErrorMessage(e);
68    onFailure(errorMessage);
69    return undefined;
70  }
71}
72
73// Shows a modal for every known type of error which can arise during recording.
74// In this way, errors occuring at different levels of the recording process
75// can be handled in a central location.
76export function showRecordingModal(message: string): void {
77  if (
78    [
79      'Unable to claim interface.',
80      'The specified endpoint is not part of a claimed and selected ' +
81        'alternate interface.',
82      // thrown when calling the 'reset' method on a WebUSB device.
83      'Unable to reset the device.',
84    ].some((partOfMessage) => message.includes(partOfMessage))
85  ) {
86    showWebUSBErrorV2();
87  } else if (
88    [
89      'A transfer error has occurred.',
90      'The device was disconnected.',
91      'The transfer was cancelled.',
92    ].some((partOfMessage) => message.includes(partOfMessage)) ||
93    isDeviceDisconnectedError(message)
94  ) {
95    showConnectionLostError();
96  } else if (message === ALLOW_USB_DEBUGGING) {
97    showAllowUSBDebugging();
98  } else if (
99    isMessageComposedOf(message, [
100      BINARY_PUSH_FAILURE,
101      BINARY_PUSH_UNKNOWN_RESPONSE,
102    ])
103  ) {
104    showFailedToPushBinary(message.substring(message.indexOf(':') + 1));
105  } else if (message === NO_DEVICE_SELECTED) {
106    showNoDeviceSelected();
107  } else if (WEBSOCKET_UNABLE_TO_CONNECT === message) {
108    showWebsocketConnectionIssue(message);
109  } else if (message === EXTENSION_NOT_INSTALLED) {
110    showExtensionNotInstalled();
111  } else if (
112    isMessageComposedOf(message, [
113      PARSING_UNKNWON_REQUEST_ID,
114      PARSING_UNABLE_TO_DECODE_METHOD,
115      PARSING_UNRECOGNIZED_PORT,
116      PARSING_UNRECOGNIZED_MESSAGE,
117    ])
118  ) {
119    showIssueParsingTheTracedResponse(message);
120  } else {
121    throw new Error(`${message}`);
122  }
123}
124
125function isDeviceDisconnectedError(message: string) {
126  return (
127    message.includes('Device with serial') &&
128    message.includes('was disconnected.')
129  );
130}
131
132function isMessageComposedOf(message: string, issues: string[]) {
133  for (const issue of issues) {
134    if (message.includes(issue)) {
135      return true;
136    }
137  }
138  return false;
139}
140
141// Exception thrown by the Recording logic.
142export class RecordingError extends Error {}
143