microsoft/vscode-react-native

Public

mirrored fromhttps://github.com/microsoft/vscode-react-nativeAvailable

CodeCommitsIssuesPull requestsActionsInsightsSecurity
1.7.1

Branches

Tags

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

Clone

HTTPS

Download ZIP

src/extension/networkInspector/networkInspectorServer.ts

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