microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
prepare-for-1.13.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/cdp-proxy/debuggerEndpointHelper.ts

230lines · 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";
e23d1841RedMickey6 years ago8import { CancellationToken } from "vscode";
09f6024fHeniker4 years ago9import { InternalErrorCode } from "../common/error/internalErrorCode";
10import { ErrorHelper } from "../common/error/errorHelper";
11import { PromiseUtil } from "../common/node/promise";
27366147Ezio Li1 years ago12import { ipToBuffer } from "../common/utils";
d9c9ddcbRedMickey6 years ago13
48ca0c62RedMickey4 years ago14interface DebuggableEndpointData {
15webSocketDebuggerUrl: string;
16title: string;
b8922151Ezio Li1 years ago17description: string;
48ca0c62RedMickey4 years ago18}
19
d9c9ddcbRedMickey6 years ago20export class DebuggerEndpointHelper {
21private localv4: Buffer;
22private localv6: Buffer;
23
24constructor() {
27366147Ezio Li1 years ago25this.localv4 = ipToBuffer("127.0.0.1");
26this.localv6 = ipToBuffer("::1");
b7451aefRedMickey6 years ago27}
28
29/**
30* Attempts to retrieve the debugger websocket URL for a process listening
31* at the given address, retrying until available.
32* @param browserURL -- Address like `http://localhost:1234`
e23d1841RedMickey6 years ago33* @param cancellationToken -- Cancellation for this operation
b7451aefRedMickey6 years ago34*/
e23d1841RedMickey6 years ago35public async retryGetWSEndpoint(
36browserURL: string,
37attemptNumber: number,
34472878RedMickey5 years ago38cancellationToken: CancellationToken,
48ca0c62RedMickey4 years ago39isHermes: boolean = false,
b84470b5Ezio Li2 years ago40settingsPort?: number,
e23d1841RedMickey6 years ago41): Promise<string> {
dcabd9c8RedMickey4 years ago42while (true) {
43try {
b84470b5Ezio Li2 years ago44let url = "";
45if (settingsPort) {
46url = `http://localhost:${settingsPort}`;
47try {
48return await this.getWSEndpoint(browserURL, isHermes);
49} catch {
50return await this.getWSEndpoint(url, isHermes);
51}
52} else {
53return await this.getWSEndpoint(browserURL, isHermes);
54}
dcabd9c8RedMickey4 years ago55} catch (err) {
56if (attemptNumber < 1 || cancellationToken.isCancellationRequested) {
57const internalError = ErrorHelper.getInternalError(
58InternalErrorCode.CouldNotConnectToDebugTarget,
59browserURL,
60err.message,
34472878RedMickey5 years ago61);
dcabd9c8RedMickey4 years ago62
63if (cancellationToken.isCancellationRequested) {
64throw ErrorHelper.getNestedError(
65internalError,
66InternalErrorCode.CancellationTokenTriggered,
67);
68}
69
70throw internalError;
e23d1841RedMickey6 years ago71}
72
dcabd9c8RedMickey4 years ago73await PromiseUtil.delay(700);
e23d1841RedMickey6 years ago74}
b7451aefRedMickey6 years ago75}
d9c9ddcbRedMickey6 years ago76}
77
78/**
79* Returns the debugger websocket URL a process listening at the given address.
80* @param browserURL -- Address like `http://localhost:1234`
81*/
48ca0c62RedMickey4 years ago82public async getWSEndpoint(browserURL: string, isHermes: boolean = false): Promise<string> {
d9c9ddcbRedMickey6 years ago83const jsonVersion = await this.fetchJson<{ webSocketDebuggerUrl?: string }>(
34472878RedMickey5 years ago84URL.resolve(browserURL, "/json/version"),
d9c9ddcbRedMickey6 years ago85);
86if (jsonVersion.webSocketDebuggerUrl) {
87return jsonVersion.webSocketDebuggerUrl;
88}
89
90// Chrome its top-level debugg on /json/version, while Node does not.
91// Request both and return whichever one got us a string.
48ca0c62RedMickey4 years ago92const jsonList = await this.fetchJson<DebuggableEndpointData[]>(
34472878RedMickey5 years ago93URL.resolve(browserURL, "/json/list"),
d9c9ddcbRedMickey6 years ago94);
95if (jsonList.length) {
48ca0c62RedMickey4 years ago96return isHermes
97? this.tryToGetHermesImprovedChromeReloadsWebSocketDebuggerUrl(jsonList)
98: jsonList[0].webSocketDebuggerUrl;
d9c9ddcbRedMickey6 years ago99}
b84470b5Ezio Li2 years ago100// Try to get websocket endpoint from default metro bundler
101const defaultJsonList = await this.fetchJson<DebuggableEndpointData[]>(
102"http://localhost:8081/json/list",
103);
104if (defaultJsonList.length) {
105return isHermes
106? this.tryToGetHermesImprovedChromeReloadsWebSocketDebuggerUrl(defaultJsonList)
107: defaultJsonList[0].webSocketDebuggerUrl;
108}
d9c9ddcbRedMickey6 years ago109
110throw new Error("Could not find any debuggable target");
111}
112
b8922151Ezio Li1 years ago113/**
114* Returns the debugger type for expo app and react-native pure app.
115* @param browserURL -- Address like `http://localhost:1234`
116*/
117public async getDebuggerTpye(browserURL: string): Promise<string> {
118const jsonList = await this.fetchJson<DebuggableEndpointData[]>(
119URL.resolve(browserURL, "/json/list"),
120);
121if (jsonList.length) {
5b113866Ezio Li9 months ago122if (jsonList[0].title || jsonList[0].description) {
123const isExpo =
124jsonList[0].title.toLowerCase().includes("exponent") ||
125jsonList[0].description.toLowerCase().includes("exponent");
126return isExpo ? "expo" : "react-native";
6967c7fbEzio Li10 months ago127}
5b113866Ezio Li9 months ago128return "react-native";
b8922151Ezio Li1 years ago129}
130throw new Error("Could not find any debuggable target");
131}
132
48ca0c62RedMickey4 years ago133private tryToGetHermesImprovedChromeReloadsWebSocketDebuggerUrl(
134jsonList: DebuggableEndpointData[],
135): string {
136const target = jsonList.find(
137target => target.title === "React Native Experimental (Improved Chrome Reloads)",
138);
139return target ? target.webSocketDebuggerUrl : jsonList[0].webSocketDebuggerUrl;
140}
141
d9c9ddcbRedMickey6 years ago142/**
143* Fetches JSON content from the given URL.
144*/
145private async fetchJson<T>(url: string): Promise<T> {
146const data = await this.fetch(url);
259c018fYuri Skorokhodov5 years ago147try {
148return JSON.parse(data);
34472878RedMickey5 years ago149} catch (err) {
259c018fYuri Skorokhodov5 years ago150return {} as T;
151}
d9c9ddcbRedMickey6 years ago152}
153
154/**
155* Fetches content from the given URL.
156*/
157private async fetch(url: string): Promise<string> {
158const isSecure = !url.startsWith("http://");
159const driver = isSecure ? https : http;
160const targetAddressIsLoopback = await this.isLoopback(url);
161
162return new Promise<string>((fulfill, reject) => {
163const requestOptions: https.RequestOptions = {};
164
165if (isSecure && targetAddressIsLoopback) {
1861e27dbenjaminbi3 years ago166requestOptions.rejectUnauthorized = false; // CodeQL [js/disabling-certificate-validation] Debug extension does not need to verify certificate
d9c9ddcbRedMickey6 years ago167}
168
169const request = driver.get(url, requestOptions, response => {
170let data = "";
171response.setEncoding("utf8");
09f6024fHeniker4 years ago172response.on("data", (chunk: string) => {
173data += chunk;
174});
d9c9ddcbRedMickey6 years ago175response.on("end", () => fulfill(data));
176response.on("error", reject);
177});
178
179request.on("error", reject);
180request.end();
181});
182}
183
184/**
185* Gets whether the IP is a loopback address.
186*/
187private async isLoopback(address: string) {
188let ipOrHostname: string;
189try {
190const url = new URL.URL(address);
191// replace brackets in ipv6 addresses:
09f6024fHeniker4 years ago192ipOrHostname = url.hostname.replace(/^\[|]$/g, "");
d9c9ddcbRedMickey6 years ago193} catch {
194ipOrHostname = address;
195}
196
197if (this.isLoopbackIp(ipOrHostname)) {
198return true;
199}
200
201try {
202const resolved = await dns.lookup(ipOrHostname);
203return this.isLoopbackIp(resolved.address);
204} catch {
205return false;
206}
207}
208
209/**
210* Checks if the given address, well-formed loopback IPs. We don't need exotic
211* variations like `127.1` because `dns.lookup()` will resolve the proper
212* version for us. The "right" way would be to parse the IP to an integer
213* like Go does (https://golang.org/pkg/net/#IP.IsLoopback), but this
214* is lightweight and works.
215*/
216private isLoopbackIp(ipOrLocalhost: string) {
217if (ipOrLocalhost.toLowerCase() === "localhost") {
218return true;
219}
220
221let buf: Buffer;
222try {
27366147Ezio Li1 years ago223buf = ipToBuffer(ipOrLocalhost);
d9c9ddcbRedMickey6 years ago224} catch {
225return false;
226}
227
5b09cf97Zhen Zhen Yuan (BEYONDSOFT CONSULTING INC)6 months ago228return buf.equals(this.localv4 as any) || buf.equals(this.localv6 as any);
d9c9ddcbRedMickey6 years ago229}
230}