• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1/*
2 * Copyright (C) 2025 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *     http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17import {assertUnreachable} from 'common/assert_utils';
18import {
19  HttpRequest,
20  HttpRequestHeaderType,
21  HttpRequestStatus,
22  HttpResponse,
23} from 'common/http_request';
24import {
25  AdbResponse,
26  OnRequestSuccessCallback,
27} from 'trace_collection/adb/adb_host_connection';
28import {ConnectionState} from 'trace_collection/connection_state';
29
30export const WINSCOPE_PROXY_URL = 'http://localhost:5544';
31export const VERSION = '6.0.0';
32
33type StateChangeCallbackType = (
34  newState: ConnectionState,
35  errorText: string,
36) => Promise<void>;
37
38export async function getFromProxy(
39  path: string,
40  securityTokenHeader: HttpRequestHeaderType,
41  onSuccess: OnRequestSuccessCallback,
42  onStateChange: StateChangeCallbackType,
43  type?: XMLHttpRequest['responseType'],
44): Promise<string> {
45  const response = await HttpRequest.get(
46    makeRequestPath(path),
47    securityTokenHeader,
48    type,
49  );
50  return await processProxyResponse(response, onSuccess, onStateChange);
51}
52
53function makeRequestPath(path: string): string {
54  return WINSCOPE_PROXY_URL + path;
55}
56
57export async function postToProxy(
58  path: string,
59  securityTokenHeader: HttpRequestHeaderType,
60  onSuccess: OnRequestSuccessCallback,
61  onStateChange: StateChangeCallbackType,
62  jsonRequest?: object,
63): Promise<string> {
64  const response = await HttpRequest.post(
65    makeRequestPath(path),
66    securityTokenHeader,
67    jsonRequest,
68  );
69  return await processProxyResponse(response, onSuccess, onStateChange);
70}
71
72async function processProxyResponse(
73  response: HttpResponse,
74  onSuccess: OnRequestSuccessCallback,
75  onStateChange: StateChangeCallbackType,
76): Promise<string> {
77  if (
78    response.status === HttpRequestStatus.SUCCESS &&
79    !isVersionCompatible(response)
80  ) {
81    await onStateChange(ConnectionState.INVALID_VERSION, '');
82    return 'invalid version';
83  }
84  const adbResponse = await processHttpResponse(response, onSuccess);
85  if (adbResponse !== undefined) {
86    await onStateChange(adbResponse.errorState, adbResponse.errorMsg ?? '');
87  }
88  try {
89    return `${JSON.parse(response.body)}`;
90  } catch (e) {
91    return typeof response.body === 'string' ? response.body : '';
92  }
93}
94
95function isVersionCompatible(req: HttpResponse): boolean {
96  const proxyVersion = req.getHeader('Winscope-Proxy-Version');
97  if (!proxyVersion) return false;
98  const [proxyMajor, proxyMinor, proxyPatch] = proxyVersion
99    .split('.')
100    .map((s) => Number(s));
101  const [clientMajor, clientMinor, clientPatch] = VERSION.split('.').map((s) =>
102    Number(s),
103  );
104
105  if (proxyMajor !== clientMajor) {
106    return false;
107  }
108
109  if (proxyMinor === clientMinor) {
110    // Check patch number to ensure user has deployed latest bug fixes
111    return proxyPatch >= clientPatch;
112  }
113
114  return proxyMinor > clientMinor;
115}
116
117async function processHttpResponse(
118  resp: HttpResponse,
119  onSuccess: OnRequestSuccessCallback,
120): Promise<AdbResponse | undefined> {
121  let errorState: ConnectionState | undefined;
122  let errorMsg: string | undefined;
123
124  switch (resp.status) {
125    case HttpRequestStatus.UNSENT:
126      errorState = ConnectionState.NOT_FOUND;
127      break;
128
129    case HttpRequestStatus.UNAUTH:
130      errorState = ConnectionState.UNAUTH;
131      break;
132
133    case HttpRequestStatus.SUCCESS:
134      try {
135        await onSuccess(resp);
136      } catch (err) {
137        errorState = ConnectionState.ERROR;
138        errorMsg =
139          `Error handling request response: ${err}\n` +
140          `Request text: ${resp.text}\n` +
141          `Request body: ${resp.body}`;
142      }
143      break;
144
145    case HttpRequestStatus.ERROR:
146      if (resp.type === 'text' || !resp.type) {
147        errorMsg = resp.text;
148      } else if (resp.type === 'arraybuffer') {
149        errorMsg = String.fromCharCode.apply(null, new Array(resp.body));
150        if (errorMsg === '\x00') {
151          errorMsg = 'No data received.';
152        }
153      }
154      errorState = ConnectionState.ERROR;
155      break;
156
157    default:
158      assertUnreachable(resp.status);
159  }
160
161  return errorState !== undefined ? {errorState, errorMsg} : undefined;
162}
163