microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.5.2

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/networkInspector/networkInspectorServer.ts

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