microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
transitive-dependency-serialize-javascript

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/networkInspector/views/inspectorConsoleView.ts

272lines · modeblame

4bb0956eRedMickey5 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 { URL, URLSearchParams } from "url";
5import * as vscode from "vscode";
09f6024fHeniker4 years ago6import { Base64 } from "js-base64";
7import { RequestParams } from "../clientDevice";
4bb0956eRedMickey5 years ago8import {
9Request,
10Response,
11Header,
12ResponseFollowupChunk,
13PartialResponse,
14} from "../networkMessageData";
15import { EditorColorThemesHelper, SystemColorTheme } from "../../../common/editorColorThemesHelper";
16import { SettingsHelper } from "../../settingsHelper";
17import { combineBase64Chunks } from "../requestBodyFormatters/utils";
18import { FormattedBody } from "../requestBodyFormatters/requestBodyFormatter";
09f6024fHeniker4 years ago19import { InspectorView } from "./inspectorView";
4bb0956eRedMickey5 years ago20
21interface ConsoleNetworkRequestDataView {
22title: string;
23networkRequestData: {
24URL: string;
25Method: string;
26Status: number;
27Duration: string;
28"Request Headers": Record<string, string>;
29"Response Headers": Record<string, string>;
30"Request Body": FormattedBody | null | undefined;
31"Request Query Parameters"?: Record<string, string> | undefined;
32"Response Body": FormattedBody | null | undefined;
33};
34}
35
36export class InspectorConsoleView extends InspectorView {
37private readonly maxResponseBodyLength = 75000;
38private readonly openDeveloperToolsCommand = "workbench.action.toggleDevTools";
39private readonly consoleLogsColors = {
40Blue: "#0000ff",
41Orange: "#f28b54",
42};
43
44private consoleLogsColor: string;
45
46public async init(): Promise<void> {
47if (!this.isInitialized) {
48this.isInitialized = true;
49await vscode.commands.executeCommand(this.openDeveloperToolsCommand);
50if (EditorColorThemesHelper.isAutoDetectColorSchemeEnabled()) {
51this.setupConsoleLogsColor(EditorColorThemesHelper.getCurrentSystemColorTheme());
52} else {
53this.setupConsoleLogsColor(
54SettingsHelper.getNetworkInspectorConsoleLogsColorTheme(),
55);
56}
57}
58}
59
60public handleMessage(data: RequestParams): void {
61if (data.params) {
62switch (data.method) {
63case "newRequest":
64this.handleRequest(data.params as Request);
65break;
66case "newResponse":
67this.handleResponse(data.params as Response);
68break;
69case "partialResponse":
70this.handlePartialResponse(data.params as Response | ResponseFollowupChunk);
71break;
72}
73}
74}
75
76private setupConsoleLogsColor(systemColorTheme: SystemColorTheme): void {
77if (systemColorTheme === SystemColorTheme.Light) {
78this.consoleLogsColor = this.consoleLogsColors.Blue;
79} else {
80this.consoleLogsColor = this.consoleLogsColors.Orange;
81}
82}
83
84private handleRequest(data: Request): void {
85this.requests.set(data.id, data);
86}
87
88private handleResponse(data: Response): void {
89this.responses.set(data.id, data);
90if (this.requests.has(data.id)) {
91this.printNetworkRequestData(
92this.createNetworkRequestData(this.requests.get(data.id) as Request, data),
93);
94}
95}
96
97private handlePartialResponse(data: Response | ResponseFollowupChunk): void {
98/* Some clients (such as low end Android devices) struggle to serialise large payloads in one go, so partial responses allow them
99to split payloads into chunks and serialise each individually.
100
101Such responses will be distinguished between normal responses by both:
102* Being sent to the partialResponse method.
103* Having a totalChunks value > 1.
104
105The first chunk will always be included in the initial response. This response must have index 0.
106The remaining chunks will be sent in ResponseFollowupChunks, which each contain another piece of the payload, along with their index from 1 onwards.
107The payload of each chunk is individually encoded in the same way that full responses are.
108
109The order that initialResponse, and followup chunks are recieved is not guaranteed to be in index order.
110*/
111
112let newPartialResponseEntry;
113let responseId;
114if (data.index !== undefined && data.index > 0) {
115// It's a follow up chunk
116const followupChunk: ResponseFollowupChunk = data as ResponseFollowupChunk;
117const partialResponseEntry = this.partialResponses.get(followupChunk.id) ?? {
118followupChunks: {},
119};
120
121newPartialResponseEntry = Object.assign({}, partialResponseEntry);
122newPartialResponseEntry.followupChunks[followupChunk.index] = followupChunk.data;
123responseId = followupChunk.id;
124} else {
125// It's an initial chunk
126const partialResponse: Response = data as Response;
127const partialResponseEntry = this.partialResponses.get(partialResponse.id) ?? {
128followupChunks: {},
129};
130newPartialResponseEntry = {
131...partialResponseEntry,
132initialResponse: partialResponse,
133};
134responseId = partialResponse.id;
135}
136const response = this.assembleChunksIfResponseIsComplete(newPartialResponseEntry);
137if (response) {
138this.handleResponse(response);
139this.partialResponses.delete(responseId);
140} else {
141this.partialResponses.set(responseId, newPartialResponseEntry);
142}
143}
144
145/**
146* @preserve
147* Start region: the code is borrowed from https://github.com/facebook/flipper/blob/v0.79.1/desktop/plugins/network/index.tsx#L276-L324
148*
149* Copyright (c) Facebook, Inc. and its affiliates.
150*
151* This source code is licensed under the MIT license found in the
152* LICENSE file in the root directory of this source tree.
153*
154* @format
155*/
156private assembleChunksIfResponseIsComplete(
157partialResponseEntry: PartialResponse,
158): Response | null {
159const numChunks = partialResponseEntry.initialResponse?.totalChunks;
160if (
161!partialResponseEntry.initialResponse ||
162!numChunks ||
163Object.keys(partialResponseEntry.followupChunks).length + 1 < numChunks
164) {
165// Partial response not yet complete, do nothing.
166return null;
167}
168// Partial response has all required chunks, convert it to a full Response.
169
170const response: Response = partialResponseEntry.initialResponse;
171const allChunks: string[] =
172response.data != null
173? [
174response.data,
175...Object.entries(partialResponseEntry.followupChunks)
176// It's important to parseInt here or it sorts lexicographically
177.sort((a, b) => parseInt(a[0], 10) - parseInt(b[0], 10))
178.map(([_k, v]: [string, string]) => v),
179]
180: [];
181const data = combineBase64Chunks(allChunks);
182
183const newResponse = {
184...response,
185// Currently data is always decoded at render time, so re-encode it to match the single response format.
186data: Base64.btoa(data),
187};
188
189return newResponse;
190}
191
192/**
193* @preserve
194* End region: https://github.com/facebook/flipper/blob/v0.79.1/desktop/plugins/network/index.tsx#L276-L324
195*/
196
197private createNetworkRequestData(
198request: Request,
199response: Response,
200): ConsoleNetworkRequestDataView {
201const url = new URL(request.url);
202const networkRequestConsoleView = {
203title: `%cNetwork request: ${request.method} ${
204url ? url.host + url.pathname : "<unknown>"
205}`,
206networkRequestData: {
207URL: request.url,
208Method: request.method,
209Status: response.status,
210Duration: this.getRequestDurationString(request.timestamp, response.timestamp),
211"Request Headers": this.prepareHeadersViewObject(request.headers),
212"Response Headers": this.prepareHeadersViewObject(response.headers),
213"Request Body": this.requestBodyDecoder.formatBody(request),
214"Response Body": this.requestBodyDecoder.formatBody(response),
215},
216};
217
218if (url.search) {
5b09cf97Zhen Zhen Yuan (BEYONDSOFT CONSULTING INC)7 months ago219(networkRequestConsoleView.networkRequestData as any)["Request Query Parameters"] =
09f6024fHeniker4 years ago220this.retrieveURLSearchParams(url.searchParams);
4bb0956eRedMickey5 years ago221}
222
223return networkRequestConsoleView;
224}
225
226private retrieveURLSearchParams(searchParams: URLSearchParams): Record<string, string> {
09f6024fHeniker4 years ago227const formattedSearchParams: Record<string, string> = {};
4bb0956eRedMickey5 years ago228searchParams.forEach((value: string, key: string) => {
229formattedSearchParams[key] = value;
230});
231return formattedSearchParams;
232}
233
234private getRequestDurationString(requestTimestamp: number, responseTimestamp: number): string {
09f6024fHeniker4 years ago235return `${String(Math.abs(responseTimestamp - requestTimestamp))}ms`;
4bb0956eRedMickey5 years ago236}
237
238private prepareHeadersViewObject(headers: Header[]): Record<string, string> {
239return headers.reduce((headersViewObject, header) => {
240headersViewObject[header.key] = header.value;
241return headersViewObject;
5b09cf97Zhen Zhen Yuan (BEYONDSOFT CONSULTING INC)7 months ago242}, {} as Record<string, string>);
4bb0956eRedMickey5 years ago243}
244
245private printNetworkRequestData(networkRequestData: ConsoleNetworkRequestDataView): void {
246const responseBody = networkRequestData.networkRequestData["Response Body"];
247if (
248responseBody &&
249typeof responseBody === "string" &&
250responseBody.length > this.maxResponseBodyLength
251) {
09f6024fHeniker4 years ago252networkRequestData.networkRequestData["Response Body"] = `${responseBody.substring(
2530,
254this.maxResponseBodyLength,
255)}... (Response body exceeds output limit, the rest its part is omitted)`;
4bb0956eRedMickey5 years ago256}
257
258console.log(
259networkRequestData.title,
260`color: ${this.consoleLogsColor}`,
261networkRequestData.networkRequestData,
262);
263
264this.logger.debug(
265`${networkRequestData.title}\ncolor: ${this.consoleLogsColor}\n${JSON.stringify(
266networkRequestData.networkRequestData,
267null,
2682,
269)}`,
270);
271}
272}