microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.8.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/networkInspector/networkInspectorServer.ts

400lines · 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 { RSocketServer } from "rsocket-core";
5import RSocketTCPServer from "rsocket-tcp-server";
6import { AdbHelper } from "../android/adb";
7import { Single } from "rsocket-flowable";
8import { appNameWithUpdateHint, buildClientId } from "./clientUtils";
9import { SecureClientQuery, ClientCsrQuery, ClientDevice, ClientQuery } from "./clientDevice";
10import { OutputChannelLogger } from "../log/OutputChannelLogger";
11import { Responder, Payload, ReactiveSocket } from "rsocket-types";
12import {
13CertificateProvider,
14SecureServerConfig,
15CertificateExchangeMedium,
16} from "./certificateProvider";
17import { ClientOS } from "./clientUtils";
18import * as net from "net";
19import * as tls from "tls";
20import * as nls from "vscode-nls";
21import { InspectorViewType } from "./views/inspectorView";
ab0238b7RedMickey4 years ago22import { TipNotificationService } from "../services/tipsNotificationsService/tipsNotificationService";
4bb0956eRedMickey5 years ago23nls.config({
24messageFormat: nls.MessageFormat.bundle,
25bundleFormat: nls.BundleFormat.standalone,
26})();
27const localize = nls.loadMessageBundle();
28
29/**
30* @preserve
31* Start region: the code is borrowed from https://github.com/facebook/flipper/blob/master/desktop/app/src/server.tsx
32*
33* Copyright (c) Facebook, Inc. and its affiliates.
34*
35* This source code is licensed under the MIT license found in the
36* LICENSE file in the root directory of this source tree.
37*
38* @format
39*/
40
41function transformCertificateExchangeMediumToType(
42medium: number | undefined,
43): CertificateExchangeMedium {
44if (medium == 1) {
45return "FS_ACCESS";
46} else if (medium == 2) {
47return "WWW";
48} else {
49return "FS_ACCESS";
50}
51}
52
53export const NETWORK_INSPECTOR_LOG_CHANNEL_NAME = "Network Inspector";
54
55export class NetworkInspectorServer {
56public static readonly SecureServerPort = 8088;
57public static readonly InsecureServerPort = 8089;
58
59private connections: Map<string, ClientDevice>;
60private secureServer: RSocketServer<any, any> | null;
61private insecureServer: RSocketServer<any, any> | null;
62private certificateProvider: CertificateProvider;
63private initialisePromise: Promise<void> | null;
64private logger: OutputChannelLogger;
65
66constructor() {
67this.connections = new Map<string, ClientDevice>();
68this.logger = OutputChannelLogger.getChannel(NETWORK_INSPECTOR_LOG_CHANNEL_NAME);
69}
70
71public async start(adbHelper: AdbHelper): Promise<void> {
72this.logger.info(localize("StartNetworkinspector", "Starting Network inspector"));
f338085detatanova4 years ago73TipNotificationService.getInstance().setKnownDateForFeatureById("networkInspector");
74TipNotificationService.getInstance().showTipNotification(
75false,
76"networkInspectorLogsColorTheme",
77);
4bb0956eRedMickey5 years ago78this.initialisePromise = new Promise(async (resolve, reject) => {
79this.certificateProvider = new CertificateProvider(adbHelper);
80
81try {
82let options = await this.certificateProvider.loadSecureServerConfig();
83this.secureServer = await this.startServer(
84NetworkInspectorServer.SecureServerPort,
85options,
86);
87this.insecureServer = await this.startServer(
88NetworkInspectorServer.InsecureServerPort,
89);
90} catch (err) {
91return reject(err);
92}
93
94this.logger.info(localize("NetworkInspectorWorking", "Network inspector is working"));
95resolve();
96});
97return this.initialisePromise;
98}
99
100public async stop(): Promise<void> {
101if (this.initialisePromise) {
102try {
103await this.initialisePromise;
104} catch (err) {
105this.logger.error(err.toString());
106}
107if (this.secureServer) {
108this.secureServer.stop();
109}
110if (this.insecureServer) {
111this.insecureServer.stop();
112}
113}
114this.logger.info(localize("NetworkInspectorStopped", "Network inspector has been stopped"));
115}
116
117private async startServer(
118port: number,
119sslConfig?: SecureServerConfig,
120): Promise<RSocketServer<any, any>> {
121return new Promise((resolve, reject) => {
122let rsServer: RSocketServer<any, any> | undefined; // eslint-disable-line prefer-const
123const serverFactory = (onConnect: (socket: net.Socket) => void) => {
124const transportServer = sslConfig
125? tls.createServer(sslConfig, socket => {
126onConnect(socket);
127})
128: net.createServer(onConnect);
129transportServer
130.on("error", err => {
131this.logger.error(
132localize(
133"ErrorOpeningNetworkInspectorServerOnPort",
134"Error while opening Network inspector server on port {0}",
135port,
136),
137);
138reject(err);
139})
140.on("listening", () => {
141this.logger.debug(
142`${
143sslConfig ? "Secure" : "Certificate"
144} server started on port ${port}`,
145);
146resolve(rsServer!);
147});
148return transportServer;
149};
150rsServer = new RSocketServer({
151getRequestHandler: sslConfig
152? this.trustedRequestHandler
153: this.untrustedRequestHandler,
154transport: new RSocketTCPServer({
155port: port,
156serverFactory: serverFactory,
157}),
158});
159rsServer && rsServer.start();
160});
161}
162
163private trustedRequestHandler = (
164socket: ReactiveSocket<string, any>,
165payload: Payload<string, any>,
166): Partial<Responder<string, any>> => {
167// eslint-disable-next-line @typescript-eslint/no-this-alias
168const server = this;
169if (!payload.data) {
170return {};
171}
172
173const clientData: SecureClientQuery = JSON.parse(payload.data);
174
175const { app, os, device, device_id, sdk_version, csr, csr_path, medium } = clientData;
176const transformedMedium = transformCertificateExchangeMediumToType(medium);
177
178const client: Promise<ClientDevice> = this.addConnection(
179socket,
180{
181app,
182os,
183device,
184device_id,
185sdk_version,
186medium: transformedMedium,
187},
188{ csr, csr_path },
189).then(client => {
190return (resolvedClient = client);
191});
192let resolvedClient: ClientDevice | undefined;
193
194socket.connectionStatus().subscribe({
195onNext(payload) {
196if (payload.kind == "ERROR" || payload.kind == "CLOSED") {
197client.then(client => {
198server.logger.info(
199localize(
200"NIDeviceDisconnected",
201"Device disconnected {0} from the Network inspector",
202client.id,
203),
204);
205server.removeConnection(client.id);
206});
207}
208},
209onSubscribe(subscription) {
210subscription.request(Number.MAX_SAFE_INTEGER);
211},
212onError(error) {
213server.logger.error("Network inspector server connection status error ", error);
214},
215});
216
217return {
218fireAndForget: (payload: { data: string }) => {
219if (resolvedClient) {
220resolvedClient.onMessage(payload.data);
221} else {
222client.then(client => {
223client.onMessage(payload.data);
224});
225}
226},
227};
228};
229
230private untrustedRequestHandler = (
231_socket: ReactiveSocket<string, any>,
232payload: Payload<string, any>,
233): Partial<Responder<string, any>> => {
234if (!payload.data) {
235return {};
236}
237const clientData: ClientQuery = JSON.parse(payload.data);
238
239return {
240requestResponse: (payload: Payload<string, any>): Single<Payload<string, any>> => {
241if (typeof payload.data !== "string") {
242return new Single(() => {});
243}
244
245let rawData;
246try {
247rawData = JSON.parse(payload.data);
248} catch (err) {
249this.logger.error(`Network inspector: invalid JSON: ${payload.data}`);
250return new Single(() => {});
251}
252
253const json: {
254method: "signCertificate";
255csr: string;
256destination: string;
257medium: number | undefined; // OSS's older Client SDK might not send medium information. This is not an issue for internal FB users, as Flipper release is insync with client SDK through launcher.
258} = rawData;
259
260if (json.method === "signCertificate") {
261this.logger.debug("CSR received from device");
262
263const { csr, destination, medium } = json;
264return new Single(subscriber => {
265subscriber.onSubscribe(undefined);
266this.certificateProvider
267.processCertificateSigningRequest(
268csr,
269clientData.os,
270destination,
271transformCertificateExchangeMediumToType(medium),
272)
273.then(result => {
274subscriber.onComplete({
275data: JSON.stringify({
276deviceId: result.deviceId,
277}),
278metadata: "",
279});
280})
281.catch(e => {
282this.logger.error(e.toString());
283subscriber.onError(e);
284});
285});
286}
287return new Single(() => {});
288},
289
290// Leaving this here for a while for backwards compatibility,
291// but for up to date SDKs it will no longer used.
292// We can delete it after the SDK change has been using requestResponse for a few weeks.
293fireAndForget: (payload: Payload<string, any>) => {
294if (typeof payload.data !== "string") {
295return;
296}
297
298let json:
299| {
300method: "signCertificate";
301csr: string;
302destination: string;
303medium: number | undefined;
304}
305| undefined;
306try {
307json = JSON.parse(payload.data);
308} catch (err) {
309this.logger.error(`Network inspector: invalid JSON: ${payload.data}`);
310return;
311}
312
313if (json && json.method === "signCertificate") {
314this.logger.debug("CSR received from device");
315const { csr, destination, medium } = json;
316this.certificateProvider
317.processCertificateSigningRequest(
318csr,
319clientData.os,
320destination,
321transformCertificateExchangeMediumToType(medium),
322)
323.catch(e => {
324this.logger.error(e.toString());
325});
326}
327},
328};
329};
330
331private async addConnection(
332conn: ReactiveSocket<any, any>,
333query: ClientQuery & { medium: CertificateExchangeMedium },
334csrQuery: ClientCsrQuery,
335): Promise<ClientDevice> {
336// try to get id by comparing giving `csr` to file from `csr_path`
337// otherwise, use given device_id
338const { csr_path, csr } = csrQuery;
339// For iOS we do not need to confirm the device id, as it never changes unlike android.
340return (csr_path && csr && query.os !== ClientOS.iOS
341? this.certificateProvider.extractAppNameFromCSR(csr).then(appName => {
342return this.certificateProvider.getTargetDeviceId(
343query.os,
344appName,
345csr_path,
346csr,
347);
348})
349: Promise.resolve(query.device_id)
350).then(async csrId => {
351query.device_id = csrId;
352query.app = appNameWithUpdateHint(query);
353
354const id = buildClientId(
355{
356app: query.app,
357os: query.os,
358device: query.device,
359device_id: csrId,
360},
361this.logger,
362);
363this.logger.info(localize("NIDeviceConnected", "Device connected: {0}", id));
364
365const client = new ClientDevice(
366id,
367query,
368conn,
369InspectorViewType.console,
370this.logger,
371);
372
373client.init().then(() => {
374this.logger.debug(`Device client initialised: ${id}`);
375/* If a device gets disconnected without being cleaned up properly,
376* Flipper won't be aware until it attempts to reconnect.
377* When it does we need to terminate the zombie connection.
378*/
379this.removeConnection(id);
380
381this.connections.set(id, client);
382});
383
384return client;
385});
386}
387
388/**
389* @preserve
390* End region: https://github.com/facebook/flipper/blob/master/desktop/app/src/server.tsx
391*/
392
393private removeConnection(id: string) {
394const clientDevice = this.connections.get(id);
395if (clientDevice) {
396clientDevice.connection && clientDevice.connection.close();
397this.connections.delete(id);
398}
399}
400}