microsoft/vscode-react-native

Public

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

CodeCommitsIssuesPull requestsActionsInsightsSecurity
dev/v-peq/issue-2705-debugger-endpoint-tests

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/networkInspector/networkInspectorServer.ts

405lines · modecode

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