mirror of
https://github.com/yume-chan/ya-webadb.git
synced 2025-10-05 02:39:26 +02:00
feat(demo): add chrome remote debugger (#537)
This commit is contained in:
parent
094b859791
commit
c6bd9e5304
10 changed files with 558 additions and 3 deletions
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
|
@ -17,6 +17,7 @@
|
|||
"DESERIALIZERS",
|
||||
"ebml",
|
||||
"Embedder",
|
||||
"entrypoints",
|
||||
"fflate",
|
||||
"fluentui",
|
||||
"genymobile",
|
||||
|
@ -49,6 +50,7 @@
|
|||
"transferables",
|
||||
"tsbuildinfo",
|
||||
"typeof",
|
||||
"undici",
|
||||
"webadb",
|
||||
"webcodecs",
|
||||
"webm",
|
||||
|
|
|
@ -64,7 +64,7 @@ module.exports = withPwa(
|
|||
},
|
||||
{
|
||||
key: "Cross-Origin-Embedder-Policy",
|
||||
value: "require-corp",
|
||||
value: "credentialless",
|
||||
},
|
||||
],
|
||||
},
|
||||
|
|
|
@ -32,6 +32,7 @@
|
|||
"@yume-chan/stream-extra": "workspace:^0.0.19",
|
||||
"@yume-chan/stream-saver": "^2.0.6",
|
||||
"@yume-chan/struct": "workspace:^0.0.19",
|
||||
"@yume-chan/undici-browser": "5.21.2-mod.9",
|
||||
"fflate": "^0.7.4",
|
||||
"mobx": "^6.7.0",
|
||||
"mobx-react-lite": "^3.4.3",
|
||||
|
|
|
@ -8,6 +8,7 @@ import {
|
|||
} from "@fluentui/react";
|
||||
import { makeStyles, mergeClasses, shorthands } from "@griffel/react";
|
||||
import type { AppProps } from "next/app";
|
||||
import getConfig from "next/config";
|
||||
import Head from "next/head";
|
||||
import Link from "next/link";
|
||||
import { useRouter } from "next/router";
|
||||
|
@ -16,7 +17,6 @@ import { Connect, ErrorDialogProvider } from "../components";
|
|||
import "../styles/globals.css";
|
||||
import { Icons } from "../utils";
|
||||
import { register as registerIcons } from "../utils/icons";
|
||||
import getConfig from "next/config";
|
||||
|
||||
registerIcons();
|
||||
|
||||
|
@ -71,6 +71,11 @@ const ROUTES = [
|
|||
icon: Icons.Power,
|
||||
name: "Power Menu",
|
||||
},
|
||||
{
|
||||
url: "/chrome-devtools",
|
||||
icon: Icons.WindowDevTools,
|
||||
name: "Chrome Remote Debugging",
|
||||
},
|
||||
{
|
||||
url: "/bug-report",
|
||||
icon: Icons.Bug,
|
||||
|
@ -136,6 +141,10 @@ function App({ Component, pageProps }: AppProps) {
|
|||
|
||||
const router = useRouter();
|
||||
|
||||
if ("noLayout" in Component) {
|
||||
return <Component {...pageProps} />;
|
||||
}
|
||||
|
||||
return (
|
||||
<ErrorDialogProvider>
|
||||
<Head>
|
||||
|
|
98
apps/demo/src/pages/chrome-devtools-frame.tsx
Normal file
98
apps/demo/src/pages/chrome-devtools-frame.tsx
Normal file
|
@ -0,0 +1,98 @@
|
|||
import { useEffect } from "react";
|
||||
|
||||
function ChromeDevToolsFrame() {
|
||||
useEffect(() => {
|
||||
var WebSocketOriginal = globalThis.WebSocket;
|
||||
globalThis.WebSocket = class WebSocket extends EventTarget {
|
||||
public static readonly CONNECTING: 0 = 0;
|
||||
public static readonly OPEN: 1 = 1;
|
||||
public static readonly CLOSING: 2 = 2;
|
||||
public static readonly CLOSED: 3 = 3;
|
||||
|
||||
public readonly CONNECTING: 0 = 0;
|
||||
public readonly OPEN: 1 = 1;
|
||||
public readonly CLOSING: 2 = 2;
|
||||
public readonly CLOSED: 3 = 3;
|
||||
|
||||
public binaryType: BinaryType = "arraybuffer";
|
||||
public readonly bufferedAmount: number = 0;
|
||||
public readonly extensions: string = "";
|
||||
|
||||
public readonly protocol: string = "";
|
||||
public readonly readyState: number = 1;
|
||||
public readonly url: string;
|
||||
|
||||
private _port: MessagePort;
|
||||
|
||||
public onclose: ((this: WebSocket, ev: CloseEvent) => any) | null =
|
||||
null;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/error_event) */
|
||||
public onerror: ((this: WebSocket, ev: Event) => any) | null = null;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/message_event) */
|
||||
public onmessage:
|
||||
| ((this: WebSocket, ev: MessageEvent) => any)
|
||||
| null = null;
|
||||
/** [MDN Reference](https://developer.mozilla.org/docs/Web/API/WebSocket/open_event) */
|
||||
public onopen: ((this: WebSocket, ev: Event) => any) | null = null;
|
||||
|
||||
constructor(url: string) {
|
||||
super();
|
||||
|
||||
console.log("WebSocket constructor", url);
|
||||
this.url = url;
|
||||
|
||||
var channel = new MessageChannel();
|
||||
this._port = channel.port1;
|
||||
|
||||
if (url.includes("/_next/")) {
|
||||
this._port.close();
|
||||
// @ts-ignore
|
||||
return new WebSocketOriginal(url);
|
||||
}
|
||||
|
||||
this._port.onmessage = (e) => {
|
||||
switch (e.data.type) {
|
||||
case "open":
|
||||
this.onopen?.(new Event("open"));
|
||||
break;
|
||||
case "message":
|
||||
this.onmessage?.(
|
||||
new MessageEvent("message", {
|
||||
data: e.data.message,
|
||||
})
|
||||
);
|
||||
break;
|
||||
case "close":
|
||||
this.onclose?.(new CloseEvent("close"));
|
||||
this._port.close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
window.postMessage({ type: "AdbWebSocket", url }, "*", [
|
||||
channel.port2,
|
||||
]);
|
||||
}
|
||||
|
||||
send(data: ArrayBuffer) {
|
||||
this._port.postMessage({ type: "message", message: data });
|
||||
}
|
||||
|
||||
public close() {
|
||||
this._port.postMessage({ type: "close" });
|
||||
this._port.close();
|
||||
}
|
||||
} as typeof WebSocket;
|
||||
console.log("WebSocket hooked");
|
||||
|
||||
const script = document.createElement("script");
|
||||
script.type = "module";
|
||||
script.src = new URLSearchParams(location.search).get(
|
||||
"script"
|
||||
) as string;
|
||||
document.body.appendChild(script);
|
||||
}, []);
|
||||
|
||||
return null;
|
||||
}
|
||||
ChromeDevToolsFrame.noLayout = true;
|
||||
export default ChromeDevToolsFrame;
|
406
apps/demo/src/pages/chrome-devtools.tsx
Normal file
406
apps/demo/src/pages/chrome-devtools.tsx
Normal file
|
@ -0,0 +1,406 @@
|
|||
import { Link, Stack } from "@fluentui/react";
|
||||
import { makeStyles } from "@griffel/react";
|
||||
import { AdbSocket } from "@yume-chan/adb";
|
||||
import {
|
||||
Consumable,
|
||||
ReadableStreamDefaultReader,
|
||||
WritableStreamDefaultWriter,
|
||||
} from "@yume-chan/stream-extra";
|
||||
import {
|
||||
Agent,
|
||||
Client,
|
||||
Duplex,
|
||||
Pool,
|
||||
Symbols,
|
||||
WebSocket,
|
||||
request,
|
||||
setGlobalDispatcher,
|
||||
} from "@yume-chan/undici-browser";
|
||||
import {
|
||||
action,
|
||||
makeAutoObservable,
|
||||
observable,
|
||||
reaction,
|
||||
runInAction,
|
||||
} from "mobx";
|
||||
import { observer } from "mobx-react-lite";
|
||||
import { NextPage } from "next";
|
||||
import getConfig from "next/config";
|
||||
import Head from "next/head";
|
||||
import type { Socket } from "node:net";
|
||||
import { useCallback, useEffect } from "react";
|
||||
import { GLOBAL_STATE } from "../state";
|
||||
import { RouteStackProps } from "../utils";
|
||||
|
||||
class AdbUndiciSocket extends Duplex {
|
||||
private _socket: AdbSocket;
|
||||
private _reader: ReadableStreamDefaultReader<Uint8Array>;
|
||||
private _writer: WritableStreamDefaultWriter<Consumable<Uint8Array>>;
|
||||
|
||||
constructor(socket: AdbSocket) {
|
||||
super();
|
||||
this._socket = socket;
|
||||
this._reader = this._socket.readable.getReader();
|
||||
this._writer = this._socket.writable.getWriter();
|
||||
this._socket.end.then(() => this.emit("end"));
|
||||
}
|
||||
|
||||
async _read(size: number): Promise<void> {
|
||||
const result = await this._reader.read();
|
||||
if (result.done) {
|
||||
this.emit("end");
|
||||
} else {
|
||||
this.push(result.value);
|
||||
}
|
||||
}
|
||||
|
||||
async _write(
|
||||
chunk: any,
|
||||
encoding: BufferEncoding,
|
||||
callback: (error?: Error | null | undefined) => void
|
||||
): Promise<void> {
|
||||
const consumable = new Consumable(chunk);
|
||||
try {
|
||||
await this._writer.write(consumable);
|
||||
callback();
|
||||
} catch (e) {
|
||||
callback(e as Error);
|
||||
}
|
||||
}
|
||||
|
||||
async _final(
|
||||
callback: (error?: Error | null | undefined) => void
|
||||
): Promise<void> {
|
||||
await this._socket.close();
|
||||
callback();
|
||||
}
|
||||
|
||||
async _destroy(
|
||||
error: Error | null,
|
||||
callback: (error: Error | null) => void
|
||||
): Promise<void> {
|
||||
await this._socket.close();
|
||||
callback(error);
|
||||
}
|
||||
}
|
||||
|
||||
const agent = new Agent({
|
||||
factory(origin, opts) {
|
||||
const pool = new Pool(origin, {
|
||||
...opts,
|
||||
factory(origin, opts) {
|
||||
const client = new Client(origin, opts);
|
||||
// Remote debugging validates `Host` header to defend against DNS rebinding attacks.
|
||||
// But we can only pass socket name using hostname, so we need to override it.
|
||||
(client as any)[Symbols.kHostHeader] = "Host: localhost\r\n";
|
||||
return client;
|
||||
},
|
||||
});
|
||||
return pool;
|
||||
},
|
||||
async connect(options, callback) {
|
||||
const socket = await GLOBAL_STATE.device!.createSocket(
|
||||
"localabstract:" + options.hostname
|
||||
);
|
||||
callback(null, new AdbUndiciSocket(socket) as unknown as Socket);
|
||||
},
|
||||
});
|
||||
// WebSocket only uses global dispatcher
|
||||
setGlobalDispatcher(agent);
|
||||
|
||||
interface Page {
|
||||
description: string;
|
||||
devtoolsFrontendUrl: string;
|
||||
id: string;
|
||||
title: string;
|
||||
type: string;
|
||||
url: string;
|
||||
webSocketDebuggerUrl: string;
|
||||
}
|
||||
|
||||
interface Version {
|
||||
"Android-Package": string;
|
||||
Browser: string;
|
||||
"Protocol-Version": string;
|
||||
"User-Agent": string;
|
||||
"V8-Version": string;
|
||||
"WebKit-Version": string;
|
||||
webSocketDebuggerUrl: string;
|
||||
}
|
||||
|
||||
// https://source.chromium.org/chromium/chromium/src/+/refs/heads/main:chrome/browser/devtools/device/devtools_device_discovery.cc;l=36;drc=4651cec294d1542d6673a89190e192e20de03240
|
||||
|
||||
async function getPages(socket: string) {
|
||||
const response = await request(`http://${socket}/json`);
|
||||
const body = await response.body.json();
|
||||
return body as Page[];
|
||||
}
|
||||
|
||||
async function getVersion(socket: string) {
|
||||
const response = await request(`http://${socket}/json/version`);
|
||||
const body = await response.body.json();
|
||||
return body as Version;
|
||||
}
|
||||
|
||||
async function focusPage(socket: string, page: Page) {
|
||||
await request(`http://${socket}/json/activate/${page.id}`);
|
||||
}
|
||||
|
||||
async function closePage(socket: string, page: Page) {
|
||||
await request(`http://${socket}/json/close/${page.id}`);
|
||||
}
|
||||
|
||||
const {
|
||||
publicRuntimeConfig: { basePath },
|
||||
} = getConfig();
|
||||
|
||||
function getPopupParams(page: Page) {
|
||||
const frontendUrl = page.devtoolsFrontendUrl;
|
||||
const [frontendBase, params] = frontendUrl.split("?");
|
||||
const script = frontendBase.startsWith(
|
||||
"https://aka.ms/docs-landing-page/serve_rev/"
|
||||
)
|
||||
? // Edge
|
||||
frontendBase
|
||||
.replace(
|
||||
"https://aka.ms/docs-landing-page/serve_rev/",
|
||||
"https://devtools.azureedge.net/serve_file/"
|
||||
)
|
||||
.replace("inspector.html", "entrypoints/inspector/inspector.js")
|
||||
: // Chrome
|
||||
frontendBase.replace(
|
||||
"inspector.html",
|
||||
"front_end/entrypoints/inspector/inspector.js"
|
||||
);
|
||||
return { script, params };
|
||||
}
|
||||
|
||||
interface Browser {
|
||||
socket: string;
|
||||
version: Version;
|
||||
pages: Page[];
|
||||
}
|
||||
|
||||
const STATE = makeAutoObservable(
|
||||
{
|
||||
browsers: [] as Browser[],
|
||||
intervalId: null as NodeJS.Timeout | null,
|
||||
visible: false,
|
||||
},
|
||||
{
|
||||
browsers: observable.deep,
|
||||
}
|
||||
);
|
||||
|
||||
async function getBrowsers() {
|
||||
const device = GLOBAL_STATE.device!;
|
||||
const sockets = await device.subprocess.spawnAndWaitLegacy(
|
||||
`cat /proc/net/unix | grep -E "@chrome_devtools_remote|@chrome_devtools_remote_[0-9]+" | awk '{print substr($8, 2)}'`
|
||||
);
|
||||
const browsers: Browser[] = [];
|
||||
for (const socket of sockets.split("\n").filter(Boolean)) {
|
||||
if (browsers.some((browser) => browser.socket == socket)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
const version = await getVersion(socket);
|
||||
const pages = await getPages(socket);
|
||||
console.log(socket, version, pages);
|
||||
browsers.push({ socket, version, pages });
|
||||
} catch (e) {
|
||||
console.error(socket, e);
|
||||
}
|
||||
}
|
||||
runInAction(() => {
|
||||
STATE.browsers = browsers;
|
||||
});
|
||||
}
|
||||
|
||||
reaction(
|
||||
() => [GLOBAL_STATE.device, STATE.visible] as const,
|
||||
([device, visible]) => {
|
||||
if (!device || !visible) {
|
||||
STATE.browsers = [];
|
||||
if (STATE.intervalId) {
|
||||
clearInterval(STATE.intervalId);
|
||||
STATE.intervalId = null;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
STATE.intervalId = setInterval(() => {
|
||||
getBrowsers();
|
||||
}, 5000);
|
||||
|
||||
getBrowsers();
|
||||
}
|
||||
);
|
||||
|
||||
function getBrowserName(version: Version) {
|
||||
const [name, versionNumber] = version.Browser.split("/");
|
||||
return `${name} (${versionNumber})`;
|
||||
}
|
||||
|
||||
const useClasses = makeStyles({
|
||||
header: {
|
||||
marginTop: "4px",
|
||||
marginBottom: "4px",
|
||||
},
|
||||
url: {
|
||||
marginLeft: "8px",
|
||||
color: "#999",
|
||||
},
|
||||
link: {
|
||||
marginRight: "12px",
|
||||
},
|
||||
});
|
||||
|
||||
const ChromeDevToolsPage: NextPage = observer(function ChromeDevTools() {
|
||||
const classes = useClasses();
|
||||
|
||||
useEffect(() => {
|
||||
runInAction(() => {
|
||||
STATE.visible = true;
|
||||
});
|
||||
|
||||
return action(() => {
|
||||
STATE.visible = false;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleInspectClick = useCallback((socket: string, page: Page) => {
|
||||
const { script, params } = getPopupParams(page);
|
||||
const childWindow = window.open(
|
||||
`${basePath}/chrome-devtools-frame?script=${script}&${params}`,
|
||||
"_blank",
|
||||
"popup"
|
||||
)!;
|
||||
childWindow.addEventListener("message", (e) => {
|
||||
if (
|
||||
typeof e.data !== "object" ||
|
||||
!"type in e.data" ||
|
||||
e.data.type !== "AdbWebSocket"
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const url = new URL(e.data.url as string);
|
||||
url.host = socket;
|
||||
|
||||
const port = e.ports[0];
|
||||
|
||||
const ws = new WebSocket(url);
|
||||
ws.binaryType = "arraybuffer";
|
||||
ws.onopen = () => {
|
||||
port.postMessage({ type: "open" });
|
||||
};
|
||||
ws.onclose = () => {
|
||||
port.postMessage({ type: "close" });
|
||||
port.close();
|
||||
};
|
||||
ws.onmessage = (e) => {
|
||||
const { data } = e;
|
||||
port.postMessage({
|
||||
type: "message",
|
||||
message: data,
|
||||
});
|
||||
};
|
||||
|
||||
port.onmessage = (e) => {
|
||||
switch (e.data.type) {
|
||||
case "message":
|
||||
ws.send(e.data.message);
|
||||
break;
|
||||
case "close":
|
||||
ws.close();
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
childWindow.addEventListener("close", () => {
|
||||
ws.close();
|
||||
});
|
||||
|
||||
window.addEventListener("beforeunload", () => {
|
||||
port.postMessage({ type: "close" });
|
||||
port.close();
|
||||
});
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handleFocusClick = useCallback((socket: string, page: Page) => {
|
||||
focusPage(socket, page);
|
||||
}, []);
|
||||
|
||||
const handleCloseClick = useCallback((socket: string, page: Page) => {
|
||||
closePage(socket, page);
|
||||
getBrowsers();
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<Stack {...RouteStackProps}>
|
||||
<Head>
|
||||
<title>Chrome Remote Debugging - Tango</title>
|
||||
</Head>
|
||||
|
||||
{STATE.browsers.map((browser) => (
|
||||
<>
|
||||
{browser.version && (
|
||||
<h3 className={classes.header}>
|
||||
{getBrowserName(browser.version)}
|
||||
</h3>
|
||||
)}
|
||||
|
||||
{browser.pages.map((page) => (
|
||||
<div key={page.id}>
|
||||
<div>
|
||||
{page.title ? (
|
||||
<span
|
||||
dangerouslySetInnerHTML={{
|
||||
__html: page.title,
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<i>No Title</i>
|
||||
)}
|
||||
|
||||
<span className={classes.url}>
|
||||
{page.url || <i>No URL</i>}
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<Link
|
||||
className={classes.link}
|
||||
onClick={() =>
|
||||
handleInspectClick(browser.socket, page)
|
||||
}
|
||||
>
|
||||
Inspect
|
||||
</Link>
|
||||
<Link
|
||||
className={classes.link}
|
||||
onClick={() =>
|
||||
handleFocusClick(browser.socket, page)
|
||||
}
|
||||
>
|
||||
Focus
|
||||
</Link>
|
||||
<Link
|
||||
className={classes.link}
|
||||
onClick={() =>
|
||||
handleCloseClick(browser.socket, page)
|
||||
}
|
||||
>
|
||||
Close
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
</Stack>
|
||||
);
|
||||
});
|
||||
|
||||
export default ChromeDevToolsPage;
|
|
@ -1,6 +1,7 @@
|
|||
import { registerIcons } from "@fluentui/react";
|
||||
import {
|
||||
AddCircleRegular,
|
||||
WindowDevToolsRegular,
|
||||
ArrowClockwiseRegular,
|
||||
ArrowRotateClockwiseRegular,
|
||||
ArrowRotateCounterclockwiseRegular,
|
||||
|
@ -108,6 +109,7 @@ export function register() {
|
|||
Warning: <WarningRegular style={STYLE} />,
|
||||
WifiSettings: <WifiSettingsRegular style={STYLE} />,
|
||||
WindowConsole: <WindowConsoleRegular style={STYLE} />,
|
||||
WindowDevTools: <WindowDevToolsRegular style={STYLE} />,
|
||||
|
||||
// Required by @fluentui/react
|
||||
Checkmark: <CheckmarkRegular style={STYLE} />,
|
||||
|
@ -179,6 +181,8 @@ const Icons = {
|
|||
Warning: "Warning",
|
||||
WifiSettings: "WifiSettings",
|
||||
WindowConsole: "WindowConsole",
|
||||
WindowDevTools: "WindowDevTools",
|
||||
|
||||
Document20: "Document20",
|
||||
};
|
||||
|
||||
|
|
21
common/config/rush/pnpm-lock.yaml
generated
21
common/config/rush/pnpm-lock.yaml
generated
|
@ -35,6 +35,7 @@ importers:
|
|||
'@yume-chan/stream-extra': workspace:^0.0.19
|
||||
'@yume-chan/stream-saver': ^2.0.6
|
||||
'@yume-chan/struct': workspace:^0.0.19
|
||||
'@yume-chan/undici-browser': 5.21.2-mod.9
|
||||
eslint: ^8.36.0
|
||||
eslint-config-next: 13.2.4
|
||||
fflate: ^0.7.4
|
||||
|
@ -74,6 +75,7 @@ importers:
|
|||
'@yume-chan/stream-extra': link:../../libraries/stream-extra
|
||||
'@yume-chan/stream-saver': 2.0.6
|
||||
'@yume-chan/struct': link:../../libraries/struct
|
||||
'@yume-chan/undici-browser': 5.21.2-mod.9
|
||||
fflate: 0.7.4
|
||||
mobx: 6.8.0
|
||||
mobx-react-lite: 3.4.3_woojb62cqeyk443mbl7msrwu2e
|
||||
|
@ -2950,6 +2952,13 @@ packages:
|
|||
resolution: {integrity: sha512-DzRADjLoHcz18ocgGHvLIanapxygX3o9dlWwE32EUZqhyAsopfdvZ79ttR9+7pqAXIQamP9M4mbDy8hHgFKOIA==}
|
||||
dev: false
|
||||
|
||||
/@yume-chan/undici-browser/5.21.2-mod.9:
|
||||
resolution: {integrity: sha512-rkpYsC6E9o26D4d38FYCT2I+9NgQPyMzIRCSjo9ZKNpnpP/QL02U9h57LqXGaY1l/nBRgoAlK6ctdMwVClAQjw==}
|
||||
engines: {node: '>=12.18'}
|
||||
dependencies:
|
||||
busboy: 1.6.0
|
||||
dev: false
|
||||
|
||||
/abab/2.0.6:
|
||||
resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==}
|
||||
dev: true
|
||||
|
@ -3361,6 +3370,13 @@ packages:
|
|||
engines: {node: '>=6'}
|
||||
dev: true
|
||||
|
||||
/busboy/1.6.0:
|
||||
resolution: {integrity: sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==}
|
||||
engines: {node: '>=10.16.0'}
|
||||
dependencies:
|
||||
streamsearch: 1.1.0
|
||||
dev: false
|
||||
|
||||
/cacheable-request/2.1.4:
|
||||
resolution: {integrity: sha512-vag0O2LKZ/najSoUwDbVlnlCFvhBE/7mGTY2B5FgCBDcRD+oVV1HYTOwM6JZfMg/hIcM6IwnTZ1uQQL5/X3xIQ==}
|
||||
dependencies:
|
||||
|
@ -7293,6 +7309,11 @@ packages:
|
|||
internal-slot: 1.0.5
|
||||
dev: true
|
||||
|
||||
/streamsearch/1.1.0:
|
||||
resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==}
|
||||
engines: {node: '>=10.0.0'}
|
||||
dev: false
|
||||
|
||||
/strict-uri-encode/1.1.0:
|
||||
resolution: {integrity: sha512-R3f198pcvnB+5IpnBlRkphuE9n46WyVl8I39W/ZUTZLz4nqSP/oLYUrcnJrw462Ds8he4YKMov2efsTIw1BDGQ==}
|
||||
engines: {node: '>=0.10.0'}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
// DO NOT MODIFY THIS FILE MANUALLY BUT DO COMMIT IT. It is generated and used by Rush.
|
||||
{
|
||||
"pnpmShrinkwrapHash": "6c01283b613e0e32d56a30dc621fbddac47bd437",
|
||||
"pnpmShrinkwrapHash": "e7166d9b8db90548f97ba0dfa755b9b6a1334125",
|
||||
"preferredVersionsHash": "bf21a9e8fbc5a3846fb05b4fa0859e0917b2202f"
|
||||
}
|
||||
|
|
|
@ -68,6 +68,11 @@ export class AdbSocketController
|
|||
return this._closed;
|
||||
}
|
||||
|
||||
private _endPromiseResolver = new PromiseResolver<void>();
|
||||
public get end() {
|
||||
return this._endPromiseResolver.promise;
|
||||
}
|
||||
|
||||
private _socket: AdbSocket;
|
||||
public get socket() {
|
||||
return this._socket;
|
||||
|
@ -99,6 +104,7 @@ export class AdbSocketController
|
|||
dispose: () => {
|
||||
// Error out the pending writes
|
||||
this._writePromise?.reject(new Error("Socket closed"));
|
||||
this._endPromiseResolver.resolve();
|
||||
},
|
||||
});
|
||||
|
||||
|
@ -196,6 +202,14 @@ export class AdbSocket
|
|||
return this._controller.writable;
|
||||
}
|
||||
|
||||
public get closed(): boolean {
|
||||
return this._controller.closed;
|
||||
}
|
||||
|
||||
public get end(): Promise<void> {
|
||||
return this._controller.end;
|
||||
}
|
||||
|
||||
public constructor(controller: AdbSocketController) {
|
||||
this._controller = controller;
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue