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 {WindowUtils} from 'common/window_utils'; 18import {AdbHostConnection} from 'trace_collection/adb/adb_host_connection'; 19import {AdbConnectionType} from 'trace_collection/adb_connection_type'; 20import {ConnectionState} from 'trace_collection/connection_state'; 21import {StreamProvider} from './stream_provider'; 22import {WdpDeviceConnection} from './wdp_device_connection'; 23 24/** 25 * A connection to the WebDeviceProxy websocket. 26 */ 27export class WdpHostConnection extends AdbHostConnection<WdpDeviceConnection> { 28 private static readonly WDP_TRACK_DEVICES_URL = 29 'ws://localhost:9167/track-devices-json'; 30 readonly connectionType = AdbConnectionType.WDP; 31 private streamProvider = new StreamProvider(); 32 33 protected override initializeExtraParameters() { 34 // do nothing 35 } 36 37 protected override destroyHost() { 38 this.streamProvider.closeAllStreams(); 39 } 40 41 override setSecurityToken(token: string) { 42 // do nothing 43 } 44 45 override cancelDeviceRequests() { 46 // do nothing 47 } 48 49 override async requestDevices() { 50 const sock = new WebSocket(WdpHostConnection.WDP_TRACK_DEVICES_URL); 51 const devicesStream = this.streamProvider.createDevicesStream( 52 sock, 53 async (message: string) => { 54 await this.handleRequestDevicesResponse(message); 55 }, 56 () => { 57 this.setState(ConnectionState.NOT_FOUND); 58 }, 59 ); 60 await devicesStream.connect(); 61 } 62 63 private async handleRequestDevicesResponse(data: string) { 64 const resp: WdpRequestDevicesResponse = JSON.parse(data); 65 if ( 66 resp.error?.type === 'ORIGIN_NOT_ALLOWLISTED' && 67 resp.error.approveUrl !== undefined 68 ) { 69 const popup = WindowUtils.showPopupWindow(resp.error.approveUrl); 70 if (popup === false) { 71 this.listener.onError(`Please enable popups and try again.`); 72 return; 73 } 74 this.setState(ConnectionState.UNAUTH); 75 return; 76 } else if (resp.error !== undefined) { 77 console.error(`Invalid WebDeviceProxy response ${data} : ${resp.error}`); 78 this.listener.onError(resp.error.message ?? 'Unknown WDP Error'); 79 return; 80 } 81 await this.onRequestDevicesResponse(resp); 82 this.setState(ConnectionState.IDLE); 83 return; 84 } 85 86 private async onRequestDevicesResponse(resp: WdpRequestDevicesResponse) { 87 const curDevs = new Map<string, WdpDeviceConnectionResponse>( 88 (resp.device ?? []).map((d) => [d.serialNumber, d]), 89 ); 90 this.devices = this.devices.filter((d) => curDevs.has(d.id)); 91 92 for (const [serial, devJson] of curDevs.entries()) { 93 const existingDevice = this.devices.find((d) => d.id === serial); 94 if (existingDevice !== undefined) { 95 await existingDevice.updateProperties(devJson); 96 } else { 97 const newDevice = new WdpDeviceConnection( 98 serial, 99 this.listener, 100 devJson.approveUrl, 101 ); 102 await newDevice.updateProperties(devJson); 103 this.devices.push(newDevice); 104 } 105 } 106 this.listener.onDevicesChange(this.devices); 107 } 108} 109 110export interface WdpRequestDevicesResponse { 111 device?: WdpDeviceConnectionResponse[]; 112 version?: string; 113 error?: { 114 type?: string; 115 message?: string; 116 approveUrl?: string; 117 }; 118} 119 120export interface WdpDeviceConnectionResponse { 121 serialNumber: string; 122 proxyStatus: 'ADB' | 'PROXY_UNAUTHORIZED'; 123 adbStatus: string; 124 adbProps?: {[key: string]: string}; 125 approveUrl?: string; 126} 127