microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.4.0

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/cdp-proxy/debuggerEndpointHelper.ts

169lines · 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 ipModule from "ip";
6const dns = require("dns").promises;
7import * as http from "http";
8import * as https from "https";
b7451aefRedMickey6 years ago9import { PromiseUtil } from "../common/node/promise";
e23d1841RedMickey6 years ago10import { ErrorHelper } from "../common/error/errorHelper";
11import { InternalErrorCode } from "../common/error/internalErrorCode";
12import { CancellationToken } from "vscode";
d9c9ddcbRedMickey6 years ago13
14export class DebuggerEndpointHelper {
15private localv4: Buffer;
16private localv6: Buffer;
17
18constructor() {
19this.localv4 = ipModule.toBuffer("127.0.0.1");
20this.localv6 = ipModule.toBuffer("::1");
b7451aefRedMickey6 years ago21}
22
23/**
24* Attempts to retrieve the debugger websocket URL for a process listening
25* at the given address, retrying until available.
26* @param browserURL -- Address like `http://localhost:1234`
e23d1841RedMickey6 years ago27* @param cancellationToken -- Cancellation for this operation
b7451aefRedMickey6 years ago28*/
e23d1841RedMickey6 years ago29public async retryGetWSEndpoint(
30browserURL: string,
31attemptNumber: number,
34472878RedMickey5 years ago32cancellationToken: CancellationToken,
e23d1841RedMickey6 years ago33): Promise<string> {
b7451aefRedMickey6 years ago34try {
35return await this.getWSEndpoint(browserURL);
e23d1841RedMickey6 years ago36} catch (err) {
37if (attemptNumber < 1 || cancellationToken.isCancellationRequested) {
34472878RedMickey5 years ago38const internalError = ErrorHelper.getInternalError(
39InternalErrorCode.CouldNotConnectToDebugTarget,
40browserURL,
41err.message,
42);
e23d1841RedMickey6 years ago43
44if (cancellationToken.isCancellationRequested) {
34472878RedMickey5 years ago45throw ErrorHelper.getNestedError(
46internalError,
47InternalErrorCode.CancellationTokenTriggered,
48);
e23d1841RedMickey6 years ago49}
50
51throw internalError;
52}
b7451aefRedMickey6 years ago53
259c018fYuri Skorokhodov5 years ago54await PromiseUtil.delay(1000);
e23d1841RedMickey6 years ago55return await this.retryGetWSEndpoint(browserURL, --attemptNumber, cancellationToken);
b7451aefRedMickey6 years ago56}
d9c9ddcbRedMickey6 years ago57}
58
59/**
60* Returns the debugger websocket URL a process listening at the given address.
61* @param browserURL -- Address like `http://localhost:1234`
62*/
63public async getWSEndpoint(browserURL: string): Promise<string> {
64const jsonVersion = await this.fetchJson<{ webSocketDebuggerUrl?: string }>(
34472878RedMickey5 years ago65URL.resolve(browserURL, "/json/version"),
d9c9ddcbRedMickey6 years ago66);
67if (jsonVersion.webSocketDebuggerUrl) {
68return jsonVersion.webSocketDebuggerUrl;
69}
70
71// Chrome its top-level debugg on /json/version, while Node does not.
72// Request both and return whichever one got us a string.
73const jsonList = await this.fetchJson<{ webSocketDebuggerUrl: string }[]>(
34472878RedMickey5 years ago74URL.resolve(browserURL, "/json/list"),
d9c9ddcbRedMickey6 years ago75);
76if (jsonList.length) {
77return jsonList[0].webSocketDebuggerUrl;
78}
79
80throw new Error("Could not find any debuggable target");
81}
82
83/**
84* Fetches JSON content from the given URL.
85*/
86private async fetchJson<T>(url: string): Promise<T> {
87const data = await this.fetch(url);
259c018fYuri Skorokhodov5 years ago88try {
89return JSON.parse(data);
34472878RedMickey5 years ago90} catch (err) {
259c018fYuri Skorokhodov5 years ago91return {} as T;
92}
d9c9ddcbRedMickey6 years ago93}
94
95/**
96* Fetches content from the given URL.
97*/
98private async fetch(url: string): Promise<string> {
99const isSecure = !url.startsWith("http://");
100const driver = isSecure ? https : http;
101const targetAddressIsLoopback = await this.isLoopback(url);
102
103return new Promise<string>((fulfill, reject) => {
104const requestOptions: https.RequestOptions = {};
105
106if (isSecure && targetAddressIsLoopback) {
107requestOptions.rejectUnauthorized = false;
108}
109
110const request = driver.get(url, requestOptions, response => {
111let data = "";
112response.setEncoding("utf8");
113response.on("data", (chunk: string) => (data += chunk));
114response.on("end", () => fulfill(data));
115response.on("error", reject);
116});
117
118request.on("error", reject);
119request.end();
120});
121}
122
123/**
124* Gets whether the IP is a loopback address.
125*/
126private async isLoopback(address: string) {
127let ipOrHostname: string;
128try {
129const url = new URL.URL(address);
130// replace brackets in ipv6 addresses:
131ipOrHostname = url.hostname.replace(/^\[|\]$/g, "");
132} catch {
133ipOrHostname = address;
134}
135
136if (this.isLoopbackIp(ipOrHostname)) {
137return true;
138}
139
140try {
141const resolved = await dns.lookup(ipOrHostname);
142return this.isLoopbackIp(resolved.address);
143} catch {
144return false;
145}
146}
147
148/**
149* Checks if the given address, well-formed loopback IPs. We don't need exotic
150* variations like `127.1` because `dns.lookup()` will resolve the proper
151* version for us. The "right" way would be to parse the IP to an integer
152* like Go does (https://golang.org/pkg/net/#IP.IsLoopback), but this
153* is lightweight and works.
154*/
155private isLoopbackIp(ipOrLocalhost: string) {
156if (ipOrLocalhost.toLowerCase() === "localhost") {
157return true;
158}
159
160let buf: Buffer;
161try {
162buf = ipModule.toBuffer(ipOrLocalhost);
163} catch {
164return false;
165}
166
167return buf.equals(this.localv4) || buf.equals(this.localv6);
168}
169}