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