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 {assertDefined} from 'common/assert_utils'; 18import {HttpRequestHeaderType, HttpResponse} from 'common/http_request'; 19import {AdbHostConnection} from 'trace_collection/adb/adb_host_connection'; 20import {AdbConnectionType} from 'trace_collection/adb_connection_type'; 21import {ConnectionState} from 'trace_collection/connection_state'; 22import {Endpoint} from './endpoint'; 23import {getFromProxy} from './utils'; 24import { 25 WinscopeProxyDeviceConnection, 26 WinscopeProxyDeviceConnectionResponse, 27} from './winscope_proxy_device_connection'; 28 29/** 30 * A connection to the Winscope Proxy server. 31 */ 32export class WinscopeProxyHostConnection extends AdbHostConnection<WinscopeProxyDeviceConnection> { 33 readonly connectionType = AdbConnectionType.WINSCOPE_PROXY; 34 35 private readonly storeKeySecurityToken = 'adb.proxyKey'; 36 37 private securityToken = ''; 38 private refreshDevicesWorker: number | undefined; 39 private cancelDeviceRequest = false; 40 41 protected override initializeExtraParameters() { 42 const urlParams = new URLSearchParams(window.location.search); 43 if (urlParams.has('token')) { 44 this.securityToken = assertDefined(urlParams.get('token')); 45 } else { 46 this.securityToken = this.store.get(this.storeKeySecurityToken) ?? ''; 47 } 48 } 49 50 protected override destroyHost() { 51 this.cancelDeviceRequests(); 52 } 53 54 override setSecurityToken(token: string) { 55 if (token.length > 0) { 56 this.securityToken = token; 57 this.store.add(this.storeKeySecurityToken, token); 58 } 59 } 60 61 override cancelDeviceRequests() { 62 this.cancelDeviceRequest = true; 63 if (this.refreshDevicesWorker !== undefined) { 64 window.clearInterval(this.refreshDevicesWorker); 65 this.refreshDevicesWorker = undefined; 66 } 67 } 68 69 override async requestDevices() { 70 this.cancelDeviceRequest = false; 71 await getFromProxy( 72 Endpoint.DEVICES, 73 this.makeSecurityTokenHeader(), 74 (resp: HttpResponse) => this.onSuccessParseDevices(resp), 75 (newState, errorText) => this.setState(newState, errorText), 76 ); 77 } 78 79 private async onSuccessParseDevices(resp: HttpResponse) { 80 try { 81 const devices: WinscopeProxyDeviceConnectionResponse[] = JSON.parse( 82 resp.text, 83 ); 84 const curDevs = new Map<string, WinscopeProxyDeviceConnectionResponse>( 85 devices.map((d) => [d.id, d]), 86 ); 87 this.devices = this.devices.filter((d) => curDevs.has(d.id)); 88 89 for (const [id, devJson] of curDevs.entries()) { 90 const existingDevice = this.devices.find((d) => d.id === id); 91 if (existingDevice !== undefined) { 92 await existingDevice.updateProperties(devJson); 93 } else { 94 const newDevice = new WinscopeProxyDeviceConnection( 95 id, 96 this.listener, 97 this.makeSecurityTokenHeader(), 98 ); 99 await newDevice.updateProperties(devJson); 100 this.devices.push(newDevice); 101 } 102 } 103 this.listener.onDevicesChange(this.devices); 104 if ( 105 this.refreshDevicesWorker === undefined && 106 !this.cancelDeviceRequest 107 ) { 108 this.refreshDevicesWorker = window.setInterval( 109 () => this.requestDevices(), 110 1000, 111 ); 112 } 113 this.setState(ConnectionState.IDLE); 114 } catch (err) { 115 this.setState( 116 ConnectionState.ERROR, 117 `Could not find devices. Received:\n${resp.text}`, 118 ); 119 } 120 } 121 122 private makeSecurityTokenHeader(): HttpRequestHeaderType { 123 const lastKey = this.store.get(this.storeKeySecurityToken); 124 if (lastKey !== undefined) { 125 this.securityToken = lastKey; 126 } 127 return [['Winscope-Token', this.securityToken]]; 128 } 129} 130