microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
smoke-actionbar-timeout-assertion

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/networkInspector/networkInspectorServer.ts

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