microsoft/vscode-react-native

Public

mirrored from https://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.9.3

Branches

Tags

  • No tags available.
0Branches0Tags
Go to file
Add file
Code

Clone

HTTPS

Download ZIP

src/cdp-proxy/debuggerEndpointHelper.ts

189lines · modeblame

d9c9ddcbRedMickey6 years ago1// Copyright (c) Microsoft Corporation. All rights reserved.
2// Licensed under the MIT license. See LICENSE file in the project root for details.
3
4import * as URL from "url";
5import * as http from "http";
6import * as https from "https";
09f6024fHeniker4 years ago7import { promises as dns } from "dns";
8import * as ipModule from "ip";
e23d1841RedMickey6 years ago9import { CancellationToken } from "vscode";
09f6024fHeniker4 years ago10import { InternalErrorCode } from "../common/error/internalErrorCode";
11import { ErrorHelper } from "../common/error/errorHelper";
12import { PromiseUtil } from "../common/node/promise";
d9c9ddcbRedMickey6 years ago13
48ca0c62RedMickey4 years ago14interface DebuggableEndpointData {
15webSocketDebuggerUrl: string;
16title: string;
17}
18
d9c9ddcbRedMickey6 years ago19export class DebuggerEndpointHelper {
20private localv4: Buffer;
21private localv6: Buffer;
22
23constructor() {
24this.localv4 = ipModule.toBuffer("127.0.0.1");
25this.localv6 = ipModule.toBuffer("::1");
b7451aefRedMickey6 years ago26}
27
28/**
29* Attempts to retrieve the debugger websocket URL for a process listening
30* at the given address, retrying until available.
31* @param browserURL -- Address like `http://localhost:1234`
e23d1841RedMickey6 years ago32* @param cancellationToken -- Cancellation for this operation
b7451aefRedMickey6 years ago33*/
e23d1841RedMickey6 years ago34public async retryGetWSEndpoint(
35browserURL: string,
36attemptNumber: number,
34472878RedMickey5 years ago37cancellationToken: CancellationToken,
48ca0c62RedMickey4 years ago38isHermes: boolean = false,
e23d1841RedMickey6 years ago39): Promise<string> {
dcabd9c8RedMickey4 years ago40while (true) {
41try {
48ca0c62RedMickey4 years ago42return await this.getWSEndpoint(browserURL, isHermes);
dcabd9c8RedMickey4 years ago43} catch (err) {
44if (attemptNumber < 1 || cancellationToken.isCancellationRequested) {
45const internalError = ErrorHelper.getInternalError(
46InternalErrorCode.CouldNotConnectToDebugTarget,
47browserURL,
48err.message,
34472878RedMickey5 years ago49);
dcabd9c8RedMickey4 years ago50
51if (cancellationToken.isCancellationRequested) {
52throw ErrorHelper.getNestedError(
53internalError,
54InternalErrorCode.CancellationTokenTriggered,
55);
56}
57
58throw internalError;
e23d1841RedMickey6 years ago59}
60
dcabd9c8RedMickey4 years ago61await PromiseUtil.delay(700);
e23d1841RedMickey6 years ago62}
b7451aefRedMickey6 years ago63}
d9c9ddcbRedMickey6 years ago64}
65
66/**
67* Returns the debugger websocket URL a process listening at the given address.
68* @param browserURL -- Address like `http://localhost:1234`
69*/
48ca0c62RedMickey4 years ago70public async getWSEndpoint(browserURL: string, isHermes: boolean = false): Promise<string> {
d9c9ddcbRedMickey6 years ago71const jsonVersion = await this.fetchJson<{ webSocketDebuggerUrl?: string }>(
34472878RedMickey5 years ago72URL.resolve(browserURL, "/json/version"),
d9c9ddcbRedMickey6 years ago73);
74if (jsonVersion.webSocketDebuggerUrl) {
75return jsonVersion.webSocketDebuggerUrl;
76}
77
78// Chrome its top-level debugg on /json/version, while Node does not.
79// Request both and return whichever one got us a string.
48ca0c62RedMickey4 years ago80const jsonList = await this.fetchJson<DebuggableEndpointData[]>(
34472878RedMickey5 years ago81URL.resolve(browserURL, "/json/list"),
d9c9ddcbRedMickey6 years ago82);
83if (jsonList.length) {
48ca0c62RedMickey4 years ago84return isHermes
85? this.tryToGetHermesImprovedChromeReloadsWebSocketDebuggerUrl(jsonList)
86: jsonList[0].webSocketDebuggerUrl;
d9c9ddcbRedMickey6 years ago87}
88
89throw new Error("Could not find any debuggable target");
90}
91
48ca0c62RedMickey4 years ago92private tryToGetHermesImprovedChromeReloadsWebSocketDebuggerUrl(
93jsonList: DebuggableEndpointData[],
94): string {
95const target = jsonList.find(
96target => target.title === "React Native Experimental (Improved Chrome Reloads)",
97);
98return target ? target.webSocketDebuggerUrl : jsonList[0].webSocketDebuggerUrl;
99}
100
d9c9ddcbRedMickey6 years ago101/**
102* Fetches JSON content from the given URL.
103*/
104private async fetchJson<T>(url: string): Promise<T> {
105const data = await this.fetch(url);
259c018fYuri Skorokhodov5 years ago106try {
107return JSON.parse(data);
34472878RedMickey5 years ago108} catch (err) {
259c018fYuri Skorokhodov5 years ago109return {} as T;
110}
d9c9ddcbRedMickey6 years ago111}
112
113/**
114* Fetches content from the given URL.
115*/
116private async fetch(url: string): Promise<string> {
117const isSecure = !url.startsWith("http://");
118const driver = isSecure ? https : http;
119const targetAddressIsLoopback = await this.isLoopback(url);
120
121return new Promise<string>((fulfill, reject) => {
122const requestOptions: https.RequestOptions = {};
123
124if (isSecure && targetAddressIsLoopback) {
125requestOptions.rejectUnauthorized = false;
126}
127
128const request = driver.get(url, requestOptions, response => {
129let data = "";
130response.setEncoding("utf8");
09f6024fHeniker4 years ago131response.on("data", (chunk: string) => {
132data += chunk;
133});
d9c9ddcbRedMickey6 years ago134response.on("end", () => fulfill(data));
135response.on("error", reject);
136});
137
138request.on("error", reject);
139request.end();
140});
141}
142
143/**
144* Gets whether the IP is a loopback address.
145*/
146private async isLoopback(address: string) {
147let ipOrHostname: string;
148try {
149const url = new URL.URL(address);
150// replace brackets in ipv6 addresses:
09f6024fHeniker4 years ago151ipOrHostname = url.hostname.replace(/^\[|]$/g, "");
d9c9ddcbRedMickey6 years ago152} catch {
153ipOrHostname = address;
154}
155
156if (this.isLoopbackIp(ipOrHostname)) {
157return true;
158}
159
160try {
161const resolved = await dns.lookup(ipOrHostname);
162return this.isLoopbackIp(resolved.address);
163} catch {
164return false;
165}
166}
167
168/**
169* Checks if the given address, well-formed loopback IPs. We don't need exotic
170* variations like `127.1` because `dns.lookup()` will resolve the proper
171* version for us. The "right" way would be to parse the IP to an integer
172* like Go does (https://golang.org/pkg/net/#IP.IsLoopback), but this
173* is lightweight and works.
174*/
175private isLoopbackIp(ipOrLocalhost: string) {
176if (ipOrLocalhost.toLowerCase() === "localhost") {
177return true;
178}
179
180let buf: Buffer;
181try {
182buf = ipModule.toBuffer(ipOrLocalhost);
183} catch {
184return false;
185}
186
187return buf.equals(this.localv4) || buf.equals(this.localv6);
188}
189}